diff --git a/.gitignore b/.gitignore
index 94f7b77..2a241e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,48 @@
-# ignore
+#
+# es5-ff-spa-loader git ignore rules
+#
+
+# Ignore npm files
+node_modules
+npm-debug.log
+
+# Ignore mocha test data
+test/data
+
+# Ignore example data
+example/node_modules
+example/npm-debug.log
+example/www_static/css/lib
+example/www_static/js/lib
+
+# Ignore binary files
+*.o
+*.class
+*.old
+*.bak
+*.backup
+*.dat
+*.data
+*.gif
+*.pdf
+*.doc
+
+# Ignore ~ backup files.
+*~
+
+# Ignore some eclipse files
+.settings
+.classpath
+
+# Ignore netbeans directory
+nbproject
+
+# Ignore mac finder files
+.DS_Store
+
+# Ignore windows files.
+Thumbs.db
+Desktop.ini
+
+# Ignore kde dolphin files
+.directory
diff --git a/.project b/.project
new file mode 100644
index 0000000..fa954bc
--- /dev/null
+++ b/.project
@@ -0,0 +1,11 @@
+
+
+ es5-ff-spa-loader
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index a93d382..3abfb6e 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,41 @@
es5-ff-spa-loader
+=========
+
+A javascript library providing server defined loading of resouces for a single page application.
+
+## Installation
+
+ Use the es5-ff-spa-loader.js in header as script resource or inline in body.
+
+## Usage
+
+ On index.html
+
+ FFSpaLoader.start({
+ url: '/my/url/for/resources.json',
+ debug: true
+ });
+
+## Options
+
+ * url = The url to fetch resources list from.
+ * debug = Enable console debug output.
+ * cachePrefix = The cache key prefix. (default: 'sync-')
+ * cacheGetFn = example; 'function(key) {return localStorage.getItem(key);}'
+ * cacheSetFn = example; 'function(key,value) {localStorage.setItem(key,value);}'
+ * cacheDelFn = example; 'function(key) {return localStorage.removeItem(key);}'
+
+## Example Application
+
+ There is a fully working express example application in the example folder.
+ todo
+
+## Contributing
+
+In lieu of a formal styleguide, take care to maintain the existing coding style.
+Add unit tests for any new or changed functionality. Lint and test your code.
+
+## Release History
+
+### 0.1.x
+* Initial release
diff --git a/es5-ff-spa-loader.js b/es5-ff-spa-loader.js
new file mode 100644
index 0000000..bf30cb3
--- /dev/null
+++ b/es5-ff-spa-loader.js
@@ -0,0 +1,299 @@
+'use strict';
+/*
+ * Copyright (c) 2015-2016, Willem Cazander
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this list of conditions and the
+ * following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
+ * the following disclaimer in the documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * 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:
+// conv to req queue
+// split js/css so css can be done later
+// set tag.media = 'only you';
+// add media in resouces
+// add userAgant match in resouces
+// NOTE: add ordered inject, for now JS in non-cache mode needs to be aggragated server side
+// add browser supported
+
+var FFSpaLoader = (function () {
+
+ var options = {};
+
+ var utilDebug = function (message) {
+ if (options.debug !== true) {
+ return;
+ }
+ console.log('FFSpaLoader.'+message);
+ };
+
+ var utilHttpFetch = function (url, cb) {
+ var startTime = new Date().getTime();
+ var httpRequest = new XMLHttpRequest();
+ httpRequest.onreadystatechange = function() {
+ if (httpRequest.readyState == 4) {
+ utilDebug('utilHttpFetch url: '+url+' done in '+(new Date().getTime()-startTime)+' ms.');
+ cb(httpRequest);
+ }
+ }
+ httpRequest.open('GET', url, true);
+ httpRequest.send();
+ };
+
+ var cacheHasItem = function (resource) {
+ if (options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined) {
+ return false;
+ }
+ var result = options.cacheGetFn(options.cachePrefix + resource.hash) !== null;
+ utilDebug('cacheHasItem resource: '+resource.url+' result: '+result);
+ return result;
+ }
+
+ var cacheGetItem = function (resource) {
+ if (options.cacheGetFn === undefined) {
+ throw new Error('no caching');
+ }
+ utilDebug('cacheGetItem resource: '+resource.url);
+ return JSON.parse(options.cacheGetFn(options.cachePrefix + resource.hash));
+ }
+
+ var cachePutRequest = function (resource, httpRequest) {
+ if (options.cacheSetFn === undefined) {
+ throw new Error('no caching');
+ }
+ var cacheKey = options.cachePrefix + resource.hash;
+ var data = httpRequest.responseText;
+ utilDebug('cachePutData url: '+resource.url+' key: '+cacheKey);
+ options.cacheSetFn(cacheKey, JSON.stringify({
+ resource: resource,
+ data: data,
+ dataDate: new Date().getTime()
+ }));
+
+ // Add all cache keys in central list so we can clear the cache item if resources are removed.
+ var cacheListKey = options.cachePrefix + 'cache-list';
+ var cacheListStr = options.cacheGetFn(cacheListKey);
+ if (cacheListStr === null) {
+ cacheListStr = '[]';
+ }
+ var cacheList = JSON.parse(cacheListStr);
+ cacheList.push(cacheKey);
+ options.cacheSetFn(cacheListKey, JSON.stringify(cacheList));
+ }
+
+ var cacheCleanup = function (resources) {
+ if (options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined) {
+ return;
+ }
+ var cacheListKey = options.cachePrefix + 'cache-list';
+ var cacheListStr = options.cacheGetFn(cacheListKey);
+ if (cacheListStr === null) {
+ return;
+ }
+ var cacheList = JSON.parse(cacheListStr);
+
+ utilDebug('cacheCleanup TODO cacheList: '+cacheList.length+' resources: '+resources.length);
+
+ // TODO: impl for removes in resource lists
+ }
+
+ var createResource = function (resource, cb) {
+ utilDebug('createResource url: '+JSON.stringify(resource));
+ if (cacheHasItem(resource)) {
+ if (cacheGetItem(resource).resource.hash === resource.hash) {
+ cb();
+ return;
+ }
+ utilDebug('createResource hash mismatch request fetch: '+resource.url);
+ } else {
+ utilDebug('createResource cache miss request fetch: '+resource.url);
+ }
+ utilHttpFetch(resource.url, function(httpRequest) {
+ if (httpRequest.status == 200) {
+ cachePutRequest(resource, httpRequest);
+ } else {
+ console.warn('error loading '+resource.url);
+ }
+ cb();
+ });
+ }
+
+ var createResources = function(resources, cb) {
+ var startTime = new Date().getTime();
+ utilDebug('createResources');
+ var resourceStack = resources;
+ var resourceLoader = function() {
+ resourceStack = resourceStack.slice(1);
+ if (resourceStack.length === 0) {
+ utilDebug('createResources done in '+(new Date().getTime()-startTime)+' ms.');
+ cb();
+ } else {
+ createResource(resourceStack[0],resourceLoader);
+ }
+ };
+ createResource(resourceStack[0],resourceLoader);
+ }
+
+ var injectResources = function(resources, cb) {
+ var startTime = new Date().getTime();
+ utilDebug('injectResources');
+ resources.forEach(function (resource) {
+ var item = cacheGetItem(resource);
+ var tag = null;
+ if (resource.type === 'css') {
+ tag = document.createElement('style');
+ tag.type = 'text/css';
+ } else {
+ tag = document.createElement('script');
+ tag.type = 'text/javascript';
+ }
+ utilDebug('injectResources resource: '+JSON.stringify(resource));
+
+ tag.appendChild(document.createTextNode(item.data));
+
+ //document.getElementsByTagName('head')[0].appendChild(tag);
+ var ref = document.getElementsByTagName('script')[0];
+ ref.parentNode.insertBefore(tag, ref); // note in reverse order
+ });
+ utilDebug('injectResources done in '+(new Date().getTime()-startTime)+' ms.');
+ cb();
+ }
+
+ var injectLinkResources = function(resources, cb) {
+ var startTime = new Date().getTime();
+ utilDebug('injectLinkResources');
+ resources.forEach(function (resource) {
+ utilDebug('injectLinkResources resource: '+JSON.stringify(resource));
+ var tag = null;
+ if (resource.type === 'css') {
+ tag = document.createElement('link');
+ tag.type = 'text/css';
+ tag.rel = 'stylesheet';
+ tag.href = resource.url;
+ }
+ if (resource.type === 'js') {
+ tag = document.createElement('script');
+ tag.type = 'text/javascript';
+ tag.src = resource.url;
+ }
+ if (tag !== null) {
+ var startTime2 = new Date().getTime();
+ document.getElementsByTagName('head')[0].appendChild(tag);
+ utilDebug('inject time in '+(new Date().getTime()-startTime2)+' ms.');
+ //var ref = document.getElementsByTagName('script')[0];
+ //ref.parentNode.insertBefore(tag, ref); // TODO reverser order
+ } else {
+ utilDebug('unknow resource type: '+resource.type);
+ // TODO add err
+ }
+ });
+ utilDebug('injectLinkResources done in '+(new Date().getTime()-startTime)+' ms.');
+ cb();
+ }
+
+ var startDefaults = function () {
+ if (options.url === undefined) {
+ throw new Error('No url defined');
+ }
+ if (options.debug === undefined) {
+ options.debug = false;
+ }
+ if (options.cachePrefix === undefined) {
+ options.cachePrefix = 'sync-';
+ }
+ }
+
+ var startDone = function (startTime) {
+ utilDebug('start done in '+(new Date().getTime()-startTime)+' ms.');
+ if (options.resultFn) {
+ options.resultFn();
+ }
+ }
+
+ var start = function (opt) {
+ if (opt === undefined) {
+ throw new Error('need options');
+ }
+ var startTime = new Date().getTime();
+ options = opt;
+ startDefaults();
+ var injectAsLink = options.cacheGetFn === undefined || options.cacheSetFn === undefined || options.cacheDelFn === undefined;
+ utilDebug('start options: '+JSON.stringify(options)+' injectAsLink: '+injectAsLink);
+
+
+
+ var resources = [];
+ utilHttpFetch(options.url, function(httpRequest) {
+ if (httpRequest.status == 200) {
+ JSON.parse(httpRequest.responseText).data.resources.forEach(function (r) {
+ resources.push(r);
+ utilDebug('build resources add: '+JSON.stringify(r));
+ });
+ if (injectAsLink) {
+ injectLinkResources(resources, function () {
+ startDone(startTime);
+ });
+ } else {
+ createResources(resources, function () {
+ injectResources(resources, function () {
+ cacheCleanup(resources);
+ startDone(startTime);
+ });
+ });
+ }
+ } else {
+ console.log('app err'); // TODO: fix async load error code path
+ }
+ })
+ };
+
+ var getOrAskUrl = function (opt) {
+
+ };
+ // TODO: getOrAskUrl: getOrAskUrl,
+
+ return {
+ start: start
+ };
+})();
+
+var autoStartFFSpaLoader = function () {
+ var scripts = document.getElementsByTagName('SCRIPT');
+ if (scripts && scripts.length>0) {
+ for (var i in scripts) {
+ if (scripts[i].src && scripts[i].src.match(/.*ffSpaLoaderUrl.*/)) {
+ var query = scripts[i].src;
+ var queryParameters = {};
+ query.split('?').forEach(function(part) {
+ var item = part.split("=");
+ queryParameters[item[0]] = decodeURIComponent(item[1]);
+ });
+ // TODO: remove debug here
+ FFSpaLoader.start({
+ debug: true,
+ injectAsLink: true,
+ url: queryParameters.ffSpaLoaderUrl
+ });
+ break;
+ }
+ }
+ }
+};
+
+autoStartFFSpaLoader(); // TODO: make private
diff --git a/example/example-assets.json b/example/example-assets.json
new file mode 100644
index 0000000..e4d3138
--- /dev/null
+++ b/example/example-assets.json
@@ -0,0 +1,27 @@
+{
+ "linkMapping" : {
+ "/static/module/bootstrap/": "node_modules/bootstrap/dist/",
+ "/static/module/flot/": "node_modules/flot/",
+ "/static/": "www_static/"
+ },
+ "css": {
+ "linkTarget": "/static/css/lib/assets.css",
+ "linkSources": [
+ "/static/module/bootstrap/css/bootstrap.css"
+ ]
+ },
+ "js": {
+ "linkTarget": "/static/js/lib/assets.js",
+ "linkSources": [
+ "/static/js/lib/jquery-2.1.3/jquery.js@http://code.jquery.com/jquery-2.1.3.js",
+ "/static/module/bootstrap/js/bootstrap.js",
+ "/static/module/flot/jquery.flot.js",
+ "/static/module/flot/jquery.flot.resize.js",
+ "/static/module/flot/jquery.flot.pie.js",
+ "/static/js/lib/angularjs-1.4.0-b4/angular.js@https://code.angularjs.org/1.4.0-beta.4/angular.js",
+ "/static/js/lib/angularjs-1.4.0-b4/angular-route.js@https://code.angularjs.org/1.4.0-beta.4/angular-route.js",
+ "/static/js/lib/angularjs-1.4.0-b4/angular-resource.js@https://code.angularjs.org/1.4.0-beta.4/angular-resource.js",
+ "/static/js/lib/angularjs-1.4.0-b4/angular-touch.js@https://code.angularjs.org/1.4.0-beta.4/angular-touch.js"
+ ]
+ }
+}
diff --git a/example/example.js b/example/example.js
new file mode 100644
index 0000000..7acc4f2
--- /dev/null
+++ b/example/example.js
@@ -0,0 +1,148 @@
+'use strict';
+
+var express = require('express');
+var async = require('async');
+var path = require('path');
+var assets = require('node-ff-assets');
+var fs = require('fs');
+var minify = require('minify');
+
+function buildAssets(server,callbackDone) {
+ var singleResult = 'false' !== process.env.DEV_ASSETS_SINGLE_RESULT;
+ var assetsConfig = require('./example-assets.json');
+ assets.build({
+ assets: {
+ js: {
+ configCreate: assets.factory.builder.configCreate.fromJSON(assetsConfig,'js'),
+ configFill: function (config, callback) {
+ async.series([
+ assets.factory.lib.async.pushLinkSources(config, '/static/js/', 'www_static/js/'),
+ assets.factory.lib.async.pushLinkSources(config, '/static/js/controller/', 'www_static/js/controller/'),
+ ],callback);
+ },
+ },
+ css: {
+ configCreate: assets.factory.builder.configCreate.fromJSON(assetsConfig,'css'),
+ configFill: function (config, callback) {
+ async.series([
+ assets.factory.lib.async.pushLinkSources(config, '/static/css/', 'www_static/css/'),
+ ],callback);
+ },
+ },
+ },
+ assemblerCreate: assets.factory.builder.assemblerCreate.readFileRegex(),
+ assemblerFill: function (assembler, callback) {
+ var serverResultKey = 'ff_assets_'+assembler.config.assetType;
+ assembler.on ('log', assets.factory.assembler.event.log.console(assembler.config.assetType));
+ assembler.on ('result', assets.factory.assembler.event.result.objectSet(server,serverResultKey));
+ assembler.config.linkTargetSingleResult = singleResult;
+ callback();
+ },
+ },callbackDone);
+}
+
+function renderTemplatePath(viewPath) {
+ return function (req, res) {
+ res.locals.query = req.query;
+ var qi = req.url.indexOf('?');
+ if (qi === -1) {
+ qi = req.url.length;
+ }
+ res.render(viewPath + req.url.substring(req.route.path.length-1, qi));
+ };
+}
+
+function renderIndex(server) {
+ var inline = '';
+ minify(__dirname+'/../es5-ff-spa-loader.js', {}, function(err, data) {
+ inline = '\n\t\t';
+ });
+ var inlineNot = '\n\t\t';
+
+ var useInline = true; // or false
+
+ return function (req, res) {
+ res.render('index', {
+ inlineNot: useInline?'':inlineNot,
+ inline: useInline?inline:''
+ });
+ };
+}
+
+function sendRedirect() {
+ return function (req, res) {
+ res.redirect('/example-ui');
+ };
+}
+
+ var stringHash = function (str) {
+ var hash = 31; // prime
+ for (var i = 0; i < str.length; i++) {
+ hash = ((hash<<5)-hash)+str.charCodeAt(i);
+ hash = hash & hash; // keep 32b
+ }
+ return hash;
+};
+
+var server = express();
+buildAssets(server,function(err) {
+ if (err) {
+ throw err;
+ }
+ console.info('Server assets done.');
+
+ server.set('view engine', 'ejs');
+ server.set('views', path.join(__dirname,'www_views'));
+ server.use('/static', express.static(path.join(__dirname,'www_static')));
+ server.use('/static/module/bootstrap', express.static(path.join(__dirname,'node_modules/bootstrap/dist')));
+ server.use('/static/module/flot', express.static(path.join(__dirname,'node_modules/flot')));
+
+ server.get('/static/es5-ff-spa-loader.js', function (req,res) {
+ res.write(fs.readFileSync(__dirname+'/../es5-ff-spa-loader.js', 'utf8'));
+ res.end();
+ });
+ server.get('/static/resources-spa-mobile', function (req,res) {
+ var hashJs = stringHash(fs.readFileSync(__dirname+'/www_static/js/lib/assets.js', 'utf8'));
+ var hashCss = stringHash(fs.readFileSync(__dirname+'/www_static/css/lib/assets.css', 'utf8'));
+ res.json({
+ data: {
+ resources: [
+ {
+ url: '/static/js/lib/assets.js----TODO',
+ type: 'js',
+ hash: hashJs
+ },
+ ]
+ }
+ });
+ });
+ server.get('/static/resources-spa-web', function (req,res) {
+ var hashJs = stringHash(fs.readFileSync(__dirname+'/www_static/js/lib/assets.js', 'utf8'));
+ var hashCss = stringHash(fs.readFileSync(__dirname+'/www_static/css/lib/assets.css', 'utf8'));
+ res.json({
+ data: {
+ resources: [
+ {
+ url: '/static/js/lib/assets.js',
+ type: 'js',
+ hash: hashJs
+ },
+ {
+ url: '/static/css/lib/assets.css',
+ type: 'css',
+ hash: hashCss
+ }
+ ]
+ }
+ });
+ });
+
+ server.get('/', sendRedirect());
+ server.get('/example-ui/thtml/*', renderTemplatePath('thtml/'));
+ server.get('/example-ui', renderIndex(server));
+ server.get('/example-ui/*', renderIndex(server)); // must be last; for HTML5 history
+ console.info('Server config done.');
+
+ server.listen(8080);
+ console.info('Server started on port 8080');
+});
diff --git a/example/package.json b/example/package.json
new file mode 100644
index 0000000..32f3b48
--- /dev/null
+++ b/example/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "es5-ff-spa-loader-example",
+ "description": "es5-ff-spa-loader-example",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node example.js"
+ },
+ "dependencies": {
+ "async": "1.2.x",
+ "bootstrap": "3.3.x",
+ "ejs": "2.3.x",
+ "express": "4.11.x",
+ "flot": "0.8.x",
+ "minify": "^2.0.2",
+ "node-ff-assets": "^0.2.5"
+ }
+}
diff --git a/example/www_static/css/boot.css b/example/www_static/css/boot.css
new file mode 100644
index 0000000..803eb92
--- /dev/null
+++ b/example/www_static/css/boot.css
@@ -0,0 +1,140 @@
+
+body {
+ margin-top: 100px;
+ background-color: #222;
+}
+
+@media ( min-width :768px) {
+ body {
+ margin-top: 50px;
+ }
+}
+
+#wrapper {
+ padding-left: 0;
+}
+
+#page-wrapper {
+ width: 100%;
+ padding: 0;
+ background-color: #fff;
+}
+
+.huge {
+ font-size: 50px;
+ line-height: normal;
+}
+
+@media ( min-width :768px) {
+ #wrapper {
+ padding-left: 225px;
+ }
+ #page-wrapper {
+ padding: 10px;
+ }
+}
+
+/* Top Navigation */
+.top-nav {
+ padding: 0 15px;
+}
+
+.top-nav>li {
+ display: inline-block;
+ float: left;
+}
+
+.top-nav>li>a {
+ padding-top: 15px;
+ padding-bottom: 15px;
+ line-height: 20px;
+ color: #999;
+}
+
+.top-nav>li>a:hover, .top-nav>li>a:focus, .top-nav>.open>a, .top-nav>.open>a:hover,
+ .top-nav>.open>a:focus {
+ color: #fff;
+ background-color: #000;
+}
+
+.top-nav>.open>.dropdown-menu {
+ float: left;
+ position: absolute;
+ margin-top: 0;
+ border: 1px solid rgba(0, 0, 0, .15);
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ background-color: #fff;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
+}
+
+.top-nav>.open>.dropdown-menu>li>a {
+ white-space: normal;
+}
+
+ul.message-dropdown {
+ padding: 0;
+ max-height: 250px;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+li.message-preview {
+ width: 275px;
+ border-bottom: 1px solid rgba(0, 0, 0, .15);
+}
+
+li.message-preview>a {
+ padding-top: 15px;
+ padding-bottom: 15px;
+}
+
+li.message-footer {
+ margin: 5px 0;
+}
+
+ul.alert-dropdown {
+ width: 200px;
+}
+
+/* Side Navigation */
+@media ( min-width :768px) {
+ .side-nav {
+ position: fixed;
+ top: 51px;
+ left: 225px;
+ width: 225px;
+ margin-left: -225px;
+ border: none;
+ border-radius: 0;
+ overflow-y: auto;
+ background-color: #222;
+ }
+ .side-nav>li>a {
+ width: 225px;
+ }
+ .side-nav li a:hover, .side-nav li a:focus {
+ outline: none;
+ background-color: #000 !important;
+ }
+}
+
+.side-nav>li>ul {
+ padding: 0;
+}
+
+.side-nav>li>ul>li>a {
+ display: block;
+ padding: 10px 15px 10px 38px;
+ text-decoration: none;
+ color: #999;
+}
+
+.side-nav>li>ul>li>a:hover {
+ color: #fff;
+}
+
+.huge {
+ font-size: 40px;
+}
diff --git a/example/www_static/css/flot.css b/example/www_static/css/flot.css
new file mode 100644
index 0000000..76b6b11
--- /dev/null
+++ b/example/www_static/css/flot.css
@@ -0,0 +1,10 @@
+
+.flot-chart {
+ display: block;
+ height: 400px;
+}
+
+.flot-chart-content {
+ width: 100%;
+ height: 100%;
+}
diff --git a/example/www_static/css/panel.css b/example/www_static/css/panel.css
new file mode 100644
index 0000000..7e97652
--- /dev/null
+++ b/example/www_static/css/panel.css
@@ -0,0 +1,56 @@
+
+.panel-green {
+ border-color: #5cb85c;
+}
+
+.panel-green .panel-heading {
+ border-color: #5cb85c;
+ color: #fff;
+ background-color: #5cb85c;
+}
+
+.panel-green a {
+ color: #5cb85c;
+}
+
+.panel-green a:hover {
+ color: #3d8b3d;
+}
+
+.panel-red {
+ border-color: #d9534f;
+}
+
+.panel-red .panel-heading {
+ border-color: #d9534f;
+ color: #fff;
+ background-color: #d9534f;
+}
+
+.panel-red a {
+ color: #d9534f;
+}
+
+.panel-red a:hover {
+ color: #b52b27;
+}
+
+.panel-yellow {
+ border-color: #f0ad4e;
+}
+
+.panel-yellow .panel-heading {
+ border-color: #f0ad4e;
+ color: #fff;
+ background-color: #f0ad4e;
+}
+
+.panel-yellow a {
+ color: #f0ad4e;
+}
+
+.panel-yellow a:hover {
+ color: #df8a13;
+}
+
+
diff --git a/example/www_static/css/style.css b/example/www_static/css/style.css
new file mode 100644
index 0000000..745a426
--- /dev/null
+++ b/example/www_static/css/style.css
@@ -0,0 +1,44 @@
+
+#wrapper {
+ padding-right: 0px;
+ padding-left: 0px;
+}
+
+.side-nav {
+ right: 0px;
+ left: 0px;
+}
+
+.btn {
+ margin-right: 10px;
+}
+
+.navbar-footer {
+ margin-left: 45%;
+}
+
+.navbar-toggle {
+ display: none;
+}
+
+.navbar-brand {
+ background-color: #C110D8;
+}
+
+.navbar-inverse .navbar-brand {
+ color: #FFFFFF;
+}
+
+.navbar-inverse .navbar-brand:focus {
+ outline: none;
+}
+
+.navbar-inverse .navbar-brand:hover {
+ background-color: #0F9A28;
+ outline: none;
+}
+
+.navbar-inverse {
+ background-color: #C110D8;
+}
+
diff --git a/example/www_static/js/controller/page-bar.js b/example/www_static/js/controller/page-bar.js
new file mode 100644
index 0000000..3f628ac
--- /dev/null
+++ b/example/www_static/js/controller/page-bar.js
@@ -0,0 +1,10 @@
+
+pageRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/example-ui/bar', {
+ templateUrl: '/example-ui/thtml/bar',
+ controller: PageFoo
+ });
+});
+
+function PageFoo($scope, $http) {
+}
diff --git a/example/www_static/js/controller/page-foo.js b/example/www_static/js/controller/page-foo.js
new file mode 100644
index 0000000..a5177b2
--- /dev/null
+++ b/example/www_static/js/controller/page-foo.js
@@ -0,0 +1,10 @@
+
+pageRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/example-ui/foo', {
+ templateUrl: '/example-ui/thtml/foo',
+ controller: PageFoo
+ });
+});
+
+function PageFoo($scope, $http) {
+}
diff --git a/example/www_static/js/controller/page-index.js b/example/www_static/js/controller/page-index.js
new file mode 100644
index 0000000..2a3fd23
--- /dev/null
+++ b/example/www_static/js/controller/page-index.js
@@ -0,0 +1,10 @@
+
+pageRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/example-ui', {
+ templateUrl: '/example-ui/thtml/index',
+ controller: PageIndex
+ });
+});
+
+function PageIndex($scope, $http) {
+}
diff --git a/example/www_static/js/example-app.js b/example/www_static/js/example-app.js
new file mode 100644
index 0000000..17bb47b
--- /dev/null
+++ b/example/www_static/js/example-app.js
@@ -0,0 +1,22 @@
+'use strict';
+
+document.title = 'FF-Spa-Loader Example';
+$('html').attr('ng-app', 'exampleUI');
+$('html').attr('lang', 'en');
+
+$(document.createElement('div')).attr('id', 'wrapper').appendTo($('body'));
+$(document.createElement('div')).attr('ng-include', '\'/example-ui/thtml/layout/header\'').appendTo($('#wrapper'));
+$(document.createElement('div')).attr('id', 'page-wrapper').appendTo($('#wrapper'));
+$(document.createElement('div')).attr('id', 'container-fluid').attr('ng-view', '').appendTo($('#page-wrapper'));
+$(document.createElement('div')).attr('ng-include', '\'/example-ui/thtml/layout/footer\'').appendTo($('body'));
+
+var pageRouteInit = [];
+angular.module('exampleUI', ['ngRoute']).
+ config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
+ for (var i = 0; i < pageRouteInit.length; i++) {
+ pageRouteInit[i]($routeProvider, $locationProvider);
+ }
+ $routeProvider.otherwise({ redirectTo: '/example-ui' });
+ $locationProvider.html5Mode({enabled: true, requireBase: false});
+ }]);
+
diff --git a/example/www_views/index.ejs b/example/www_views/index.ejs
new file mode 100644
index 0000000..03c00a4
--- /dev/null
+++ b/example/www_views/index.ejs
@@ -0,0 +1,18 @@
+
+
+
+
+ Loading<%- inlineNot %>
+
+ <%- inline %>
+
+
+
diff --git a/example/www_views/thtml/bar.ejs b/example/www_views/thtml/bar.ejs
new file mode 100644
index 0000000..0029a6b
--- /dev/null
+++ b/example/www_views/thtml/bar.ejs
@@ -0,0 +1,32 @@
+
+
Bar
+
Welcome to the bar.
+
+
+
+
+ Chair |
+ Person |
+ Drinking |
+
+
+
+
+ seat 1 |
+ empty |
+ none |
+
+
+ seat 2 |
+ you |
+ coffee |
+
+
+ seat 3 |
+ cat |
+ water |
+
+
+
+
+
diff --git a/example/www_views/thtml/foo.ejs b/example/www_views/thtml/foo.ejs
new file mode 100644
index 0000000..52c464f
--- /dev/null
+++ b/example/www_views/thtml/foo.ejs
@@ -0,0 +1,4 @@
+
+
Foo
+
Welcome to the foo.
+
diff --git a/example/www_views/thtml/index.ejs b/example/www_views/thtml/index.ejs
new file mode 100644
index 0000000..c6c6dcc
--- /dev/null
+++ b/example/www_views/thtml/index.ejs
@@ -0,0 +1,4 @@
+
+
Example UI Index
+
Welcome make yourself at home.
+
diff --git a/example/www_views/thtml/layout/footer.ejs b/example/www_views/thtml/layout/footer.ejs
new file mode 100644
index 0000000..3ff644a
--- /dev/null
+++ b/example/www_views/thtml/layout/footer.ejs
@@ -0,0 +1,5 @@
+
diff --git a/example/www_views/thtml/layout/header.ejs b/example/www_views/thtml/layout/header.ejs
new file mode 100644
index 0000000..109a1c9
--- /dev/null
+++ b/example/www_views/thtml/layout/header.ejs
@@ -0,0 +1,13 @@
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..56e7db7
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "es5-ff-spa-loader",
+ "version": "0.0.1",
+ "description": "Javascript Single Page Application Loader",
+ "main": "es5-ff-spa-loader.js",
+ "author": "Willem (http://forwardfire.net/)",
+ "keywords": ["website","assets","js","css","loader","spa","mobile","resources","sync","cache"],
+ "license": "BSD-2-Clause",
+ "repository": {
+ "type": "git",
+ "url": "https://bitbucket.org/im_ik/es5-ff-spa-loader.git"
+ }
+}