From a0a7f35a6f5ea30090c24362b5f04b3760457cec Mon Sep 17 00:00:00 2001 From: Willem Date: Fri, 26 Feb 2016 23:40:29 +0100 Subject: [PATCH] Added progres bar --- README.md | 11 ++- es5-ff-spa-loader.css | 159 ++++++++++++++++++++++++++++++---- es5-ff-spa-loader.js | 193 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 321 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index c52b6d5..4b2b786 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,13 @@ A javascript library providing server defined loading of assets for a single pag * question.validate.max.message =The error message (default: 'Server name is to long.') * question.validate.regex.value = The regex to validate the hostname, false is disabled. (default: '^([a-zA-Z0-9\.\:])*$') * question.validate.regex.message = The error message (default: 'Server name is invalid.') + * loader.title = The loader title (default: 'Loading Application') + * loader.footer = The loader footer (default: '© FFSpaLoader') + * loader.await = The timeout in ms before the loader displays. (default: 250) + * loader.progres.items.enable = Enables the per item progres. (default: true) + * loader.progres.items.size = Shorten longer urls to this size. (default: 50) + * loader.progres.bar.enable = Enables the progres bar. (default: true) + * loader.progres.bar.percentage = Print percentage's in bar. (default: true) * cache.meta = The cache backend for the meta information(server.url+content), null is auto select,false is disable. (default: null) * cache.js = The cache backend for for js, null is auto select,false is disable. (default: null) * cache.css = The cache backend for for css, null is auto select,false is disable. (default: null) @@ -191,7 +198,6 @@ A javascript library providing server defined loading of assets for a single pag ## Todo * test in production - * Add loader progress bar * Server header check support * Add instance websql options so it can also be used in application code. * Split assets per type so do js first then boot then css + cssData. @@ -211,7 +217,8 @@ Add unit tests for any new or changed functionality. Lint and test your code. ### 0.2.0 * Dropped error.style and question.style for css file. * Change dist with extra css/js folder. - +* Added loader progress bar. +* refined css so question/loader/error are equal. ### 0.1.1 * Moved websql delete timeout to cleanServerlUrl for faster boot. diff --git a/es5-ff-spa-loader.css b/es5-ff-spa-loader.css index 866ebe9..c8aebca 100644 --- a/es5-ff-spa-loader.css +++ b/es5-ff-spa-loader.css @@ -1,49 +1,172 @@ +/* ========= default css */ + body { color: #EFF0F1; background: #484948; } -.ffError { - margin: 3em; - border-left: 0.3em solid #B55858; - border-radius: 1em; - padding: 0em 1em 0.3em 1em; +.ffWrapper { + align-items: center; + color: #EFF0F1; + background: linear-gradient(to top right, #2F38BD 10%, #cf7c9d 65%, #ffc01a 125%); + bottom: 0em; + display: flex; + justify-content: center; + left: 0em; + position: absolute; + right: 0em; + top: 0em; + border: 0.2em solid #000000; + min-height: 10em; } +.ffWrapperFadeOut { + opacity:0.1; + transition: opacity 0.5s; +} + +.ffTitle { + top: 1em; + color: white; + display: flex; + justify-content: center; + left: 0em; + position: fixed; + right: 0em; + font-weight: bold; + font-size: 1.5em; +} + +.ffFooter { + bottom: 1.5em; + color: white; + display: flex; + justify-content: center; + left: 0em; + position: fixed; + right: 0em; + opacity:0.2; + font-size: 0.7em; +} + +/* ========== url question css */ + .ffQuestion { - margin: 3em; - border-left: 0.3em solid #3F68AD; + padding: 1em; + padding-left: 2em; + padding-right: 2em; border-radius: 1em; - padding: 0em 1em 0.3em 1em; + border: 0.2em solid rgba(0, 0, 0, 0); + min-height: 7em; + background-color: rgba(0, 0, 0, 0.3); } -.ffQuestion>div>input { +.ffQuestion>input { margin: 0.4em; padding: 0.4em; line-height: 2em; - background-color: #454442; - color: #EFF0F1; + background-color: rgba(0, 0, 0, 0.1); + color: #FFFFFF; border: none; border-radius: 0.4em; outline: none; min-width: 5em; } -.ffQuestion>div>input:focus { +.ffQuestion>input:focus,.ffQuestion>input:active,.ffQuestion>input:hover,.ffQuestion>input::-moz-focus-inner { border: none; + outline:none; +} + +.ffQuestionTitle { + font-size: 1.1em; + margin-bottom: 0.5em; +} + +.ffQuestionText { + font-size: 0.8em; } .ffQuestionError { + padding-top: 1em; + padding-left: 1em; color: #B55858; } -.ffQuestionLoad { - transition: all 0.5s ease; - color: #484948; +/* ========= Error dialog css */ + +.ffError { + padding: 2em; + padding-bottom: 1em; + border-radius: 1em; + border: 0.2em solid rgba(255, 0, 0, 0.5); + min-height: 7em; + background-color: rgba(0, 0, 0, 0.1); } -.ffQuestionLoad>div>input { - background-color: #484948; - color: #484948; +/* ========= Loader progres css */ + +.ffLoaderBar { + display: block; + position: relative; + justify-content: center; + margin: auto; + border-top:0.2em solid #EFF0F1; + border-bottom:0.2em solid #EFF0F1; +} + +.ffLoaderBarStep,.ffLoaderBarStepDone { + background-color:#2F48FD; + float:left; + padding: 1em; +} + +.ffLoaderBarStep { + opacity:0; +} + +.ffLoaderBarStepDone { + opacity:0.2; + transition: opacity 1s; +} + +.ffLoaderItem { + top: 4em; + color: #EFF0F1; + display: flex; + justify-content: center; + left: 20%; + position: fixed; + right: 20%; + + border-radius: 1em; + border: 0.2em solid #EFF0F1; +} + +.ffLoaderItem>div { + position: absolute; + display: flex; + justify-content: center; + top: 1em; + left: 0em; + right: 0em; + font-size: 0.8em; +} + +.ffLoaderItemText { + opacity: 0; + margin-top: 0em; +} + +.ffLoaderItemTextStart { + opacity: 1; + margin-top: 1em; + transition: all 0.5s; +} + +.ffLoaderItemTextDone { + opacity: 0; + margin-top: 0em; + transition: all 0.5s; } diff --git a/es5-ff-spa-loader.js b/es5-ff-spa-loader.js index 6afa2a7..468978d 100644 --- a/es5-ff-spa-loader.js +++ b/es5-ff-spa-loader.js @@ -99,6 +99,21 @@ } } }, + loader: { + title: 'Loading Application', + footer: '© FFSpaLoader', + await: 250, + progres: { + items: { + enable: true, + size: 50, + }, + bar: { + enable: true, + percentage: true, + } + }, + }, cache: { meta: null, js: null, @@ -280,16 +295,21 @@ utilDebug('utilErrorHandler error '+err.name+' '+err.message); var rootTag = document.createElement('div'); - rootTag.setAttribute('class','ffError'); + rootTag.setAttribute('class','ffWrapper'); document.getElementsByTagName('body')[0].appendChild(rootTag); - var titleTag = document.createElement('h1'); + var titleTag = document.createElement('div'); + titleTag.setAttribute('class','ffTitle'); titleTag.appendChild(document.createTextNode(options.error.title+err.name)); rootTag.appendChild(titleTag); + + var dialogTag = document.createElement('div'); + dialogTag.setAttribute('class','ffError'); + rootTag.appendChild(dialogTag); var questionTag = document.createElement('p'); questionTag.appendChild(document.createTextNode(err.message)); - rootTag.appendChild(questionTag); + dialogTag.appendChild(questionTag); try { var stack = err.stack || ''; @@ -298,7 +318,7 @@ var traceTag = document.createElement('pre'); traceTag.appendChild(document.createTextNode(stackText)); - rootTag.appendChild(traceTag); + dialogTag.appendChild(traceTag); } catch (stackError) { utilDebug('No stack: '+stackError); } @@ -572,6 +592,113 @@ } }; + var createLoaderBar = function (resources) { + + var rootTag = null; + var prevResource = null; + var step = 0; + var stepMax = resources.length; + var stepProgres = 0; + var stepProgres10 = 0; + + var createUITimeout = setTimeout( function () { + utilDebug('createLoaderBar'); + + rootTag = document.createElement('div'); + rootTag.setAttribute('class','ffWrapper'); + + var loaderTitleTag = document.createElement('div'); + loaderTitleTag.setAttribute('class','ffTitle'); + loaderTitleTag.appendChild(document.createTextNode(options.loader.title)); + rootTag.appendChild(loaderTitleTag); + + if (options.loader.progres.items.enable) { + var loaderItemTag = document.createElement('div'); + loaderItemTag.setAttribute('class','ffLoaderItem'); + rootTag.appendChild(loaderItemTag); + + resources.forEach(function(resource) { + var shortUrl = resource.url; + var shortUrlSize = options.loader.progres.items.size; + if (shortUrl.length > shortUrlSize) { + shortUrl = '...'+shortUrl.substring(shortUrl.length-shortUrlSize,shortUrl.length); + } + + var loaderItemResourceTag = document.createElement('div'); + loaderItemResourceTag.setAttribute('id',resource.hash); + loaderItemResourceTag.setAttribute('class','ffLoaderItemText'); + loaderItemResourceTag.appendChild(document.createTextNode(shortUrl)); + + loaderItemTag.appendChild(loaderItemResourceTag); + }); + } + + if (options.loader.progres.bar.enable) { + var loaderBarTag = document.createElement('div'); + loaderBarTag.setAttribute('class','ffLoaderBar'); + rootTag.appendChild(loaderBarTag); + + for (var i=1;i<=10;i++) { + var loaderBarStepTag = document.createElement('div'); + loaderBarStepTag.setAttribute('id','barStep'+(i*10)); + loaderBarStepTag.setAttribute('class','ffLoaderBarStep'); + if (options.loader.progres.bar.percentage) { + loaderBarStepTag.appendChild(document.createTextNode(i*10)); + } + loaderBarTag.appendChild(loaderBarStepTag); + } + } + var footerTag = document.createElement('div'); + footerTag.setAttribute('class','ffFooter'); + footerTag.appendChild(document.createTextNode(options.loader.footer)); + rootTag.appendChild(footerTag); + + document.getElementsByTagName('body')[0].appendChild(rootTag); + }, options.loader.await); + + return { + nextResource: function(resource) { + var resourceTag = document.getElementById(resource.hash); + if (resourceTag !== null) { + resourceTag.setAttribute('class','ffLoaderItemTextStart'); + } + if (prevResource !== null) { + var prevResourceTag = document.getElementById(prevResource.hash); + if (prevResourceTag !== null) { + prevResourceTag.setAttribute('class','ffLoaderItemTextDone'); + } + } + + prevResource = resource; + step++; + stepProgres = Math.round(step*100/stepMax); + + if (stepProgres > stepProgres10) { + stepProgres10 += 10; + + for (var i=1;i<=10;i++) { + if ((i*10) > stepProgres10) { + continue; + } + var barStepTag = document.getElementById('barStep'+(i*10)); + if (barStepTag !== null) { + barStepTag.setAttribute('class','ffLoaderBarStepDone'); + } + } + } + }, + done: function() { + clearTimeout(createUITimeout); + if (rootTag !== null) { + rootTag.setAttribute('class','ffWrapper ffWrapperFadeOut'); + setTimeout ( function () { + document.getElementsByTagName('body')[0].removeChild(rootTag); + }, 500); + } + } + } + }; + /** * Starts loader by downloading resource list or using cache version to * load/refresh/inject the resources. @@ -616,16 +743,30 @@ } var resources = JSON.parse(httpRequest.responseText).data.resources; utilDebug('startLoader resources '+resources.length); + + var progressBar = createLoaderBar(resources); + var loadResourceStep = function (resource, cb) { + //setTimeout(function() { + loadResource(resource,cb); + progressBar.nextResource(resource); + //}, 1000); + } + if (cacheHasService('meta')) { cacheSetValue('meta','server_resources',resources, function (err) { if (err !== null) { return cb(err); } - utilRunStack('loadResources', resources, loadResource , function (err) { + utilRunStack('loadResources', resources, loadResourceStep , function (err) { if (err !== null) { return cb(err); } cleanupCache(resources,false,cb); // only clean when fetched + cached + progressBar.done(); }); }); } else { - utilRunStack('loadResources', resources, loadResource , cb); + utilRunStack('loadResources', resources, loadResourceStep , function (err) { + if (err !== null) { return cb(err); } + cb(); + progressBar.done(); + }); } }); }; @@ -689,7 +830,7 @@ return; } - deleteTag.setAttribute('class','ffQuestion ffQuestionLoad'); + deleteTag.setAttribute('class','ffWrapper ffWrapperFadeOut'); var clearUi = function(err) { document.getElementsByTagName('body')[0].removeChild(deleteTag); // also delete on error cb(err); @@ -715,39 +856,47 @@ utilDebug('askUrl create ui'); var rootTag = document.createElement('div'); - rootTag.setAttribute('class','ffQuestion'); - - var titleTag = document.createElement('h1'); - titleTag.appendChild(document.createTextNode(options.question.title)); - rootTag.appendChild(titleTag); - - var questionTag = document.createElement('p'); - questionTag.appendChild(document.createTextNode(options.question.text)); - rootTag.appendChild(questionTag); + rootTag.setAttribute('class','ffWrapper'); var formTag = document.createElement('div'); + formTag.setAttribute('class','ffQuestion'); rootTag.appendChild(formTag); + var titleTag = document.createElement('div'); + titleTag.setAttribute('class','ffQuestionTitle'); + titleTag.appendChild(document.createTextNode(options.question.title)); + formTag.appendChild(titleTag); + + var questionTag = document.createElement('div'); + questionTag.setAttribute('class','ffQuestionText'); + questionTag.appendChild(document.createTextNode(options.question.text)); + formTag.appendChild(questionTag); + var inputTag = document.createElement('input'); - inputTag.type = 'text'; - inputTag.id = 'serverInput'; + inputTag.setAttribute('type','text'); + inputTag.setAttribute('id','serverInput'); inputTag.setAttribute('autofocus',''); inputTag.setAttribute('onkeydown','if (event.keyCode == 13) {document.getElementById(\'serverSubmit\').click()}'); inputTag.setAttribute('size',options.question.size); formTag.appendChild(inputTag); var submitTag = document.createElement('input'); - submitTag.id = 'serverSubmit'; - submitTag.type = 'submit'; - submitTag.value = options.question.submit; + submitTag.setAttribute('id','serverSubmit'); + submitTag.setAttribute('type','submit'); + submitTag.setAttribute('value',options.question.submit); submitTag.onclick = function() {askUrlValidate(rootTag,cb);}; formTag.appendChild(submitTag); var serverErrorTag = document.createElement('div'); - serverErrorTag.id = 'serverInputError'; + serverErrorTag.setAttribute('id','serverInputError'); serverErrorTag.setAttribute('class','ffQuestionError'); formTag.appendChild(serverErrorTag); + var footerTag = document.createElement('div'); + footerTag.setAttribute('class','ffFooter'); + footerTag.appendChild(document.createTextNode(options.loader.footer)); + rootTag.appendChild(footerTag); + document.getElementsByTagName('body')[0].appendChild(rootTag); };