diff --git a/README.md b/README.md index 0fb647f..ca6e69a 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/es5-ff-spa-loader.js b/es5-ff-spa-loader.js index b6846de..a5f914a 100644 --- a/es5-ff-spa-loader.js +++ b/es5-ff-spa-loader.js @@ -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, diff --git a/example/app_server/example.js b/example/app_server/example.js index 6c06ba8..e20eec3 100644 --- a/example/app_server/example.js +++ b/example/app_server/example.js @@ -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); + } }); diff --git a/example/app_server/www_static/js/example-app.js b/example/app_server/www_static/js/example-app.js index 7ee69f3..3934085 100644 --- a/example/app_server/www_static/js/example-app.js +++ b/example/app_server/www_static/js/example-app.js @@ -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 ); }; }); diff --git a/example/app_server/www_views/index.ejs b/example/app_server/www_views/index.ejs index c230f77..3d33ecf 100644 --- a/example/app_server/www_views/index.ejs +++ b/example/app_server/www_views/index.ejs @@ -3,12 +3,12 @@