2
0
Fork 0

Fixed websql init async,cleanup options,add tested browsers

This commit is contained in:
Willem 2016-01-16 18:24:37 +01:00
parent ec4e9a0dae
commit e5e2dfc56f
5 changed files with 261 additions and 182 deletions

View file

@ -13,29 +13,23 @@ A javascript library providing server defined loading of resources for a single
Single index.html Single index.html
FFSpaLoader.options.debug = true; FFSpaLoader.options.debug.enable = true;
FFSpaLoader.options.boot.angular.modules.push('exampleUI');
FFSpaLoader.options.server.url = 'http://myserver'; FFSpaLoader.options.server.url = 'http://myserver';
FFSpaLoader.options.server.urlPath = '/api/path/to/spa/client/resources'; FFSpaLoader.options.server.assets = '/api/path/to/spa/client/resources';
FFSpaLoader.bootDevice(function() { FFSpaLoader.start();
FFSpaLoader.options.cache.factory = FFSpaLoader.autoSelectCache();// need deviceready via bootDevice for sqllite.
FFSpaLoader.start(function() {
console.log('FFExample.bootstrap angular');
angular.bootstrap( $('body'), ['exampleUI']);
});
});
## Usage Web ## Usage Web
Single index.html Single index.html
FFSpaLoader.options.debug = true; FFSpaLoader.options.debug.enable = true;
FFSpaLoader.options.server.url = ''; FFSpaLoader.options.boot.angular.modules.push('exampleUI');
FFSpaLoader.options.server.urlPath = '/api/path/to/spa/client/resources'; FFSpaLoader.options.server.url = 'http://myserver';
FFSpaLoader.options.cache.factory = FFSpaLoader.autoSelectCache(); FFSpaLoader.options.server.assets = '/api/path/to/spa/client/resources';
FFSpaLoader.start(function() { FFSpaLoader.start(function() {
console.log('FFExample.bootstrap angular'); console.log('FFExample.boot done');
angular.bootstrap( $('body'), ['exampleUI']);
}); });
## Usage Multi Server ## Usage Multi Server
@ -45,12 +39,20 @@ A javascript library providing server defined loading of resources for a single
## Options ## Options
* options.debug = Enable console debug output. * options.debug.enable = Enable console debug output.
* TODO
## Example Application ## Example Application
There is a fully working express example application in the example folder. There is a fully working express example application in the example folder.
## Tested Browsers
* Chromium 46
* Iceweasel 43
* Opera 12 Presto
* Android 5 in Cordova
## Contributing ## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. In lieu of a formal styleguide, take care to maintain the existing coding style.

View file

