2
0
Fork 0

Linted code and added basic tests.

This commit is contained in:
Willem 2016-01-17 21:57:59 +01:00
parent e5e2dfc56f
commit 0fd0f026ec
9 changed files with 219 additions and 107 deletions

5
.jshintignore Normal file
View file

@ -0,0 +1,5 @@
node_modules
example/node_modules
example/www_static
example/www_views
example/mobile_app

38
.jshintrc Normal file
View file

@ -0,0 +1,38 @@
{
"predef": [
"before",
"after",
"it",
"describe",
"beforeEach",
"afterEach"
],
"asi" : false,
"bitwise" : true,
"boss" : false,
"curly" : true,
"debug": false,
"devel": false,
"eqeqeq": true,
"evil": false,
"expr": true,
"forin": false,
"immed": true,
"latedef" : false,
"laxbreak": false,
"multistr": true,
"newcap": true,
"noarg": true,
"node" : true,
"noempty": false,
"nonew": true,
"onevar": false,
"plusplus": false,
"proto": true,
"regexp": false,
"strict": false,
"sub": false,
"trailing" : true,
"undef": true,
"unused": true
}

View file

@ -53,6 +53,17 @@ A javascript library providing server defined loading of resources for a single
* Opera 12 Presto
* Android 5 in Cordova
## Todo
* Auto cache cleanup code.
* test in production
* Server header set+check support
* Redo css(?divs) of server question
* Add more tests
* css: set tag.media = 'only you';
* css: add media in resouces
* Add in browser tests
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style.

View file

@ -1,4 +1,3 @@
'use strict';
/*
* Copyright (c) 2015-2016, Willem Cazander
* All rights reserved.
@ -21,11 +20,8 @@
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// TODO:
// set tag.media = 'only you';
// add media in resouces
/* jslint browser: true */
/* global angular,define */
/**
* FFSpaLoader is an assets loader for single page applications.
@ -44,7 +40,16 @@
*
* @module FFSpaLoader
*/
var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
(function (root, factory) {
if ( typeof define === 'function' && define.amd ) {
define('FFSpaLoader', factory(root));
} else if ( typeof exports === 'object' ) {
module.exports = factory(root);
} else {
root.FFSpaLoader = factory(root);
}
})(this || window, /** @lends module:FFSpaLoader */ function (rootWindow) {
'use strict';
var options = {
debug: {
@ -102,27 +107,27 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
localStorage: function() {
try {
var testData = 'localStorageDetect';
localStorage.setItem(testData, testData);
localStorage.removeItem(testData);
rootWindow.localStorage.setItem(testData, testData);
rootWindow.localStorage.removeItem(testData);
return true;
} catch(e) {
return false;
}
},
openDatabase: function() {
return 'openDatabase' in window;
return 'openDatabase' in rootWindow;
},
sqlitePlugin: function() {
return 'sqlitePlugin' in window;
return 'sqlitePlugin' in rootWindow;
},
cordova: function() {
return 'cordova' in window;
return 'cordova' in rootWindow;
},
cordovaDevice: function() {
return options.boot.cordova.flag in window;
return options.boot.cordova.flag in rootWindow;
},
mobileAgent: function() {
return navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/);
return rootWindow.navigator !== undefined && rootWindow.navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|IEMobile)/);
}
},
cache: {
@ -130,7 +135,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
return {
cacheGetValue: function(key, cb) {
try {
var dataRaw = localStorage.getItem(key);
var dataRaw = rootWindow.localStorage.getItem(key);
var data = JSON.parse(dataRaw);
cb(null, data);
} catch(e) {
@ -139,7 +144,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
},
cacheSetValue: function(key, value, cb) {
try {
localStorage.setItem(key,JSON.stringify(value));
rootWindow.localStorage.setItem(key,JSON.stringify(value));
cb(null);
} catch(e) {
cb(e);
@ -147,22 +152,22 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
},
cacheDeleteValue: function(key, cb) {
try {
localStorage.removeItem(key);
rootWindow.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; }
if (opt.openDatabase === undefined) { opt.openDatabase = rootWindow.openDatabase; }
var nullDataHandler = function(cb) {
return function (tx, results) {
return function () {
cb(null);
};
};
@ -175,26 +180,26 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cacheDB.transaction(function(tx) {
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) {
tx.executeSql(query, [], function(tx) {
var query = 'CREATE UNIQUE INDEX cache_store__key__udx ON cache_store (key)';
utilDebug('websql.init query '+query);
tx.executeSql(query, [], function(tx,res) {cb(null)}, sqlErrorHandler(cb));
tx.executeSql(query, [], function() {cb(null);}, sqlErrorHandler(cb));
}, sqlErrorHandler(cb));
});
}
};
return {
cacheOpen: function(cb) {
if (cacheDB !== null) {
cb(null); // open once.
return;
}
cacheDB = window.openDatabase(opt.name, opt.version, opt.name, opt.size);
cacheDB = rootWindow.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) {
tx.executeSql(query, [], function() {
cb(null);
}, function(tx,errorNoTable) {
}, function() {
cacheDBInit(cb);
});
});
@ -219,13 +224,13 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
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));
var queryInsert = 'INSERT INTO cache_store (key,value) VALUES (?,?)';
utilDebug('websql.cacheSetValue query '+queryInsert);
tx.executeSql(queryInsert, [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));
var queryUpdate = 'UPDATE cache_store SET value = ? WHERE key = ?';
utilDebug('websql.cacheSetValue query '+queryUpdate);
tx.executeSql(queryUpdate, [JSON.stringify(value),key], nullDataHandler(cb), sqlErrorHandler(cb));
}
}, sqlErrorHandler(cb));
});
@ -234,12 +239,12 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cacheDB.transaction(function(tx) {
var query = 'DELETE FROM cache_store WHERE key = ?';
utilDebug('websql.cacheDeleteValue query '+query);
tx.executeSql(query, [key], function (tx, res) {
tx.executeSql(query, [key], function () {
setTimeout(function() {cb(null);}); // return next tick so transaction is flushed before location.reload
}, sqlErrorHandler(cb));
});
}
}
};
},
}
};
@ -293,13 +298,13 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
var startTime = new Date().getTime();
var httpRequest = new XMLHttpRequest();
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.');
cb(null, httpRequest);
} else if (httpRequest.readyState == 4) {
} else if (httpRequest.readyState === 4) {
cb('Wrong status '+httpRequest.status);
}
}
};
httpRequest.open('GET', url, true);
httpRequest.timeout = options.server.timeout; // ieX: after open()
httpRequest.ontimeout = function() {
@ -347,12 +352,11 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
});
};
var cleanupCache = function (resources, cb) {
utilDebug('cleanupCache TODO cacheList '+0+' resources '+resources.length);
// TODO: impl for removes in resource lists
cb(null);
};
// var cleanupCache = function (resources, cb) {
// utilDebug('cleanupCache TODO cacheList '+0+' resources '+resources.length);
// // TODO: impl for removes in resource lists
// cb(null);
// };
var injectResourceData = function(resource, data, cb) {
utilDebug('injectResourceData resource '+JSON.stringify(resource)+' data '+data.length);
@ -387,14 +391,14 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
value.push(resource.hash);
cacheSetValue('meta',cacheKey,value, 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);
@ -412,10 +416,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('loadResource '+JSON.stringify(resource));
if (cacheHasService(resource.type)) {
cacheGetValue(resource.type,resource.hash,function(err, value) {
if (err !== null) {
cb(err);
return;
}
if (err !== null) { return cb(err); }
if (value === null) {
utilDebug('loadResource cache miss'); // + hash mismatch as its the key
} else if (value.resource === undefined) {
@ -426,23 +427,18 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
return;
}
utilHttpFetch(resourceUrl, function(err, httpRequest) {
if (err !== null) {
cb(err);
} else {
storeResource(resource, httpRequest, function (err) {
injectResourceData(resource,httpRequest.responseText,cb);
});
}
if (err !== null) { return cb(err); }
storeResource(resource, httpRequest, function (err) {
if (err !== null) { return cb(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);
}
if (err !== null) { return cb(err); }
injectResourceData(resource,httpRequest.responseText,cb);
});
}
};
@ -452,10 +448,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('loadResources');
var resourceStack = resources;
var resourceLoader = function(err) {
if (err !== null) {
cb(err);
return;
}
if (err !== null) { return cb(err); }
resourceStack = resourceStack.slice(1);
if (resourceStack.length === 0) {
utilDebug('loadResources done in '+(new Date().getTime()-startTime)+' ms.');
@ -512,7 +505,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
cb('Missing options.server.assets');
return;
}
window[options.server.flag] = options.server.url;
rootWindow[options.server.flag] = options.server.url;
var resourcesUrl = options.server.url + options.server.assets;
utilDebug('startLoader assets \"'+resourcesUrl+'\"');
@ -541,11 +534,8 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('startLoader resources '+resources.length);
if (cacheHasService('meta')) {
cacheSetValue('meta','server_resources',resources, function (err) {
if (err !== null) {
cb(err);
} else {
loadResources(resources, cb);
}
if (err !== null) { return cb(err); }
loadResources(resources, cb);
});
} else {
loadResources(resources, cb);
@ -578,25 +568,27 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('askUrlStart check assets '+resourcesUrl);
// TODO: add+check headers
utilHttpFetch(resourcesUrl,function(err, req) {
utilHttpFetch(resourcesUrl,function(err, httpRequest) {
if (err !== null) {
inputErrorTag.appendChild(document.createTextNode('Error could not get data.'));
return;
}
if (httpRequest.responseText.length === 0) {
inputErrorTag.appendChild(document.createTextNode('Error Got empty 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);
}
if (err !== null) { return cb(err); }
startLoader(cb);
});
} else {
startLoader(cb);
}
})
}
});
};
/**
* Creates the ask url ui.
@ -666,7 +658,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
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});
options.cache[type] = factory.cache.websql({openDatabase: rootWindow.sqlitePlugin});
} else if (factory.detect.openDatabase()) {
utilDebug('startCacheType auto openDatabase for '+type);
options.cache[type] = factory.cache.websql();
@ -677,7 +669,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('startCacheType '+type+' none');
}
}
if (options.cache[type] !== null && typeof options.cache[type]['cacheOpen'] === "function") {
if (options.cache[type] !== null && typeof options.cache[type].cacheOpen === 'function') {
options.cache[type].cacheOpen(cb);
} else {
cb(null);
@ -711,12 +703,12 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
bootAngular(function(err) {
if (err !== null) { return options.error.handler(err); }
if (typeof cbArgu === "function") {
if (typeof cbArgu === 'function') {
cbArgu();
}
});
}
}
};
if (options.debug.enable === true) {
var optionsKeys = Object.keys(options);
for (var keyId in optionsKeys) {
@ -759,7 +751,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
} else {
cb(null);
}
}
};
var bootAngular = function(cb) {
if (options.boot.angular.enable !== true) {
@ -773,7 +765,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
utilDebug('bootAngular start '+options.boot.angular.modules);
angular.bootstrap(document, options.boot.angular.modules);
cb(null);
}
};
/**
* Helper for cordova applications which want to use the sqllite plugin as cache.
@ -805,7 +797,7 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
bootOnce();
}, options.boot.cordova.timeout);
document.addEventListener("deviceready", function () {
window[options.boot.cordova.flag] = true;
rootWindow[options.boot.cordova.flag] = true;
utilDebug('bootCordova '+options.boot.cordova.flag);
bootOnce();
}, false);
@ -820,4 +812,4 @@ var FFSpaLoader = (/** @lends module:FFSpaLoader */function () {
start: start,
clearServerUrl: clearServerUrl
};
})();
});

View file

@ -6,7 +6,7 @@ var fs = require('fs');
var minify = require('minify');
var fetch = require('fetch');
var cors = require('cors');
var morgan = require('morgan')
var morgan = require('morgan');
// example options;
var serverUrl = 'http://localhost:8080';
@ -17,19 +17,21 @@ var clientResources = {
js: [],
css: [],
cssData: []
}
};
var addClientResource = function(clientResource, resourceType) {
clientResources[resourceType].push(clientResource);
}
};
var stringHash = function (str) {
/* jslint bitwise: true */
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;
/* jslint bitwise: false */
};
var fetchHashResource = function(fetchEntry,cb) {
@ -60,17 +62,17 @@ var fetchHashResources = function(fetchList, cb) {
var createClientResourceFetchList = function() {
var fetchList = [];
for (var clientResourceIdx in clientResources.js) {
var url = clientResources.js[clientResourceIdx];
fetchList.push({url:url,type:'js'});
for (var clientResourceIdxJs in clientResources.js) {
var urlJs = clientResources.js[clientResourceIdxJs];
fetchList.push({url:urlJs,type:'js'});
}
for (var clientResourceIdx in clientResources.css) {
var url = clientResources.css[clientResourceIdx];
fetchList.push({url:url,type:'css'});
for (var clientResourceIdxCss in clientResources.css) {
var urlCss = clientResources.css[clientResourceIdxCss];
fetchList.push({url:urlCss,type:'css'});
}
for (var clientResourceIdx in clientResources.cssData) {
var url = clientResources.cssData[clientResourceIdx];
fetchList.push({url:url,type:'cssData'});
for (var clientResourceIdxCssData in clientResources.cssData) {
var urlCssData = clientResources.cssData[clientResourceIdxCssData];
fetchList.push({url:urlCssData,type:'cssData'});
}
return fetchList;
};
@ -86,7 +88,7 @@ function renderTemplatePath(viewPath) {
};
}
function renderIndex(server) {
function renderIndex() {
var inline = '';
minify(__dirname+'/../es5-ff-spa-loader.js', {}, function(err, data) {
inline = '\n\t\t<script>'+data+'</script>';
@ -113,7 +115,7 @@ addClientResource('/static/js/controller/page-foo.js','js');
addClientResource('/static/js/controller/page-index.js','js');
var server = express();
server.use(morgan('dev'))
server.use(morgan('dev'));
server.use(cors({credentials: true, origin: '*'}));
server.set('view engine', 'ejs');
server.set('views', path.join(__dirname,'www_views'));
@ -133,8 +135,7 @@ server.get('/static/spa-client-resources', function (req,res) {
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
server.get('/example-ui', renderIndex());
console.info('Server config done.');
server.listen(8080);
@ -142,7 +143,7 @@ console.info('Server started on port 8080');
var res = createClientResourceFetchList();
fetchHashResources(res, function(err) {
if (err !== null) {console.log(err);};
if (err !== null) {console.log(err);}
console.log('Total assets build: '+clientResourcesWeb.length);
});

View file

@ -3,6 +3,9 @@
"version": "0.0.1",
"description": "Javascript Single Page Application Loader",
"main": "es5-ff-spa-loader.js",
"scripts": {
"test": "export JUNIT_REPORT_PATH=test/data/report.xml;export JUNIT_REPORT_STACK=1;node_modules/mocha/bin/mocha --reporter mocha-jenkins-reporter"
},
"author": "Willem <willem.git.2016@forwardfire.net> (http://forwardfire.net/)",
"keywords": [
"website",
@ -14,15 +17,22 @@
"mobile",
"resources",
"sync",
"cache"
"cache",
"localStorage",
"openDatabase"
],
"license": "BSD-2-Clause",
"repository": {
"type": "git",
"url": "https://bitbucket.org/im_ik/es5-ff-spa-loader.git"
},
"devDependencies": {
"jsdoc": "^3.4.0",
"mocha": "2.2.x",
"mocha-jenkins-reporter": "0.1.x",
"mocha-jshint": "2.2.x"
},
"dependencies": {
"jsdoc": "^3.4.0"
},
"files": [
"README.md",

3
test/jshint.spec.js Normal file
View file

@ -0,0 +1,3 @@
'use strict';
require('mocha-jshint')(['es5-ff-spa-loader.js','example']);

26
test/test-exports.js Normal file
View file

@ -0,0 +1,26 @@
'use strict';
var assert = require('assert');
var FFSpaLoader = require('../es5-ff-spa-loader');
describe('Check module exports', function() {
it('FFSpaLoader.options should be defined', function() {
assert.equal(false, FFSpaLoader.options === undefined);
});
it('FFSpaLoader.factory should be defined', function() {
assert.equal(false, FFSpaLoader.factory === undefined);
});
it('FFSpaLoader.start should be defined', function() {
assert.equal(false, FFSpaLoader.start === undefined);
});
it('FFSpaLoader.start should be function', function() {
assert.equal(true, typeof FFSpaLoader.start === 'function');
});
it('FFSpaLoader.clearServerUrl should be defined', function() {
assert.equal(false, FFSpaLoader.clearServerUrl === undefined);
});
it('FFSpaLoader.clearServerUrl should be function', function() {
assert.equal(true, typeof FFSpaLoader.clearServerUrl === 'function');
});
});

26
test/test-factory.js Normal file
View file

@ -0,0 +1,26 @@
'use strict';
var assert = require('assert');
var FFSpaLoader = require('../es5-ff-spa-loader');
describe('Check factory detect', function() {
it('FFSpaLoader.factory.detect.localStorage', function() {
assert.equal(false, FFSpaLoader.factory.detect.localStorage());
});
it('FFSpaLoader.factory.detect.openDatabase', function() {
assert.equal(false, FFSpaLoader.factory.detect.openDatabase());
});
it('FFSpaLoader.factory.detect.sqlitePlugin', function() {
assert.equal(false, FFSpaLoader.factory.detect.sqlitePlugin());
});
it('FFSpaLoader.factory.detect.cordova', function() {
assert.equal(false, FFSpaLoader.factory.detect.cordova());
});
it('FFSpaLoader.factory.detect.cordovaDevice', function() {
assert.equal(false, FFSpaLoader.factory.detect.cordovaDevice());
});
it('FFSpaLoader.factory.detect.mobileAgent', function() {
assert.equal(false, FFSpaLoader.factory.detect.mobileAgent());
});
});