UNPKG

ria

Version:

Node tool for developing RIA Apps using the RIA app framework. Helps initialize the app and create modules using UI templates and archetecture.

608 lines (520 loc) 19.4 kB
var app=( function(){ /************\ Scope \************/ var config={ modulesPath : 'app/module/' } var modules, constructors={}, moduleQueue = {}, events=[], dataStore={ hasWebkitSpeech:(document.createElement("input").hasOwnProperty('webkitSpeech')), hasSpeech:(document.createElement("input").hasOwnProperty('speech')), HTML:{}, JS:{}, CSS:{} } if(history){ history={ pushState:function(stateObject,screen){ history.state=stateObject; }, state:{} } } function setConfig(userConfig){ for(var property in userConfig){ if(!userConfig.hasOwnProperty('property')) return; config[property] = userConfig[property]; } } /************\ Modules \************/ function getModules(){ modules=document.getElementsByClassName('appModule'); } function buildModules(elements){ if(!elements){ elements=modules; if(!elements) elements=[]; } if( !elements[0]) elements=[elements]; if( !elements[0].getAttribute) return; var moduleCount=elements.length; for(var i=0; i<moduleCount; i++){ var el=elements[i]; var moduleType=el.getAttribute('data-moduletype'); if(!constructors[moduleType]){ ( function(config,moduleType,moduleQueue,el){ setTimeout( function(){ if(moduleQueue[moduleType]) return; var module=config.modulesPath+moduleType+'/module'; moduleQueue[moduleType]=true; var js = document.createElement('script'); js.async=true; js.setAttribute('src', module+'.js'); document.head.appendChild(js); dataStore.JS[moduleType]=js.outerHTML; if(el.getAttribute('data-css')!='true') return; var css = document.createElement('link'); css.rel='stylesheet'; css.type='text/css'; css.setAttribute('href', module+'.css'); document.head.appendChild(css); dataStore.CSS[moduleType]=css.outerHTML; }, 0 ); } )(config,moduleType,moduleQueue,el); if(el.getAttribute('data-html')=='true') fetchModuleHTML(moduleType); continue; } if(app.data.HTML[moduleType]) HTMLLoaded(el,moduleType); if( (el.innerHTML!='' && el.getAttribute('data-html')=='true') || el.getAttribute('data-html')!='true') { constructors[moduleType](el); continue; }else{ ( function(){ setTimeout( function(){ //console.log(el); buildModules(el); }, 50 ) } )(el) } } } function fetchModuleHTML(moduleType){ var xmlhttp; xmlhttp=new XMLHttpRequest(); ( function(){ xmlhttp.onreadystatechange=function(){ if (xmlhttp.readyState==4 && xmlhttp.status==200){ dataStore.HTML[moduleType]=xmlhttp.responseText; HTMLLoaded( document.querySelectorAll('[data-moduletype="'+moduleType+'"]'), moduleType ); } } } )(moduleType); xmlhttp.open('GET',config.modulesPath+moduleType+'/module.html',true); xmlhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded'); xmlhttp.send(); } function HTMLLoaded(modules,moduleType){ if(!modules.length) modules=[modules]; var totalModules=modules.length; for(var i=0; i<totalModules; i++){ var module=modules[i]; module.innerHTML=dataStore.HTML[moduleType]; findAndInitDynamicModules(module); } } function deferredLoad(type){ buildModules(document.querySelectorAll('[data-moduletype="'+type+'"]')); } function renderCompiledApp(){ var modules=Object.keys(constructors); for(var i=0; i<modules.length; i++){ constructors[modules[i]]( document.getElementById(modules[i]+'-module') ) } } function appendDOMNode(el){ ( function(){ setTimeout( function(){ document.querySelector( el.getAttribute('data-dompath') ).appendChild(el); } ,0 ); } )(el); } /* * * @param {string} screen : the name of the screen or group of modules * from the app.data.layout object you wish to see * * @param {object} stateObject : defaults to {screen:the passed screen name} * @returns {undefined} */ function showModuleGroup(screen,stateObject){ if(!app.data.layout) return; if(!screen) screen=app.data.layout.startAt; if(!stateObject) stateObject={ screen:screen }; if(!stateObject.screen) stateObject.screen=screen; var modules=document.getElementsByClassName('appModule'); var screenContains=Object.keys( app.data.layout.modules.ui[screen] ); for(var i=0; i<modules.length; i++){ var type=modules[i].getAttribute( 'data-moduletype' ); //console.log(type) if(screenContains.indexOf(type)>-1){ modules[i].classList.remove('hidden'); continue; } modules[i].classList.add('hidden'); } triggerEvent( 'app.navigated.'+screen, stateObject ) history.pushState( stateObject, screen, '#'+screen ); } registerEvent( 'app.navigate', showModuleGroup ) window.addEventListener( 'popstate', function(e){ var state=e.state; if(!state) state={}; if(!state.screen) state.screen=document.location.hash.slice(1); showModuleGroup( state.screen, state ); } ); function layoutApp(layout){ if(!layout.lib) layout.lib=[]; if(!layout.modules) layout.modules={}; for(var i=0; i<layout.lib.length; i++){ var lib; switch(layout.lib[i].type){ case 'css' : lib = document.createElement('link'); lib.setAttribute('href', layout.lib[i].path); lib.setAttribute('rel', 'stylesheet'); dataStore.CSS[layout.lib[i].path]=lib.outerHTML; break; case 'js': lib = document.createElement('script'); lib.setAttribute('async', true); lib.setAttribute('src', layout.lib[i].path); dataStore.JS[layout.lib[i].path]=lib.outerHTML; break; } document.head.appendChild(lib); } for(var i=0; i<layout.modules.logic.length; i++){ var newModule=createModuleElement(layout.modules.logic[i],'false','false'); appendDOMNode(newModule); } var modulesToUse=Object.keys(layout.modules.ui); for(var i=0; i<modulesToUse.length; i++){ var modulesInGroup=Object.keys(layout.modules.ui[modulesToUse[i]]); for(var j=0; j<modulesInGroup.length; j++){ var name=modulesInGroup[j]; //console.log(name); } } var fullList={}; var screenList=Object.keys(layout.modules.ui); for(var i=0; i<screenList.length; i++){ var screenModules=Object.keys( layout.modules.ui[screenList[i]] ); //console.log('screenMuduleList',screenModules); for(var j=0; j<screenModules.length; j++){ //console.log('screenMudule',screenModules[j]); fullList[screenModules[j]]=true; } } //console.log(fullList) if(dataStore.compiled) return; var layoutModules=Object.keys(fullList); for(var i=0; i<layoutModules.length; i++){ //console.log('screenMudule',layoutModules[i]); name=layoutModules[i]; var newModule=createModuleElement(name); appendDOMNode(newModule); } } function createModuleElement(name,html,css){ var newModule=document.createElement("div"); //console.log(name) if(!html) html='true'; if(!css) css='true'; newModule.id=name+'-module'; newModule.classList.add( 'appModule', 'hidden', name+'-module' ); newModule.setAttribute( 'data-dompath', 'body' ); newModule.setAttribute( 'data-moduletype', name ); newModule.setAttribute( 'data-html', html ); newModule.setAttribute( 'data-css', css ); return newModule; } function checkModuleExists(moduleType){ return !!!constructors[moduleType]; } function findAndInitDynamicModules(parent){ buildModules(parent.querySelectorAll('[data-moduletype]')); } function addConstructor(type, moduleInit){ if(constructors[type]) return; constructors[type] = moduleInit; if(document.readyState == 'complete') deferredLoad(type); } function initModules(){ switch(document.readyState){ case 'interactive' : //dom not yet ready break; case 'complete' : getModules(); buildModules(); break; } } /************\ Utils \************/ /* * * @param {string} id id of element to fetch innerHTML as contents for template * or raw string to be used as template if rawString set to true * @param {object} values should contain the key value pairs for all template Data * @param {bool} rawString use id as a raw string * @param {bool} asString return as string * @returns {DomElement} if asString is false or not specified will return Filled out Template Element * @returns {string} if asString is true will return a filled out template string */ function fillTemplate(id, values, rawString, asString){ var template=id; if(!id) throw new AppError('Templates must specify either id or a string+rawString flag','app.template'); if(!rawString) template=document.getElementById(id).innerHTML; var keys=Object.keys(values); for(var i=0; i<keys.length; i++){ var regEx=new RegExp( '\\$\\{'+keys[i]+'\\}', 'g' ); template=template.replace( regEx,values[keys[i]] ) } var completeTemplate=template; if(!asString){ completeTemplate = document.createElement('div'); completeTemplate.innerHTML=template; completeTemplate=completeTemplate.querySelector('*'); } return completeTemplate; } /* * * @param {string} type * @returns {string} compiledApp */ function getCurrentCompiledState(type){ var html='<html class="compiled-app"><head>${head}</head><body>${body}</body></html>'; var defaults='<script src="app/core/app.js"></script>' +'<script src="app/core/app.layout.js"></script>'; if(type=='test'){ defaults+='<link rel="stylesheet" type="text/css" href="jasmine/lib/jasmine-2.0.2/jasmine.css">' +'<script type="text/javascript" src="jasmine/lib/jasmine-2.0.2/jasmine.js"></script>' +'<script type="text/javascript" src="jasmine/lib/jasmine-2.0.2/jasmine-html.js"></script>' +'<script type="text/javascript" src="jasmine/lib/jasmine-2.0.2/boot.js"></script>' +'<script src="app/core/app.test.js"></script>'; } this.body=''; this.head=''; var list=[ 'HTML', 'CSS', 'JS' ]; function compileModule(name,content){ var module=createModuleElement( name ); module.innerHTML=content; this.body+=module.outerHTML; } for(var j=0; j<list.length; j++){ var keys=Object.keys(dataStore[list[j]]); for(var i=0; i<keys.length;i++){ if(list[j]!="HTML"){ console.log(list[j],keys[i]); this.head+=dataStore[ list[j] ][ keys[i] ]; continue; } console.log(list[j],keys[i]); compileModule.call( this, keys[i], dataStore[ list[j] ][ keys[i] ] ); } } return fillTemplate( html, { head:defaults+this.head, body:this.body }, true, true ).replace( /\s+/g, ' ' ); } /************\ Error \************/ /** * custom application error * * @param {string} message * @param {string} type * @returns {AppError} */ function AppError(message, type) { this.name = "AppError"; this.message = message || "Application Error Message"; this.type = type || "Generic Application Error"; // possible error types are User, Api, etc... } AppError.prototype = new Error(); AppError.prototype.constructor = AppError; window.AppError = AppError; /************\ Events \************/ function registerEvent(eventName,handler){ if(!events[eventName]) events[eventName]=[]; events[eventName].push(handler); } function removeEvent(eventName){ delete events[eventName] } function triggerEvent(eventName){ if(!events[eventName]) return; var totalEvents=events[eventName].length, args=Array.prototype.slice.call(arguments,1); for(var i=0; i<totalEvents; i++){ ( function(event){ setTimeout( function(){ event.apply(null,args); } ,0 ); } )(events[eventName][i],args); } } window.exports=addConstructor; document.addEventListener( 'readystatechange', function(){ dataStore.compiled=document.querySelector('html').classList.contains('compiled-app'); if(!dataStore.compiled){ initModules(); return; } renderCompiledApp(); } ); return { register : addConstructor, layout : layoutApp, navigate : showModuleGroup, createModule : createModuleElement, build : buildModules, inject : appendDOMNode, config : setConfig, on : registerEvent, off : removeEvent, template : fillTemplate, trigger : triggerEvent, error : AppError, compile : getCurrentCompiledState, exists : checkModuleExists, data : dataStore } } )();