@ -26,7 +26,6 @@
// TODO: // TODO:
// set tag.media = 'only you'; // set tag.media = 'only you';
// add media in resouces // add media in resouces
// check supported browsers
/** /**
* FFSpaLoader is an assets loader for single page applications. * FFSpaLoader is an assets loader for single page applications.
@ -38,40 +37,57 @@
* - Assets hashing for fast syncing. * - Assets hashing for fast syncing.
* - Assets types: js,css,cssData * - Assets types: js,css,cssData
* - Caching backends: null,localStorage,webSqlDB,sqllite * - Caching backends: null,localStorage,webSqlDB,sqllite
* - Optional ask and cache server assets url. * - Server url question ui.
* - Cordova deviceready event booting. * - Loader error ui.
* - Build-in Cordova booting.
* - Build-in AngularJS booting.
* *
* @module FFSpaLoader * @module FFSpaLoader
*/ */
var FFSpaLoader = (/** @lends module:FFSpaLoader */function () { var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var options = { var options = {
debug: false, debug: {
errorHandler: null, enable: false,
bootDevice: { handler: null, // auto filled
timeout: 4096, prefix: 'FFSpaLoader.'
windowFlag: 'FFCordovaDevice'
}, },
askUrl: { error: {
urlTransport: 'http://', handler: null, // auto filled
questionText: 'Please provide the server name;', title: 'Loader Error',
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}', style: '.ffError { margin: auto;width: 90%;border: 3px solid red;padding-left: 1em;padding-bottom: 1em;}'
},
boot: {
cordova: {
enable: true,
timeout: 4096, // FIXME: Test really slow devices -> increase ? or add support -1 for disable or ?
flag: 'FFCordovaDevice'
},
angular: {
enable: true,
modules: []
},
}, },
server: { server: {
url: null, url: null,
urlPath: null, assets: null,
timeout: 4096, timeout: 4096,
windowFlag: 'FFServerUrl', flag: 'FFServerUrl',
header: { header: {
request: { request: { // TODO: add header support
'X-FFSpaLoader': '42' 'X-FFSpaLoader': '42'
}, },
response: { response: {
} }
},
question: {
transport: 'http://',
title: 'Server',
text: 'Please provide the server name;', // TODO: rename .ffAskUrl
style: '.ffAskUrl { font-size: 1em;margin: auto;width: 90%;border: 3px solid #73AD21;padding-left: 1em;padding-bottom: 1em;} .ffAskUrl > div {font-size: 0.8em;color: #ccc;} .ffAskUrl > div > * {} .ffAskUrl > div > input {} .ffAskUrlError{ color: red}',
} }
}, },
cache: { cache: {
factory: null,
meta: null, meta: null,
js: null, js: null,
css: null, css: null,
@ -79,18 +95,8 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
} }
}; };
/** var cacheDB = null; // single instance for websql
* Prints debug message to the console if options.debug is true.
* @param {String} message The message to log.
* @private
*/
var utilDebug = function (message) {
if (options.debug !== true) {
return;
}
console.log('FFSpaLoader.'+message);
};
var factory = { var factory = {
detect: { detect: {
localStorage: function() { localStorage: function() {
@ -113,7 +119,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
return 'cordova' in window; return 'cordova' in window;
}, },
cordovaDevice: function() { cordovaDevice: function() {
return options.bootDevice.windowFlag in window; return options.boot.cordova.flag in window;
}, },
mobileAgent: function() { mobileAgent: function() {
return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/); return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/);
@ -165,25 +171,38 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cb(err.message+' code: '+err.code); cb(err.message+' code: '+err.code);
}; };
}; };
var cacheDB = window.openDatabase(opt.name, opt.version, opt.name, opt.size); var cacheDBInit = function(cb) {
cacheDB.transaction(function(tx) { 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)'; var query = 'CREATE TABLE cache_store(id INTEGER PRIMARY KEY AUTOINCREMENT, key TEXT NOT NULL, value TEXT NOT NULL)';
utilDebug('websql.init query: '+query); utilDebug('websql.init query '+query);
tx.executeSql(query, [], function(tx,res) { tx.executeSql(query, [], function(tx,res) {
var query = 'CREATE UNIQUE INDEX cache_store__key__udx ON cache_store (key)'; var query = 'CREATE UNIQUE INDEX cache_store__key__udx ON cache_store (key)';
utilDebug('websql.init query: '+query); utilDebug('websql.init query '+query);
tx.executeSql(query, [], function(tx,res) {}, sqlErrorHandler(options.errorHandler)); // FIX<E: asyns error ? tx.executeSql(query, [], function(tx,res) {cb(null)}, sqlErrorHandler(cb));
}, sqlErrorHandler(options.errorHandler)); }, sqlErrorHandler(cb));
}); });
}); }
return { return {
cacheOpen: function(cb) {
if (cacheDB !== null) {
cb(null); // open once.
return;
}
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.cacheOpen query '+query);
tx.executeSql(query, [], function(tx,res) {
cb(null);
}, function(tx,errorNoTable) {
cacheDBInit(cb);
});
});
},
cacheGetValue: function(key, cb) { cacheGetValue: function(key, cb) {
cacheDB.transaction(function(tx) { cacheDB.transaction(function(tx) {
var query = 'SELECT value FROM cache_store WHERE key = ?'; var query = 'SELECT value FROM cache_store WHERE key = ?';
utilDebug('websql.cacheGetValue query: '+query); utilDebug('websql.cacheGetValue query '+query);
tx.executeSql(query,[key], function(tx, res) { tx.executeSql(query,[key], function(tx, res) {
if (res.rows.length === 0) { if (res.rows.length === 0) {
cb(null, null); cb(null, null);
@ -197,15 +216,15 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cacheSetValue: function(key, value, cb) { cacheSetValue: function(key, value, cb) {
cacheDB.transaction(function(tx) { cacheDB.transaction(function(tx) {
var query = 'SELECT value FROM cache_store WHERE key = ?'; var query = 'SELECT value FROM cache_store WHERE key = ?';
utilDebug('websql.cacheSetValue query: '+query); utilDebug('websql.cacheSetValue query '+query);
tx.executeSql(query,[key], function(tx, res) { tx.executeSql(query,[key], function(tx, res) {
if (res.rows.length === 0) { if (res.rows.length === 0) {
var query = 'INSERT INTO cache_store (key,value) VALUES (?,?)'; var query = 'INSERT INTO cache_store (key,value) VALUES (?,?)';
utilDebug('websql.cacheSetValue query: '+query); utilDebug('websql.cacheSetValue query '+query);
tx.executeSql(query, [key,JSON.stringify(value)], nullDataHandler(cb), sqlErrorHandler(cb)); tx.executeSql(query, [key,JSON.stringify(value)], nullDataHandler(cb), sqlErrorHandler(cb));
} else { } else {
var query = 'UPDATE cache_store SET value = ? WHERE key = ?'; var query = 'UPDATE cache_store SET value = ? WHERE key = ?';
utilDebug('websql.cacheSetValue query: '+query); utilDebug('websql.cacheSetValue query '+query);
tx.executeSql(query, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb)); tx.executeSql(query, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb));
} }
}, sqlErrorHandler(cb)); }, sqlErrorHandler(cb));
@ -214,8 +233,10 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cacheDeleteValue: function(key, cb) { cacheDeleteValue: function(key, cb) {
cacheDB.transaction(function(tx) { cacheDB.transaction(function(tx) {
var query = 'DELETE FROM cache_store WHERE key = ?'; var query = 'DELETE FROM cache_store WHERE key = ?';
utilDebug('websql.cacheDeleteValue query: '+query); utilDebug('websql.cacheDeleteValue query '+query);
tx.executeSql(query, [key], nullDataHandler(cb), sqlErrorHandler(cb)); tx.executeSql(query, [key], function (tx, res) {
setTimeout(function() {cb(null);}); // return next tick so transaction is flushed before location.reload
}, sqlErrorHandler(cb));
}); });
} }
} }
@ -223,24 +244,36 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
} }
}; };
/**
* Prints the debug message with prefix to the options.debug.handler if options.debug.enable is true.
* @param {String} message The message to log.
* @private
*/
var utilDebug = function (message) {
if (options.debug.enable !== true) {
return;
}
options.debug.handler(options.debug.prefix+message);
};
/** /**
* The default error handler which renders the error in the browser. * The default error handler which renders the error in the browser.
* @param {String|Error} err The error message. * @param {String|Error} err The error message.
* @private * @private
*/ */
var utilErrorHandler = function(err) { var utilErrorHandler = function(err) {
utilDebug('utilErrorHandler error: '+err); utilDebug('utilErrorHandler error '+err); // TODO: Add Error object support
var rootTag = document.createElement('div'); var rootTag = document.createElement('div');
rootTag.setAttribute('class','ffError'); rootTag.setAttribute('class','ffError');
var cssTag = document.createElement("style"); var cssTag = document.createElement("style");
cssTag.type = "text/css"; 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;}'; cssTag.innerHTML = options.error.style;
rootTag.appendChild(cssTag); rootTag.appendChild(cssTag);
var titleTag = document.createElement('h1'); var titleTag = document.createElement('h1');
titleTag.appendChild(document.createTextNode('Error')); titleTag.appendChild(document.createTextNode(options.error.title));
rootTag.appendChild(titleTag); rootTag.appendChild(titleTag);
var questionTag = document.createElement('p'); var questionTag = document.createElement('p');
@ -249,7 +282,6 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
document.getElementsByTagName('body')[0].appendChild(rootTag); document.getElementsByTagName('body')[0].appendChild(rootTag);
}; };
options.errorHandler = utilErrorHandler;
/** /**
* Fetches an url resource with a XMLHttpRequest. * Fetches an url resource with a XMLHttpRequest.
@ -262,16 +294,16 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var httpRequest = new XMLHttpRequest(); var httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function() { httpRequest.onreadystatechange = function() {
if (httpRequest.readyState == 4 && httpRequest.status === 200) { 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(null, httpRequest); cb(null, httpRequest);
} else if (httpRequest.readyState == 4) { } else if (httpRequest.readyState == 4) {
cb('Wrong status: '+httpRequest.status); cb('Wrong status '+httpRequest.status);
} }
} }
httpRequest.open('GET', url, true); httpRequest.open('GET', url, true);
httpRequest.timeout = options.server.timeout; // ieX: after open() httpRequest.timeout = options.server.timeout; // ieX: after open()
httpRequest.ontimeout = function() { httpRequest.ontimeout = function() {
cb('timeout after '+options.server.timeout+' of url: '+url); cb('timeout after '+options.server.timeout+' of url '+url);
}; };
httpRequest.send(); httpRequest.send();
}; };
@ -280,9 +312,6 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
if (options.cache[type]) { if (options.cache[type]) {
return options.cache[type]; return options.cache[type];
} }
if (options.cache['factory']) {
return options.cache['factory'];
}
return null; return null;
}; };
@ -291,13 +320,13 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
}; };
var cacheCheckType = function (type, cb, action) { var cacheCheckType = function (type, cb, action) {
cacheHasService(type)?action():cb('No caching for: '+type); cacheHasService(type)?action():cb('No caching for '+type);
}; };
var cacheGetValue = function(type, key , cb) { var cacheGetValue = function(type, key , cb) {
cacheCheckType(type, cb, function() { cacheCheckType(type, cb, function() {
var cacheKey = type+'_'+key; var cacheKey = type+'_'+key;
utilDebug('cacheGetValue key: '+cacheKey); utilDebug('cacheGetValue key '+cacheKey);
cacheGetService(type).cacheGetValue(cacheKey,cb); cacheGetService(type).cacheGetValue(cacheKey,cb);
}); });
}; };
@ -305,7 +334,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var cacheSetValue = function(type, key, value, cb) { var cacheSetValue = function(type, key, value, cb) {
cacheCheckType(type, cb, function() { cacheCheckType(type, cb, function() {
var cacheKey = type+'_'+key; var cacheKey = type+'_'+key;
utilDebug('cacheSetValue key: '+cacheKey); utilDebug('cacheSetValue key '+cacheKey);
cacheGetService(type).cacheSetValue(cacheKey,value,cb); cacheGetService(type).cacheSetValue(cacheKey,value,cb);
}); });
}; };
@ -313,20 +342,20 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var cacheDeleteValue = function(type, key, cb) { var cacheDeleteValue = function(type, key, cb) {
cacheCheckType(type, cb, function() { cacheCheckType(type, cb, function() {
var cacheKey = type+'_'+key; var cacheKey = type+'_'+key;
utilDebug('cacheDeleteValue key: '+cacheKey); utilDebug('cacheDeleteValue key '+cacheKey);
cacheGetService(type).cacheDeleteValue(cacheKey,cb); cacheGetService(type).cacheDeleteValue(cacheKey,cb);
}); });
}; };
var cleanupCache = function (resources, cb) { var cleanupCache = function (resources, cb) {
utilDebug('cleanupCache TODO cacheList: '+0+' resources: '+resources.length); utilDebug('cleanupCache TODO cacheList '+0+' resources '+resources.length);
// TODO: impl for removes in resource lists // TODO: impl for removes in resource lists
cb(null); cb(null);
}; };
var injectResourceData = function(resource, data, cb) { var injectResourceData = function(resource, data, cb) {
utilDebug('injectResourceData resource: '+JSON.stringify(resource)+' data: '+data.length); utilDebug('injectResourceData resource '+JSON.stringify(resource)+' data '+data.length);
var tag = null; var tag = null;
if (resource.type === 'css' || resource.type === 'cssData') { if (resource.type === 'css' || resource.type === 'cssData') {
tag = document.createElement('style'); tag = document.createElement('style');
@ -347,7 +376,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var storeResourceKey = function (resource, cb) { var storeResourceKey = function (resource, cb) {
// Add all cache keys in central list so we can clear the cache item if resources are removed. // Add all cache keys in central list so we can clear the cache item if resources are removed.
var cacheKey = resource.type+'_keys'; var cacheKey = resource.type+'_keys';
cacheGetValue('meta',cacheKey,function (err,value) { cacheGetValue('meta',cacheKey,function (err,value) { // TODO: move to resource.type
if (err !== null) { if (err !== null) {
cb(err); cb(err);
return; return;
@ -361,7 +390,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
} }
var storeResource = function (resource, httpRequest, cb) { var storeResource = function (resource, httpRequest, cb) {
utilDebug('storeResource url: '+resource.url+' hash: '+resource.hash); utilDebug('storeResource url '+resource.url+' hash '+resource.hash);
var item = { var item = {
resource: resource, resource: resource,
data: httpRequest.responseText data: httpRequest.responseText
@ -388,11 +417,9 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
return; return;
} }
if (value === null) { if (value === null) {
utilDebug('loadResource cache miss'); utilDebug('loadResource cache miss'); // + hash mismatch as its the key
} else if (value.resource === undefined) { } else if (value.resource === undefined) {
utilDebug('loadResource cache wrong obj'); utilDebug('loadResource cache wrong obj');
} else if (value.resource.hash !== resource.hash) {
utilDebug('loadResource cache hash mismatch');
} else { } else {
utilDebug('loadResource cache hit'); utilDebug('loadResource cache hit');
injectResourceData(value.resource,value.data,cb); injectResourceData(value.resource,value.data,cb);
@ -456,7 +483,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
tag = document.createElement('script'); tag = document.createElement('script');
tag.type = 'text/javascript'; tag.type = 'text/javascript';
} }
utilDebug('injectResources resource: '+JSON.stringify(resource)); utilDebug('injectResources from '+JSON.stringify(resource));
tag.appendChild(document.createTextNode(item.data)); tag.appendChild(document.createTextNode(item.data));
@ -481,29 +508,29 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cb('Missing options.server.url'); cb('Missing options.server.url');
return; return;
} }
if (options.server.urlPath === null) { if (options.server.assets === null) {
cb('Missing options.server.urlPath'); cb('Missing options.server.assets');
return; return;
} }
window[options.server.windowFlag] = options.server.url; window[options.server.flag] = options.server.url;
var resourcesUrl = options.server.url + options.server.urlPath; var resourcesUrl = options.server.url + options.server.assets;
utilDebug('startLoader serverUrl: '+resourcesUrl); utilDebug('startLoader assets \"'+resourcesUrl+'\"');
utilHttpFetch(resourcesUrl, function(err, httpRequest) { utilHttpFetch(resourcesUrl, function(err, httpRequest) {
if (err !== null) { if (err !== null) {
utilDebug('startLoader fetch error: '+err); utilDebug('startLoader fetch error '+err);
if (cacheHasService('meta')) { if (cacheHasService('meta')) {
cacheGetValue('meta','server_resources',function(err, value) { cacheGetValue('meta','server_resources',function(err, value) {
if (err !== null) { if (err !== null) {
cb(err); cb(err);
} else if (value === null) { } else if (value === null) {
cb('Have no cache of server resouces from: '+options.server.url); cb('Have no cache of server resouces from '+options.server.url);
} else { } else {
injectResources(value, cb); injectResources(value, cb);
} }
}); });
} else { } else {
cb('Could not fetch server resouces from: '+options.server.url); cb('Could not fetch server resouces from '+options.server.url);
} }
return; return;
} }
@ -511,7 +538,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) { JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) {
resources.push(r); resources.push(r);
}); });
utilDebug('startLoader resources: '+resources.length); utilDebug('startLoader resources '+resources.length);
if (cacheHasService('meta')) { if (cacheHasService('meta')) {
cacheSetValue('meta','server_resources',resources, function (err) { cacheSetValue('meta','server_resources',resources, function (err) {
if (err !== null) { if (err !== null) {
@ -542,13 +569,13 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
} }
options.server.url = ''; options.server.url = '';
if (options.askUrl.urlTransport !== undefined) { if (options.server.question.transport !== undefined) {
options.server.url += options.askUrl.urlTransport; options.server.url += options.server.question.transport;
} }
options.server.url += inputTag.value; options.server.url += inputTag.value;
var resourcesUrl = options.server.url + options.server.urlPath; var resourcesUrl = options.server.url + options.server.assets;
utilDebug('askUrlStart check url: '+resourcesUrl); utilDebug('askUrlStart check assets '+resourcesUrl);
// TODO: add+check headers // TODO: add+check headers
utilHttpFetch(resourcesUrl,function(err, req) { utilHttpFetch(resourcesUrl,function(err, req) {
@ -560,7 +587,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
if (cacheHasService('meta')) { if (cacheHasService('meta')) {
cacheSetValue('meta','server_url',options.server.url, function(err) { cacheSetValue('meta','server_url',options.server.url, function(err) {
if (err !== null) { if (err !== null) {
cb(err); cb(err);
} else { } else {
startLoader(cb); startLoader(cb);
} }
@ -585,20 +612,24 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var cssTag = document.createElement("style"); var cssTag = document.createElement("style");
cssTag.type = "text/css"; cssTag.type = "text/css";
cssTag.innerHTML = options.askUrl.cssText; cssTag.innerHTML = options.server.question.style;
rootTag.appendChild(cssTag); rootTag.appendChild(cssTag);
var titleTag = document.createElement('h1');
titleTag.appendChild(document.createTextNode(options.server.question.title));
rootTag.appendChild(titleTag);
var questionTag = document.createElement('p'); var questionTag = document.createElement('p');
questionTag.appendChild(document.createTextNode(options.askUrl.questionText)); questionTag.appendChild(document.createTextNode(options.server.question.text));
rootTag.appendChild(questionTag); rootTag.appendChild(questionTag);
var formTag = document.createElement('div'); var formTag = document.createElement('div');
rootTag.appendChild(formTag); rootTag.appendChild(formTag);
if (options.askUrl.urlTransport !== undefined) { if (options.server.question.transport !== undefined) {
var transportTag = document.createElement('label'); var transportTag = document.createElement('label');
rootTag.setAttribute('for','serverInput'); rootTag.setAttribute('for','serverInput');
transportTag.appendChild(document.createTextNode(options.askUrl.urlTransport)); transportTag.appendChild(document.createTextNode(options.server.question.transport));
formTag.appendChild(transportTag); formTag.appendChild(transportTag);
} }
var inputTag = document.createElement('input'); var inputTag = document.createElement('input');
@ -626,6 +657,46 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
document.getElementsByTagName('body')[0].appendChild(rootTag); document.getElementsByTagName('body')[0].appendChild(rootTag);
}; };
var startCacheType = function (type,cb) {
if (options.cache[type] !== null && options.cache[type] === false) {
utilDebug('startCacheType '+type+' disabled');
options.cache[type] = null;
return cb(null);
}
if (options.cache[type] === null) {
if (factory.detect.cordovaDevice() && factory.detect.sqlitePlugin()) {
utilDebug('startCacheType auto sqlitePlugin for '+type);
options.cache[type] = factory.cache.websql({openDatabase: window.sqlitePlugin});
} else if (factory.detect.openDatabase()) {
utilDebug('startCacheType auto openDatabase for '+type);
options.cache[type] = factory.cache.websql();
} else if (factory.detect.localStorage()) {
utilDebug('startCacheType auto localStorage for '+type);
options.cache[type] = factory.cache.localStorage();
} else {
utilDebug('startCacheType '+type+' none');
}
}
if (options.cache[type] !== null && typeof options.cache[type]['cacheOpen'] === "function") {
options.cache[type].cacheOpen(cb);
} else {
cb(null);
}
};
var startCache = function (cb) {
startCacheType('meta', function(err) {
if (err !== null) { return cb(err); }
startCacheType('js', function(err) {
if (err !== null) { return cb(err); }
startCacheType('css', function(err) {
if (err !== null) { return cb(err); }
startCacheType('cssData', cb);
});
});
});
};
/** /**
* Starts the loader. * Starts the loader.
* *
@ -635,72 +706,73 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var startTime = new Date().getTime(); var startTime = new Date().getTime();
var cb = function(err) { var cb = function(err) {
if (err !== null) { if (err !== null) {
options.errorHandler(err); options.error.handler(err);
} else { } else {
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.'); utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
if (typeof cbArgu === "function") { bootAngular(function(err) {
cbArgu(); if (err !== null) { return options.error.handler(err); }
} if (typeof cbArgu === "function") {
cbArgu();
}
});
} }
} }
utilDebug('start serverUrl: '+options.server.url); if (options.debug.enable === true) {
if (options.server.url !== null) { var optionsKeys = Object.keys(options);
startLoader(cb); for (var keyId in optionsKeys) {
return; var key = optionsKeys[keyId];
utilDebug('start config '+key+' '+JSON.stringify(options[key]));
}
} }
if (cacheHasService('meta')) { bootCordova(function(err) {
cacheGetValue('meta','server_url',function(err, value) { if (err !== null) { return cb(err); }
if (err !== null) { startCache(function(err) {
cb(err); if (err !== null) { return cb(err); }
} else if (value === undefined || value === null || value === '') { if (options.server.url !== null) {
askUrl(cb);
} else {
options.server.url = value;
startLoader(cb); 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);
} }
}); });
} else { });
askUrl(cb);
}
}; };
var clearServer = function(cb) { var clearServerUrl = function(cb) {
if (cb === undefined) { if (cb === undefined) {
cb = function() {}; cb = function() {};
} }
if (cacheHasService('meta')) { if (cacheHasService('meta')) {
cacheDeleteValue('meta','server_url',function(err) { cacheDeleteValue('meta','server_url',cb);
if (err !== null) {
cb(err);
} else {
cb(null);
}
});
} else { } else {
cb(null); cb(null);
} }
} }
/** var bootAngular = function(cb) {
* Helper for automatic selecting the best to none cache backend option. if (options.boot.angular.enable !== true) {
* utilDebug('bootAngular disabled by options');
* Order of detection; sqllite,openDatabase,localStorage,none. return cb(null);
*/
var autoSelectCache = function() {
if (factory.detect.cordovaDevice() && factory.detect.sqlitePlugin()) {
utilDebug('autoSelectCache sqlitePlugin');
return factory.cache.websql({openDatabase: window.sqlitePlugin});
} }
if (factory.detect.openDatabase()) { if (options.boot.angular.modules.length === 0) {
utilDebug('autoSelectCache openDatabase'); utilDebug('bootAngular disabled by no modules');
return factory.cache.websql(); return cb(null);
} }
if (factory.detect.localStorage()) { utilDebug('bootAngular start '+options.boot.angular.modules);
utilDebug('autoSelectCache localStorage'); angular.bootstrap(document, options.boot.angular.modules);
return factory.cache.localStorage(); cb(null);
}
utilDebug('autoSelectCache none');
return null; // no caching
} }
/** /**
@ -709,40 +781,43 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
* Note: On none cordova device page is will timeout. * Note: On none cordova device page is will timeout.
* *
* @param {function} cb Callback gets called device is ready to use. * @param {function} cb Callback gets called device is ready to use.
* @private
*/ */
var bootDevice = function(cb) { var bootCordova = function(cb) {
if (options.boot.cordova.enable !== true) {
utilDebug('bootCordova disabled by options');
return cb(null);
}
if (factory.detect.cordova() !== true) {
utilDebug('bootCordova disabled by detect');
return cb(null);
}
var startTime = new Date().getTime(); var startTime = new Date().getTime();
var bootOnce = function() { var bootOnce = function() {
if (typeof cb === "function") { var callback = cb;
var callback = cb; cb = null;
cb = null; utilDebug('bootCordova done in '+(new Date().getTime()-startTime)+' ms.');
utilDebug('bootDevice done in '+(new Date().getTime()-startTime)+' ms.'); callback();
callback();
}
}; };
if (!factory.detect.cordova()) { utilDebug('bootCordova timeout '+options.boot.cordova.timeout);
utilDebug('bootDevice webapp');
bootOnce();
return;
}
utilDebug('bootDevice timeout: '+options.bootDeviceTimeout);
setTimeout ( function () { setTimeout ( function () {
utilDebug('bootDevice timeout'); utilDebug('bootCordova timeout');
bootOnce(); bootOnce();
}, options.bootDevice.timeout); }, options.boot.cordova.timeout);
document.addEventListener("deviceready", function () { document.addEventListener("deviceready", function () {
window[options.bootDevice.windowFlag] = true; window[options.boot.cordova.flag] = true;
utilDebug('bootDevice '+options.bootDevice.windowFlag); utilDebug('bootCordova '+options.boot.cordova.flag);
bootOnce(); bootOnce();
}, false); }, false);
}; };
// Auto fill handlers and return public object.
options.debug.handler = function(msg) {console.log(msg);};
options.error.handler = utilErrorHandler;
return { return {
options: options, options: options,
factory: factory, factory: factory,
start: start, start: start,
bootDevice: bootDevice, clearServerUrl: clearServerUrl
autoSelectCache: autoSelectCache,
clearServer: clearServer
}; };
})(); })();

View file

@ -1,7 +1,7 @@
var tpl = '<div><h2>Foo</h2><p>Welcome to the foo.</p></div>'; var tpl = '<div><h2>Foo</h2><p>Welcome to the foo.</p></div>';
tpl += '<input type=\"button\" class=\"btn btn-default\" ng-click=\"doReboot()\" value=\"Reboot\"></input>'; tpl += '<input type=\"button\" class=\"btn btn-default\" ng-click=\"doReboot()\" value=\"Reboot\"></input>';
tpl += '<input type=\"button\" class=\"btn btn-default\" ng-click=\"doClearServer()\" value=\"Clear Server\"></input>'; tpl += '<input type=\"button\" class=\"btn btn-default\" ng-click=\"doClearServerUrl()\" value=\"Clear Server\"></input>';
pageRouteInit.push(function ($routeProvider, $locationProvider) { pageRouteInit.push(function ($routeProvider, $locationProvider) {
$routeProvider.when('/example-ui/foo', { $routeProvider.when('/example-ui/foo', {
@ -15,8 +15,13 @@ function PageFoo($scope) {
window.location.reload(true); window.location.reload(true);
}; };
$scope.doClearServer = function ( path ) { $scope.doClearServerUrl = function ( path ) {
FFSpaLoader.clearServer(); FFSpaLoader.clearServerUrl(function(err) {
window.location.reload(true); if (err) {
window.alert(err);
} else {
window.location.reload(true);
}
});
}; };
} }

View file

@ -1,8 +1,8 @@
'use strict'; 'use strict';
document.title = 'FF-Spa-Loader Example'; document.title = 'FFSpaLoader Example';
var serverUrl = window.FFServerUrl; var serverUrl = window.FFServerUrl;
console.log('FF provided serverUrl: '+serverUrl); console.log('FFExample provided serverUrl \"'+serverUrl+'\"');
$(document.createElement('div')).attr('id', 'wrapper').appendTo($('body')); $(document.createElement('div')).attr('id', 'wrapper').appendTo($('body'));
$(document.createElement('div')).attr('ng-controller', 'ApplicationController').attr('ng-include', '\''+serverUrl+'/example-ui/thtml/header\'').appendTo($('#wrapper')); $(document.createElement('div')).attr('ng-controller', 'ApplicationController').attr('ng-include', '\''+serverUrl+'/example-ui/thtml/header\'').appendTo($('#wrapper'));

View file

@ -6,13 +6,10 @@
</head> </head>
<body><%- inline %> <body><%- inline %>
<script> <script>
FFSpaLoader.options.debug = true; FFSpaLoader.options.debug.enable = true;
FFSpaLoader.options.server.urlPath = '/static/spa-client-resources'; FFSpaLoader.options.boot.angular.modules.push('exampleUI');
FFSpaLoader.options.cache.factory = FFSpaLoader.autoSelectCache(); FFSpaLoader.options.server.assets = '/static/spa-client-resources';
FFSpaLoader.start(function() { FFSpaLoader.start();
console.log('FFExample.bootstrap angular');
angular.bootstrap( $('body'), ['exampleUI']);
});
</script> </script>
</body> </body>
</html> </html>