├── .gitignore ├── .gitattributes ├── css └── app.css ├── package.json ├── .idea └── vcs.xml ├── .editorconfig ├── readme.md ├── index.html └── js ├── app.js └── litespeed.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /css/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | app-template.css overrides 4 | 5 | remove this comment if used 6 | remove this file if not 7 | 8 | */ 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "todomvc-app-css": "^2.0.0", 5 | "todomvc-common": "^1.0.0" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [package.json] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Litespeed.js • [TodoMVC](http://todomvc.com) 2 | 3 | > Official description of the framework (from its website) 4 | 5 | 6 | ## Resources 7 | 8 | - [Website](https://github.com/litespeed-js/litespeed.js) 9 | - [Documentation](https://github.com/litespeed-js/litespeed.js/blob/master/docs/get-started.md) 10 | 11 | ### Articles 12 | 13 | - [Interesting article]() 14 | 15 | ### Support 16 | 17 | - [Stack Overflow](http://stackoverflow.com/questions/tagged/litespeed) 18 | - [Twitter](http://twitter.com/__) 19 | 20 | 21 | ## Implementation 22 | 23 | How was the app created? Anything worth sharing about the process of creating the app? Any spec violations? 24 | 25 | 26 | ## Credit 27 | 28 | Created by [Eldad Fux](https://twitter.com/eldadfux) 29 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Template • TodoMVC 7 | 8 | 9 | 10 | 11 |
12 |
13 |

todos

14 |
15 | 16 |
17 |
18 |
19 | 20 | 21 | 22 | 32 |
33 | 48 |
49 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | "use strict"; 3 | 4 | let ENTER_KEY = 13, ESCAPE_KEY = 27, STORAGE_KEY = 'todos-litespeed-0.2'; 5 | 6 | window.ls.router 7 | .add('', { // Default 8 | controller: function (tasks) { 9 | tasks.showAll(); 10 | } 11 | }) 12 | .add('#completed', { 13 | controller: function (tasks) { 14 | tasks.showCompleted(); 15 | } 16 | }) 17 | .add('#active', { 18 | controller: function (tasks) { 19 | tasks.showActive(); 20 | } 21 | }) 22 | ; 23 | 24 | window.ls.container 25 | .set('tasks', function () { 26 | let data = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); 27 | return { 28 | title: 'TODO App', 29 | filter: 'all', 30 | list: data, 31 | remaining: 0, 32 | add: function (task) { 33 | task.id = this.list.length + Math.random().toString(); 34 | this.list.push(task); 35 | }, 36 | remove: function (key) { 37 | for(let i = 0; i < this.list.length; i++) { 38 | if (this.list[i].id === key) { 39 | return this.list.splice(i, 1); 40 | } 41 | } 42 | }, 43 | toggle: function (value) { 44 | let list = []; 45 | 46 | this.list.forEach(function(task) { 47 | let node = Object.assign({}, task); 48 | node.completed = value; 49 | list.push(node); 50 | }); 51 | 52 | this.list = list; 53 | list = null; 54 | }, 55 | showAll: function () { 56 | this.filter = 'all'; 57 | }, 58 | showCompleted: function () { 59 | this.filter = 'completed'; 60 | }, 61 | showActive: function () { 62 | this.filter = 'active'; 63 | }, 64 | clearCompleted: function () { 65 | this.list = this.list.filter(function (task) { 66 | return !task.completed; 67 | }) 68 | } 69 | } 70 | }, true) 71 | ; 72 | 73 | window.ls.container.get('tasks').__watch = function(tasks) { 74 | localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks.list)); 75 | tasks.remaining = tasks.list.filter(function (task) {return (!task.completed)}).length; 76 | }; 77 | 78 | window.ls.filter 79 | .add('show', function ($value, tasks) { 80 | $value = JSON.parse($value); 81 | 82 | switch (tasks.filter) { 83 | case 'completed': 84 | return $value; 85 | case 'active': 86 | return !$value; 87 | } 88 | 89 | return true; 90 | }) 91 | .add('completed', function ($value) { 92 | return ($value) ? 'completed' : ''; 93 | }) 94 | .add('pluralize', function ($value) { 95 | return $value + ' ' + (('1' === $value) ? 'item' : 'items') + ' left'; 96 | }) 97 | ; 98 | 99 | window.ls.view 100 | .add({ 101 | selector: 'data-tasks-add', 102 | controller: function(element, tasks) { 103 | element.addEventListener('submit', function (event) { 104 | event.preventDefault(); 105 | tasks.add({completed: false, title: element.task.value}); 106 | element.reset(); 107 | }); 108 | } 109 | }) 110 | .add({ 111 | selector: 'data-tasks-remove', 112 | controller: function(element, tasks, expression) { 113 | let id = expression.parse(element.dataset['tasksRemove']); 114 | element.addEventListener('click', function () { 115 | tasks.remove(id); 116 | }); 117 | } 118 | }) 119 | .add({ 120 | selector: 'data-tasks-complete-all', 121 | controller: function(element, tasks) { 122 | element.addEventListener('click', function () { 123 | tasks.toggle(element.checked); 124 | }); 125 | } 126 | }) 127 | .add({ 128 | selector: 'data-tasks-clear-completed', 129 | controller: function(element, tasks) { 130 | element.addEventListener('click', function () { 131 | tasks.clearCompleted(); 132 | }); 133 | } 134 | }) 135 | .add({ 136 | selector: 'data-tasks-edit', 137 | controller: function(element, tasks, expression) { 138 | let id = expression.parse(element.dataset['tasksEdit']); 139 | let input = element.getElementsByClassName('edit')[0]; 140 | 141 | element.addEventListener('dblclick', function () { 142 | if(element.classList.contains('editing')) { 143 | element.classList.remove('editing'); 144 | } 145 | else { 146 | element.classList.add('editing'); 147 | 148 | input.focus(); 149 | } 150 | }); 151 | 152 | input.addEventListener('blur', function () { 153 | element.classList.remove('editing'); 154 | }); 155 | 156 | input.addEventListener('keydown', function (e) { 157 | if ((e.which === ENTER_KEY) || (e.which === ESCAPE_KEY)) { 158 | element.classList.remove('editing'); 159 | } 160 | 161 | if(input.value === '') { 162 | tasks.remove(id); 163 | } 164 | }); 165 | } 166 | }) 167 | .add({ 168 | selector: 'data-tasks-selected', 169 | controller: function(element, router) { 170 | let filter = element.dataset['tasksSelected'] || ''; 171 | 172 | let check = function () { 173 | if(filter === router.hash) { 174 | element.classList.add('selected'); 175 | } 176 | else { 177 | element.classList.remove('selected'); 178 | } 179 | }; 180 | 181 | document.addEventListener('state-changed', check); 182 | 183 | check(); 184 | } 185 | }) 186 | ; 187 | 188 | window.ls.run(window); 189 | 190 | }(window)); -------------------------------------------------------------------------------- /js/litespeed.js: -------------------------------------------------------------------------------- 1 | 2 | window.ls=window.ls||{};window.ls.container=function(){let stock={};let cachePrefix='none';let memory={};let setCachePrefix=function(prefix){cachePrefix=prefix;return this;};let getCachePrefix=function(){return cachePrefix;};let set=function(name,object,singleton,cache=false,watch=true){if(typeof name!=='string'){throw new Error('var name must be of type string');} 3 | if(typeof singleton!=='boolean'){throw new Error('var singleton "'+singleton+'" of service "'+name+'" must be of type boolean');} 4 | if(cache){window.localStorage.setItem(getCachePrefix()+'.'+name,JSON.stringify(object));} 5 | stock[name]={name:name,object:object,singleton:singleton,instance:null,watch:watch,};return this;};let get=function(name){let service=(undefined!==stock[name])?stock[name]:null;if(null===service){if(memory[getCachePrefix()+'.'+name]){return memory[getCachePrefix()+'.'+name];} 6 | let cached=window.localStorage.getItem(getCachePrefix()+'.'+name);if(cached){cached=JSON.parse(cached);memory[getCachePrefix()+'.'+name]=cached;return cached;} 7 | return null;} 8 | if(service.instance===null){let instance=(typeof service.object==='function')?this.resolve(service.object):service.object;let skip=false;if(service.watch&&name!=='window'&&name!=='document'&&name!=='element'&&typeof instance==='object'&&instance!==null){let handler={name:service.name,watch:function(){},get:function(target,key){if(key==="__name"){return this.name;} 9 | if(key==="__watch"){return this.watch;} 10 | if(key==="__proxy"){return true;} 11 | if(typeof target[key]==='object'&&target[key]!==null&&!target[key].__proxy){let handler=Object.assign({},this);handler.name=handler.name+'.'+key;return new Proxy(target[key],handler)} 12 | else{return target[key];}},set:function(target,key,value,receiver){if(key==="__name"){return this.name=value;} 13 | if(key==="__watch"){return this.watch=value;} 14 | target[key]=value;let path=receiver.__name+'.'+key;document.dispatchEvent(new CustomEvent(path+'.changed'));if(skip){return true;} 15 | skip=true;container.set('$prop',key,true);container.set('$value',value,true);container.resolve(this.watch);container.set('$key',null,true);container.set('$value',null,true);skip=false;return true;},};instance=new Proxy(instance,handler);} 16 | if(service.singleton){service.instance=instance;} 17 | return instance;} 18 | return service.instance;};let resolve=function(target){if(!target){return function(){};} 19 | let self=this;let FN_ARGS=/^function\s*[^\(]*\(\s*([^\)]*)\)/m;let text=target.toString()||'';let args=text.match(FN_ARGS)[1].split(',');return target.apply(target,args.map(function(value){return self.get(value.trim());}));};let path=function(path,value,as,prefix){as=(as)?as:container.get('$as');prefix=(prefix)?prefix:container.get('$prefix');path=path.replace(as+'.',prefix+'.').split('.');let name=path.shift();let object=this.get(name);let result=null;while(path.length>1){if(undefined===object){return null;} 20 | object=object[path.shift()];} 21 | if(undefined!==value){object[path.shift()]=value;return true;} 22 | if(undefined===object){return null;} 23 | let shift=path.shift();if(undefined===shift){result=object;} 24 | else{return object[shift];} 25 | return result;};let bind=function(element,path,callback,as,prefix){as=(as)?as:container.get('$as');prefix=(prefix)?prefix:container.get('$prefix');let event=path.replace(as+'.',prefix+'.')+'.changed';let printer=function(){if(!document.body.contains(element)){element=null;document.removeEventListener(event,printer,false);return false;} 26 | callback();};document.addEventListener(event,printer);};let container={set:set,get:get,resolve:resolve,path:path,bind:bind,setCachePrefix:setCachePrefix,getCachePrefix:getCachePrefix};set('container',container,true,false,false);return container;}();window.ls.container.set('http',function(document){let globalParams=[],globalHeaders=[];let addParam=function(url,param,value){param=encodeURIComponent(param);let a=document.createElement('a');param+=(value?"="+encodeURIComponent(value):"");a.href=url;a.search+=(a.search?"&":"")+param;return a.href;};let request=function(method,url,headers,payload,progress){let i;if(-1===['GET','POST','PUT','DELETE','TRACE','HEAD','OPTIONS','CONNECT','PATCH'].indexOf(method)){throw new Error('var method must contain a valid HTTP method name');} 27 | if(typeof url!=='string'){throw new Error('var url must be of type string');} 28 | if(typeof headers!=='object'){throw new Error('var headers must be of type object');} 29 | if(typeof url!=='string'){throw new Error('var url must be of type string');} 30 | for(i=0;i-1?part.substr(0,eq):part;let val=eq>-1?decodeURIComponent(part.substr(eq+1)):'';let from=key.indexOf('[');if(from===-1){result[decodeURIComponent(key)]=val;} 63 | else{let to=key.indexOf(']');let index=decodeURIComponent(key.substring(from+1,to));key=decodeURIComponent(key.substring(0,from));if(!result[key]){result[key]=[];} 64 | if(!index){result[key].push(val);} 65 | else{result[key][index]=val;}}});return result;};let state={setParam:setParam,getParam:getParam,getParams:getParams,getURL:getURL,add:add,change:change,reload:reload,reset:reset,match:match,getCurrent:getCurrent,setCurrent:setCurrent,getPrevious:getPrevious,setPrevious:setPrevious,params:getJsonFromUrl(window.location.search),hash:window.location.hash};return state;},true,false,false);window.ls.container.set('expression',function(container,filter,$as,$prefix){let reg=/(\{{.*?\}})/gi;let paths=[];return{parse:function(string,def,as,prefix){def=def||'';paths=[];return string.replace(reg,function(match) 66 | {let reference=match.substring(2,match.length-2).replace('[\'','.').replace('\']','').trim();reference=reference.split('|');let path=(reference[0]||'');let result=container.path(path,undefined,as,prefix);if(!paths.includes(path)){paths.push(path);} 67 | result=(null===result||undefined===result)?def:result;result=(typeof result==='object')?JSON.stringify(result):result;if(reference.length>=2){for(let i=1;i/g,'>').replace(/\"/g,'"').replace(/\'/g,''').replace(/\//g,'/');});window.ls=window.ls||{};window.ls.container.set('window',window,true,false,false).set('document',window.document,true,false,false).set('element',window.document,true,false,false);window.ls.run=function(window){try{this.view.render(window.document);} 70 | catch(error){let handler=window.ls.container.resolve(this.error);handler(error);}};window.ls.error=function(){return function(error){console.error('ls-error',error.message,error.stack,error.toString());}};window.ls.router=window.ls.container.get('router');window.ls.view=window.ls.container.get('view');window.ls.filter=window.ls.container.get('filter');window.ls.container.get('view').add({selector:'data-ls-router',controller:function(element,window,document,view,router){let firstFromServer=(element.getAttribute('data-first-from-server')==='true');let scope={selector:'data-ls-scope',template:false,repeat:true,controller:function(){},};let scopeElement=document.createElement('div');let init=function(route){window.scrollTo(0,0);if(window.document.body.scrollTo){window.document.body.scrollTo(0,0);} 71 | router.reset();if(null===route){return;} 72 | scope.template=(undefined!==route.view.template)?route.view.template:null;scope.controller=(undefined!==route.view.controller)?route.view.controller:function(){};document.dispatchEvent(new CustomEvent('state-change'));if(firstFromServer&&null===router.getPrevious()){scope.template='';} 73 | else if(null!==router.getPrevious()){view.render(element);} 74 | document.dispatchEvent(new CustomEvent('state-changed'));};let findParent=function(tagName,el){if((el.nodeName||el.tagName).toLowerCase()===tagName.toLowerCase()){return el;} 75 | while(el=el.parentNode){if((el.nodeName||el.tagName).toLowerCase()===tagName.toLowerCase()){return el;}} 76 | return null;};scopeElement.setAttribute('data-ls-scope','');element.insertBefore(scopeElement,element.firstChild);view.add(scope);document.addEventListener('click',function(event){let target=findParent('a',event.target);if(!target){return false;} 77 | if(!target.href){return false;} 78 | if((event.metaKey)){return false;} 79 | if((target.hasAttribute('target'))&&('_blank'===target.getAttribute('target'))){return false;} 80 | if(target.hostname!==window.location.hostname){return false;} 81 | let route=router.match(target);if(null===route){return false;} 82 | event.preventDefault();if(window.location===target.href){return false;} 83 | route.view.state=(undefined===route.view.state)?true:route.view.state;if(true===route.view.state){if(router.getPrevious()&&router.getPrevious().view&&(router.getPrevious().view.scope!==route.view.scope)){window.location.href=target.href;return false;} 84 | window.history.pushState({},'Unknown',target.href);} 85 | init(route);return true;});window.addEventListener('popstate',function(){init(router.match(window.location));});window.addEventListener('hashchange',function(){init(router.match(window.location));});init(router.match(window.location));}});window.ls.container.get('view').add({selector:'data-ls-attrs',controller:function(element,expression,container,$as,$prefix){let attrs=element.getAttribute('data-ls-attrs').trim().split(',');let paths=[];let check=function(){for(let i=0;i';} 104 | return;} 105 | http.get(template).then(function(element){return function(data){parse(data,element);}}(element),function(){throw new Error('Failed loading template');});}}); --------------------------------------------------------------------------------