Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
todomvc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Sven Franck
todomvc
Commits
29a5abcd
Commit
29a5abcd
authored
May 03, 2013
by
Addy Osmani
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #510 from OscarGodson/gh-pages
VanillaJS app rewrite - landed!
parents
b8365f4b
3e59664c
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
1962 additions
and
301 deletions
+1962
-301
vanilla-examples/vanillajs/bower.json
vanilla-examples/vanillajs/bower.json
+8
-0
vanilla-examples/vanillajs/bower_components/director/build/director.js
...les/vanillajs/bower_components/director/build/director.js
+712
-0
vanilla-examples/vanillajs/bower_components/todomvc-common/base.css
...amples/vanillajs/bower_components/todomvc-common/base.css
+414
-0
vanilla-examples/vanillajs/bower_components/todomvc-common/base.js
...xamples/vanillajs/bower_components/todomvc-common/base.js
+38
-0
vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png
...examples/vanillajs/bower_components/todomvc-common/bg.png
+0
-0
vanilla-examples/vanillajs/index.html
vanilla-examples/vanillajs/index.html
+24
-8
vanilla-examples/vanillajs/js/app.js
vanilla-examples/vanillajs/js/app.js
+53
-293
vanilla-examples/vanillajs/js/controller.js
vanilla-examples/vanillajs/js/controller.js
+342
-0
vanilla-examples/vanillajs/js/helpers.js
vanilla-examples/vanillajs/js/helpers.js
+19
-0
vanilla-examples/vanillajs/js/model.js
vanilla-examples/vanillajs/js/model.js
+119
-0
vanilla-examples/vanillajs/js/store.js
vanilla-examples/vanillajs/js/store.js
+142
-0
vanilla-examples/vanillajs/js/view.js
vanilla-examples/vanillajs/js/view.js
+91
-0
No files found.
vanilla-examples/vanillajs/bower.json
0 → 100644
View file @
29a5abcd
{
"name"
:
"todomvc-vanillajs"
,
"version"
:
"0.0.0"
,
"dependencies"
:
{
"todomvc-common"
:
"~0.1.4"
,
"director"
:
"~1.2.0"
}
}
vanilla-examples/vanillajs/bower_components/director/build/director.js
0 → 100644
View file @
29a5abcd
//
// Generated on Sun Dec 16 2012 22:47:05 GMT-0500 (EST) by Nodejitsu, Inc (Using Codesurgeon).
// Version 1.1.9
//
(
function
(
exports
)
{
/*
* browser.js: Browser specific functionality for director.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
if
(
!
Array
.
prototype
.
filter
)
{
Array
.
prototype
.
filter
=
function
(
filter
,
that
)
{
var
other
=
[],
v
;
for
(
var
i
=
0
,
n
=
this
.
length
;
i
<
n
;
i
++
)
{
if
(
i
in
this
&&
filter
.
call
(
that
,
v
=
this
[
i
],
i
,
this
))
{
other
.
push
(
v
);
}
}
return
other
;
};
}
if
(
!
Array
.
isArray
){
Array
.
isArray
=
function
(
obj
)
{
return
Object
.
prototype
.
toString
.
call
(
obj
)
===
'
[object Array]
'
;
};
}
var
dloc
=
document
.
location
;
function
dlocHashEmpty
()
{
// Non-IE browsers return '' when the address bar shows '#'; Director's logic
// assumes both mean empty.
return
dloc
.
hash
===
''
||
dloc
.
hash
===
'
#
'
;
}
var
listener
=
{
mode
:
'
modern
'
,
hash
:
dloc
.
hash
,
history
:
false
,
check
:
function
()
{
var
h
=
dloc
.
hash
;
if
(
h
!=
this
.
hash
)
{
this
.
hash
=
h
;
this
.
onHashChanged
();
}
},
fire
:
function
()
{
if
(
this
.
mode
===
'
modern
'
)
{
this
.
history
===
true
?
window
.
onpopstate
()
:
window
.
onhashchange
();
}
else
{
this
.
onHashChanged
();
}
},
init
:
function
(
fn
,
history
)
{
var
self
=
this
;
this
.
history
=
history
;
if
(
!
Router
.
listeners
)
{
Router
.
listeners
=
[];
}
function
onchange
(
onChangeEvent
)
{
for
(
var
i
=
0
,
l
=
Router
.
listeners
.
length
;
i
<
l
;
i
++
)
{
Router
.
listeners
[
i
](
onChangeEvent
);
}
}
//note IE8 is being counted as 'modern' because it has the hashchange event
if
(
'
onhashchange
'
in
window
&&
(
document
.
documentMode
===
undefined
||
document
.
documentMode
>
7
))
{
// At least for now HTML5 history is available for 'modern' browsers only
if
(
this
.
history
===
true
)
{
// There is an old bug in Chrome that causes onpopstate to fire even
// upon initial page load. Since the handler is run manually in init(),
// this would cause Chrome to run it twise. Currently the only
// workaround seems to be to set the handler after the initial page load
// http://code.google.com/p/chromium/issues/detail?id=63040
setTimeout
(
function
()
{
window
.
onpopstate
=
onchange
;
},
500
);
}
else
{
window
.
onhashchange
=
onchange
;
}
this
.
mode
=
'
modern
'
;
}
else
{
//
// IE support, based on a concept by Erik Arvidson ...
//
var
frame
=
document
.
createElement
(
'
iframe
'
);
frame
.
id
=
'
state-frame
'
;
frame
.
style
.
display
=
'
none
'
;
document
.
body
.
appendChild
(
frame
);
this
.
writeFrame
(
''
);
if
(
'
onpropertychange
'
in
document
&&
'
attachEvent
'
in
document
)
{
document
.
attachEvent
(
'
onpropertychange
'
,
function
()
{
if
(
event
.
propertyName
===
'
location
'
)
{
self
.
check
();
}
});
}
window
.
setInterval
(
function
()
{
self
.
check
();
},
50
);
this
.
onHashChanged
=
onchange
;
this
.
mode
=
'
legacy
'
;
}
Router
.
listeners
.
push
(
fn
);
return
this
.
mode
;
},
destroy
:
function
(
fn
)
{
if
(
!
Router
||
!
Router
.
listeners
)
{
return
;
}
var
listeners
=
Router
.
listeners
;
for
(
var
i
=
listeners
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
listeners
[
i
]
===
fn
)
{
listeners
.
splice
(
i
,
1
);
}
}
},
setHash
:
function
(
s
)
{
// Mozilla always adds an entry to the history
if
(
this
.
mode
===
'
legacy
'
)
{
this
.
writeFrame
(
s
);
}
if
(
this
.
history
===
true
)
{
window
.
history
.
pushState
({},
document
.
title
,
s
);
// Fire an onpopstate event manually since pushing does not obviously
// trigger the pop event.
this
.
fire
();
}
else
{
dloc
.
hash
=
(
s
[
0
]
===
'
/
'
)
?
s
:
'
/
'
+
s
;
}
return
this
;
},
writeFrame
:
function
(
s
)
{
// IE support...
var
f
=
document
.
getElementById
(
'
state-frame
'
);
var
d
=
f
.
contentDocument
||
f
.
contentWindow
.
document
;
d
.
open
();
d
.
write
(
"
<script>_hash = '
"
+
s
+
"
'; onload = parent.listener.syncHash;<script>
"
);
d
.
close
();
},
syncHash
:
function
()
{
// IE support...
var
s
=
this
.
_hash
;
if
(
s
!=
dloc
.
hash
)
{
dloc
.
hash
=
s
;
}
return
this
;
},
onHashChanged
:
function
()
{}
};
var
Router
=
exports
.
Router
=
function
(
routes
)
{
if
(
!
(
this
instanceof
Router
))
return
new
Router
(
routes
);
this
.
params
=
{};
this
.
routes
=
{};
this
.
methods
=
[
'
on
'
,
'
once
'
,
'
after
'
,
'
before
'
];
this
.
scope
=
[];
this
.
_methods
=
{};
this
.
_insert
=
this
.
insert
;
this
.
insert
=
this
.
insertEx
;
this
.
historySupport
=
(
window
.
history
!=
null
?
window
.
history
.
pushState
:
null
)
!=
null
this
.
configure
();
this
.
mount
(
routes
||
{});
};
Router
.
prototype
.
init
=
function
(
r
)
{
var
self
=
this
;
this
.
handler
=
function
(
onChangeEvent
)
{
var
newURL
=
onChangeEvent
&&
onChangeEvent
.
newURL
||
window
.
location
.
hash
;
var
url
=
self
.
history
===
true
?
self
.
getPath
()
:
newURL
.
replace
(
/.*#/
,
''
);
self
.
dispatch
(
'
on
'
,
url
);
};
listener
.
init
(
this
.
handler
,
this
.
history
);
if
(
this
.
history
===
false
)
{
if
(
dlocHashEmpty
()
&&
r
)
{
dloc
.
hash
=
r
;
}
else
if
(
!
dlocHashEmpty
())
{
self
.
dispatch
(
'
on
'
,
dloc
.
hash
.
replace
(
/^#/
,
''
));
}
}
else
{
var
routeTo
=
dlocHashEmpty
()
&&
r
?
r
:
!
dlocHashEmpty
()
?
dloc
.
hash
.
replace
(
/^#/
,
''
)
:
null
;
if
(
routeTo
)
{
window
.
history
.
replaceState
({},
document
.
title
,
routeTo
);
}
// Router has been initialized, but due to the chrome bug it will not
// yet actually route HTML5 history state changes. Thus, decide if should route.
if
(
routeTo
||
this
.
run_in_init
===
true
)
{
this
.
handler
();
}
}
return
this
;
};
Router
.
prototype
.
explode
=
function
()
{
var
v
=
this
.
history
===
true
?
this
.
getPath
()
:
dloc
.
hash
;
if
(
v
.
charAt
(
1
)
===
'
/
'
)
{
v
=
v
.
slice
(
1
)
}
return
v
.
slice
(
1
,
v
.
length
).
split
(
"
/
"
);
};
Router
.
prototype
.
setRoute
=
function
(
i
,
v
,
val
)
{
var
url
=
this
.
explode
();
if
(
typeof
i
===
'
number
'
&&
typeof
v
===
'
string
'
)
{
url
[
i
]
=
v
;
}
else
if
(
typeof
val
===
'
string
'
)
{
url
.
splice
(
i
,
v
,
s
);
}
else
{
url
=
[
i
];
}
listener
.
setHash
(
url
.
join
(
'
/
'
));
return
url
;
};
//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router
.
prototype
.
insertEx
=
function
(
method
,
path
,
route
,
parent
)
{
if
(
method
===
"
once
"
)
{
method
=
"
on
"
;
route
=
function
(
route
)
{
var
once
=
false
;
return
function
()
{
if
(
once
)
return
;
once
=
true
;
return
route
.
apply
(
this
,
arguments
);
};
}(
route
);
}
return
this
.
_insert
(
method
,
path
,
route
,
parent
);
};
Router
.
prototype
.
getRoute
=
function
(
v
)
{
var
ret
=
v
;
if
(
typeof
v
===
"
number
"
)
{
ret
=
this
.
explode
()[
v
];
}
else
if
(
typeof
v
===
"
string
"
){
var
h
=
this
.
explode
();
ret
=
h
.
indexOf
(
v
);
}
else
{
ret
=
this
.
explode
();
}
return
ret
;
};
Router
.
prototype
.
destroy
=
function
()
{
listener
.
destroy
(
this
.
handler
);
return
this
;
};
Router
.
prototype
.
getPath
=
function
()
{
var
path
=
window
.
location
.
pathname
;
if
(
path
.
substr
(
0
,
1
)
!==
'
/
'
)
{
path
=
'
/
'
+
path
;
}
return
path
;
};
function
_every
(
arr
,
iterator
)
{
for
(
var
i
=
0
;
i
<
arr
.
length
;
i
+=
1
)
{
if
(
iterator
(
arr
[
i
],
i
,
arr
)
===
false
)
{
return
;
}
}
}
function
_flatten
(
arr
)
{
var
flat
=
[];
for
(
var
i
=
0
,
n
=
arr
.
length
;
i
<
n
;
i
++
)
{
flat
=
flat
.
concat
(
arr
[
i
]);
}
return
flat
;
}
function
_asyncEverySeries
(
arr
,
iterator
,
callback
)
{
if
(
!
arr
.
length
)
{
return
callback
();
}
var
completed
=
0
;
(
function
iterate
()
{
iterator
(
arr
[
completed
],
function
(
err
)
{
if
(
err
||
err
===
false
)
{
callback
(
err
);
callback
=
function
()
{};
}
else
{
completed
+=
1
;
if
(
completed
===
arr
.
length
)
{
callback
();
}
else
{
iterate
();
}
}
});
})();
}
function
paramifyString
(
str
,
params
,
mod
)
{
mod
=
str
;
for
(
var
param
in
params
)
{
if
(
params
.
hasOwnProperty
(
param
))
{
mod
=
params
[
param
](
str
);
if
(
mod
!==
str
)
{
break
;
}
}
}
return
mod
===
str
?
"
([._a-zA-Z0-9-]+)
"
:
mod
;
}
function
regifyString
(
str
,
params
)
{
var
matches
,
last
=
0
,
out
=
""
;
while
(
matches
=
str
.
substr
(
last
).
match
(
/
[^\w\d\-
%@&
]
*
\*[^\w\d\-
%@&
]
*/
))
{
last
=
matches
.
index
+
matches
[
0
].
length
;
matches
[
0
]
=
matches
[
0
].
replace
(
/^
\*
/
,
"
([_.()!
\\
%@&a-zA-Z0-9-]+)
"
);
out
+=
str
.
substr
(
0
,
matches
.
index
)
+
matches
[
0
];
}
str
=
out
+=
str
.
substr
(
last
);
var
captures
=
str
.
match
(
/:
([^\/]
+
)
/ig
),
length
;
if
(
captures
)
{
length
=
captures
.
length
;
for
(
var
i
=
0
;
i
<
length
;
i
++
)
{
str
=
str
.
replace
(
captures
[
i
],
paramifyString
(
captures
[
i
],
params
));
}
}
return
str
;
}
function
terminator
(
routes
,
delimiter
,
start
,
stop
)
{
var
last
=
0
,
left
=
0
,
right
=
0
,
start
=
(
start
||
"
(
"
).
toString
(),
stop
=
(
stop
||
"
)
"
).
toString
(),
i
;
for
(
i
=
0
;
i
<
routes
.
length
;
i
++
)
{
var
chunk
=
routes
[
i
];
if
(
chunk
.
indexOf
(
start
,
last
)
>
chunk
.
indexOf
(
stop
,
last
)
||
~
chunk
.
indexOf
(
start
,
last
)
&&
!~
chunk
.
indexOf
(
stop
,
last
)
||
!~
chunk
.
indexOf
(
start
,
last
)
&&
~
chunk
.
indexOf
(
stop
,
last
))
{
left
=
chunk
.
indexOf
(
start
,
last
);
right
=
chunk
.
indexOf
(
stop
,
last
);
if
(
~
left
&&
!~
right
||
!~
left
&&
~
right
)
{
var
tmp
=
routes
.
slice
(
0
,
(
i
||
1
)
+
1
).
join
(
delimiter
);
routes
=
[
tmp
].
concat
(
routes
.
slice
((
i
||
1
)
+
1
));
}
last
=
(
right
>
left
?
right
:
left
)
+
1
;
i
=
0
;
}
else
{
last
=
0
;
}
}
return
routes
;
}
Router
.
prototype
.
configure
=
function
(
options
)
{
options
=
options
||
{};
for
(
var
i
=
0
;
i
<
this
.
methods
.
length
;
i
++
)
{
this
.
_methods
[
this
.
methods
[
i
]]
=
true
;
}
this
.
recurse
=
options
.
recurse
||
this
.
recurse
||
false
;
this
.
async
=
options
.
async
||
false
;
this
.
delimiter
=
options
.
delimiter
||
"
/
"
;
this
.
strict
=
typeof
options
.
strict
===
"
undefined
"
?
true
:
options
.
strict
;
this
.
notfound
=
options
.
notfound
;
this
.
resource
=
options
.
resource
;
this
.
history
=
options
.
html5history
&&
this
.
historySupport
||
false
;
this
.
run_in_init
=
this
.
history
===
true
&&
options
.
run_handler_in_init
!==
false
;
this
.
every
=
{
after
:
options
.
after
||
null
,
before
:
options
.
before
||
null
,
on
:
options
.
on
||
null
};
return
this
;
};
Router
.
prototype
.
param
=
function
(
token
,
matcher
)
{
if
(
token
[
0
]
!==
"
:
"
)
{
token
=
"
:
"
+
token
;
}
var
compiled
=
new
RegExp
(
token
,
"
g
"
);
this
.
params
[
token
]
=
function
(
str
)
{
return
str
.
replace
(
compiled
,
matcher
.
source
||
matcher
);
};
};
Router
.
prototype
.
on
=
Router
.
prototype
.
route
=
function
(
method
,
path
,
route
)
{
var
self
=
this
;
if
(
!
route
&&
typeof
path
==
"
function
"
)
{
route
=
path
;
path
=
method
;
method
=
"
on
"
;
}
if
(
Array
.
isArray
(
path
))
{
return
path
.
forEach
(
function
(
p
)
{
self
.
on
(
method
,
p
,
route
);
});
}
if
(
path
.
source
)
{
path
=
path
.
source
.
replace
(
/
\\\/
/ig
,
"
/
"
);
}
if
(
Array
.
isArray
(
method
))
{
return
method
.
forEach
(
function
(
m
)
{
self
.
on
(
m
.
toLowerCase
(),
path
,
route
);
});
}
path
=
path
.
split
(
new
RegExp
(
this
.
delimiter
));
path
=
terminator
(
path
,
this
.
delimiter
);
this
.
insert
(
method
,
this
.
scope
.
concat
(
path
),
route
);
};
Router
.
prototype
.
dispatch
=
function
(
method
,
path
,
callback
)
{
var
self
=
this
,
fns
=
this
.
traverse
(
method
,
path
,
this
.
routes
,
""
),
invoked
=
this
.
_invoked
,
after
;
this
.
_invoked
=
true
;
if
(
!
fns
||
fns
.
length
===
0
)
{
this
.
last
=
[];
if
(
typeof
this
.
notfound
===
"
function
"
)
{
this
.
invoke
([
this
.
notfound
],
{
method
:
method
,
path
:
path
},
callback
);
}
return
false
;
}
if
(
this
.
recurse
===
"
forward
"
)
{
fns
=
fns
.
reverse
();
}
function
updateAndInvoke
()
{
self
.
last
=
fns
.
after
;
self
.
invoke
(
self
.
runlist
(
fns
),
self
,
callback
);
}
after
=
this
.
every
&&
this
.
every
.
after
?
[
this
.
every
.
after
].
concat
(
this
.
last
)
:
[
this
.
last
];
if
(
after
&&
after
.
length
>
0
&&
invoked
)
{
if
(
this
.
async
)
{
this
.
invoke
(
after
,
this
,
updateAndInvoke
);
}
else
{
this
.
invoke
(
after
,
this
);
updateAndInvoke
();
}
return
true
;
}
updateAndInvoke
();
return
true
;
};
Router
.
prototype
.
invoke
=
function
(
fns
,
thisArg
,
callback
)
{
var
self
=
this
;
if
(
this
.
async
)
{
_asyncEverySeries
(
fns
,
function
apply
(
fn
,
next
)
{
if
(
Array
.
isArray
(
fn
))
{
return
_asyncEverySeries
(
fn
,
apply
,
next
);
}
else
if
(
typeof
fn
==
"
function
"
)
{
fn
.
apply
(
thisArg
,
fns
.
captures
.
concat
(
next
));
}
},
function
()
{
if
(
callback
)
{
callback
.
apply
(
thisArg
,
arguments
);
}
});
}
else
{
_every
(
fns
,
function
apply
(
fn
)
{
if
(
Array
.
isArray
(
fn
))
{
return
_every
(
fn
,
apply
);
}
else
if
(
typeof
fn
===
"
function
"
)
{
return
fn
.
apply
(
thisArg
,
fns
.
captures
||
[]);
}
else
if
(
typeof
fn
===
"
string
"
&&
self
.
resource
)
{
self
.
resource
[
fn
].
apply
(
thisArg
,
fns
.
captures
||
[]);
}
});
}
};
Router
.
prototype
.
traverse
=
function
(
method
,
path
,
routes
,
regexp
,
filter
)
{
var
fns
=
[],
current
,
exact
,
match
,
next
,
that
;
function
filterRoutes
(
routes
)
{
if
(
!
filter
)
{
return
routes
;
}
function
deepCopy
(
source
)
{
var
result
=
[];
for
(
var
i
=
0
;
i
<
source
.
length
;
i
++
)
{
result
[
i
]
=
Array
.
isArray
(
source
[
i
])
?
deepCopy
(
source
[
i
])
:
source
[
i
];
}
return
result
;
}
function
applyFilter
(
fns
)
{
for
(
var
i
=
fns
.
length
-
1
;
i
>=
0
;
i
--
)
{
if
(
Array
.
isArray
(
fns
[
i
]))
{
applyFilter
(
fns
[
i
]);
if
(
fns
[
i
].
length
===
0
)
{
fns
.
splice
(
i
,
1
);
}
}
else
{
if
(
!
filter
(
fns
[
i
]))
{
fns
.
splice
(
i
,
1
);
}
}
}
}
var
newRoutes
=
deepCopy
(
routes
);
newRoutes
.
matched
=
routes
.
matched
;
newRoutes
.
captures
=
routes
.
captures
;
newRoutes
.
after
=
routes
.
after
.
filter
(
filter
);
applyFilter
(
newRoutes
);
return
newRoutes
;
}
if
(
path
===
this
.
delimiter
&&
routes
[
method
])
{
next
=
[
[
routes
.
before
,
routes
[
method
]
].
filter
(
Boolean
)
];
next
.
after
=
[
routes
.
after
].
filter
(
Boolean
);
next
.
matched
=
true
;
next
.
captures
=
[];
return
filterRoutes
(
next
);
}
for
(
var
r
in
routes
)
{
if
(
routes
.
hasOwnProperty
(
r
)
&&
(
!
this
.
_methods
[
r
]
||
this
.
_methods
[
r
]
&&
typeof
routes
[
r
]
===
"
object
"
&&
!
Array
.
isArray
(
routes
[
r
])))
{
current
=
exact
=
regexp
+
this
.
delimiter
+
r
;
if
(
!
this
.
strict
)
{
exact
+=
"
[
"
+
this
.
delimiter
+
"
]?
"
;
}
match
=
path
.
match
(
new
RegExp
(
"
^
"
+
exact
));
if
(
!
match
)
{
continue
;
}
if
(
match
[
0
]
&&
match
[
0
]
==
path
&&
routes
[
r
][
method
])
{
next
=
[
[
routes
[
r
].
before
,
routes
[
r
][
method
]
].
filter
(
Boolean
)
];
next
.
after
=
[
routes
[
r
].
after
].
filter
(
Boolean
);
next
.
matched
=
true
;
next
.
captures
=
match
.
slice
(
1
);
if
(
this
.
recurse
&&
routes
===
this
.
routes
)
{
next
.
push
([
routes
.
before
,
routes
.
on
].
filter
(
Boolean
));
next
.
after
=
next
.
after
.
concat
([
routes
.
after
].
filter
(
Boolean
));
}
return
filterRoutes
(
next
);
}
next
=
this
.
traverse
(
method
,
path
,
routes
[
r
],
current
);
if
(
next
.
matched
)
{
if
(
next
.
length
>
0
)
{
fns
=
fns
.
concat
(
next
);
}
if
(
this
.
recurse
)
{
fns
.
push
([
routes
[
r
].
before
,
routes
[
r
].
on
].
filter
(
Boolean
));
next
.
after
=
next
.
after
.
concat
([
routes
[
r
].
after
].
filter
(
Boolean
));
if
(
routes
===
this
.
routes
)
{
fns
.
push
([
routes
[
"
before
"
],
routes
[
"
on
"
]
].
filter
(
Boolean
));
next
.
after
=
next
.
after
.
concat
([
routes
[
"
after
"
]
].
filter
(
Boolean
));
}
}
fns
.
matched
=
true
;
fns
.
captures
=
next
.
captures
;
fns
.
after
=
next
.
after
;
return
filterRoutes
(
fns
);
}
}
}
return
false
;
};
Router
.
prototype
.
insert
=
function
(
method
,
path
,
route
,
parent
)
{
var
methodType
,
parentType
,
isArray
,
nested
,
part
;
path
=
path
.
filter
(
function
(
p
)
{
return
p
&&
p
.
length
>
0
;
});
parent
=
parent
||
this
.
routes
;
part
=
path
.
shift
();
if
(
/
\:
|
\*
/
.
test
(
part
)
&&
!
/
\\
d|
\\
w/
.
test
(
part
))
{
part
=
regifyString
(
part
,
this
.
params
);
}
if
(
path
.
length
>
0
)
{
parent
[
part
]
=
parent
[
part
]
||
{};
return
this
.
insert
(
method
,
path
,
route
,
parent
[
part
]);
}
if
(
!
part
&&
!
path
.
length
&&
parent
===
this
.
routes
)
{
methodType
=
typeof
parent
[
method
];
switch
(
methodType
)
{
case
"
function
"
:
parent
[
method
]
=
[
parent
[
method
],
route
];
return
;
case
"
object
"
:
parent
[
method
].
push
(
route
);
return
;
case
"
undefined
"
:
parent
[
method
]
=
route
;
return
;
}
return
;
}
parentType
=
typeof
parent
[
part
];
isArray
=
Array
.
isArray
(
parent
[
part
]);
if
(
parent
[
part
]
&&
!
isArray
&&
parentType
==
"
object
"
)
{
methodType
=
typeof
parent
[
part
][
method
];
switch
(
methodType
)
{
case
"
function
"
:
parent
[
part
][
method
]
=
[
parent
[
part
][
method
],
route
];
return
;
case
"
object
"
:
parent
[
part
][
method
].
push
(
route
);
return
;
case
"
undefined
"
:
parent
[
part
][
method
]
=
route
;
return
;
}
}
else
if
(
parentType
==
"
undefined
"
)
{
nested
=
{};
nested
[
method
]
=
route
;
parent
[
part
]
=
nested
;
return
;
}
throw
new
Error
(
"
Invalid route context:
"
+
parentType
);
};
Router
.
prototype
.
extend
=
function
(
methods
)
{
var
self
=
this
,
len
=
methods
.
length
,
i
;
function
extend
(
method
)
{
self
.
_methods
[
method
]
=
true
;
self
[
method
]
=
function
()
{
var
extra
=
arguments
.
length
===
1
?
[
method
,
""
]
:
[
method
];
self
.
on
.
apply
(
self
,
extra
.
concat
(
Array
.
prototype
.
slice
.
call
(
arguments
)));
};
}
for
(
i
=
0
;
i
<
len
;
i
++
)
{
extend
(
methods
[
i
]);
}
};
Router
.
prototype
.
runlist
=
function
(
fns
)
{
var
runlist
=
this
.
every
&&
this
.
every
.
before
?
[
this
.
every
.
before
].
concat
(
_flatten
(
fns
))
:
_flatten
(
fns
);
if
(
this
.
every
&&
this
.
every
.
on
)
{
runlist
.
push
(
this
.
every
.
on
);
}
runlist
.
captures
=
fns
.
captures
;
runlist
.
source
=
fns
.
source
;
return
runlist
;
};
Router
.
prototype
.
mount
=
function
(
routes
,
path
)
{
if
(
!
routes
||
typeof
routes
!==
"
object
"
||
Array
.
isArray
(
routes
))
{
return
;
}
var
self
=
this
;
path
=
path
||
[];
if
(
!
Array
.
isArray
(
path
))
{
path
=
path
.
split
(
self
.
delimiter
);
}
function
insertOrMount
(
route
,
local
)
{
var
rename
=
route
,
parts
=
route
.
split
(
self
.
delimiter
),
routeType
=
typeof
routes
[
route
],
isRoute
=
parts
[
0
]
===
""
||
!
self
.
_methods
[
parts
[
0
]],
event
=
isRoute
?
"
on
"
:
rename
;
if
(
isRoute
)
{
rename
=
rename
.
slice
((
rename
.
match
(
new
RegExp
(
self
.
delimiter
))
||
[
""
])[
0
].
length
);
parts
.
shift
();
}
if
(
isRoute
&&
routeType
===
"
object
"
&&
!
Array
.
isArray
(
routes
[
route
]))
{
local
=
local
.
concat
(
parts
);
self
.
mount
(
routes
[
route
],
local
);
return
;
}
if
(
isRoute
)
{
local
=
local
.
concat
(
rename
.
split
(
self
.
delimiter
));
local
=
terminator
(
local
,
self
.
delimiter
);
}
self
.
insert
(
event
,
local
,
routes
[
route
]);
}
for
(
var
route
in
routes
)
{
if
(
routes
.
hasOwnProperty
(
route
))
{
insertOrMount
(
route
,
path
.
slice
(
0
));
}
}
};
}(
typeof
exports
===
"
object
"
?
exports
:
window
));
\ No newline at end of file
vanilla-examples/vanillajs/bower_components/todomvc-common/base.css
0 → 100644
View file @
29a5abcd
html
,
body
{
margin
:
0
;
padding
:
0
;
}
button
{
margin
:
0
;
padding
:
0
;
border
:
0
;
background
:
none
;
font-size
:
100%
;
vertical-align
:
baseline
;
font-family
:
inherit
;
color
:
inherit
;
-webkit-appearance
:
none
;
/*-moz-appearance: none;*/
-ms-appearance
:
none
;
-o-appearance
:
none
;
appearance
:
none
;
}
body
{
font
:
14px
'Helvetica Neue'
,
Helvetica
,
Arial
,
sans-serif
;
line-height
:
1.4em
;
background
:
#eaeaea
url('bg.png')
;
color
:
#4d4d4d
;
width
:
550px
;
margin
:
0
auto
;
-webkit-font-smoothing
:
antialiased
;
-moz-font-smoothing
:
antialiased
;
-ms-font-smoothing
:
antialiased
;
-o-font-smoothing
:
antialiased
;
font-smoothing
:
antialiased
;
}
#todoapp
{
background
:
#fff
;
background
:
rgba
(
255
,
255
,
255
,
0.9
);
margin
:
130px
0
40px
0
;
border
:
1px
solid
#ccc
;
position
:
relative
;
border-top-left-radius
:
2px
;
border-top-right-radius
:
2px
;
box-shadow
:
0
2px
6px
0
rgba
(
0
,
0
,
0
,
0.2
),
0
25px
50px
0
rgba
(
0
,
0
,
0
,
0.15
);
}
#todoapp
:before
{
content
:
''
;
border-left
:
1px
solid
#f5d6d6
;
border-right
:
1px
solid
#f5d6d6
;
width
:
2px
;
position
:
absolute
;
top
:
0
;
left
:
40px
;
height
:
100%
;
}
#todoapp
input
::-webkit-input-placeholder
{
font-style
:
italic
;
}
#todoapp
input
:-moz-placeholder
{
font-style
:
italic
;
color
:
#a9a9a9
;
}
#todoapp
h1
{
position
:
absolute
;
top
:
-120px
;
width
:
100%
;
font-size
:
70px
;
font-weight
:
bold
;
text-align
:
center
;
color
:
#b3b3b3
;
color
:
rgba
(
255
,
255
,
255
,
0.3
);
text-shadow
:
-1px
-1px
rgba
(
0
,
0
,
0
,
0.2
);
-webkit-text-rendering
:
optimizeLegibility
;
-moz-text-rendering
:
optimizeLegibility
;
-ms-text-rendering
:
optimizeLegibility
;
-o-text-rendering
:
optimizeLegibility
;
text-rendering
:
optimizeLegibility
;
}
#header
{
padding-top
:
15px
;
border-radius
:
inherit
;
}
#header
:before
{
content
:
''
;
position
:
absolute
;
top
:
0
;
right
:
0
;
left
:
0
;
height
:
15px
;
z-index
:
2
;
border-bottom
:
1px
solid
#6c615c
;
background
:
#8d7d77
;
background
:
-webkit-gradient
(
linear
,
left
top
,
left
bottom
,
from
(
rgba
(
132
,
110
,
100
,
0.8
)),
to
(
rgba
(
101
,
84
,
76
,
0.8
)));
background
:
-webkit-linear-gradient
(
top
,
rgba
(
132
,
110
,
100
,
0.8
),
rgba
(
101
,
84
,
76
,
0.8
));
background
:
-moz-linear-gradient
(
top
,
rgba
(
132
,
110
,
100
,
0.8
),
rgba
(
101
,
84
,
76
,
0.8
));
background
:
-o-linear-gradient
(
top
,
rgba
(
132
,
110
,
100
,
0.8
),
rgba
(
101
,
84
,
76
,
0.8
));
background
:
-ms-linear-gradient
(
top
,
rgba
(
132
,
110
,
100
,
0.8
),
rgba
(
101
,
84
,
76
,
0.8
));
background
:
linear-gradient
(
top
,
rgba
(
132
,
110
,
100
,
0.8
),
rgba
(
101
,
84
,
76
,
0.8
));
filter
:
progid
:
DXImageTransform
.
Microsoft
.
gradient
(
GradientType
=
0
,
StartColorStr
=
'#9d8b83'
,
EndColorStr
=
'#847670'
);
border-top-left-radius
:
1px
;
border-top-right-radius
:
1px
;
}
#new-todo
,
.edit
{
position
:
relative
;
margin
:
0
;
width
:
100%
;
font-size
:
24px
;
font-family
:
inherit
;
line-height
:
1.4em
;
border
:
0
;
outline
:
none
;
color
:
inherit
;
padding
:
6px
;
border
:
1px
solid
#999
;
box-shadow
:
inset
0
-1px
5px
0
rgba
(
0
,
0
,
0
,
0.2
);
-webkit-box-sizing
:
border-box
;
-moz-box-sizing
:
border-box
;
-ms-box-sizing
:
border-box
;
-o-box-sizing
:
border-box
;
box-sizing
:
border-box
;
-webkit-font-smoothing
:
antialiased
;
-moz-font-smoothing
:
antialiased
;
-ms-font-smoothing
:
antialiased
;
-o-font-smoothing
:
antialiased
;
font-smoothing
:
antialiased
;
}
#new-todo
{
padding
:
16px
16px
16px
60px
;
border
:
none
;
background
:
rgba
(
0
,
0
,
0
,
0.02
);
z-index
:
2
;
box-shadow
:
none
;
}
#main
{
position
:
relative
;
z-index
:
2
;
border-top
:
1px
dotted
#adadad
;
}
label
[
for
=
'toggle-all'
]
{
display
:
none
;
}
#toggle-all
{
position
:
absolute
;
top
:
-42px
;
left
:
-4px
;
width
:
40px
;
text-align
:
center
;
border
:
none
;
/* Mobile Safari */
}
#toggle-all
:before
{
content
:
'»'
;
font-size
:
28px
;
color
:
#d9d9d9
;
padding
:
0
25px
7px
;
}
#toggle-all
:checked:before
{
color
:
#737373
;
}
#todo-list
{
margin
:
0
;
padding
:
0
;
list-style
:
none
;
}
#todo-list
li
{
position
:
relative
;
font-size
:
24px
;
border-bottom
:
1px
dotted
#ccc
;
}
#todo-list
li
:last-child
{
border-bottom
:
none
;
}
#todo-list
li
.editing
{
border-bottom
:
none
;
padding
:
0
;
}
#todo-list
li
.editing
.edit
{
display
:
block
;
width
:
506px
;
padding
:
13px
17px
12px
17px
;
margin
:
0
0
0
43px
;
}
#todo-list
li
.editing
.view
{
display
:
none
;
}
#todo-list
li
.toggle
{
text-align
:
center
;
width
:
40px
;
/* auto, since non-WebKit browsers doesn't support input styling */
height
:
auto
;
position
:
absolute
;
top
:
0
;
bottom
:
0
;
margin
:
auto
0
;
border
:
none
;
/* Mobile Safari */
-webkit-appearance
:
none
;
/*-moz-appearance: none;*/
-ms-appearance
:
none
;
-o-appearance
:
none
;
appearance
:
none
;
}
#todo-list
li
.toggle
:after
{
content
:
'✔'
;
line-height
:
43px
;
/* 40 + a couple of pixels visual adjustment */
font-size
:
20px
;
color
:
#d9d9d9
;
text-shadow
:
0
-1px
0
#bfbfbf
;
}
#todo-list
li
.toggle
:checked:after
{
color
:
#85ada7
;
text-shadow
:
0
1px
0
#669991
;
bottom
:
1px
;
position
:
relative
;
}
#todo-list
li
label
{
word-break
:
break-word
;
padding
:
15px
;
margin-left
:
45px
;
display
:
block
;
line-height
:
1.2
;
-webkit-transition
:
color
0.4s
;
-moz-transition
:
color
0.4s
;
-ms-transition
:
color
0.4s
;
-o-transition
:
color
0.4s
;
transition
:
color
0.4s
;
}
#todo-list
li
.completed
label
{
color
:
#a9a9a9
;
text-decoration
:
line-through
;
}
#todo-list
li
.destroy
{
display
:
none
;
position
:
absolute
;
top
:
0
;
right
:
10px
;
bottom
:
0
;
width
:
40px
;
height
:
40px
;
margin
:
auto
0
;
font-size
:
22px
;
color
:
#a88a8a
;
-webkit-transition
:
all
0.2s
;
-moz-transition
:
all
0.2s
;
-ms-transition
:
all
0.2s
;
-o-transition
:
all
0.2s
;
transition
:
all
0.2s
;
}
#todo-list
li
.destroy
:hover
{
text-shadow
:
0
0
1px
#000
,
0
0
10px
rgba
(
199
,
107
,
107
,
0.8
);
-webkit-transform
:
scale
(
1.3
);
-moz-transform
:
scale
(
1.3
);
-ms-transform
:
scale
(
1.3
);
-o-transform
:
scale
(
1.3
);
transform
:
scale
(
1.3
);
}
#todo-list
li
.destroy
:after
{
content
:
'✖'
;
}
#todo-list
li
:hover
.destroy
{
display
:
block
;
}
#todo-list
li
.edit
{
display
:
none
;
}
#todo-list
li
.editing
:last-child
{
margin-bottom
:
-1px
;
}
#footer
{
color
:
#777
;
padding
:
0
15px
;
position
:
absolute
;
right
:
0
;
bottom
:
-31px
;
left
:
0
;
height
:
20px
;
z-index
:
1
;
text-align
:
center
;
}
#footer
:before
{
content
:
''
;
position
:
absolute
;
right
:
0
;
bottom
:
31px
;
left
:
0
;
height
:
50px
;
z-index
:
-1
;
box-shadow
:
0
1px
1px
rgba
(
0
,
0
,
0
,
0.3
),
0
6px
0
-3px
rgba
(
255
,
255
,
255
,
0.8
),
0
7px
1px
-3px
rgba
(
0
,
0
,
0
,
0.3
),
0
43px
0
-6px
rgba
(
255
,
255
,
255
,
0.8
),
0
44px
2px
-6px
rgba
(
0
,
0
,
0
,
0.2
);
}
#todo-count
{
float
:
left
;
text-align
:
left
;
}
#filters
{
margin
:
0
;
padding
:
0
;
list-style
:
none
;
position
:
absolute
;
right
:
0
;
left
:
0
;
}
#filters
li
{
display
:
inline
;
}
#filters
li
a
{
color
:
#83756f
;
margin
:
2px
;
text-decoration
:
none
;
}
#filters
li
a
.selected
{
font-weight
:
bold
;
}
#clear-completed
{
float
:
right
;
position
:
relative
;
line-height
:
20px
;
text-decoration
:
none
;
background
:
rgba
(
0
,
0
,
0
,
0.1
);
font-size
:
11px
;
padding
:
0
10px
;
border-radius
:
3px
;
box-shadow
:
0
-1px
0
0
rgba
(
0
,
0
,
0
,
0.2
);
}
#clear-completed
:hover
{
background
:
rgba
(
0
,
0
,
0
,
0.15
);
box-shadow
:
0
-1px
0
0
rgba
(
0
,
0
,
0
,
0.3
);
}
#info
{
margin
:
65px
auto
0
;
color
:
#a6a6a6
;
font-size
:
12px
;
text-shadow
:
0
1px
0
rgba
(
255
,
255
,
255
,
0.7
);
text-align
:
center
;
}
#info
a
{
color
:
inherit
;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media
screen
and
(
-webkit-min-device-pixel-ratio
:
0
)
{
#toggle-all
,
#todo-list
li
.toggle
{
background
:
none
;
}
#todo-list
li
.toggle
{
height
:
40px
;
}
#toggle-all
{
top
:
-56px
;
left
:
-15px
;
width
:
65px
;
height
:
41px
;
-webkit-transform
:
rotate
(
90deg
);
transform
:
rotate
(
90deg
);
-webkit-appearance
:
none
;
appearance
:
none
;
}
}
.hidden
{
display
:
none
;
}
vanilla-examples/vanillajs/bower_components/todomvc-common/base.js
0 → 100644
View file @
29a5abcd
(
function
()
{
'
use strict
'
;
if
(
location
.
hostname
===
'
todomvc.com
'
)
{
window
.
_gaq
=
[[
'
_setAccount
'
,
'
UA-31081062-1
'
],[
'
_trackPageview
'
]];(
function
(
d
,
t
){
var
g
=
d
.
createElement
(
t
),
s
=
d
.
getElementsByTagName
(
t
)[
0
];
g
.
src
=
'
//www.google-analytics.com/ga.js
'
;
s
.
parentNode
.
insertBefore
(
g
,
s
)}(
document
,
'
script
'
));
}
function
getSourcePath
()
{
// If accessed via addyosmani.github.io/todomvc/, strip the project path.
if
(
location
.
hostname
.
indexOf
(
'
github.io
'
)
>
0
)
{
return
location
.
pathname
.
replace
(
/todomvc
\/
/
,
''
);
}
return
location
.
pathname
;
}
function
appendSourceLink
()
{
var
sourceLink
=
document
.
createElement
(
'
a
'
);
var
paragraph
=
document
.
createElement
(
'
p
'
);
var
footer
=
document
.
getElementById
(
'
info
'
);
var
urlBase
=
'
https://github.com/addyosmani/todomvc/tree/gh-pages
'
;
if
(
footer
)
{
sourceLink
.
href
=
urlBase
+
getSourcePath
();
sourceLink
.
appendChild
(
document
.
createTextNode
(
'
Check out the source
'
));
paragraph
.
appendChild
(
sourceLink
);
footer
.
appendChild
(
paragraph
);
}
}
function
redirect
()
{
if
(
location
.
hostname
===
'
addyosmani.github.io
'
)
{
location
.
href
=
location
.
href
.
replace
(
'
addyosmani.github.io/todomvc
'
,
'
todomvc.com
'
);
}
}
appendSourceLink
();
redirect
();
})();
vanilla-examples/vanillajs/bower_components/todomvc-common/bg.png
0 → 100644
View file @
29a5abcd
2.08 KB
vanilla-examples/vanillajs/index.html
View file @
29a5abcd
...
@@ -4,10 +4,7 @@
...
@@ -4,10 +4,7 @@
<meta
charset=
"utf-8"
>
<meta
charset=
"utf-8"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge,chrome=1"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge,chrome=1"
>
<title>
VanillaJS • TodoMVC
</title>
<title>
VanillaJS • TodoMVC
</title>
<link
rel=
"stylesheet"
href=
"../../assets/base.css"
>
<link
rel=
"stylesheet"
href=
"bower_components/todomvc-common/base.css"
>
<!--[if IE]>
<script src="../../assets/ie.js"></script>
<![endif]-->
</head>
</head>
<body>
<body>
<section
id=
"todoapp"
>
<section
id=
"todoapp"
>
...
@@ -22,16 +19,35 @@
...
@@ -22,16 +19,35 @@
</section>
</section>
<footer
id=
"footer"
>
<footer
id=
"footer"
>
<span
id=
"todo-count"
></span>
<span
id=
"todo-count"
></span>
<ul
id=
"filters"
>
<li>
<a
href=
"#/"
>
All
</a>
</li>
<li>
<a
href=
"#/active"
>
Active
</a>
</li>
<li>
<a
href=
"#/completed"
>
Completed
</a>
</li>
</ul>
<button
id=
"clear-completed"
>
Clear completed
</button>
<button
id=
"clear-completed"
>
Clear completed
</button>
</footer>
</footer>
</section>
</section>
<footer
id=
"info"
>
<footer
id=
"info"
>
<p>
Double-click to edit a todo
</p>
<p>
Double-click to edit a todo
</p>
<p>
Created by
<a
href=
"http://twitter.com/ffesseler"
>
Florian Fesseler
</a></p>
<p>
Created by
<a
href=
"http://twitter.com/oscargodson"
>
Oscar Godson
</a></p>
<p>
Cleanup, edits by
<a
href=
"http://github.com/boushley"
>
Aaron Boushley
</a></p>
<p>
Part of
<a
href=
"http://todomvc.com"
>
TodoMVC
</a></p>
</footer>
</footer>
<script
src=
"../../assets/base.js"
></script>
<script
src=
"bower_components/todomvc-common/base.js"
></script>
<script
src=
"bower_components/director/build/director.js"
></script>
<script>
// Bootstrap app data
window
.
app
=
{};
</script>
<script
src=
"js/helpers.js"
></script>
<script
src=
"js/store.js"
></script>
<script
src=
"js/model.js"
></script>
<script
src=
"js/view.js"
></script>
<script
src=
"js/controller.js"
></script>
<script
src=
"js/app.js"
></script>
<script
src=
"js/app.js"
></script>
</body>
</body>
</html>
</html>
vanilla-examples/vanillajs/js/app.js
View file @
29a5abcd
/*global Store, Model, View, Controller, $$ */
(
function
()
{
(
function
()
{
'
use strict
'
;
'
use strict
'
;
var
todos
=
[],
/**
stat
=
{},
* Sets up a brand new Todo list.
ENTER_KEY
=
13
;
*
* @param {string} name The name of your new to do list.
window
.
addEventListener
(
'
load
'
,
windowLoadHandler
,
false
);
*/
function
Todo
(
name
)
{
function
Todo
(
title
,
completed
)
{
this
.
storage
=
new
app
.
Store
(
name
);
this
.
id
=
getUuid
();
this
.
model
=
new
app
.
Model
(
this
.
storage
);
this
.
title
=
title
;
this
.
view
=
new
app
.
View
();
this
.
completed
=
completed
;
this
.
controller
=
new
app
.
Controller
(
this
.
model
,
this
.
view
);
}
}
function
Stat
()
{
var
todo
=
new
Todo
(
'
todos-vanillajs
'
);
this
.
todoLeft
=
0
;
this
.
todoCompleted
=
0
;
/**
this
.
totalTodo
=
0
;
* Finds the model ID of the clicked DOM element
}
*
* @param {object} target The starting point in the DOM for it to try to find
function
windowLoadHandler
()
{
* the ID of the model.
loadTodos
();
*/
refreshData
();
function
lookupId
(
target
)
{
addEventListeners
();
var
lookup
=
target
;
}
while
(
lookup
.
nodeName
!==
'
LI
'
)
{
function
addEventListeners
()
{
lookup
=
lookup
.
parentNode
;
document
.
getElementById
(
'
new-todo
'
).
addEventListener
(
'
keypress
'
,
newTodoKeyPressHandler
,
false
);
document
.
getElementById
(
'
toggle-all
'
).
addEventListener
(
'
change
'
,
toggleAllChangeHandler
,
false
);
}
function
inputEditTodoKeyPressHandler
(
event
)
{
var
inputEditTodo
=
event
.
target
,
trimmedText
=
inputEditTodo
.
value
.
trim
(),
todoId
=
event
.
target
.
id
.
slice
(
6
);
if
(
trimmedText
)
{
if
(
event
.
keyCode
===
ENTER_KEY
)
{
editTodo
(
todoId
,
trimmedText
);
}
}
else
{
removeTodoById
(
todoId
);
refreshData
();
}
}
function
inputEditTodoBlurHandler
(
event
)
{
var
inputEditTodo
=
event
.
target
,
todoId
=
event
.
target
.
id
.
slice
(
6
);
editTodo
(
todoId
,
inputEditTodo
.
value
);
}
function
newTodoKeyPressHandler
(
event
)
{
if
(
event
.
keyCode
===
ENTER_KEY
)
{
addTodo
(
document
.
getElementById
(
'
new-todo
'
).
value
);
}
}
function
toggleAllChangeHandler
(
event
)
{
for
(
var
i
in
todos
)
{
todos
[
i
].
completed
=
event
.
target
.
checked
;
}
refreshData
();
}
function
spanDeleteClickHandler
(
event
)
{
removeTodoById
(
event
.
target
.
getAttribute
(
'
data-todo-id
'
));
refreshData
();
}
function
hrefClearClickHandler
()
{
removeTodosCompleted
();
refreshData
();
}
function
todoContentHandler
(
event
)
{
var
todoId
=
event
.
target
.
getAttribute
(
'
data-todo-id
'
),
div
=
document
.
getElementById
(
'
li_
'
+
todoId
),
inputEditTodo
=
document
.
getElementById
(
'
input_
'
+
todoId
);
div
.
className
=
'
editing
'
;
inputEditTodo
.
focus
();
}
function
checkboxChangeHandler
(
event
)
{
var
checkbox
=
event
.
target
,
todo
=
getTodoById
(
checkbox
.
getAttribute
(
'
data-todo-id
'
));
todo
.
completed
=
checkbox
.
checked
;
refreshData
();
}
function
loadTodos
()
{
if
(
!
localStorage
.
getItem
(
'
todos-vanillajs
'
))
{
localStorage
.
setItem
(
'
todos-vanillajs
'
,
JSON
.
stringify
([]));
}
todos
=
JSON
.
parse
(
localStorage
.
getItem
(
'
todos-vanillajs
'
));
}
function
addTodo
(
text
)
{
var
trimmedText
=
text
.
trim
();
if
(
trimmedText
)
{
var
todo
=
new
Todo
(
trimmedText
,
false
);
todos
.
push
(
todo
);
refreshData
();
}
}
function
editTodo
(
todoId
,
text
)
{
var
i
,
l
;
for
(
i
=
0
,
l
=
todos
.
length
;
i
<
l
;
i
++
)
{
if
(
todos
[
i
].
id
===
todoId
)
{
todos
[
i
].
title
=
text
;
}
}
refreshData
();
}
function
removeTodoById
(
id
)
{
var
i
=
todos
.
length
;
while
(
i
--
)
{
if
(
todos
[
i
].
id
===
id
)
{
todos
.
splice
(
i
,
1
);
}
}
}
}
function
removeTodosCompleted
()
{
var
i
=
todos
.
length
;
while
(
i
--
)
{
console
.
log
(
i
);
if
(
todos
[
i
].
completed
)
{
todos
.
splice
(
i
,
1
);
}
}
}
function
getTodoById
(
id
)
{
var
i
,
l
;
for
(
i
=
0
,
l
=
todos
.
length
;
i
<
l
;
i
++
)
{
return
lookup
.
dataset
.
id
;
if
(
todos
[
i
].
id
===
id
)
{
return
todos
[
i
];
}
}
}
function
refreshData
()
{
saveTodos
();
computeStats
();
redrawTodosUI
();
redrawStatsUI
();
changeToggleAllCheckboxState
();
}
}
function
saveTodos
()
{
// When the enter key is pressed fire the addItem method.
localStorage
.
setItem
(
'
todos-vanillajs
'
,
JSON
.
stringify
(
todos
));
$$
(
'
#new-todo
'
).
addEventListener
(
'
keypress
'
,
function
(
e
)
{
}
todo
.
controller
.
addItem
(
e
);
});
function
computeStats
()
{
var
i
,
l
;
stat
=
new
Stat
();
// A delegation event. Will check what item was clicked whenever you click on any
stat
.
totalTodo
=
todos
.
length
;
// part of a list item.
$$
(
'
#todo-list
'
).
addEventListener
(
'
click
'
,
function
(
e
)
{
var
target
=
e
.
target
;
for
(
i
=
0
,
l
=
todos
.
length
;
i
<
l
;
i
++
)
{
// If you click a destroy button
if
(
todos
[
i
].
completed
)
{
if
(
target
.
className
.
indexOf
(
'
destroy
'
)
>
-
1
)
{
stat
.
todoCompleted
++
;
todo
.
controller
.
removeItem
(
lookupId
(
target
));
}
}
}
stat
.
todoLeft
=
stat
.
totalTodo
-
stat
.
todoCompleted
;
// If you click the checkmark
}
if
(
target
.
className
.
indexOf
(
'
toggle
'
)
>
-
1
)
{
todo
.
controller
.
toggleComplete
(
lookupId
(
target
),
target
);
function
redrawTodosUI
()
{
var
todo
,
checkbox
,
label
,
deleteLink
,
divDisplay
,
inputEditTodo
,
li
,
i
,
l
,
ul
=
document
.
getElementById
(
'
todo-list
'
);
document
.
getElementById
(
'
main
'
).
style
.
display
=
todos
.
length
?
'
block
'
:
'
none
'
;
ul
.
innerHTML
=
''
;
document
.
getElementById
(
'
new-todo
'
).
value
=
''
;
for
(
i
=
0
,
l
=
todos
.
length
;
i
<
l
;
i
++
)
{
todo
=
todos
[
i
];
// create checkbox
checkbox
=
document
.
createElement
(
'
input
'
);
checkbox
.
className
=
'
toggle
'
;
checkbox
.
setAttribute
(
'
data-todo-id
'
,
todo
.
id
);
checkbox
.
type
=
'
checkbox
'
;
checkbox
.
addEventListener
(
'
change
'
,
checkboxChangeHandler
);
// create div text
label
=
document
.
createElement
(
'
label
'
);
label
.
setAttribute
(
'
data-todo-id
'
,
todo
.
id
);
label
.
appendChild
(
document
.
createTextNode
(
todo
.
title
));
label
.
addEventListener
(
'
dblclick
'
,
todoContentHandler
);
// create delete button
deleteLink
=
document
.
createElement
(
'
button
'
);
deleteLink
.
className
=
'
destroy
'
;
deleteLink
.
setAttribute
(
'
data-todo-id
'
,
todo
.
id
);
deleteLink
.
addEventListener
(
'
click
'
,
spanDeleteClickHandler
);
// create divDisplay
divDisplay
=
document
.
createElement
(
'
div
'
);
divDisplay
.
className
=
'
view
'
;
divDisplay
.
setAttribute
(
'
data-todo-id
'
,
todo
.
id
);
divDisplay
.
appendChild
(
checkbox
);
divDisplay
.
appendChild
(
label
);
divDisplay
.
appendChild
(
deleteLink
);
// create todo input
inputEditTodo
=
document
.
createElement
(
'
input
'
);
inputEditTodo
.
id
=
'
input_
'
+
todo
.
id
;
inputEditTodo
.
className
=
'
edit
'
;
inputEditTodo
.
value
=
todo
.
title
;
inputEditTodo
.
addEventListener
(
'
keypress
'
,
inputEditTodoKeyPressHandler
);
inputEditTodo
.
addEventListener
(
'
blur
'
,
inputEditTodoBlurHandler
);
// create li
li
=
document
.
createElement
(
'
li
'
);
li
.
id
=
'
li_
'
+
todo
.
id
;
li
.
appendChild
(
divDisplay
);
li
.
appendChild
(
inputEditTodo
);
if
(
todo
.
completed
)
{
li
.
className
+=
'
completed
'
;
checkbox
.
checked
=
true
;
}
ul
.
appendChild
(
li
);
}
}
}
function
changeToggleAllCheckboxState
()
{
});
var
toggleAll
=
document
.
getElementById
(
'
toggle-all
'
);
toggleAll
.
checked
=
stat
.
todoCompleted
===
todos
.
length
;
$$
(
'
#todo-list
'
).
addEventListener
(
'
dblclick
'
,
function
(
e
)
{
}
var
target
=
e
.
target
;
function
redrawStatsUI
()
{
removeChildren
(
document
.
getElementsByTagName
(
'
footer
'
)[
0
]);
document
.
getElementById
(
'
footer
'
).
style
.
display
=
todos
.
length
?
'
block
'
:
'
none
'
;
if
(
stat
.
todoCompleted
)
{
drawTodoClear
();
}
if
(
stat
.
totalTodo
)
{
if
(
target
.
nodeName
===
'
LABEL
'
)
{
drawTodoCount
(
);
todo
.
controller
.
editItem
(
lookupId
(
target
),
target
);
}
}
}
});
function
drawTodoCount
()
{
var
number
=
document
.
createElement
(
'
strong
'
),
remaining
=
document
.
createElement
(
'
span
'
),
text
=
'
'
+
(
stat
.
todoLeft
===
1
?
'
item
'
:
'
items
'
)
+
'
left
'
;
// create remaining count
$$
(
'
#toggle-all
'
).
addEventListener
(
'
click
'
,
function
(
e
)
{
number
.
innerHTML
=
stat
.
todoLeft
;
todo
.
controller
.
toggleAll
(
e
);
});
remaining
.
id
=
'
todo-count
'
;
$$
(
'
#clear-completed
'
).
addEventListener
(
'
click
'
,
function
()
{
remaining
.
appendChild
(
number
);
todo
.
controller
.
removeCompletedItems
();
remaining
.
appendChild
(
document
.
createTextNode
(
text
));
});
document
.
getElementsByTagName
(
'
footer
'
)[
0
].
appendChild
(
remaining
);
}
function
drawTodoClear
()
{
var
buttonClear
=
document
.
createElement
(
'
button
'
);
buttonClear
.
id
=
'
clear-completed
'
;
buttonClear
.
addEventListener
(
'
click
'
,
hrefClearClickHandler
);
buttonClear
.
innerHTML
=
'
Clear completed (
'
+
stat
.
todoCompleted
+
'
)
'
;
document
.
getElementsByTagName
(
'
footer
'
)[
0
].
appendChild
(
buttonClear
);
}
function
removeChildren
(
node
)
{
node
.
innerHTML
=
''
;
}
function
getUuid
()
{
var
i
,
random
,
uuid
=
''
;
for
(
i
=
0
;
i
<
32
;
i
++
)
{
random
=
Math
.
random
()
*
16
|
0
;
if
(
i
===
8
||
i
===
12
||
i
===
16
||
i
===
20
)
{
uuid
+=
'
-
'
;
}
uuid
+=
(
i
===
12
?
4
:
(
i
===
16
?
(
random
&
3
|
8
)
:
random
)).
toString
(
16
);
}
return
uuid
;
}
})();
})();
vanilla-examples/vanillajs/js/controller.js
0 → 100644
View file @
29a5abcd
/*global Router, $$, $ */
(
function
(
window
)
{
'
use strict
'
;
/**
* Takes a model and view and acts as the controller between them
*
* @constructor
* @param {object} model The model constructor
* @param {object} view The view constructor
*/
function
Controller
(
model
,
view
)
{
this
.
model
=
model
;
this
.
view
=
view
;
this
.
ENTER_KEY
=
13
;
this
.
ESCAPE_KEY
=
27
;
this
.
$main
=
$$
(
'
#main
'
);
this
.
$toggleAll
=
$$
(
'
#toggle-all
'
);
this
.
$todoList
=
$$
(
'
#todo-list
'
);
this
.
$todoItemCounter
=
$$
(
'
#todo-count
'
);
this
.
$clearCompleted
=
$$
(
'
#clear-completed
'
);
this
.
$footer
=
$$
(
'
#footer
'
);
this
.
router
=
new
Router
();
this
.
router
.
init
();
window
.
addEventListener
(
'
load
'
,
function
()
{
this
.
_updateFilterState
();
}.
bind
(
this
));
// Couldn't figure out how to get flatiron to run some code on all pages. I
// tried '*', but then it overwrites ALL handlers for all the other pages
// and only runs this.
window
.
addEventListener
(
'
hashchange
'
,
function
()
{
this
.
_updateFilterState
();
}.
bind
(
this
));
// Make sure on page load we start with a hash to trigger the flatiron and
// onhashchange routes
if
(
window
.
location
.
href
.
indexOf
(
'
#
'
)
===
-
1
)
{
window
.
location
.
hash
=
'
#/
'
;
}
}
/**
* An event to fire on load. Will get all items and display them in the
* todo-list
*/
Controller
.
prototype
.
showAll
=
function
()
{
this
.
model
.
read
(
function
(
data
)
{
this
.
$todoList
.
innerHTML
=
this
.
view
.
show
(
data
);
}.
bind
(
this
));
};
/**
* Renders all active tasks
*/
Controller
.
prototype
.
showActive
=
function
()
{
this
.
model
.
read
({
completed
:
0
},
function
(
data
)
{
this
.
$todoList
.
innerHTML
=
this
.
view
.
show
(
data
);
}.
bind
(
this
));
};
/**
* Renders all completed tasks
*/
Controller
.
prototype
.
showCompleted
=
function
()
{
this
.
model
.
read
({
completed
:
1
},
function
(
data
)
{
this
.
$todoList
.
innerHTML
=
this
.
view
.
show
(
data
);
}.
bind
(
this
));
};
/**
* An event to fire whenever you want to add an item. Simply pass in the event
* object and it'll handle the DOM insertion and saving of the new item.
*
* @param {object} e The event object
*/
Controller
.
prototype
.
addItem
=
function
(
e
)
{
var
input
=
$$
(
'
#new-todo
'
);
var
title
=
title
||
''
;
if
(
e
.
keyCode
===
this
.
ENTER_KEY
)
{
if
(
e
.
target
.
value
.
trim
()
===
''
)
{
return
;
}
this
.
model
.
create
(
e
.
target
.
value
,
function
(
data
)
{
input
.
value
=
''
;
this
.
_filter
(
true
);
}.
bind
(
this
));
}
};
/**
* Hides the label text and creates an input to edit the title of the item.
* When you hit enter or blur out of the input it saves it and updates the UI
* with the new name.
*
* @param {number} id The id of the item to edit
* @param {object} label The label you want to edit the text of
*/
Controller
.
prototype
.
editItem
=
function
(
id
,
label
)
{
var
li
=
label
;
// This finds the <label>'s parent <li>
while
(
li
.
nodeName
!==
'
LI
'
)
{
li
=
li
.
parentNode
;
}
var
onSaveHandler
=
function
()
{
var
value
=
input
.
value
.
trim
();
var
discarding
=
input
.
dataset
.
discard
;
if
(
value
.
length
&&
!
discarding
)
{
this
.
model
.
update
(
id
,
{
title
:
input
.
value
});
// Instead of re-rendering the whole view just update
// this piece of it
label
.
innerHTML
=
value
;
}
else
if
(
value
.
length
===
0
)
{
// No value was entered in the input. We'll remove the todo item.
this
.
removeItem
(
id
);
}
// Remove the input since we no longer need it
// Less DOM means faster rendering
li
.
removeChild
(
input
);
// Remove the editing class
li
.
className
=
li
.
className
.
replace
(
'
editing
'
,
''
);
}.
bind
(
this
);
// Append the editing class
li
.
className
=
li
.
className
+
'
editing
'
;
var
input
=
document
.
createElement
(
'
input
'
);
input
.
className
=
'
edit
'
;
// Get the innerHTML of the label instead of requesting the data from the
// ORM. If this were a real DB this would save a lot of time and would avoid
// a spinner gif.
input
.
value
=
label
.
innerHTML
;
li
.
appendChild
(
input
);
input
.
addEventListener
(
'
blur
'
,
onSaveHandler
);
input
.
addEventListener
(
'
keypress
'
,
function
(
e
)
{
if
(
e
.
keyCode
===
this
.
ENTER_KEY
)
{
// Remove the cursor from the input when you hit enter just like if it
// were a real form
input
.
blur
();
}
if
(
e
.
keyCode
===
this
.
ESCAPE_KEY
)
{
// Discard the changes
input
.
dataset
.
discard
=
true
;
input
.
blur
();
}
}.
bind
(
this
));
input
.
focus
();
};
/**
* By giving it an ID it'll find the DOM element matching that ID,
* remove it from the DOM and also remove it from storage.
*
* @param {number} id The ID of the item to remove from the DOM and
* storage
*/
Controller
.
prototype
.
removeItem
=
function
(
id
)
{
this
.
model
.
remove
(
id
,
function
()
{
this
.
$todoList
.
removeChild
(
$$
(
'
[data-id="
'
+
id
+
'
"]
'
));
}.
bind
(
this
));
this
.
_filter
();
};
/**
* Will remove all completed items from the DOM and storage.
*/
Controller
.
prototype
.
removeCompletedItems
=
function
()
{
this
.
model
.
read
({
completed
:
1
},
function
(
data
)
{
data
.
forEach
(
function
(
item
)
{
this
.
removeItem
(
item
.
id
);
}.
bind
(
this
));
}.
bind
(
this
));
this
.
_filter
();
};
/**
* Give it an ID of a model and a checkbox and it will update the item
* in storage based on the checkbox's state.
*
* @param {number} id The ID of the element to complete or uncomplete
* @param {object} checkbox The checkbox to check the state of complete
* or not
* @param {boolean|undefined} silent Prevent re-filtering the todo items
*/
Controller
.
prototype
.
toggleComplete
=
function
(
id
,
checkbox
,
silent
)
{
var
completed
=
checkbox
.
checked
?
1
:
0
;
this
.
model
.
update
(
id
,
{
completed
:
completed
},
function
()
{
var
listItem
=
$$
(
'
[data-id="
'
+
id
+
'
"]
'
);
if
(
!
listItem
)
{
return
;
}
listItem
.
className
=
completed
?
'
completed
'
:
''
;
// In case it was toggled from an event and not by clicking the checkbox
listItem
.
querySelector
(
'
input
'
).
checked
=
completed
;
});
if
(
!
silent
)
{
this
.
_filter
();
}
};
/**
* Will toggle ALL checkboxe's on/off state and completeness of models.
* Just pass in the event object.
*
* @param {object} e The event object
*/
Controller
.
prototype
.
toggleAll
=
function
(
e
)
{
var
completed
=
e
.
target
.
checked
?
1
:
0
;
var
query
=
0
;
if
(
completed
===
0
)
{
query
=
1
;
}
this
.
model
.
read
({
completed
:
query
},
function
(
data
)
{
data
.
forEach
(
function
(
item
)
{
this
.
toggleComplete
(
item
.
id
,
e
.
target
,
true
);
}.
bind
(
this
));
}.
bind
(
this
));
this
.
_filter
();
};
/**
* Updates the pieces of the page which change depending on the remaining
* number of todos.
*/
Controller
.
prototype
.
_updateCount
=
function
()
{
var
todos
=
this
.
model
.
getCount
();
this
.
$todoItemCounter
.
innerHTML
=
this
.
view
.
itemCounter
(
todos
.
active
);
this
.
$clearCompleted
.
innerHTML
=
this
.
view
.
clearCompletedButton
(
todos
.
completed
);
this
.
$clearCompleted
.
style
.
display
=
todos
.
completed
>
0
?
'
block
'
:
'
none
'
;
this
.
$toggleAll
.
checked
=
todos
.
completed
===
todos
.
total
;
this
.
_toggleFrame
(
todos
);
};
/**
* The main body and footer elements should not be visible when there are no
* todos left.
*
* @param {object} todos Contains a count of all todos, and their statuses.
*/
Controller
.
prototype
.
_toggleFrame
=
function
(
todos
)
{
var
frameDisplay
=
this
.
$main
.
style
.
display
;
var
frameVisible
=
frameDisplay
===
'
block
'
||
frameDisplay
===
''
;
if
(
todos
.
total
===
0
&&
frameVisible
)
{
this
.
$main
.
style
.
display
=
'
none
'
;
this
.
$footer
.
style
.
display
=
'
none
'
;
}
if
(
todos
.
total
>
0
&&
!
frameVisible
)
{
this
.
$main
.
style
.
display
=
'
block
'
;
this
.
$footer
.
style
.
display
=
'
block
'
;
}
};
/**
* Re-filters the todo items, based on the active route.
* @param {boolean|undefined} force forces a re-painting of todo items.
*/
Controller
.
prototype
.
_filter
=
function
(
force
)
{
var
activeRoute
=
this
.
_activeRoute
.
charAt
(
0
).
toUpperCase
()
+
this
.
_activeRoute
.
substr
(
1
);
// Update the elements on the page, which change with each completed todo
this
.
_updateCount
();
// If the last active route isn't "All", or we're switching routes, we
// re-create the todo item elements, calling:
// this.show[All|Active|Completed]();
if
(
force
||
this
.
_lastActiveRoute
!==
'
All
'
||
this
.
_lastActiveRoute
!==
activeRoute
)
{
this
[
'
show
'
+
activeRoute
]();
}
this
.
_lastActiveRoute
=
activeRoute
;
};
/**
* Simply updates the filter nav's selected states
*/
Controller
.
prototype
.
_updateFilterState
=
function
()
{
var
currentPage
=
this
.
_getCurrentPage
()
||
''
;
// Store a reference to the active route, allowing us to re-filter todo
// items as they are marked complete or incomplete.
this
.
_activeRoute
=
currentPage
;
if
(
currentPage
===
''
)
{
this
.
_activeRoute
=
'
All
'
;
}
this
.
_filter
();
// Remove all other selected states. We loop through all of them in case the
// UI gets in a funky state with two selected.
$
(
'
#filters .selected
'
).
each
(
function
(
item
)
{
item
.
className
=
''
;
});
$$
(
'
#filters [href="#/
'
+
currentPage
+
'
"]
'
).
className
=
'
selected
'
;
};
/**
* A getter for getting the current page
*/
Controller
.
prototype
.
_getCurrentPage
=
function
()
{
return
document
.
location
.
hash
.
split
(
'
/
'
)[
1
];
};
// Export to window
window
.
app
.
Controller
=
Controller
;
})(
window
);
vanilla-examples/vanillajs/js/helpers.js
0 → 100644
View file @
29a5abcd
(
function
(
window
)
{
'
use strict
'
;
// Cache the querySelector/All for easier and faster reuse
window
.
$
=
document
.
querySelectorAll
.
bind
(
document
);
window
.
$$
=
document
.
querySelector
.
bind
(
document
);
// Allow for looping on Objects by chaining:
// $('.foo').each(function () {})
Object
.
prototype
.
each
=
function
(
callback
)
{
for
(
var
x
in
this
)
{
if
(
this
.
hasOwnProperty
(
x
))
{
callback
.
call
(
this
,
this
[
x
]);
}
}
};
})(
window
);
vanilla-examples/vanillajs/js/model.js
0 → 100644
View file @
29a5abcd
(
function
(
window
)
{
'
use strict
'
;
/**
* Creates a new Model instance and hooks up the storage.
*
* @constructor
* @param {object} storage A reference to the client side storage class
*/
function
Model
(
storage
)
{
this
.
storage
=
storage
;
}
/**
* Creates a new todo model
*
* @param {string} [title] The title of the task
* @param {function} [callback] The callback to fire after the model is created
*/
Model
.
prototype
.
create
=
function
(
title
,
callback
)
{
title
=
title
||
''
;
callback
=
callback
||
function
()
{};
var
newItem
=
{
title
:
title
.
trim
(),
completed
:
0
};
this
.
storage
.
save
(
newItem
,
callback
);
};
/**
* Finds and returns a model in storage. If no query is given it'll simply
* return everything. If you pass in a string or number it'll look that up as
* the ID of the model to find. Lastly, you can pass it an object to match
* against.
*
* @param {string|number|object} [query] A query to match models against
* @param {function} [callback] The callback to fire after the model is found
*
* @example
* model.read(1, func); // Will find the model with an ID of 1
* model.read('1'); // Same as above
* //Below will find a model with foo equalling bar and hello equalling world.
* model.read({ foo: 'bar', hello: 'world' });
*/
Model
.
prototype
.
read
=
function
(
query
,
callback
)
{
var
queryType
=
typeof
query
;
callback
=
callback
||
function
()
{};
if
(
queryType
===
'
function
'
)
{
callback
=
query
;
return
this
.
storage
.
findAll
(
callback
);
}
else
if
(
queryType
===
'
string
'
||
queryType
===
'
number
'
)
{
this
.
storage
.
find
({
id
:
query
},
callback
);
}
else
{
this
.
storage
.
find
(
query
,
callback
);
}
};
/**
* Updates a model by giving it an ID, data to update, and a callback to fire when
* the update is complete.
*
* @param {number} id The id of the model to update
* @param {object} data The properties to update and their new value
* @param {function} callback The callback to fire when the update is complete.
*/
Model
.
prototype
.
update
=
function
(
id
,
data
,
callback
)
{
this
.
storage
.
save
(
id
,
data
,
callback
);
};
/**
* Removes a model from storage
*
* @param {number} id The ID of the model to remove
* @param {function} callback The callback to fire when the removal is complete.
*/
Model
.
prototype
.
remove
=
function
(
id
,
callback
)
{
this
.
storage
.
remove
(
id
,
callback
);
};
/**
* WARNING: Will remove ALL data from storage.
*
* @param {function} callback The callback to fire when the storage is wiped.
*/
Model
.
prototype
.
removeAll
=
function
(
callback
)
{
this
.
storage
.
drop
(
callback
);
};
/**
* Returns a count of all todos
*/
Model
.
prototype
.
getCount
=
function
()
{
var
todos
=
{
active
:
0
,
completed
:
0
,
total
:
0
};
this
.
storage
.
findAll
(
function
(
data
)
{
data
.
each
(
function
(
todo
)
{
if
(
todo
.
completed
===
1
)
{
todos
.
completed
++
;
}
else
{
todos
.
active
++
;
}
todos
.
total
++
;
});
});
return
todos
;
};
// Export to window
window
.
app
.
Model
=
Model
;
})(
window
);
vanilla-examples/vanillajs/js/store.js
0 → 100644
View file @
29a5abcd
/*jshint eqeqeq:false */
(
function
(
window
)
{
'
use strict
'
;
/**
* Creates a new client side storage object and will create an empty
* collection if no collection already exists.
*
* @param {string} name The name of our DB we want to use
* @param {function} callback Our fake DB uses callbacks because in
* real life you probably would be making AJAX calls
*/
function
Store
(
name
,
callback
)
{
var
data
;
var
dbName
;
callback
=
callback
||
function
()
{};
dbName
=
this
.
_dbName
=
name
;
if
(
!
localStorage
[
dbName
])
{
data
=
{
todos
:
[]
};
localStorage
[
dbName
]
=
JSON
.
stringify
(
data
);
}
callback
.
call
(
this
,
JSON
.
parse
(
localStorage
[
dbName
]));
}
/**
* Finds items based on a query given as a JS object
*
* @param {object} query The query to match against (i.e. {foo: 'bar'})
* @param {function} callback The callback to fire when the query has
* completed running
*
* @example
* db.find({foo: 'bar', hello: 'world'}, function (data) {
* // data will return any items that have foo: bar and
* // hello: world in their properties
* });
*/
Store
.
prototype
.
find
=
function
(
query
,
callback
)
{
if
(
!
callback
)
{
return
;
}
var
todos
=
JSON
.
parse
(
localStorage
[
this
.
_dbName
]).
todos
;
callback
.
call
(
this
,
todos
.
filter
(
function
(
todo
)
{
for
(
var
q
in
query
)
{
return
query
[
q
]
===
todo
[
q
];
}
}));
};
/**
* Will retrieve all data from the collection
*
* @param {function} callback The callback to fire upon retrieving data
*/
Store
.
prototype
.
findAll
=
function
(
callback
)
{
callback
=
callback
||
function
()
{};
callback
.
call
(
this
,
JSON
.
parse
(
localStorage
[
this
.
_dbName
]).
todos
);
};
/**
* Will save the given data to the DB. If no item exists it will create a new
* item, otherwise it'll simply update an existing item's properties
*
* @param {number} id An optional param to enter an ID of an item to update
* @param {object} data The data to save back into the DB
* @param {function} callback The callback to fire after saving
*/
Store
.
prototype
.
save
=
function
(
id
,
updateData
,
callback
)
{
var
data
=
JSON
.
parse
(
localStorage
[
this
.
_dbName
]);
var
todos
=
data
.
todos
;
callback
=
callback
||
function
()
{};
// If an ID was actually given, find the item and update each property
if
(
typeof
id
!==
'
object
'
)
{
for
(
var
i
=
0
;
i
<
todos
.
length
;
i
++
)
{
if
(
todos
[
i
].
id
==
id
)
{
for
(
var
x
in
updateData
)
{
todos
[
i
][
x
]
=
updateData
[
x
];
}
}
}
localStorage
[
this
.
_dbName
]
=
JSON
.
stringify
(
data
);
callback
.
call
(
this
,
JSON
.
parse
(
localStorage
[
this
.
_dbName
]).
todos
);
}
else
{
callback
=
updateData
;
updateData
=
id
;
// Generate an ID
updateData
.
id
=
new
Date
().
getTime
();
todos
.
push
(
updateData
);
localStorage
[
this
.
_dbName
]
=
JSON
.
stringify
(
data
);
callback
.
call
(
this
,
[
updateData
]);
}
};
/**
* Will remove an item from the Store based on its ID
*
* @param {number} id The ID of the item you want to remove
* @param {function} callback The callback to fire after saving
*/
Store
.
prototype
.
remove
=
function
(
id
,
callback
)
{
var
data
=
JSON
.
parse
(
localStorage
[
this
.
_dbName
]);
var
todos
=
data
.
todos
;
for
(
var
i
=
0
;
i
<
todos
.
length
;
i
++
)
{
if
(
todos
[
i
].
id
==
id
)
{
todos
.
splice
(
i
,
1
);
break
;
}
}
localStorage
[
this
.
_dbName
]
=
JSON
.
stringify
(
data
);
callback
.
call
(
this
,
JSON
.
parse
(
localStorage
[
this
.
_dbName
]).
todos
);
};
/**
* Will drop all storage and start fresh
*
* @param {function} callback The callback to fire after dropping the data
*/
Store
.
prototype
.
drop
=
function
(
callback
)
{
localStorage
[
this
.
_dbName
]
=
JSON
.
stringify
({
todos
:
[]});
callback
.
call
(
this
,
JSON
.
parse
(
localStorage
[
this
.
_dbName
]).
todos
);
};
// Export to window
window
.
app
.
Store
=
Store
;
})(
window
);
vanilla-examples/vanillajs/js/view.js
0 → 100644
View file @
29a5abcd
/*jshint laxbreak:true */
(
function
(
window
)
{
'
use strict
'
;
/**
* Sets up defaults for all the View methods such as a default template
*
* @constructor
*/
function
View
()
{
this
.
defaultTemplate
=
'
<li data-id="{{id}}" class="{{completed}}">
'
+
'
<div class="view">
'
+
'
<input class="toggle" type="checkbox" {{checked}}>
'
+
'
<label>{{title}}</label>
'
+
'
<button class="destroy"></button>
'
+
'
</div>
'
+
'
</li>
'
;
}
/**
* Creates an <li> HTML string and returns it for placement in your app.
*
* NOTE: In real life you should be using a templating engine such as Mustache
* or Handlebars, however, this is a vanilla JS example.
*
* @param {object} data The object containing keys you want to find in the
* template to replace.
* @returns {string} HTML String of an <li> element
*
* @example
* view.show({
* id: 1,
* title: "Hello World",
* completed: 0,
* });
*/
View
.
prototype
.
show
=
function
(
data
)
{
var
i
,
l
;
var
view
=
''
;
for
(
i
=
0
,
l
=
data
.
length
;
i
<
l
;
i
++
)
{
var
template
=
this
.
defaultTemplate
;
var
completed
=
''
;
var
checked
=
''
;
if
(
data
[
i
].
completed
===
1
)
{
completed
=
'
completed
'
;
checked
=
'
checked
'
;
}
template
=
template
.
replace
(
'
{{id}}
'
,
data
[
i
].
id
);
template
=
template
.
replace
(
'
{{title}}
'
,
data
[
i
].
title
);
template
=
template
.
replace
(
'
{{completed}}
'
,
completed
);
template
=
template
.
replace
(
'
{{checked}}
'
,
checked
);
view
=
view
+
template
;
}
return
view
;
};
/**
* Displays a counter of how many to dos are left to complete
*
* @param {number} activeTodos The number of active todos.
* @returns {string} String containing the count
*/
View
.
prototype
.
itemCounter
=
function
(
activeTodos
)
{
var
plural
=
activeTodos
===
1
?
''
:
'
s
'
;
return
'
<strong>
'
+
activeTodos
+
'
</strong> item
'
+
plural
+
'
left
'
;
};
/**
* Updates the text within the "Clear completed" button
*
* @param {[type]} completedTodos The number of completed todos.
* @returns {string} String containing the count
*/
View
.
prototype
.
clearCompletedButton
=
function
(
completedTodos
)
{
if
(
completedTodos
>
0
)
{
return
'
Clear completed (
'
+
completedTodos
+
'
)
'
;
}
else
{
return
''
;
}
};
// Export to window
window
.
app
.
View
=
View
;
})(
window
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment