2
0
Fork 0

prepare 0.3.0, moved example to main package.json and move

options.error/debug to options.boot
This commit is contained in:
Willem 2016-11-17 19:05:26 +01:00
parent a9798d05b6
commit 7b94313f90
11 changed files with 208 additions and 122 deletions

View File

@ -15,10 +15,13 @@ A javascript library providing server defined loading of assets for a single pag
* Assets hashing for fast syncing.
* Assets types: js,css,dss
* Caching backends: localStorage,webSqlDB,sqllite,none
* Server url question ui.
* Loader error ui.
* Multi server/version support by url question ui.
* Progress indicator ui.
* Loader error ui.
* No external dependencies.
* Technology agnostic booting.
* Build-in Cordova booting.
* Build-in AngularJS booting.
* Build-in AngularJS-1 booting.
## Usage
@ -29,7 +32,7 @@ A javascript library providing server defined loading of assets for a single pag
### Usage Single Server
FFSpaLoader.options.server.url = 'http://myserver';
FFSpaLoader.options.server.url = 'http://myserver'; // or window.location.href;
FFSpaLoader.options.server.assets = '/api/path/to/spa/client/resources';
FFSpaLoader.start();
@ -39,7 +42,7 @@ A javascript library providing server defined loading of assets for a single pag
FFSpaLoader.options.boot.angular.modules.push('exampleUI');
FFSpaLoader.options.server.url = 'http://myserver';
FFSpaLoader.options.server.assets = '/api/path/to/spa/client/resources';
FFSpaLoader.start(function() {
FFSpaLoader.start(function(err) {
console.log('FFExample.boot done');
});
@ -73,11 +76,11 @@ A javascript library providing server defined loading of assets for a single pag
The available FFSpaLoader.options.* values;
* debug.enable = Enable debug output. (default: false)
* debug.handler = Prints/log debug message. (default: console.log)
* debug.prefix = Debug message prefix. (default: 'FFSpaLoader.')
* error.handler = The error handler. (default: internal error handler ui)
* error.title = The error title. (default: 'Loader ');
* boot.debug.enable = Enable debug output. (default: false)
* boot.debug.handler = Prints/log debug message. (default: console.log)
* boot.debug.prefix = Debug message prefix. (default: 'FFSpaLoader.')
* boot.error.enable = Enables the ui error handler. (default: true)
* boot.error.title = The error title. (default: 'Loader ');
* boot.cordova.enable = Use deviceready event to boot when cordova is detected. (default: true)
* boot.cordova.timeout = Boot after (if<0=no-)timeout when deviceready event is not received. (default: -1)
* boot.cordova.flag = The window flag which is set when cordova is booted. (default: 'FFCordovaDevice')
@ -118,8 +121,9 @@ A javascript library providing server defined loading of assets for a single pag
## Functions
The functions iin FFSpaLoader.*;
The functions in FFSpaLoader.*;
* start(cb) = Starts loading your application, optional argument used for done or error callback.
* clearServerUrl(cb) = Clears the cached server url so after reload user get promted again.
* clearCache(cb) = Clears the cached values so after reload all assets get refetched.
@ -175,7 +179,6 @@ A javascript library providing server defined loading of assets for a single pag
* git clone https://bitbucket.org/im_ik/es5-ff-spa-loader.git
* cd es5-ff-spa-loader
* cd example
* npm install
* npm start
@ -219,7 +222,7 @@ Add unit tests for any new or changed functionality. Lint and test your code.
## Release History
### 0.2.2
### 0.3.0
* Fixed clearServerUrl() to also clean the cached server resources json.
* Auto clean option.server.url from # and ? endings for: FFSpaLoader.options.server.url = window.location.href;
* Added server.depath option to strip the ui-prefix from the server.url.
@ -228,6 +231,11 @@ Add unit tests for any new or changed functionality. Lint and test your code.
* Fixed duplicate starts on askUrl submit multiple clicks.
* Switch to karma + jasmine unit tests.
* Fixed define.amd module constructor.
* Integrated example package.json to main file.
* Removed options.error.[handler|title].
* Added options.boot.error.[enable|title].
* start(cb) function callback now includes error.
* Moved options.debug.* to options.boot.debug.*.
### 0.2.1
* Fixed clearCache method to added json header.

View File

@ -33,30 +33,31 @@
(function (root, factory) {
'use strict';
if ( typeof define === 'function' && define.amd ) {
define(factory(root));
} else if ( typeof exports === 'object' ) {
module.exports = factory(root);
} else {
root.FFSpaLoader = factory(root);
}
if ( typeof define === 'function' && define.amd ) {
define(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';
/**
* The options to customize the loader.
*/
* The options to customize the loader.
*/
var options = {
debug: {
enable: false,
handler: null, // auto filled
prefix: 'FFSpaLoader.'
},
error: {
handler: null, // auto filled
title: 'Loader '
},
boot: {
debug: {
enable: false,
handler: null, // auto filled
prefix: 'FFSpaLoader.'
},
error: {
enable: true,
title: 'Loader '
},
cordova: {
enable: true,
timeout: -1,
@ -132,49 +133,49 @@
};
/**
* Use single instance for websql
* @private
*/
* Use single instance for websql
* @private
*/
var cacheDB = null;
/**
* Use delayed fetch
* @private
*/
* Use delayed fetch
* @private
*/
var delayedResources = [];
/**
* Ask Url submit lock.
* @private
*/
* Ask Url submit lock.
* @private
*/
var askUrlSubmitLock = false;
/**
* 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
*/
* Prints the debug message with prefix to the options.boot.debug.handler if options.boot.debug.enable is true.
* @param {String} message The message to log.
* @private
*/
var utilDebug = function (message) {
if (options.debug.enable !== true) {
if (options.boot.debug.enable !== true) {
return;
}
options.debug.handler(options.debug.prefix+message);
options.boot.debug.handler(options.boot.debug.prefix+message);
};
/**
* The factory which contains detection helpers and cache backend builders.
*/
* The factory which contains detection helpers and cache backend builders.
*/
var factory = {
detect: {
localStorage: function() {
try {
var testData = 'localStorageDetect';
rootWindow.localStorage.setItem(testData, testData); // throws err in private browsing mode
rootWindow.localStorage.removeItem(testData);
return true;
} catch(e) {
return false;
}
try {
var testData = 'localStorageDetect';
rootWindow.localStorage.setItem(testData, testData); // throws err in private browsing mode
rootWindow.localStorage.removeItem(testData);
return true;
} catch(e) {
return false;
}
},
openDatabase: function() {
return 'openDatabase' in rootWindow;
@ -305,10 +306,10 @@
};
/**
* The default error handler which renders the error in the browser.
* @param {Error|String} err The error object or message.
* @private
*/
* The default error handler which renders the error in the browser.
* @param {Error|String} err The error object or message.
* @private
*/
var utilErrorHandler = function(err) {
if (!(err instanceof Error)) {
err = new Error(err);
@ -318,10 +319,10 @@
var rootTag = document.createElement('div');
rootTag.setAttribute('class','ffWrapper');
document.getElementsByTagName('body')[0].appendChild(rootTag);
var titleTag = document.createElement('div');
titleTag.setAttribute('class','ffTitle');
titleTag.appendChild(document.createTextNode(options.error.title+err.name));
titleTag.appendChild(document.createTextNode(options.boot.error.title+err.name));
rootTag.appendChild(titleTag);
var dialogTag = document.createElement('div');
@ -336,6 +337,7 @@
var stack = err.stack || '';
stack = stack.split('\n').map(function (line) { return line.trim()+'\n'; });
var stackText = stack.splice(stack[0] === 'Error' ? 2 : 1);
err.stackText = stackText;
var traceTag = document.createElement('pre');
traceTag.appendChild(document.createTextNode(stackText));
@ -424,6 +426,7 @@
};
var utilCleanServerUrl = function() {
var serverUrlOrg = options.server.url;
var index1 = options.server.url.indexOf('#');
if (index1 > 0){
options.server.url = options.server.url.substring(0,index1);
@ -433,13 +436,14 @@
options.server.url = options.server.url.substring(0,index2);
}
if (options.server.depath !== null && options.server.url.indexOf(options.server.depath) > 0) {
utilDebug('start server url depathing');
utilDebug('start server.url depathing');
options.server.url = options.server.url.substring(0,options.server.url.indexOf(options.server.depath) - 1);
}
if (options.server.url.indexOf('file://') === 0) {
utilDebug('start server url cleared because is file uri');
utilDebug('start server.url cleared because is file uri');
options.server.url = null;
}
utilDebug('start cleaned server.url \"'+options.server.url+'\" from \"'+serverUrlOrg+'\"');
};
var cacheGetService = function (type) {
@ -1149,38 +1153,47 @@
rootWindow[options.boot.cordova.flag] = true;
utilDebug('bootCordova '+options.boot.cordova.flag);
bootOnce();
}, false);
}, false);
};
/**
* Starts the loader.
*
* @param {function} cb Optional callback gets called when loader is done.
* @param {function} cb Optional callback gets called when loader is done or has error.
*/
var start = function (cbArgu) {
var startTime = new Date().getTime();
var cbRoot = function(err) {
if (err !== null) {
if (options.boot.error.enable !== true) {
utilDebug('bootError disabled by options');
} else {
utilErrorHandler(err);
}
}
if (typeof cbArgu === 'function') {
cbArgu(err);
}
};
var cb = function(err) {
if (err !== null) {
options.error.handler(err);
cbRoot(err);
} else {
utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.'); // last debug line TODO: move bootAngular to onjsloaded
bootCleanup(); // move after ang.
bootAngular(function(err) {
if (err !== null) { return options.error.handler(err); }
loadDelayedResouces();
if (typeof cbArgu === 'function') {
cbArgu();
}
cbRoot(err);
});
}
};
utilDebug('start spa loader'); // first debug line TODO: Add version
utilDebug('start spa-loader'); // first debug line TODO: Add version
if (options.server.url !== null) {
utilCleanServerUrl();
}
if (options.debug.enable === true) {
if (options.boot.debug.enable === true) {
var optionsKeys = Object.keys(options);
for (var keyId in optionsKeys) {
var key = optionsKeys[keyId];
@ -1271,8 +1284,7 @@
};
// Auto fill handlers and return public object.
options.debug.handler = function(msg) {console.log(msg);};
options.error.handler = utilErrorHandler;
options.boot.debug.handler = function(msg) {console.log(msg);};
return {
options: options,
factory: factory,

View File

@ -11,6 +11,7 @@ var UglifyJS = require("uglify-js");
var Hashes = require('jshashes');
var minify = require('minify');
var appPath = '/test'; // dynamic context path for version or proxy/etc of server app.
var clientResourcesWeb = [];
var clientResources = {
js: [],
@ -23,10 +24,11 @@ var addClientResource = function(clientResource, resourceType) {
};
var fetchHashResource = function(fetchEntry,cb) {
var serverUrl = 'http://localhost:'+httpPort;
var serverUrl = 'http://localhost:'+httpPort+appPath;
var hashDigest = new Hashes.SHA1;
fetch.fetchUrl(serverUrl + fetchEntry.url,function(err, meta, data) {
if (err !== null) { return cb(err); }
if (meta.status !== 200) { return cb('wrong status: '+meta.status); }
var assetHash = hashDigest.hex(''+data);
clientResourcesWeb.push({
url: fetchEntry.url,
@ -39,7 +41,8 @@ var fetchHashResource = function(fetchEntry,cb) {
var fetchHashResources = function(fetchList, cb) {
var resourceStack = fetchList;
var resourceLoader = function() {
var resourceLoader = function(err) {
if (err !== null) { return cb(err); }
resourceStack = resourceStack.slice(1);
if (resourceStack.length === 0) {
cb(null);
@ -83,7 +86,7 @@ function renderIndex() {
var inlineScript = UglifyJS.minify(__dirname+'/../../es5-ff-spa-loader.js');
minify(__dirname+'/../../es5-ff-spa-loader.css', {}, function(err, data) {
res.render('index', {
inlineScript: inlineScript.code,
inlineScript: 'console.log(\'test\');'+inlineScript.code,
inlineStyle: data
});
});
@ -102,8 +105,9 @@ addClientResource('/static/js/example-app.js','js'); // deps: jquery,angular
addClientResource('/static/js/controller/page-bar.js','js'); // deps: example-app.js
addClientResource('/static/js/controller/page-foo.js','js');
addClientResource('/static/js/controller/page-index.js','js');
// NOTE: appPath should be done as request parameter which auto prefixes the data, as only the client knows the true context path of a http application.
var appPath = '/test';
var server = express();
server.use(morgan('dev'));
server.use(cors({credentials: true, origin: '*', exposedHeaders: ['X-My-Api']}));
@ -111,10 +115,10 @@ server.set('view engine', 'ejs');
server.set('views', path.join(__dirname,'www_views'));
server.use(function(req, res, next) { res.header('X-My-Api', 'noknok');next(); });
server.use(appPath+'/static', express.static(path.join(__dirname,'www_static')));
server.use(appPath+'/static/module/bootstrap', express.static(path.join(__dirname,'../node_modules/bootstrap/dist')));
server.use(appPath+'/static/module/jquery', express.static(path.join(__dirname,'../node_modules/jquery/dist')));
server.use(appPath+'/static/module/angular', express.static(path.join(__dirname,'../node_modules/angular')));
server.use(appPath+'/static/module/angular-route', express.static(path.join(__dirname,'../node_modules/angular-route')));
server.use(appPath+'/static/module/bootstrap', express.static(path.join(__dirname,'../../node_modules/bootstrap/dist')));
server.use(appPath+'/static/module/jquery', express.static(path.join(__dirname,'../../node_modules/jquery/dist')));
server.use(appPath+'/static/module/angular', express.static(path.join(__dirname,'../../node_modules/angular')));
server.use(appPath+'/static/module/angular-route', express.static(path.join(__dirname,'../../node_modules/angular-route')));
server.get(appPath+'/static/spa-client-resources', function (req,res) {res.json({data: {resources: clientResourcesWeb}});});
server.get(appPath+'/static/spa-loader.css', function (req,res) {res.sendFile('es5-ff-spa-loader.css', { root: path.join(__dirname, '/../../') });});
server.get(appPath+'/', function (req, res) {res.redirect(appPath+'/example-ui');});
@ -125,9 +129,12 @@ server.get('/', function (req, res) {res.redi
server.listen(httpPort);
console.info('Server started on port '+httpPort);
var res = createClientResourceFetchList();
fetchHashResources(res, function(err) {
if (err !== null) {console.log(err);}
console.log('Total assets build: '+clientResourcesWeb.length);
fetchHashResources(createClientResourceFetchList(), function(err) {
if (err !== null) {
console.log('Fatal error '+err);
process.exit(1);
} else {
console.log('Total assets build: '+clientResourcesWeb.length);
}
});

View File

@ -4,7 +4,7 @@ document.title = 'FFSpaLoader Example';
var serverUrl = window.FFServerUrl;
console.log('FFExample provided serverUrl \"'+serverUrl+'\"');
// TODO for 0.2.1
// TODO for 0.4.0
//var tplCache = FFSpaLoader.factory.cache.websql({table: 'angular_tpl'});
//tplCache.cacheOpen(function(err) {
// tplCache.cacheSetValue('key123','value456',function(err) {
@ -30,6 +30,6 @@ var exampleUI = angular.module('exampleUI', ['ngRoute']).config(
exampleUI.controller('ApplicationController',function($scope,$http,$location) {
$scope.goLink = function ( path ) {
$location.path( path );
$location.path( path );
};
});

View File

@ -3,12 +3,12 @@
<head>
<meta charset="UTF-8"/>
<title>Loading</title>
<script id="ffCleanupScript"><%- inlineScript %></script>
<style id="ffCleanupStyle"><%- inlineStyle %></style>
<script id="ffCleanupScript" type="text/javascript"><%- inlineScript %></script>
<style id="ffCleanupStyle" type="text/css"><%- inlineStyle %></style>
</head>
<body>
<script id="ffCleanupConfig">
FFSpaLoader.options.debug.enable = true;
<script id="ffCleanupConfig" type="text/javascript">
FFSpaLoader.options.boot.debug.enable = true;
FFSpaLoader.options.boot.angular.modules.push('exampleUI');
FFSpaLoader.options.boot.cleanup.tags.push('ffCleanupStyle');
FFSpaLoader.options.boot.cleanup.tags.push('ffCleanupScript');

View File

@ -20,11 +20,11 @@ gulp.task('clean', function() {
});
gulp.task('buildScript', ['test'], function() {
gulp.src(srcFile, {cwd: srcPath}).pipe(gulp.dest(distPathJS));
return gulp.src(srcFile, {cwd: srcPath}).pipe(gulp.dest(distPathJS));
});
gulp.task('buildScriptMin', ['buildScript'], function() {
gulp.src(srcFile, {cwd: srcPath})
return gulp.src(srcFile, {cwd: srcPath})
.pipe(srcmaps.init())
.pipe(uglify({/*preserveComments: 'license'*/}))
.pipe(rename({ extname: '.min.js' }))
@ -33,20 +33,20 @@ gulp.task('buildScriptMin', ['buildScript'], function() {
});
gulp.task('buildCss', ['test'], function() {
gulp.src(srcCss, {cwd: srcPath}).pipe(gulp.dest(distPathCSS));
return gulp.src(srcCss, {cwd: srcPath}).pipe(gulp.dest(distPathCSS));
});
gulp.task('buildCssMin',['buildCss'], function () {
return gulp.src(srcCss, {cwd: srcPath})
.pipe(srcmaps.init())
.pipe(cssnano())
.pipe(rename({ extname: '.min.css' }))
.pipe(srcmaps.write('.'))
.pipe(gulp.dest(distPathCSS));
return gulp.src(srcCss, {cwd: srcPath})
.pipe(srcmaps.init())
.pipe(cssnano())
.pipe(rename({ extname: '.min.css' }))
.pipe(srcmaps.write('.'))
.pipe(gulp.dest(distPathCSS));
});
gulp.task('testKarma',['clean'], function (done) {
testServer.start(function () {
return testServer.start(function () {
new karma.Server({
configFile: __dirname + '/test/karma.conf.js',
singleRun: true
@ -69,10 +69,10 @@ gulp.task('buildJSDoc', function (cb) {
cleverLinks: true
}
};
gulp.src(srcFile, {read: false}).pipe(jsdoc(jsdocConfig, cb));
return gulp.src(srcFile, {read: false}).pipe(jsdoc(jsdocConfig, cb));
});
gulp.task('test', ['clean','testKarma']);
gulp.task('test', ['testKarma']);
gulp.task('build', ['test','buildCssMin','buildScriptMin'/*,'buildJSDoc'*/]);

View File

@ -56,6 +56,7 @@
"karma": "0.13.9",
"karma-coverage": "0.5.5",
"karma-jasmine": "0.3.8",
"karma-jasmine-ajax": "0.1.13",
"karma-jshint": "0.1.0",
"karma-junit-reporter": "0.4.2",
"karma-mocha-reporter": "2.0.1",

View File

@ -4,12 +4,13 @@ module.exports = function(config) {
plugins : ['karma-requirejs',
'karma-coverage',
'karma-phantomjs-launcher',
'karma-jasmine-ajax',
'karma-jasmine',
'karma-jshint',
'karma-mocha-reporter',
'karma-junit-reporter'
],
frameworks : [ 'jasmine', 'requirejs' ],
frameworks : [ 'jasmine-ajax', 'jasmine', 'requirejs' ],
files : [ {
pattern : 'es5-ff-spa-loader.js',
included : false

View File

@ -25,4 +25,4 @@ require.config({
waitSeconds : 30,
deps : testModules,
callback : window.__karma__.start
});
});

View File

@ -3,31 +3,46 @@ define(['es5-ff-spa-loader'], function(FFSpaLoader) {
'use strict';
describe('Start loader', function() {
beforeEach(function () {
function emptyElement(element) {
var myNode = element;
while (myNode.firstChild) {
myNode.removeChild(myNode.firstChild);
}
}
emptyElement(document.head);
emptyElement(document.body);
});
it('FFSpaLoader start with error', function(done) {
FFSpaLoader.options.server.url = 'http://localhost:9999';
FFSpaLoader.options.error.handler = function(err) {
done();
};
FFSpaLoader.start();
FFSpaLoader.start(function(err) {
if (err !== null) {
done();
} else {
fail();
}
});
});
it('FFSpaLoader starts', function(done) {
FFSpaLoader.options.server.url = 'http://localhost:9090/test';
FFSpaLoader.options.server.assets = '/static/spa-client-resources';
FFSpaLoader.options.error.handler = function(err) {
fail(); // TIODI
};
FFSpaLoader.start(function() {
done();
FFSpaLoader.start(function(err) {
if (err !== null) {
fail();
} else {
done();
}
});
});
it('FFSpaLoader starts again', function(done) {
FFSpaLoader.options.server.url = 'http://localhost:9090/test';
FFSpaLoader.options.server.assets = '/static/spa-client-resources';
FFSpaLoader.options.error.handler = function(err) {
fail(); // TIODI
};
FFSpaLoader.start(function() {
done();
FFSpaLoader.start(function(err) {
if (err !== null) {
fail();
} else {
done();
}
});
});
});

42
test/spec/test-cache.js Normal file
View File

@ -0,0 +1,42 @@
define(['es5-ff-spa-loader'], function(FFSpaLoader) {
'use strict';
describe('Test Cache', function() {
beforeEach(function() {
jasmine.Ajax.install();
});
afterEach(function() {
jasmine.Ajax.uninstall();
});
it('FFSpaLoader boot ', function(done) {
var clientResource = {
data: {
resources: [{
url: '/static/js/foobar.js',
type: 'js',
hash: 12381273
}]
}
};
jasmine.Ajax.stubRequest('http://localhost:999/static/spa-client-resources').andReturn({
'responseText': JSON.stringify(clientResource)
});
jasmine.Ajax.stubRequest('http://localhost:999/static/js/foobar.js').andReturn({
'responseText': '\nvar FOOBAR = \'loaded\';\n'
});
//FFSpaLoader.options.boot.debug.enable = true;
FFSpaLoader.options.server.url = 'http://localhost:999';
FFSpaLoader.options.server.assets = '/static/spa-client-resources';
FFSpaLoader.start(function(err) {
if (err !== null) {
fail();
} else {
done();
}
});
});
});
});