├── .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 |
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;iMissing template "'+template+'"';}
104 | return;}
105 | http.get(template).then(function(element){return function(data){parse(data,element);}}(element),function(){throw new Error('Failed loading template');});}});
--------------------------------------------------------------------------------