Added lots op options
This commit is contained in:
parent
b8b21655cf
commit
95a90b51fb
6
.project
6
.project
|
@ -5,7 +5,13 @@
|
||||||
<projects>
|
<projects>
|
||||||
</projects>
|
</projects>
|
||||||
<buildSpec>
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
</buildSpec>
|
</buildSpec>
|
||||||
<natures>
|
<natures>
|
||||||
|
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
|
||||||
</natures>
|
</natures>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
|
|
@ -11,10 +11,7 @@ A javascript library providing server defined loading of resouces for a single p
|
||||||
|
|
||||||
On index.html
|
On index.html
|
||||||
|
|
||||||
FFSpaLoader.start({
|
FFSpaLoader.start(...);
|
||||||
url: '/my/url/for/resources.json',
|
|
||||||
debug: true
|
|
||||||
});
|
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
|
|
|
@ -24,18 +24,66 @@
|
||||||
|
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// conv to req queue
|
|
||||||
// split js/css so css can be done later
|
|
||||||
// set tag.media = 'only you';
|
// set tag.media = 'only you';
|
||||||
// add media in resouces
|
// add media in resouces
|
||||||
// add userAgant match in resouces
|
// check supported browsers
|
||||||
// NOTE: add ordered inject, for now JS in non-cache mode needs to be aggragated server side
|
|
||||||
// add browser supported
|
|
||||||
|
|
||||||
var FFSpaLoader = (function () {
|
/**
|
||||||
|
* FFSpaLoader is an assets loader for single page applications.
|
||||||
|
* Its build around the concept the there is only a single static index.html which
|
||||||
|
* synces all its assets to local cache for offline and or fast page loads.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Assets caching for offline use.
|
||||||
|
* - Assets hashing for fast syncing.
|
||||||
|
* - Assets types: js,css,cssData
|
||||||
|
* - Caching backends: null,localStorage,webSqlDB,sqllite
|
||||||
|
* - Optional ask and cache server assets url.
|
||||||
|
* - Cordova deviceready event booting.
|
||||||
|
*
|
||||||
|
* @module FFSpaLoader
|
||||||
|
*/
|
||||||
|
var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
|
||||||
|
|
||||||
var options = {};
|
var options = {
|
||||||
|
debug: false,
|
||||||
|
errorHandler: null,
|
||||||
|
bootDevice: {
|
||||||
|
timeout: 4096,
|
||||||
|
windowFlag: 'FFCordovaDevice'
|
||||||
|
},
|
||||||
|
askUrl: {
|
||||||
|
urlTransport: 'http://',
|
||||||
|
questionText: 'Please provide the server name;',
|
||||||
|
cssText: '.ffAskUrl { font-size: 1em;margin: auto;width: 50%;border: 3px solid #73AD21;padding: 1em;} .ffAskUrl > div {font-size: 0.8em;color: #ccc;} .ffAskUrl > div > * {} .ffAskUrl > div > input {} .ffAskUrlError{ color: red}',
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
url: null,
|
||||||
|
urlPath: null,
|
||||||
|
timeout: 4096,
|
||||||
|
windowFlag: 'FFServerUrl',
|
||||||
|
header: {
|
||||||
|
request: {
|
||||||
|
'X-FFSpaLoader': '42'
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
factory: null,
|
||||||
|
meta: null,
|
||||||
|
js: null,
|
||||||
|
css: null,
|
||||||
|
cssData: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints debug message to the console if options.debug is true.
|
||||||
|
* @param {String} message The message to log.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
var utilDebug = function (message) {
|
var utilDebug = function (message) {
|
||||||
if (options.debug !== true) {
|
if (options.debug !== true) {
|
||||||
return;
|
return;
|
||||||
|
@ -43,257 +91,640 @@ var FFSpaLoader = (function () {
|
||||||
console.log('FFSpaLoader.'+message);
|
console.log('FFSpaLoader.'+message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var factory = {
|
||||||
|
detect: {
|
||||||
|
localStorage: function() {
|
||||||
|
try {
|
||||||
|
var testData = 'localStorageDetect';
|
||||||
|
localStorage.setItem(testData, testData);
|
||||||
|
localStorage.removeItem(testData);
|
||||||
|
return true;
|
||||||
|
} catch(e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openDatabase: function() {
|
||||||
|
return 'openDatabase' in window;
|
||||||
|
},
|
||||||
|
sqlitePlugin: function() {
|
||||||
|
return 'sqlitePlugin' in window;
|
||||||
|
},
|
||||||
|
cordova: function() {
|
||||||
|
return 'cordova' in window;
|
||||||
|
},
|
||||||
|
cordovaDevice: function() {
|
||||||
|
return options.bootDevice.windowFlag in window;
|
||||||
|
},
|
||||||
|
mobileAgent: function() {
|
||||||
|
return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
localStorage: function() {
|
||||||
|
return {
|
||||||
|
cacheGetValue: function(key, cb) {
|
||||||
|
try {
|
||||||
|
var dataRaw = localStorage.getItem(key);
|
||||||
|
var data = JSON.parse(dataRaw);
|
||||||
|
cb(null, data);
|
||||||
|
} catch(e) {
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cacheSetValue: function(key, value, cb) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(key,JSON.stringify(value));
|
||||||
|
cb(null);
|
||||||
|
} catch(e) {
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cacheDeleteValue: function(key, cb) {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem(key);
|
||||||
|
cb(null);
|
||||||
|
} catch(e) {
|
||||||
|
cb(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
websql: function(opt) {
|
||||||
|
if (opt === undefined) { opt = {}; }
|
||||||
|
if (opt.name === undefined) { opt.name = 'ffSpaLoader.db'; }
|
||||||
|
if (opt.size === undefined) { opt.size = -1; }
|
||||||
|
if (opt.version === undefined) { opt.version = '1.0'; }
|
||||||
|
if (opt.openDatabase === undefined) { opt.openDatabase = window.openDatabase; }
|
||||||
|
var nullDataHandler = function(cb) {
|
||||||
|
return function (tx, results) {
|
||||||
|
cb(null);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var sqlErrorHandler = function(cb) {
|
||||||
|
return function (tx, err) {
|
||||||
|
cb(err.message+' code: '+err.code);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
var cacheDB = window.openDatabase(opt.name, opt.version, opt.name, opt.size);
|
||||||
|
cacheDB.transaction(function(tx) {
|
||||||
|
var query = 'SELECT value FROM cache_store WHERE key = \"test-for-table\"';
|
||||||
|
utilDebug('websql.init query: '+query);
|
||||||
|
tx.executeSql(query, [], function(tx,res) {}, function(tx,errorNoTable) {
|
||||||
|
var query = 'CREATE TABLE cache_store(id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, value TEXT NOT NULL)';
|
||||||
|
utilDebug('websql.init query: '+query);
|
||||||
|
tx.executeSql(query, [], function(tx,res) {
|
||||||
|
var query = 'CREATE UNIQUE INDEX cache_store__key__udx ON TABLE cache_store';
|
||||||
|
utilDebug('websql.init query: '+query);
|
||||||
|
tx.executeSql(query, [], function(tx,res) {}, sqlErrorHandler(options.errorHandler)); // FIX<E: asyns error ?
|
||||||
|
}, sqlErrorHandler(options.errorHandler));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
cacheGetValue: function(key, cb) {
|
||||||
|
cacheDB.transaction(function(tx) {
|
||||||
|
var query = 'SELECT value FROM cache_store WHERE key = ?';
|
||||||
|
utilDebug('websql.cacheGetValue query: '+query);
|
||||||
|
tx.executeSql(query,[key], function(tx, res) {
|
||||||
|
if (res.rows.length === 0) {
|
||||||
|
cb(null, null);
|
||||||
|
} else {
|
||||||
|
var value = res.rows.item(0).value;
|
||||||
|
cb(null, JSON.parse(value));
|
||||||
|
}
|
||||||
|
}, sqlErrorHandler(cb));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cacheSetValue: function(key, value, cb) {
|
||||||
|
cacheDB.transaction(function(tx) {
|
||||||
|
var query = 'SELECT value FROM cache_store WHERE key = ?';
|
||||||
|
utilDebug('websql.cacheSetValue query: '+query);
|
||||||
|
tx.executeSql(query,[key], function(tx, res) {
|
||||||
|
if (res.rows.length === 0) {
|
||||||
|
var query = 'INSERT INTO cache_store (key,value) VALUES (?,?)';
|
||||||
|
utilDebug('websql.cacheSetValue query: '+query);
|
||||||
|
tx.executeSql(query, [key,JSON.stringify(value)], nullDataHandler(cb), sqlErrorHandler(cb));
|
||||||
|
} else {
|
||||||
|
var query = 'UPDATE cache_store SET value = ? WHERE key = ?';
|
||||||
|
utilDebug('websql.cacheSetValue query: '+query);
|
||||||
|
tx.executeSql(query, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb));
|
||||||
|
}
|
||||||
|
}, sqlErrorHandler(cb));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
cacheDeleteValue: function(key, cb) {
|
||||||
|
cacheDB.transaction(function(tx) {
|
||||||
|
var query = 'DELETE FROM cache_store WHERE key = ?';
|
||||||
|
utilDebug('websql.cacheDeleteValue query: '+query);
|
||||||
|
tx.executeSql(query, [key], nullDataHandler(cb), sqlErrorHandler(cb));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default error handler which renders the error in the browser.
|
||||||
|
* @param {String|Error} err The error message.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var utilErrorHandler = function(err) {
|
||||||
|
utilDebug('utilErrorHandler error: '+err);
|
||||||
|
|
||||||
|
var rootTag = document.createElement('div');
|
||||||
|
rootTag.setAttribute('class','ffError');
|
||||||
|
|
||||||
|
var cssTag = document.createElement("style");
|
||||||
|
cssTag.type = "text/css";
|
||||||
|
cssTag.innerHTML = '.ffError { margin: auto;width: 50%;border: 3px solid red;padding: 1em;} .ffAskUrl > div {font-size: 0.8em;color: red;}';
|
||||||
|
rootTag.appendChild(cssTag);
|
||||||
|
|
||||||
|
var titleTag = document.createElement('h1');
|
||||||
|
titleTag.appendChild(document.createTextNode('Error'));
|
||||||
|
rootTag.appendChild(titleTag);
|
||||||
|
|
||||||
|
var questionTag = document.createElement('p');
|
||||||
|
questionTag.appendChild(document.createTextNode(err));
|
||||||
|
rootTag.appendChild(questionTag);
|
||||||
|
|
||||||
|
document.getElementsByTagName('body')[0].appendChild(rootTag);
|
||||||
|
};
|
||||||
|
options.errorHandler = utilErrorHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches an url resource with a XMLHttpRequest.
|
||||||
|
* @param {String} url The url to fetch.
|
||||||
|
* @param {function} cb The callback for error and request if ready.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
var utilHttpFetch = function (url, cb) {
|
var utilHttpFetch = function (url, cb) {
|
||||||
var startTime = new Date().getTime();
|
var startTime = new Date().getTime();
|
||||||
var httpRequest = new XMLHttpRequest();
|
var httpRequest = new XMLHttpRequest();
|
||||||
httpRequest.onreadystatechange = function() {
|
httpRequest.onreadystatechange = function() {
|
||||||
if (httpRequest.readyState == 4) {
|
if (httpRequest.readyState == 4 && httpRequest.status === 200) {
|
||||||
utilDebug('utilHttpFetch url: '+url+' done in '+(new Date().getTime()-startTime)+' ms.');
|
utilDebug('utilHttpFetch url: '+url+' done in '+(new Date().getTime()-startTime)+' ms.');
|
||||||
cb(httpRequest);
|
cb(null, httpRequest);
|
||||||
|
} else if (httpRequest.readyState == 4) {
|
||||||
|
cb('Wrong status: '+httpRequest.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
httpRequest.open('GET', url, true);
|
httpRequest.open('GET', url, true);
|
||||||
|
httpRequest.timeout = options.server.timeout; // ieX: after open()
|
||||||
|
httpRequest.ontimeout = function() {
|
||||||
|
cb('timeout after '+options.server.timeout+' of url: '+url);
|
||||||
|
};
|
||||||
httpRequest.send();
|
httpRequest.send();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var cacheGetService = function (type) {
|
||||||
|
if (options.cache[type]) {
|
||||||
|
return options.cache[type];
|
||||||
|
}
|
||||||
|
if (options.cache['factory']) {
|
||||||
|
return options.cache['factory'];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
var cacheHasItem = function (resource) {
|
var cacheHasService = function (type) {
|
||||||
if (options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined) {
|
return cacheGetService(type) !== null;
|
||||||
return false;
|
};
|
||||||
}
|
|
||||||
var result = options.cacheGetFn(options.cachePrefix + resource.hash) !== null;
|
var cacheCheckType = function (type, cb, action) {
|
||||||
utilDebug('cacheHasItem resource: '+resource.url+' result: '+result);
|
cacheHasService(type)?action():cb('No caching for: '+type);
|
||||||
return result;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
var cacheGetItem = function (resource) {
|
var cacheGetValue = function(type, key , cb) {
|
||||||
if (options.cacheGetFn === undefined) {
|
cacheCheckType(type, cb, function() {
|
||||||
throw new Error('no caching');
|
var cacheKey = type+'_'+key;
|
||||||
}
|
utilDebug('cacheGetValue key: '+cacheKey);
|
||||||
utilDebug('cacheGetItem resource: '+resource.url);
|
cacheGetService(type).cacheGetValue(cacheKey,cb);
|
||||||
return JSON.parse(options.cacheGetFn(options.cachePrefix + resource.hash));
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
var cachePutRequest = function (resource, httpRequest) {
|
var cacheSetValue = function(type, key, value, cb) {
|
||||||
if (options.cacheSetFn === undefined) {
|
cacheCheckType(type, cb, function() {
|
||||||
throw new Error('no caching');
|
var cacheKey = type+'_'+key;
|
||||||
}
|
utilDebug('cacheSetValue key: '+cacheKey);
|
||||||
var cacheKey = options.cachePrefix + resource.hash;
|
cacheGetService(type).cacheSetValue(cacheKey,value,cb);
|
||||||
var data = httpRequest.responseText;
|
});
|
||||||
utilDebug('cachePutData url: '+resource.url+' key: '+cacheKey);
|
};
|
||||||
options.cacheSetFn(cacheKey, JSON.stringify({
|
|
||||||
resource: resource,
|
|
||||||
data: data,
|
|
||||||
dataDate: new Date().getTime()
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Add all cache keys in central list so we can clear the cache item if resources are removed.
|
|
||||||
var cacheListKey = options.cachePrefix + 'cache-list';
|
|
||||||
var cacheListStr = options.cacheGetFn(cacheListKey);
|
|
||||||
if (cacheListStr === null) {
|
|
||||||
cacheListStr = '[]';
|
|
||||||
}
|
|
||||||
var cacheList = JSON.parse(cacheListStr);
|
|
||||||
cacheList.push(cacheKey);
|
|
||||||
options.cacheSetFn(cacheListKey, JSON.stringify(cacheList));
|
|
||||||
}
|
|
||||||
|
|
||||||
var cacheCleanup = function (resources) {
|
var cacheDeleteValue = function(type, key, cb) {
|
||||||
if (options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined) {
|
cacheCheckType(type, cb, function() {
|
||||||
return;
|
var cacheKey = type+'_'+key;
|
||||||
}
|
utilDebug('cacheDeleteValue key: '+cacheKey);
|
||||||
var cacheListKey = options.cachePrefix + 'cache-list';
|
cacheGetService(type).cacheDeleteValue(cacheKey,cb);
|
||||||
var cacheListStr = options.cacheGetFn(cacheListKey);
|
});
|
||||||
if (cacheListStr === null) {
|
};
|
||||||
return;
|
|
||||||
}
|
var cleanupCache = function (resources, cb) {
|
||||||
var cacheList = JSON.parse(cacheListStr);
|
utilDebug('cleanupCache TODO cacheList: '+0+' resources: '+resources.length);
|
||||||
|
|
||||||
utilDebug('cacheCleanup TODO cacheList: '+cacheList.length+' resources: '+resources.length);
|
|
||||||
|
|
||||||
// TODO: impl for removes in resource lists
|
// TODO: impl for removes in resource lists
|
||||||
}
|
cb(null);
|
||||||
|
};
|
||||||
|
|
||||||
var createResource = function (resource, cb) {
|
var injectResourceData = function(resource, data, cb) {
|
||||||
utilDebug('createResource url: '+JSON.stringify(resource));
|
utilDebug('injectResourceData resource: '+JSON.stringify(resource)+' data: '+data.length);
|
||||||
if (cacheHasItem(resource)) {
|
var tag = null;
|
||||||
if (cacheGetItem(resource).resource.hash === resource.hash) {
|
if (resource.type === 'css' || resource.type === 'cssData') {
|
||||||
cb();
|
tag = document.createElement('style');
|
||||||
|
tag.type = 'text/css';
|
||||||
|
}
|
||||||
|
if (resource.type === 'js') {
|
||||||
|
tag = document.createElement('script');
|
||||||
|
tag.type = 'text/javascript';
|
||||||
|
}
|
||||||
|
tag.appendChild(document.createTextNode(data));
|
||||||
|
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(tag);
|
||||||
|
//var ref = document.getElementsByTagName('script')[0];
|
||||||
|
//ref.parentNode.insertBefore(tag, ref); // note in reverse order
|
||||||
|
cb(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
var storeResourceKey = function (resource, cb) {
|
||||||
|
// Add all cache keys in central list so we can clear the cache item if resources are removed.
|
||||||
|
var cacheKey = resource.type+'_keys';
|
||||||
|
cacheGetValue('meta',cacheKey,function (err,value) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
utilDebug('createResource hash mismatch request fetch: '+resource.url);
|
if (value === null) {
|
||||||
} else {
|
value = [];
|
||||||
utilDebug('createResource cache miss request fetch: '+resource.url);
|
|
||||||
}
|
|
||||||
utilHttpFetch(resource.url, function(httpRequest) {
|
|
||||||
if (httpRequest.status == 200) {
|
|
||||||
cachePutRequest(resource, httpRequest);
|
|
||||||
} else {
|
|
||||||
console.warn('error loading '+resource.url);
|
|
||||||
}
|
}
|
||||||
cb();
|
value.push(resource.hash);
|
||||||
|
cacheSetValue('meta',cacheKey,value, cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var createResources = function(resources, cb) {
|
var storeResource = function (resource, httpRequest, cb) {
|
||||||
|
utilDebug('storeResource url: '+resource.url+' hash: '+resource.hash);
|
||||||
|
var item = {
|
||||||
|
resource: resource,
|
||||||
|
data: httpRequest.responseText
|
||||||
|
}
|
||||||
|
cacheSetValue(resource.type, resource.hash, item , function(err) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
storeResourceKey(resource,cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var loadResource = function (resource, cb) {
|
||||||
|
var resourceUrl = resource.url;
|
||||||
|
if (resourceUrl.indexOf('http') === -1) {
|
||||||
|
resourceUrl = options.server.url + resourceUrl;
|
||||||
|
}
|
||||||
|
utilDebug('loadResource '+JSON.stringify(resource));
|
||||||
|
if (cacheHasService(resource.type)) {
|
||||||
|
cacheGetValue(resource.type,resource.hash,function(err, value) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value === null) {
|
||||||
|
utilDebug('loadResource cache miss');
|
||||||
|
} else if (value.resource === undefined) {
|
||||||
|
utilDebug('loadResource cache wrong obj');
|
||||||
|
} else if (value.resource.hash !== resource.hash) {
|
||||||
|
utilDebug('loadResource cache hash mismatch');
|
||||||
|
} else {
|
||||||
|
utilDebug('loadResource cache hit');
|
||||||
|
injectResourceData(value.resource,value.data,cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
utilHttpFetch(resourceUrl, function(err, httpRequest) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
storeResource(resource, httpRequest, function (err) {
|
||||||
|
injectResourceData(resource,httpRequest.responseText,cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// note: was links but now download + inject so order stays sequenced
|
||||||
|
utilHttpFetch(resourceUrl, function(err, httpRequest) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
injectResourceData(resource,httpRequest.responseText,cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var loadResources = function(resources, cb) {
|
||||||
var startTime = new Date().getTime();
|
var startTime = new Date().getTime();
|
||||||
utilDebug('createResources');
|
utilDebug('loadResources');
|
||||||
var resourceStack = resources;
|
var resourceStack = resources;
|
||||||
var resourceLoader = function() {
|
var resourceLoader = function(err) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
resourceStack = resourceStack.slice(1);
|
resourceStack = resourceStack.slice(1);
|
||||||
if (resourceStack.length === 0) {
|
if (resourceStack.length === 0) {
|
||||||
utilDebug('createResources done in '+(new Date().getTime()-startTime)+' ms.');
|
utilDebug('loadResources done in '+(new Date().getTime()-startTime)+' ms.');
|
||||||
cb();
|
cb(null);
|
||||||
} else {
|
} else {
|
||||||
createResource(resourceStack[0],resourceLoader);
|
loadResource(resourceStack[0],resourceLoader);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
createResource(resourceStack[0],resourceLoader);
|
loadResource(resourceStack[0],resourceLoader);
|
||||||
}
|
};
|
||||||
|
|
||||||
var injectResources = function(resources, cb) {
|
var injectResources = function(resources, cb) {
|
||||||
var startTime = new Date().getTime();
|
var startTime = new Date().getTime();
|
||||||
utilDebug('injectResources');
|
utilDebug('injectResources');
|
||||||
resources.forEach(function (resource) {
|
resources.forEach(function (resource) {
|
||||||
var item = cacheGetItem(resource);
|
cacheGetValue(resource.type,resource.hash,function(err,item) {
|
||||||
var tag = null;
|
// TODO injectResourceData
|
||||||
if (resource.type === 'css') {
|
|
||||||
tag = document.createElement('style');
|
var tag = null;
|
||||||
tag.type = 'text/css';
|
if (resource.type === 'css' || resource.type === 'cssData') {
|
||||||
} else {
|
tag = document.createElement('style');
|
||||||
tag = document.createElement('script');
|
tag.type = 'text/css';
|
||||||
tag.type = 'text/javascript';
|
}
|
||||||
}
|
if (resource.type === 'js') {
|
||||||
utilDebug('injectResources resource: '+JSON.stringify(resource));
|
tag = document.createElement('script');
|
||||||
|
tag.type = 'text/javascript';
|
||||||
tag.appendChild(document.createTextNode(item.data));
|
}
|
||||||
|
utilDebug('injectResources resource: '+JSON.stringify(resource));
|
||||||
//document.getElementsByTagName('head')[0].appendChild(tag);
|
|
||||||
var ref = document.getElementsByTagName('script')[0];
|
tag.appendChild(document.createTextNode(item.data));
|
||||||
ref.parentNode.insertBefore(tag, ref); // note in reverse order
|
|
||||||
|
//document.getElementsByTagName('head')[0].appendChild(tag);
|
||||||
|
var ref = document.getElementsByTagName('script')[0];
|
||||||
|
ref.parentNode.insertBefore(tag, ref); // note in reverse order
|
||||||
|
});
|
||||||
});
|
});
|
||||||
utilDebug('injectResources done in '+(new Date().getTime()-startTime)+' ms.');
|
utilDebug('injectResources done in '+(new Date().getTime()-startTime)+' ms.');
|
||||||
cb();
|
cb(null); // FIXME async
|
||||||
}
|
};
|
||||||
|
|
||||||
var injectLinkResources = function(resources, cb) {
|
/**
|
||||||
var startTime = new Date().getTime();
|
* Starts loader by downloading resource list or using cache version to
|
||||||
utilDebug('injectLinkResources');
|
* load/refresh/inject the resources.
|
||||||
resources.forEach(function (resource) {
|
*
|
||||||
utilDebug('injectLinkResources resource: '+JSON.stringify(resource));
|
* @param {function} cb Callback gets called when loader is done.
|
||||||
var tag = null;
|
* @private
|
||||||
if (resource.type === 'css') {
|
*/
|
||||||
tag = document.createElement('link');
|
var startLoader = function (cb) {
|
||||||
tag.type = 'text/css';
|
if (options.server.url === null) {
|
||||||
tag.rel = 'stylesheet';
|
cb('Missing options.server.url');
|
||||||
tag.href = resource.url;
|
return;
|
||||||
}
|
|
||||||
if (resource.type === 'js') {
|
|
||||||
tag = document.createElement('script');
|
|
||||||
tag.type = 'text/javascript';
|
|
||||||
tag.src = resource.url;
|
|
||||||
}
|
|
||||||
if (tag !== null) {
|
|
||||||
var startTime2 = new Date().getTime();
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(tag);
|
|
||||||
utilDebug('inject time in '+(new Date().getTime()-startTime2)+' ms.');
|
|
||||||
//var ref = document.getElementsByTagName('script')[0];
|
|
||||||
//ref.parentNode.insertBefore(tag, ref); // TODO reverser order
|
|
||||||
} else {
|
|
||||||
utilDebug('unknow resource type: '+resource.type);
|
|
||||||
// TODO add err
|
|
||||||
}
|
|
||||||
});
|
|
||||||
utilDebug('injectLinkResources done in '+(new Date().getTime()-startTime)+' ms.');
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
|
|
||||||
var startDefaults = function () {
|
|
||||||
if (options.url === undefined) {
|
|
||||||
throw new Error('No url defined');
|
|
||||||
}
|
}
|
||||||
if (options.debug === undefined) {
|
if (options.server.urlPath === null) {
|
||||||
options.debug = false;
|
cb('Missing options.server.urlPath');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (options.cachePrefix === undefined) {
|
window[options.server.windowFlag] = options.server.url;
|
||||||
options.cachePrefix = 'sync-';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var startDone = function (startTime) {
|
|
||||||
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
|
|
||||||
if (options.resultFn) {
|
|
||||||
options.resultFn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var start = function (opt) {
|
|
||||||
if (opt === undefined) {
|
|
||||||
throw new Error('need options');
|
|
||||||
}
|
|
||||||
var startTime = new Date().getTime();
|
|
||||||
options = opt;
|
|
||||||
startDefaults();
|
|
||||||
var injectAsLink = options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined;
|
|
||||||
utilDebug('start options: '+JSON.stringify(options)+' injectAsLink: '+injectAsLink);
|
|
||||||
|
|
||||||
|
var resourcesUrl = options.server.url + options.server.urlPath;
|
||||||
|
utilDebug('startLoader serverUrl: '+resourcesUrl);
|
||||||
var resources = [];
|
utilHttpFetch(resourcesUrl, function(err, httpRequest) {
|
||||||
utilHttpFetch(options.url, function(httpRequest) {
|
if (err !== null) {
|
||||||
if (httpRequest.status == 200) {
|
utilDebug('startLoader fetch error: '+err);
|
||||||
JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) {
|
if (cacheHasService('meta')) {
|
||||||
resources.push(r);
|
cacheGetValue('meta','server_resources',function(err, value) {
|
||||||
utilDebug('build resources add: '+JSON.stringify(r));
|
if (err !== null) {
|
||||||
});
|
cb(err);
|
||||||
if (injectAsLink) {
|
} else if (value === null) {
|
||||||
injectLinkResources(resources, function () {
|
cb('Have no cache of server resouces from: '+options.server.url);
|
||||||
startDone(startTime);
|
} else {
|
||||||
|
injectResources(value, cb);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
createResources(resources, function () {
|
cb('Could not fetch server resouces from: '+options.server.url);
|
||||||
injectResources(resources, function () {
|
|
||||||
cacheCleanup(resources);
|
|
||||||
startDone(startTime);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var resources = [];
|
||||||
|
JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) {
|
||||||
|
resources.push(r);
|
||||||
|
});
|
||||||
|
utilDebug('startLoader resources: '+resources.length);
|
||||||
|
if (cacheHasService('meta')) {
|
||||||
|
cacheSetValue('meta','server_resources',resources, function (err) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
loadResources(resources, cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('app err'); // TODO: fix async load error code path
|
loadResources(resources, cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the user input url by downloading it.
|
||||||
|
*
|
||||||
|
* @param {function} cb Callback gets called when loader is done.
|
||||||
|
* @param {Element} deleteTag The dom element to remove from the body tag after success.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var askUrlValidate = function (cb,deleteTag) {
|
||||||
|
var inputTag = document.getElementById('serverInput');
|
||||||
|
var inputErrorTag = document.getElementById('serverInputError');
|
||||||
|
|
||||||
|
while (inputErrorTag.firstChild) {
|
||||||
|
inputErrorTag.removeChild(inputErrorTag.firstChild); // clear error
|
||||||
|
}
|
||||||
|
|
||||||
|
options.server.url = '';
|
||||||
|
if (options.askUrl.urlTransport !== undefined) {
|
||||||
|
options.server.url += options.askUrl.urlTransport;
|
||||||
|
}
|
||||||
|
options.server.url += inputTag.value;
|
||||||
|
|
||||||
|
var resourcesUrl = options.server.url + options.server.urlPath;
|
||||||
|
utilDebug('askUrlStart check url: '+resourcesUrl);
|
||||||
|
|
||||||
|
// TODO: add+check headers
|
||||||
|
utilHttpFetch(resourcesUrl,function(err, req) {
|
||||||
|
if (err !== null) {
|
||||||
|
inputErrorTag.appendChild(document.createTextNode('Error could not get data.'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementsByTagName('body')[0].removeChild(deleteTag);
|
||||||
|
if (cacheHasService('meta')) {
|
||||||
|
cacheSetValue('meta','server_url',options.server.url, function(err) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
} else {
|
||||||
|
startLoader(cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
startLoader(cb);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
|
||||||
var getOrAskUrl = function (opt) {
|
/**
|
||||||
|
* Creates the ask url ui.
|
||||||
|
*
|
||||||
|
* @param {function} cb Callback gets called when loader is done.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
var askUrl = function (cb) {
|
||||||
|
utilDebug('askUrl create ui');
|
||||||
|
|
||||||
|
var rootTag = document.createElement('div');
|
||||||
|
rootTag.setAttribute('class','ffAskUrl');
|
||||||
|
|
||||||
|
var cssTag = document.createElement("style");
|
||||||
|
cssTag.type = "text/css";
|
||||||
|
cssTag.innerHTML = options.askUrl.cssText;
|
||||||
|
rootTag.appendChild(cssTag);
|
||||||
|
|
||||||
|
var questionTag = document.createElement('p');
|
||||||
|
questionTag.appendChild(document.createTextNode(options.askUrl.questionText));
|
||||||
|
rootTag.appendChild(questionTag);
|
||||||
|
|
||||||
|
var formTag = document.createElement('div');
|
||||||
|
rootTag.appendChild(formTag);
|
||||||
|
|
||||||
|
if (options.urlTransport !== undefined) {
|
||||||
|
var transportTag = document.createElement('label');
|
||||||
|
rootTag.setAttribute('for','serverInput');
|
||||||
|
transportTag.appendChild(document.createTextNode(options.urlTransport));
|
||||||
|
formTag.appendChild(transportTag);
|
||||||
|
}
|
||||||
|
var inputTag = document.createElement('input');
|
||||||
|
inputTag.type = 'text';
|
||||||
|
inputTag.id = 'serverInput';
|
||||||
|
inputTag.setAttribute('autofocus','');
|
||||||
|
inputTag.setAttribute('onkeydown','if (event.keyCode == 13) {document.getElementById(\'serverSubmit\').click()}');
|
||||||
|
formTag.appendChild(inputTag);
|
||||||
|
|
||||||
|
var submitLineTag = document.createElement('br');
|
||||||
|
formTag.appendChild(submitLineTag);
|
||||||
|
|
||||||
|
var submitTag = document.createElement('input');
|
||||||
|
submitTag.id = 'serverSubmit';
|
||||||
|
submitTag.type = 'submit';
|
||||||
|
submitTag.value = 'Start';
|
||||||
|
submitTag.onclick = function() {askUrlValidate(cb,rootTag);};
|
||||||
|
formTag.appendChild(submitTag);
|
||||||
|
|
||||||
|
var serverErrorTag = document.createElement('div');
|
||||||
|
serverErrorTag.id = 'serverInputError';
|
||||||
|
serverErrorTag.setAttribute('class','ffAskUrlError');
|
||||||
|
formTag.appendChild(serverErrorTag);
|
||||||
|
|
||||||
|
document.getElementsByTagName('body')[0].appendChild(rootTag);
|
||||||
};
|
};
|
||||||
// TODO: getOrAskUrl: getOrAskUrl,
|
|
||||||
|
|
||||||
return {
|
/**
|
||||||
start: start
|
* Starts the loader.
|
||||||
};
|
*
|
||||||
})();
|
* @param {function} cb Callback gets called when loader is done.
|
||||||
|
*/
|
||||||
var autoStartFFSpaLoader = function () {
|
var start = function (cbArgu) {
|
||||||
var scripts = document.getElementsByTagName('SCRIPT');
|
var startTime = new Date().getTime();
|
||||||
if (scripts && scripts.length>0) {
|
var cb = function(err) {
|
||||||
for (var i in scripts) {
|
if (err !== null) {
|
||||||
if (scripts[i].src && scripts[i].src.match(/.*ffSpaLoaderUrl.*/)) {
|
options.errorHandler(err);
|
||||||
var query = scripts[i].src;
|
} else {
|
||||||
var queryParameters = {};
|
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
|
||||||
query.split('?').forEach(function(part) {
|
if (typeof cbArgu === "function") {
|
||||||
var item = part.split("=");
|
cbArgu();
|
||||||
queryParameters[item[0]] = decodeURIComponent(item[1]);
|
}
|
||||||
});
|
|
||||||
// TODO: remove debug here
|
|
||||||
FFSpaLoader.start({
|
|
||||||
debug: true,
|
|
||||||
injectAsLink: true,
|
|
||||||
url: queryParameters.ffSpaLoaderUrl
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
utilDebug('start serverUrl: '+options.server.url);
|
||||||
|
if (options.server.url !== null) {
|
||||||
|
startLoader(cb);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cacheHasService('meta')) {
|
||||||
|
cacheGetValue('meta','server_url',function(err, value) {
|
||||||
|
if (err !== null) {
|
||||||
|
cb(err);
|
||||||
|
} else if (value === undefined || value === null || value === '') {
|
||||||
|
askUrl(cb);
|
||||||
|
} else {
|
||||||
|
options.server.url = value;
|
||||||
|
startLoader(cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
askUrl(cb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for automatic selecting the best to none cache backend option.
|
||||||
|
*
|
||||||
|
* Order of detection; sqllite,openDatabase,localStorage,none.
|
||||||
|
*/
|
||||||
|
var autoSelectCache = function() {
|
||||||
|
if (factory.detect.cordovaDevice() && factory.detect.sqlitePlugin()) {
|
||||||
|
utilDebug('autoSelectCache sqlitePlugin');
|
||||||
|
return factory.cache.websql({openDatabase: window.sqlitePlugin});
|
||||||
|
}
|
||||||
|
if (factory.detect.openDatabase()) {
|
||||||
|
utilDebug('autoSelectCache openDatabase');
|
||||||
|
return factory.cache.websql();
|
||||||
|
}
|
||||||
|
if (factory.detect.localStorage()) {
|
||||||
|
utilDebug('autoSelectCache localStorage');
|
||||||
|
return factory.cache.localStorage();
|
||||||
|
}
|
||||||
|
utilDebug('autoSelectCache none');
|
||||||
|
return null; // no caching
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
/**
|
||||||
autoStartFFSpaLoader(); // TODO: make private
|
* Helper for cordova applications which want to use the sqllite plugin as cache.
|
||||||
|
* Note: On none cordova page it will callback directly.
|
||||||
|
* Note: On none cordova device page is will timeout.
|
||||||
|
*
|
||||||
|
* @param {function} cb Callback gets called device is ready to use.
|
||||||
|
*/
|
||||||
|
var bootDevice = function(cb) {
|
||||||
|
var startTime = new Date().getTime();
|
||||||
|
var bootOnce = function() {
|
||||||
|
if (typeof cb === "function") {
|
||||||
|
var callback = cb;
|
||||||
|
cb = null;
|
||||||
|
utilDebug('bootDevice done in '+(new Date().getTime()-startTime)+' ms.');
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (!factory.detect.cordova()) {
|
||||||
|
utilDebug('bootDevice webapp');
|
||||||
|
bootOnce();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
utilDebug('bootDevice timeout: '+options.bootDeviceTimeout);
|
||||||
|
setTimeout ( function () {
|
||||||
|
utilDebug('bootDevice timeout');
|
||||||
|
bootOnce();
|
||||||
|
}, options.bootDevice.timeout);
|
||||||
|
document.addEventListener("deviceready", function () {
|
||||||
|
window[options.bootDevice.windowFlag] = true;
|
||||||
|
utilDebug('bootDevice '+options.bootDevice.windowFlag);
|
||||||
|
bootOnce();
|
||||||
|
}, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: options,
|
||||||
|
factory: factory,
|
||||||
|
start: start,
|
||||||
|
bootDevice: bootDevice,
|
||||||
|
autoSelectCache: autoSelectCache
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"linkMapping" : {
|
|
||||||
"/static/module/bootstrap/": "node_modules/bootstrap/dist/",
|
|
||||||
"/static/module/flot/": "node_modules/flot/",
|
|
||||||
"/static/": "www_static/"
|
|
||||||
},
|
|
||||||
"css": {
|
|
||||||
"linkTarget": "/static/css/lib/assets.css",
|
|
||||||
"linkSources": [
|
|
||||||
"/static/module/bootstrap/css/bootstrap.css"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"js": {
|
|
||||||
"linkTarget": "/static/js/lib/assets.js",
|
|
||||||
"linkSources": [
|
|
||||||
"/static/js/lib/jquery-2.1.3/jquery.js@http://code.jquery.com/jquery-2.1.3.js",
|
|
||||||
"/static/module/bootstrap/js/bootstrap.js",
|
|
||||||
"/static/module/flot/jquery.flot.js",
|
|
||||||
"/static/module/flot/jquery.flot.resize.js",
|
|
||||||
"/static/module/flot/jquery.flot.pie.js",
|
|
||||||
"/static/js/lib/angularjs-1.4.0-b4/angular.js@https://code.angularjs.org/1.4.0-beta.4/angular.js",
|
|
||||||
"/static/js/lib/angularjs-1.4.0-b4/angular-route.js@https://code.angularjs.org/1.4.0-beta.4/angular-route.js",
|
|
||||||
"/static/js/lib/angularjs-1.4.0-b4/angular-resource.js@https://code.angularjs.org/1.4.0-beta.4/angular-resource.js",
|
|
||||||
"/static/js/lib/angularjs-1.4.0-b4/angular-touch.js@https://code.angularjs.org/1.4.0-beta.4/angular-touch.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +1,80 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var express = require('express');
|
var express = require('express');
|
||||||
var async = require('async');
|
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var assets = require('node-ff-assets');
|
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
var minify = require('minify');
|
var minify = require('minify');
|
||||||
|
var fetch = require('fetch');
|
||||||
|
var cors = require('cors');
|
||||||
|
|
||||||
function buildAssets(server,callbackDone) {
|
// example options;
|
||||||
var singleResult = 'false' !== process.env.DEV_ASSETS_SINGLE_RESULT;
|
var serverUrl = 'http://localhost:8080';
|
||||||
var assetsConfig = require('./example-assets.json');
|
var useInline = true; // or false
|
||||||
assets.build({
|
|
||||||
assets: {
|
var clientResourcesWeb = [];
|
||||||
js: {
|
var clientResources = {
|
||||||
configCreate: assets.factory.builder.configCreate.fromJSON(assetsConfig,'js'),
|
js: [],
|
||||||
configFill: function (config, callback) {
|
css: [],
|
||||||
async.series([
|
cssData: []
|
||||||
assets.factory.lib.async.pushLinkSources(config, '/static/js/', 'www_static/js/'),
|
|
||||||
assets.factory.lib.async.pushLinkSources(config, '/static/js/controller/', 'www_static/js/controller/'),
|
|
||||||
],callback);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
css: {
|
|
||||||
configCreate: assets.factory.builder.configCreate.fromJSON(assetsConfig,'css'),
|
|
||||||
configFill: function (config, callback) {
|
|
||||||
async.series([
|
|
||||||
assets.factory.lib.async.pushLinkSources(config, '/static/css/', 'www_static/css/'),
|
|
||||||
],callback);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
assemblerCreate: assets.factory.builder.assemblerCreate.readFileRegex(),
|
|
||||||
assemblerFill: function (assembler, callback) {
|
|
||||||
var serverResultKey = 'ff_assets_'+assembler.config.assetType;
|
|
||||||
assembler.on ('log', assets.factory.assembler.event.log.console(assembler.config.assetType));
|
|
||||||
assembler.on ('result', assets.factory.assembler.event.result.objectSet(server,serverResultKey));
|
|
||||||
assembler.config.linkTargetSingleResult = singleResult;
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
},callbackDone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var addClientResource = function(clientResource, resourceType) {
|
||||||
|
clientResources[resourceType].push(clientResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringHash = function (str) {
|
||||||
|
var hash = 31; // prime
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
hash = ((hash<<5)-hash)+str.charCodeAt(i);
|
||||||
|
hash = hash & hash; // keep 32b
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
var fetchHashResource = function(fetchEntry,cb) {
|
||||||
|
fetch.fetchUrl(serverUrl + fetchEntry.url,function(err, meta, data) {
|
||||||
|
if (err !== null) { return cb(err); }
|
||||||
|
console.log('fetched url: '+fetchEntry.url+' length: '+meta.responseHeaders['content-length']);
|
||||||
|
var assetHash = stringHash(''+data);
|
||||||
|
clientResourcesWeb.push({
|
||||||
|
url: fetchEntry.url,
|
||||||
|
type: fetchEntry.type,
|
||||||
|
hash: assetHash
|
||||||
|
});
|
||||||
|
cb(null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var fetchHashResources = function(fetchList, cb) {
|
||||||
|
var resourceStack = fetchList;
|
||||||
|
var resourceLoader = function() {
|
||||||
|
resourceStack = resourceStack.slice(1);
|
||||||
|
if (resourceStack.length === 0) {
|
||||||
|
cb(null);
|
||||||
|
} else {
|
||||||
|
fetchHashResource(resourceStack[0],resourceLoader);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchHashResource(resourceStack[0],resourceLoader);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createClientResourceFetchList = function() {
|
||||||
|
var fetchList = [];
|
||||||
|
for (var clientResourceIdx in clientResources.js) {
|
||||||
|
var url = clientResources.js[clientResourceIdx];
|
||||||
|
fetchList.push({url:url,type:'js'});
|
||||||
|
}
|
||||||
|
for (var clientResourceIdx in clientResources.css) {
|
||||||
|
var url = clientResources.css[clientResourceIdx];
|
||||||
|
fetchList.push({url:url,type:'css'});
|
||||||
|
}
|
||||||
|
for (var clientResourceIdx in clientResources.cssData) {
|
||||||
|
var url = clientResources.cssData[clientResourceIdx];
|
||||||
|
fetchList.push({url:url,type:'cssData'});
|
||||||
|
}
|
||||||
|
return fetchList;
|
||||||
|
};
|
||||||
|
|
||||||
function renderTemplatePath(viewPath) {
|
function renderTemplatePath(viewPath) {
|
||||||
return function (req, res) {
|
return function (req, res) {
|
||||||
res.locals.query = req.query;
|
res.locals.query = req.query;
|
||||||
|
@ -57,10 +91,7 @@ function renderIndex(server) {
|
||||||
minify(__dirname+'/../es5-ff-spa-loader.js', {}, function(err, data) {
|
minify(__dirname+'/../es5-ff-spa-loader.js', {}, function(err, data) {
|
||||||
inline = '\n\t\t<script>'+data+'</script>';
|
inline = '\n\t\t<script>'+data+'</script>';
|
||||||
});
|
});
|
||||||
var inlineNot = '\n\t\t<script src=\"/static/es5-ff-spa-loader.js\"></script>';
|
var inlineNot = '\n\t\t<script src=\"/static/es5-ff-spa-loader.js\"></script>';
|
||||||
|
|
||||||
var useInline = true; // or false
|
|
||||||
|
|
||||||
return function (req, res) {
|
return function (req, res) {
|
||||||
res.render('index', {
|
res.render('index', {
|
||||||
inlineNot: useInline?'':inlineNot,
|
inlineNot: useInline?'':inlineNot,
|
||||||
|
@ -69,80 +100,50 @@ function renderIndex(server) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendRedirect() {
|
|
||||||
return function (req, res) {
|
|
||||||
res.redirect('/example-ui');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var stringHash = function (str) {
|
|
||||||
var hash = 31; // prime
|
addClientResource('/static/module/jquery/jquery.js','js');
|
||||||
for (var i = 0; i < str.length; i++) {
|
addClientResource('/static/module/angular/angular.js','js');
|
||||||
hash = ((hash<<5)-hash)+str.charCodeAt(i);
|
addClientResource('/static/module/angular-route/angular-route.js','js');
|
||||||
hash = hash & hash; // keep 32b
|
addClientResource('/static/module/bootstrap/css/bootstrap.css','css');
|
||||||
}
|
addClientResource('/static/module/bootstrap/js/bootstrap.js','js');
|
||||||
return hash;
|
addClientResource('/static/css/boot.css','css');
|
||||||
};
|
addClientResource('/static/css/style.css','css');
|
||||||
|
addClientResource('/static/js/example-app.js','js');
|
||||||
|
addClientResource('/static/js/controller/page-bar.js','js');
|
||||||
|
addClientResource('/static/js/controller/page-foo.js','js');
|
||||||
|
addClientResource('/static/js/controller/page-index.js','js');
|
||||||
|
|
||||||
var server = express();
|
var server = express();
|
||||||
buildAssets(server,function(err) {
|
server.use(cors({credentials: true, origin: '*'}));
|
||||||
if (err) {
|
server.set('view engine', 'ejs');
|
||||||
throw err;
|
server.set('views', path.join(__dirname,'www_views'));
|
||||||
}
|
server.use('/static', express.static(path.join(__dirname,'www_static')));
|
||||||
console.info('Server assets done.');
|
server.use('/static/module/bootstrap', express.static(path.join(__dirname,'node_modules/bootstrap/dist')));
|
||||||
|
server.use('/static/module/jquery', express.static(path.join(__dirname,'node_modules/jquery/dist')));
|
||||||
server.set('view engine', 'ejs');
|
server.use('/static/module/angular', express.static(path.join(__dirname,'node_modules/angular')));
|
||||||
server.set('views', path.join(__dirname,'www_views'));
|
server.use('/static/module/angular-route', express.static(path.join(__dirname,'node_modules/angular-route')));
|
||||||
server.use('/static', express.static(path.join(__dirname,'www_static')));
|
|
||||||
server.use('/static/module/bootstrap', express.static(path.join(__dirname,'node_modules/bootstrap/dist')));
|
server.get('/static/es5-ff-spa-loader.js', function (req,res) {
|
||||||
server.use('/static/module/flot', express.static(path.join(__dirname,'node_modules/flot')));
|
res.write(fs.readFileSync(__dirname+'/../es5-ff-spa-loader.js', 'utf8'));
|
||||||
|
res.end();
|
||||||
server.get('/static/es5-ff-spa-loader.js', function (req,res) {
|
|
||||||
res.write(fs.readFileSync(__dirname+'/../es5-ff-spa-loader.js', 'utf8'));
|
|
||||||
res.end();
|
|
||||||
});
|
|
||||||
server.get('/static/resources-spa-mobile', function (req,res) {
|
|
||||||
var hashJs = stringHash(fs.readFileSync(__dirname+'/www_static/js/lib/assets.js', 'utf8'));
|
|
||||||
var hashCss = stringHash(fs.readFileSync(__dirname+'/www_static/css/lib/assets.css', 'utf8'));
|
|
||||||
res.json({
|
|
||||||
data: {
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
url: '/static/js/lib/assets.js----TODO',
|
|
||||||
type: 'js',
|
|
||||||
hash: hashJs
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
server.get('/static/resources-spa-web', function (req,res) {
|
|
||||||
var hashJs = stringHash(fs.readFileSync(__dirname+'/www_static/js/lib/assets.js', 'utf8'));
|
|
||||||
var hashCss = stringHash(fs.readFileSync(__dirname+'/www_static/css/lib/assets.css', 'utf8'));
|
|
||||||
res.json({
|
|
||||||
data: {
|
|
||||||
resources: [
|
|
||||||
{
|
|
||||||
url: '/static/js/lib/assets.js',
|
|
||||||
type: 'js',
|
|
||||||
hash: hashJs
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/static/css/lib/assets.css',
|
|
||||||
type: 'css',
|
|
||||||
hash: hashCss
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.get('/', sendRedirect());
|
|
||||||
server.get('/example-ui/thtml/*', renderTemplatePath('thtml/'));
|
|
||||||
server.get('/example-ui', renderIndex(server));
|
|
||||||
server.get('/example-ui/*', renderIndex(server)); // must be last; for HTML5 history
|
|
||||||
console.info('Server config done.');
|
|
||||||
|
|
||||||
server.listen(8080);
|
|
||||||
console.info('Server started on port 8080');
|
|
||||||
});
|
});
|
||||||
|
server.get('/static/spa-client-resources', function (req,res) {
|
||||||
|
res.json({data: {resources: clientResourcesWeb}});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.get('/', function (req, res) {res.redirect('/example-ui');});
|
||||||
|
server.get('/example-ui/thtml/*', renderTemplatePath('thtml/'));
|
||||||
|
server.get('/example-ui', renderIndex(server));
|
||||||
|
server.get('/example-ui/*', renderIndex(server)); // must be last; for HTML5 history
|
||||||
|
console.info('Server config done.');
|
||||||
|
|
||||||
|
server.listen(8080);
|
||||||
|
console.info('Server started on port 8080');
|
||||||
|
|
||||||
|
var res = createClientResourceFetchList();
|
||||||
|
fetchHashResources(res, function(err) {
|
||||||
|
if (err !== null) {console.log(err);};
|
||||||
|
console.log('Total assets build: '+clientResourcesWeb.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,14 @@
|
||||||
"start": "node example.js"
|
"start": "node example.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "1.2.x",
|
"angular": "^1.4.8",
|
||||||
|
"angular-route": "^1.4.8",
|
||||||
"bootstrap": "3.3.x",
|
"bootstrap": "3.3.x",
|
||||||
|
"cors": "^2.7.1",
|
||||||
"ejs": "2.3.x",
|
"ejs": "2.3.x",
|
||||||
"express": "4.11.x",
|
"express": "4.11.x",
|
||||||
"flot": "0.8.x",
|
"fetch": "0.3.x",
|
||||||
"minify": "^2.0.2",
|
"jquery": "^2.1.4",
|
||||||
"node-ff-assets": "^0.2.5"
|
"minify": "^2.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
|
|
||||||
.flot-chart {
|
|
||||||
display: block;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flot-chart-content {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
|
|
||||||
.panel-green {
|
|
||||||
border-color: #5cb85c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-green .panel-heading {
|
|
||||||
border-color: #5cb85c;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #5cb85c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-green a {
|
|
||||||
color: #5cb85c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-green a:hover {
|
|
||||||
color: #3d8b3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-red {
|
|
||||||
border-color: #d9534f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-red .panel-heading {
|
|
||||||
border-color: #d9534f;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #d9534f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-red a {
|
|
||||||
color: #d9534f;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-red a:hover {
|
|
||||||
color: #b52b27;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-yellow {
|
|
||||||
border-color: #f0ad4e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-yellow .panel-heading {
|
|
||||||
border-color: #f0ad4e;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #f0ad4e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-yellow a {
|
|
||||||
color: #f0ad4e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-yellow a:hover {
|
|
||||||
color: #df8a13;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
pageRouteInit.push(function ($routeProvider, $locationProvider) {
|
pageRouteInit.push(function ($routeProvider, $locationProvider) {
|
||||||
$routeProvider.when('/example-ui/bar', {
|
$routeProvider.when('/example-ui/bar', {
|
||||||
templateUrl: '/example-ui/thtml/bar',
|
templateUrl: window.serverUrl+'/example-ui/thtml/bar',
|
||||||
controller: PageFoo
|
controller: PageFoo
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
pageRouteInit.push(function ($routeProvider, $locationProvider) {
|
pageRouteInit.push(function ($routeProvider, $locationProvider) {
|
||||||
$routeProvider.when('/example-ui/foo', {
|
$routeProvider.when('/example-ui/foo', {
|
||||||
templateUrl: '/example-ui/thtml/foo',
|
template: '<div><h2>Foo</h2><p>Welcome to the foo.</p></div>',
|
||||||
controller: PageFoo
|
controller: PageFoo
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
pageRouteInit.push(function ($routeProvider, $locationProvider) {
|
pageRouteInit.push(function ($routeProvider, $locationProvider) {
|
||||||
$routeProvider.when('/example-ui', {
|
$routeProvider.when('/example-ui', {
|
||||||
templateUrl: '/example-ui/thtml/index',
|
template: '<div><h2>Example UI Index</h2><p>Welcome make yourself at home.</p></div>',
|
||||||
controller: PageIndex
|
controller: PageIndex
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
document.title = 'FF-Spa-Loader Example';
|
document.title = 'FF-Spa-Loader Example';
|
||||||
$('html').attr('ng-app', 'exampleUI');
|
|
||||||
$('html').attr('lang', 'en');
|
var serverUrl = window.FFServerUrl;
|
||||||
|
console.log('FF provided serverUrl: '+serverUrl);
|
||||||
|
|
||||||
$(document.createElement('div')).attr('id', 'wrapper').appendTo($('body'));
|
$(document.createElement('div')).attr('id', 'wrapper').appendTo($('body'));
|
||||||
$(document.createElement('div')).attr('ng-include', '\'/example-ui/thtml/layout/header\'').appendTo($('#wrapper'));
|
$(document.createElement('div')).attr('ng-include', '\''+serverUrl+'/example-ui/thtml/header\'').appendTo($('#wrapper'));
|
||||||
$(document.createElement('div')).attr('id', 'page-wrapper').appendTo($('#wrapper'));
|
$(document.createElement('div')).attr('id', 'page-wrapper').appendTo($('#wrapper'));
|
||||||
$(document.createElement('div')).attr('id', 'container-fluid').attr('ng-view', '').appendTo($('#page-wrapper'));
|
$(document.createElement('div')).attr('id', 'container-fluid').attr('ng-view', '').appendTo($('#page-wrapper'));
|
||||||
$(document.createElement('div')).attr('ng-include', '\'/example-ui/thtml/layout/footer\'').appendTo($('body'));
|
$(document.createElement('div')).attr('ng-include', '\''+serverUrl+'/example-ui/thtml/footer\'').appendTo($('body'));
|
||||||
|
|
||||||
var pageRouteInit = [];
|
var pageRouteInit = [];
|
||||||
angular.module('exampleUI', ['ngRoute']).
|
angular.module('exampleUI', ['ngRoute']).config(
|
||||||
config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
|
['$routeProvider', '$locationProvider', function
|
||||||
for (var i = 0; i < pageRouteInit.length; i++) {
|
($routeProvider, $locationProvider) {
|
||||||
pageRouteInit[i]($routeProvider, $locationProvider);
|
|
||||||
}
|
pageRouteInit.forEach(function(init) { init($routeProvider, $locationProvider); });
|
||||||
$routeProvider.otherwise({ redirectTo: '/example-ui' });
|
$routeProvider.otherwise({ redirectTo: '/example-ui' });
|
||||||
$locationProvider.html5Mode({enabled: true, requireBase: false});
|
$locationProvider.html5Mode({enabled: true, requireBase: false});
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<title>Loading</title><%- inlineNot %>
|
<title>Loading</title><%- inlineNot %>
|
||||||
</head>
|
</head>
|
||||||
<body><%- inline %>
|
<body><%- inline %>
|
||||||
<script>
|
<script>
|
||||||
FFSpaLoader.start({
|
FFSpaLoader.options.debug = true;
|
||||||
url: '/static/resources-spa-web',
|
FFSpaLoader.options.server.urlPath = '/static/spa-client-resources';
|
||||||
debug: true,
|
FFSpaLoader.options.cache.factory = FFSpaLoader.autoSelectCache();
|
||||||
cacheGetFn: function(key) { return localStorage.getItem(key); },
|
FFSpaLoader.start(function() {
|
||||||
cacheSetFn: function(key,value) { localStorage.setItem(key,value); },
|
console.log('FFExample.bootstrap angular');
|
||||||
cacheDelFn: function(key) { return localStorage.removeItem(key); }
|
angular.bootstrap( $('body'), ['exampleUI']);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,32 +1,7 @@
|
||||||
<div>
|
<div><h2>Bar</h2><p>Welcome to the bar.</p><div class="table-responsive">
|
||||||
<h2>Bar</h2>
|
<table class="table table-bordered table-hover table-striped"><tbody>
|
||||||
<p>Welcome to the bar.</p>
|
<tr><th>Chair</th><th>Person</th><th>Drinking</th></tr></tbody><tbody>
|
||||||
<div class="table-responsive">
|
<tr><td>seat 1</td><td>empty</td><td>none</td></tr>
|
||||||
<table class="table table-bordered table-hover table-striped">
|
<tr><td>seat 2</td><td>you</td><td>coffee</td></tr>
|
||||||
<tbody>
|
<tr><td>seat 3</td><td>cat</td><td>water</td></tr>
|
||||||
<tr>
|
</tbody></table></div></div>
|
||||||
<th>Chair</th>
|
|
||||||
<th>Person</th>
|
|
||||||
<th>Drinking</th>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>seat 1</td>
|
|
||||||
<td>empty</td>
|
|
||||||
<td>none</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>seat 2</td>
|
|
||||||
<td>you</td>
|
|
||||||
<td>coffee</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>seat 3</td>
|
|
||||||
<td>cat</td>
|
|
||||||
<td>water</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,4 +0,0 @@
|
||||||
<div>
|
|
||||||
<h2>Foo</h2>
|
|
||||||
<p>Welcome to the foo.</p>
|
|
||||||
</div>
|
|
|
@ -1,4 +0,0 @@
|
||||||
<div>
|
|
||||||
<h2>Example UI Index</h2>
|
|
||||||
<p>Welcome make yourself at home.</p>
|
|
||||||
</div>
|
|
23
package.json
23
package.json
|
@ -4,10 +4,29 @@
|
||||||
"description": "Javascript Single Page Application Loader",
|
"description": "Javascript Single Page Application Loader",
|
||||||
"main": "es5-ff-spa-loader.js",
|
"main": "es5-ff-spa-loader.js",
|
||||||
"author": "Willem <willem.git.2016@forwardfire.net> (http://forwardfire.net/)",
|
"author": "Willem <willem.git.2016@forwardfire.net> (http://forwardfire.net/)",
|
||||||
"keywords": ["website","assets","js","css","loader","spa","mobile","resources","sync","cache"],
|
"keywords": [
|
||||||
|
"website",
|
||||||
|
"assets",
|
||||||
|
"js",
|
||||||
|
"css",
|
||||||
|
"loader",
|
||||||
|
"spa",
|
||||||
|
"mobile",
|
||||||
|
"resources",
|
||||||
|
"sync",
|
||||||
|
"cache"
|
||||||
|
],
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://bitbucket.org/im_ik/es5-ff-spa-loader.git"
|
"url": "https://bitbucket.org/im_ik/es5-ff-spa-loader.git"
|
||||||
}
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jsdoc": "^3.4.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"README.md",
|
||||||
|
"es5-ff-spa-loader.js",
|
||||||
|
"example/"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue