@ -1,4 +1,54 @@ |
|||
todomvc-on-hoodie |
|||
================= |
|||
# TodoMVC On Hoodie |
|||
|
|||
Implementations of the hoodie store using Backbone.js, Ember.js, AngularJS, and many more. Examples forked from tastejs/todomvc. |
|||
|
|||
## What for? |
|||
|
|||
"We want to enable you to build complete web apps in days, without having to worry about backends, databases or servers, all with an open-source library that's as simple to use as jQuery." -- http://hood.ie/ |
|||
|
|||
Hoodie is very good for building rapid prototyped web applications. It currently feels a bit like the missing piece for the numerous MV* applications out there. They all come with wonderful and smart solutions toward standard View and Controller problems. Sadly most of the time they totally lack a convenient way for data-storage. You almos always need to write you own backends. Or sometimes you can't even write a backend application, because you are a frontend developer only. Whoops! There went your personal rapid prototype. |
|||
|
|||
## Hoodie can fix that |
|||
|
|||
Due to a popular demand, I created TodoMVC On Hoodie to demonstrate sample implementations of hoodie with several MV* frameworks. Since tastejs/todomvc is very famous and accepted for offering framework specific example usage, I decided to take a step beyond and replace the local storage solution with the hoodie way. |
|||
|
|||
See this as an starting point to get an idea, how hoodie can be merged into you favourite technology. |
|||
|
|||
## State of Examples |
|||
|
|||
This is a very young work in progress. The repository already contains the "architecture-examples" folder of todomvc. However they don't build all up on hoodie yet. So here is a progresslist: |
|||
|
|||
### Examples Running on Hoodie |
|||
|
|||
* angularjs |
|||
|
|||
### NOT Running on Hoodie |
|||
|
|||
* agilityjs |
|||
* angularjs-perf |
|||
* backbone |
|||
* canjs |
|||
* closure |
|||
* dojo |
|||
* emberjs |
|||
* gwt |
|||
* jquery |
|||
* knockback |
|||
* knockout |
|||
* maria |
|||
* polymer |
|||
* react |
|||
* spine |
|||
* yui |
|||
|
|||
## Licence |
|||
|
|||
Everything in this repo is MIT License unless otherwise specified: |
|||
|
|||
MIT © Dennis Wilson |
|||
|
|||
In attribution and big thank you to tastejs/todomvc: |
|||
|
|||
MIT © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk. |
|||
|
|||
Also have a look in the footer of each example. There are credits to those involved but not mentioned here. |
|||
|
@ -0,0 +1,170 @@ |
|||
# Hoodie Deployment Guide |
|||
|
|||
Note that this is a incomplete draft. It should get you going with production system, but there is no guarantee that there isn’t anything wrong with the setup form a performance or security perspective. Please check with your local sysadmin to make sure and please let us know if we can improve anything :) |
|||
|
|||
This guide is for Linux only at this point. Other UNIXes should be covered too, with slight modifications. |
|||
|
|||
## Install Dependencies: |
|||
|
|||
- Install CouchDB 1.2.0 or later, 1.4.0 or later recommended for performance. |
|||
- Install NodeJS 0.10.0 or later. |
|||
- Install nginx, any recent version will do. |
|||
- Install Monit, any recent version will do. |
|||
- Install git. |
|||
|
|||
|
|||
## Set Up |
|||
|
|||
### CouchDB: |
|||
|
|||
Hoodie is in development mode by default and there it starts its own CouchDB instance to manage. For production deployments, we recommend running CouchDB in a separate instance for various reasons like operational simplicity and security. |
|||
|
|||
We assume you set up CouchDB with your package manager or manually following the installation procedure documented in the CouchDB source tree in the INSTALL.Unix file. |
|||
|
|||
If you are already using CouchDB for other things, recommend starting a second instance of CouchDB that is completely separate from your original one. See below* for instructions. |
|||
|
|||
For now we assume, your CouchDB is available at http://127.0.0.1:5984/ |
|||
|
|||
Create a CouchDB admin user called “admin” with a strong password of your choice at http://127.0.0.1:5984/_utils/ by clicking on the “Fix this” link in the lower right corner. Remember that password. |
|||
|
|||
Next we want to change CouchDB’s default configuration on a few points. The easiest thing is to go to http://127.0.0.1:5984/_utils/config.html and edit the following fields (double click the value to get into the editing mode): |
|||
|
|||
couchdb -> delayed_commits: false |
|||
couchdb -> max_dbs_open: 1024 |
|||
couch_httpd_auth -> timeout: 1209600 ; that’s two weeks |
|||
|
|||
### System |
|||
|
|||
Add this to /etc/security/limits.conf: |
|||
|
|||
``` |
|||
hoodie soft nofile 768 |
|||
hoodie hard nofile 1024 |
|||
``` |
|||
|
|||
### Hoodie |
|||
|
|||
Create a new system user: |
|||
|
|||
``` |
|||
useradd --system \ |
|||
--home /home/hoodie \ |
|||
--shell /bin/bash \ |
|||
--no-user-group \ |
|||
-c "Hoodie Administrator" hoodie |
|||
``` |
|||
|
|||
This will create a new user and its home directory `/home/hoodie`. |
|||
|
|||
`cd` in to that directory. |
|||
|
|||
As user Hoodie, install your application, either with Hoodie’s application template function: |
|||
|
|||
``` |
|||
[sudo -u hoodie] hoodie new appname githubname/reponame # think https://github.com/githubname/reponame |
|||
``` |
|||
|
|||
Or via a git checkout and manual setup |
|||
|
|||
``` |
|||
[sudo -u hoodie] git clone appname repourl |
|||
# make sure package.json has a valid `name` property. |
|||
[sudo -u hoodie] npm install |
|||
``` |
|||
|
|||
To start, copy over the script from this gist: https://gist.github.com/janl/b097f7a578ec07e4101c |
|||
|
|||
``` |
|||
wget https://gist.github.com/janl/b097f7a578ec07e4101c/raw/60d60bfe48506bbf5fb79b564c132ea8fc626f00/hoodie-daemon.sh |
|||
chmod +x hoodie-daemon.sh |
|||
``` |
|||
|
|||
It is meant to be run as `root`. It is also an early version of what should eventually be an `init.d` script should be, but we are not there yet, so bear with us :) |
|||
|
|||
To launch Hoodie now, do this, as `root`: |
|||
|
|||
``` |
|||
HOODIE_ADMIN_PASS=yourcouchdbadminpasswordfromearlier ./hoodie-daemon.sh |
|||
``` |
|||
|
|||
That’s it! You can check if the setup was correct by checking the log files: |
|||
|
|||
``` |
|||
tail -f /home/hoodie/log/* |
|||
``` |
|||
|
|||
If you see any errors, check if you missed any steps from above. Let us know if you get stuck. |
|||
|
|||
Your Hoodie app should now be running on http://127.0.0.1:6001/ |
|||
|
|||
|
|||
## WWW |
|||
|
|||
You likely want your Hoodie app to be listening on port 80 and maybe even have it available under a regular domain. The domain mapping is out of scope for this setup, but listening to port 80 can be done. On UNIX, non-admin users can’t run programs that bind to ports under 1024. But instead of running Hoodie as `root`, which we’d consider a security hazard. DO NOT RUN HOODIE AS `root`. |
|||
|
|||
Instead we use some software that is battle tested and gives us a bonus feature that we think you are going to like. |
|||
|
|||
We will be using nginx as an HTTP proxy. Other HTTP proxy software should be equally suitable, Apache 2, HAProxy are just two that came to mind. We are going with nginx because our hosting provider offers support for it, so we are going with that :) |
|||
|
|||
We add a new file `/etc/nginx/vhosts.d/appname.conf` with the contents of https://gist.github.com/janl/2a8e6ebc80a25817dca0 |
|||
|
|||
You will need to adjust the domain name and paths to log files and SSL certificates and keys. |
|||
|
|||
Once this is all set up, you can reload the nginx configuration: |
|||
|
|||
``` |
|||
[sudo] nginx reload |
|||
``` |
|||
|
|||
Now your app should be available on the public IP for that machine and from any domains that point to that domain. All requests should be automatically forwarded to HTTPS for security. DO NOT RUN HOODIE WITHOUT HTTPS. |
|||
|
|||
### The Bonus Feature |
|||
|
|||
Now all your app’s request will be served over HTTPS. nginx terminates the HTTPS and proxies to Hoodie over HTTP. We also told nginx to server `/` from your app’s `/www` directory and proxy only `/_api` to Hoodie instead of letting Hoodie serve the static files and the dynamic backend. |
|||
|
|||
This has the advantage that in case of an issue with Hoodie, your clients will still be able to request all the static assets and should largely still be able to use your application. This is a big benefit of an offline-first architecture: Your users can keep using your app, even if the backend is down. While the design is meant for users to be offline, it also allows backends to be offline :) |
|||
|
|||
|
|||
## logrotate |
|||
|
|||
We’ve set up a bunch of log files, you will want to make sure you don’t run out of space. Let’s set up log rotation in `/etc/logrotate.d/hoodie`: |
|||
|
|||
``` |
|||
/var/log/hoodie.std* { |
|||
weekly |
|||
rotate 10 |
|||
copytruncate |
|||
delaycompress |
|||
compress |
|||
notifempty |
|||
missingok |
|||
} |
|||
``` |
|||
|
|||
## Monit |
|||
|
|||
Finally, we want to make sure that Hoodie stays up in healthy even if the main node process terminates for whatever reason. We are using Monit to watch and restart Hoodie on demand. |
|||
|
|||
Create `/etc/monit.d/hoodie` with: |
|||
``` |
|||
check process hoodie with pidfile /home/hoodie/log/hoodie.pid |
|||
start program = "/home/hoodie/hoodie-daemon.sh start" |
|||
stop program = "/home/hoodie/hoodie-daemon.sh stop" |
|||
if failed URL https://yourapp.com/_api then restart |
|||
``` |
|||
|
|||
Again, adjust with your app’s domain. This will restart if the `/_api` endpoints (Hoodie’s main API) is not available. |
|||
|
|||
While we are here, we set up the same thing for nginx: |
|||
|
|||
`/etc/monit.d/nginx` |
|||
check process nginx with pidfile /var/run/nginx.pid |
|||
start program = "/etc/init.d/nginx start" |
|||
stop program = "/etc/init.d/nginx stop" |
|||
if failed host 127.0.0.1 port 80 then restart |
|||
if failed host 127.0.0.1 port 443 then restart |
|||
|
|||
|
|||
### *Set up a secondary CouchDB instance |
|||
|
|||
TBD |
@ -0,0 +1,26 @@ |
|||
{ |
|||
"name": "reads", |
|||
"version": "1.0.16", |
|||
"type": "app", |
|||
"dependencies": { |
|||
"hoodie-server": "0.9.19", |
|||
"hoodie-plugin-appconfig": "~0.1.0", |
|||
"hoodie-plugin-email": "~0.1.1", |
|||
"hoodie-plugin-users": "0.1.0" |
|||
}, |
|||
"subdomain": "hoodie-reads", |
|||
"domains": [ |
|||
"admin.hoodie-reads.jit.su", |
|||
"couch.hoodie-reads.jit.su" |
|||
], |
|||
"scripts": { |
|||
"start": "node node_modules/hoodie-server/bin/start" |
|||
}, |
|||
"hoodie": { |
|||
"plugins": [ |
|||
"hoodie-plugin-appconfig", |
|||
"hoodie-plugin-email", |
|||
"hoodie-plugin-users" |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"name": "agilityjs", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"todomvc-common": "~0.1.4", |
|||
"agility": "~0.1.3", |
|||
"jquery": "~1.9.1" |
|||
} |
|||
} |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,55 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="agilityjs"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Agility.js • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp"> |
|||
<header id="header"> |
|||
<h1>todos</h1> |
|||
<input id="new-todo" type="text" data-bind="newtitle" placeholder="What needs to be done?" autofocus> |
|||
</header> |
|||
<section id="main" data-bind="class = mainStyle"> |
|||
<input id="toggle-all" type="checkbox"> |
|||
<label for="toggle-all">Mark all as complete</label> |
|||
<ul id="todo-list"> |
|||
<li> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" data-bind="completed"> |
|||
<label data-bind="title"></label> |
|||
<button class="destroy"></button> |
|||
</div> |
|||
<input class="edit" data-bind="title"> |
|||
</li> |
|||
</ul> |
|||
</section> |
|||
<footer id="footer" data-bind="class = mainStyle"> |
|||
<span id="todo-count"><strong data-bind='todoCount'></strong> item<span data-bind='pluralizer'></span> left</span> |
|||
<ul id="filters"> |
|||
<li> |
|||
<a class="selected" href="#/">All</a> |
|||
</li> |
|||
<li> |
|||
<a href="#/active">Active</a> |
|||
</li> |
|||
<li> |
|||
<a href="#/completed">Completed</a> |
|||
</li> |
|||
</ul> |
|||
<button id="clear-completed" data-bind="class = clearBtnStyle">Clear completed (<span data-bind="completedCount"></span>)</button> |
|||
</footer> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Created by <a href="http://github.com/tshm/todomvc/">Tosh Shimayama</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script src="bower_components/jquery/jquery.js"></script> |
|||
<script src="bower_components/agility/agility.js"></script> |
|||
<script src="js/localstorage.js"></script> |
|||
<script src="js/app.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,184 @@ |
|||
(function ($, $$) { |
|||
'use strict'; |
|||
|
|||
var ENTER_KEY = 13; |
|||
|
|||
// hack of taking out html elements from DOM so that agility's view can use it
|
|||
// we need 'outerhtml' also, as agilityjs will append DOM, so removing it
|
|||
var drawHtml = function (selector) { |
|||
return $(selector).remove().wrap('<div>').parent().html(); |
|||
}; |
|||
|
|||
// simple Two layer composition:
|
|||
// individual 'todoitem' and 'app' which holds multiple todoitems
|
|||
$(function () { |
|||
// todo item
|
|||
var todoitem = $$({ |
|||
model: { |
|||
title: '', |
|||
completed: false |
|||
}, |
|||
view: { |
|||
format: drawHtml('#todo-list li'), |
|||
style: '.hidden { display: none }' |
|||
}, |
|||
controller: { |
|||
'change:completed': function () { |
|||
this.view.$().toggleClass('completed', this.model.get('completed')); |
|||
app.updateStatus(); |
|||
}, |
|||
'dblclick &': function () { |
|||
this.view.$().addClass('editing'); |
|||
this.view.$('.edit').focus(); |
|||
}, |
|||
'click .destroy': function () { |
|||
this.destroy(); |
|||
}, |
|||
'create': function () { |
|||
this.view.$().toggleClass('completed', this.model.get('completed')); |
|||
}, |
|||
'change': function () { |
|||
this.save(); |
|||
}, |
|||
'destroy': function () { |
|||
this.erase(); |
|||
}, |
|||
'blur input': function () { |
|||
this.updateTitle(); |
|||
}, |
|||
'keyup input': function () { |
|||
if (event.which === ENTER_KEY) { |
|||
this.updateTitle(); |
|||
} |
|||
} |
|||
}, |
|||
updateTitle: function () { |
|||
var title = this.model.get('title').trim(); |
|||
|
|||
this.view.$().removeClass('editing'); |
|||
|
|||
if (title) { |
|||
this.model.set({ |
|||
title: title |
|||
}); |
|||
} else { |
|||
this.destroy(); |
|||
} |
|||
} |
|||
}).persist($$.adapter.localStorage, { |
|||
collection: 'todos-agilityjs' |
|||
}); |
|||
|
|||
// the main application which holds todo items
|
|||
var app = $$({ |
|||
model: { |
|||
todoCount: '0', |
|||
pluralizer: '', |
|||
completedCount: '0', |
|||
newtitle: '', |
|||
mainStyle: '', |
|||
clearBtnStyle: '' |
|||
}, |
|||
view: { |
|||
format: drawHtml('#todoapp'), |
|||
style: '.hidden { display: none }' |
|||
}, |
|||
controller: { |
|||
'remove': function () { |
|||
this.updateStatus(); |
|||
}, |
|||
'append': function () { |
|||
this.updateStatus(); |
|||
}, |
|||
'keyup #new-todo': function (e) { |
|||
var title = $('#new-todo').val().trim(); |
|||
if (e.which === ENTER_KEY && title) { |
|||
var item = $$(todoitem, { |
|||
title: title |
|||
}).save(); |
|||
this.append(item, '#todo-list'); |
|||
e.target.value = ''; // clear input field
|
|||
} |
|||
}, |
|||
'click #toggle-all': function () { |
|||
var ischecked = this.view.$('#toggle-all').prop('checked'); |
|||
this.each(function (id, item) { |
|||
item.model.set({ |
|||
completed: ischecked |
|||
}); |
|||
}); |
|||
}, |
|||
'click #clear-completed': function () { |
|||
this.each(function (id, item) { |
|||
if (item.model.get('completed')) { |
|||
item.destroy(); |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
// utility functions
|
|||
updateStatus: function () { |
|||
// update counts
|
|||
var count = this.size(); |
|||
var completedCount = 0; |
|||
|
|||
this.each(function (id, item) { |
|||
if (item.model.get('completed')) { |
|||
completedCount++; |
|||
} |
|||
}); |
|||
|
|||
this.model.set({ |
|||
todoCount: count - completedCount + '', |
|||
pluralizer: count - completedCount === 1 ? '' : 's', |
|||
completedCount: completedCount + '', |
|||
mainStyle: count === 0 ? 'hidden' : '', |
|||
clearBtnStyle: completedCount === 0 ? 'hidden' : '' |
|||
}); |
|||
|
|||
// update toggle-all checked status
|
|||
$('#toggle-all').prop('checked', completedCount === count); |
|||
// update the view according to the current filter.
|
|||
this.applyFilter(); |
|||
}, |
|||
// filter handler
|
|||
filters: { |
|||
'#/': function () { |
|||
return true; |
|||
}, |
|||
'#/active': function (item) { |
|||
return !item.model.get('completed'); |
|||
}, |
|||
'#/completed': function (item) { |
|||
return item.model.get('completed'); |
|||
} |
|||
}, |
|||
applyFilter: function (hash) { |
|||
var isVisible = this.filters[hash || location.hash]; |
|||
if (isVisible) { |
|||
this.each(function (id, item) { |
|||
item.view.$().toggleClass('hidden', !isVisible(item)); |
|||
}); |
|||
} |
|||
} |
|||
}).persist(); |
|||
|
|||
$$.document.prepend(app); |
|||
|
|||
// load from localStorage
|
|||
app.gather(todoitem, 'append', '#todo-list').updateStatus(); |
|||
|
|||
// manual routing (not supported by agilityjs)
|
|||
$(window).on('hashchange', function () { |
|||
var hash = location.hash; |
|||
app.applyFilter(hash); |
|||
$('#filters a').each(function () { |
|||
$(this).toggleClass('selected', hash === $(this).attr('href')); |
|||
}); |
|||
}); |
|||
|
|||
if (location.hash) { |
|||
$(window).trigger('hashchange'); |
|||
} |
|||
}); |
|||
})(window.jQuery, window.agility); |
@ -0,0 +1,35 @@ |
|||
// custom agilityjs adapter for localstorage
|
|||
(function ($$, undefined) { |
|||
'use strict'; |
|||
|
|||
$$.adapter.localStorage = function (_params) { |
|||
var storageKey = (this._data.persist.baseUrl || '') + this._data.persist.collection; |
|||
var storageStr = localStorage[storageKey]; |
|||
var items = (storageStr ? JSON.parse(storageStr) : {}); |
|||
|
|||
if (_params.type === 'GET') { |
|||
if (_params.id !== undefined) { // normal get
|
|||
if (typeof items[_params.id] === 'object') { |
|||
_params.success(items[_params.id]); |
|||
} else { |
|||
_params.error(); |
|||
} |
|||
} else { // gather call
|
|||
_params.success(items); |
|||
} |
|||
} else if (_params.type === 'DELETE') { |
|||
delete items[_params.id]; |
|||
localStorage[storageKey] = JSON.stringify(items); |
|||
} else if (_params.type === 'PUT' || _params.type === 'POST') { |
|||
if (_params.id === undefined) { |
|||
_params.id = (new Date()).getTime(); |
|||
_params.data.id = _params.id; |
|||
} |
|||
items[_params.id] = _params.data; |
|||
localStorage[storageKey] = JSON.stringify(items); |
|||
} else { |
|||
_params.error(); |
|||
} |
|||
_params.complete(); |
|||
}; |
|||
})(window.agility); |
@ -0,0 +1,33 @@ |
|||
# Agility.js TodoMVC Example |
|||
|
|||
> [Agility.js](http://agilityjs.com) is an MVC library for Javascript that lets you write maintainable and reusable browser code without the verbose or infrastructural overhead found in other MVC libraries. The goal is to enable developers to write web apps at least as quickly as with jQuery, while simplifying long-term maintainability through MVC objects. |
|||
|
|||
> _[Agility.js - agilityjs.com](http://agilityjs.com)_ |
|||
|
|||
|
|||
## Learning Agility.js |
|||
|
|||
The [Agility.js website](http://agilityjs.com) is a great resource for getting started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [Official Documentation](http://agilityjs.com/docs/docs.html) |
|||
* [Try it out on JSBin](http://jsbin.com/agility/224/edit) |
|||
* [Applications built with Agility.js](http://agilityjs.com/docs/gallery.html) |
|||
|
|||
Articles and guides from the community: |
|||
|
|||
* [Step by step from jQuery to Agility.js](https://gist.github.com/pindia/3166678) |
|||
|
|||
Get help from other Agility.js users: |
|||
|
|||
* [Google Groups mailing list](http://groups.google.com/group/agilityjs) |
|||
* [agility.js on Stack Overflow](http://stackoverflow.com/questions/tagged/agility.js) |
|||
* [Agility.js on Twitter](https://twitter.com/agilityjs) |
|||
* [Agility.js on Google +](https://plus.google.com/116251025970928820842/posts) |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
|||
|
|||
|
|||
## Credit |
|||
This TodoMVC application was created by [tshm](https://github.com/tshm). |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"name": "todomvc-angular-perf", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"angular": "1.2.9", |
|||
"todomvc-common": "~0.1.9" |
|||
} |
|||
} |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,69 @@ |
|||
<!doctype html> |
|||
<html lang="en" ng-app="todomvc" data-framework="angularjs"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>AngularJS • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
<style>[ng-cloak] { display: none; }</style> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp" ng-controller="TodoCtrl"> |
|||
<header id="header"> |
|||
<h1>todos</h1> |
|||
<form id="todo-form" ng-submit="addTodo()"> |
|||
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus> |
|||
</form> |
|||
</header> |
|||
<section id="main" ng-show="todos.length" ng-cloak> |
|||
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)"> |
|||
<label for="toggle-all">Mark all as complete</label> |
|||
<ul id="todo-list"> |
|||
<li ng-repeat="todo in todos | filter:statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedTodo}"> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" ng-model="todo.completed" ng-change="todoCompleted(todo)"> |
|||
<label ng-dblclick="editTodo(todo)">{{todo.title}}</label> |
|||
<button class="destroy" ng-click="removeTodo(todo)"></button> |
|||
</div> |
|||
<form ng-submit="doneEditing(todo)"> |
|||
<input class="edit" ng-trim="false" ng-model="todo.title" ng-blur="doneEditing(todo)" todo-escape="revertEditing(todo)" todo-focus="todo == editedTodo"> |
|||
</form> |
|||
</li> |
|||
</ul> |
|||
</section> |
|||
<footer id="footer" ng-show="todos.length" ng-cloak> |
|||
<span id="todo-count"><strong>{{remainingCount}}</strong> |
|||
<ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize> |
|||
</span> |
|||
<ul id="filters"> |
|||
<li> |
|||
<a ng-class="{selected: location.path() == '/'} " href="#/">All</a> |
|||
</li> |
|||
<li> |
|||
<a ng-class="{selected: location.path() == '/active'}" href="#/active">Active</a> |
|||
</li> |
|||
<li> |
|||
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">Completed</a> |
|||
</li> |
|||
</ul> |
|||
<button id="clear-completed" ng-click="clearCompletedTodos()" ng-show="remainingCount < todos.length">Clear completed ({{todos.length - remainingCount}})</button> |
|||
</footer> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Credits: |
|||
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>, |
|||
<a href="http://ericbidelman.com">Eric Bidelman</a>, |
|||
<a href="http://jacobmumm.com">Jacob Mumm</a> and |
|||
<a href="http://igorminar.com">Igor Minar</a> |
|||
</p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script src="bower_components/angular/angular.js"></script> |
|||
<script src="js/app.js"></script> |
|||
<script src="js/controllers/todoCtrl.js"></script> |
|||
<script src="js/services/todoStorage.js"></script> |
|||
<script src="js/directives/todoFocus.js"></script> |
|||
<script src="js/directives/todoEscape.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,10 @@ |
|||
/*global angular */ |
|||
/*jshint unused:false */ |
|||
'use strict'; |
|||
|
|||
/** |
|||
* The main TodoMVC app module |
|||
* |
|||
* @type {angular.Module} |
|||
*/ |
|||
var todomvc = angular.module('todomvc', []); |
@ -0,0 +1,93 @@ |
|||
/*global todomvc, angular */ |
|||
'use strict'; |
|||
|
|||
/** |
|||
* The main controller for the app. The controller: |
|||
* - retrieves and persists the model via the todoStorage service |
|||
* - exposes the model to the template and provides event handlers |
|||
*/ |
|||
todomvc.controller('TodoCtrl', function TodoCtrl($scope, $location, $filter, todoStorage) { |
|||
var todos = $scope.todos = todoStorage.get(); |
|||
|
|||
$scope.newTodo = ''; |
|||
$scope.remainingCount = $filter('filter')(todos, {completed: false}).length; |
|||
$scope.editedTodo = null; |
|||
|
|||
if ($location.path() === '') { |
|||
$location.path('/'); |
|||
} |
|||
|
|||
$scope.location = $location; |
|||
|
|||
$scope.$watch('location.path()', function (path) { |
|||
$scope.statusFilter = { '/active': {completed: false}, '/completed': {completed: true} }[path]; |
|||
}); |
|||
|
|||
$scope.$watch('remainingCount == 0', function (val) { |
|||
$scope.allChecked = val; |
|||
}); |
|||
|
|||
$scope.addTodo = function () { |
|||
var newTodo = $scope.newTodo.trim(); |
|||
if (newTodo.length === 0) { |
|||
return; |
|||
} |
|||
|
|||
todos.push({ |
|||
title: newTodo, |
|||
completed: false |
|||
}); |
|||
todoStorage.put(todos); |
|||
|
|||
$scope.newTodo = ''; |
|||
$scope.remainingCount++; |
|||
}; |
|||
|
|||
$scope.editTodo = function (todo) { |
|||
$scope.editedTodo = todo; |
|||
// Clone the original todo to restore it on demand.
|
|||
$scope.originalTodo = angular.extend({}, todo); |
|||
}; |
|||
|
|||
$scope.doneEditing = function (todo) { |
|||
$scope.editedTodo = null; |
|||
todo.title = todo.title.trim(); |
|||
|
|||
if (!todo.title) { |
|||
$scope.removeTodo(todo); |
|||
} |
|||
|
|||
todoStorage.put(todos); |
|||
}; |
|||
|
|||
$scope.revertEditing = function (todo) { |
|||
todos[todos.indexOf(todo)] = $scope.originalTodo; |
|||
$scope.doneEditing($scope.originalTodo); |
|||
}; |
|||
|
|||
$scope.removeTodo = function (todo) { |
|||
$scope.remainingCount -= todo.completed ? 0 : 1; |
|||
todos.splice(todos.indexOf(todo), 1); |
|||
todoStorage.put(todos); |
|||
}; |
|||
|
|||
$scope.todoCompleted = function (todo) { |
|||
$scope.remainingCount += todo.completed ? -1 : 1; |
|||
todoStorage.put(todos); |
|||
}; |
|||
|
|||
$scope.clearCompletedTodos = function () { |
|||
$scope.todos = todos = todos.filter(function (val) { |
|||
return !val.completed; |
|||
}); |
|||
todoStorage.put(todos); |
|||
}; |
|||
|
|||
$scope.markAll = function (completed) { |
|||
todos.forEach(function (todo) { |
|||
todo.completed = !completed; |
|||
}); |
|||
$scope.remainingCount = completed ? todos.length : 0; |
|||
todoStorage.put(todos); |
|||
}; |
|||
}); |
@ -0,0 +1,17 @@ |
|||
/*global todomvc */ |
|||
'use strict'; |
|||
|
|||
/** |
|||
* Directive that executes an expression when the element it is applied to gets |
|||
* an `escape` keydown event. |
|||
*/ |
|||
todomvc.directive('todoEscape', function () { |
|||
var ESCAPE_KEY = 27; |
|||
return function (scope, elem, attrs) { |
|||
elem.bind('keydown', function (event) { |
|||
if (event.keyCode === ESCAPE_KEY) { |
|||
scope.$apply(attrs.todoEscape); |
|||
} |
|||
}); |
|||
}; |
|||
}); |
@ -0,0 +1,17 @@ |
|||
/*global todomvc */ |
|||
'use strict'; |
|||
|
|||
/** |
|||
* Directive that places focus on the element it is applied to when the expression it binds to evaluates to true |
|||
*/ |
|||
todomvc.directive('todoFocus', function ($timeout) { |
|||
return function (scope, elem, attrs) { |
|||
scope.$watch(attrs.todoFocus, function (newVal) { |
|||
if (newVal) { |
|||
$timeout(function () { |
|||
elem[0].focus(); |
|||
}, 0, false); |
|||
} |
|||
}); |
|||
}; |
|||
}); |
@ -0,0 +1,19 @@ |
|||
/*global todomvc */ |
|||
'use strict'; |
|||
|
|||
/** |
|||
* Services that persists and retrieves TODOs from localStorage |
|||
*/ |
|||
todomvc.factory('todoStorage', function () { |
|||
var STORAGE_ID = 'todos-angularjs-perf'; |
|||
|
|||
return { |
|||
get: function () { |
|||
return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]'); |
|||
}, |
|||
|
|||
put: function (todos) { |
|||
localStorage.setItem(STORAGE_ID, JSON.stringify(todos)); |
|||
} |
|||
}; |
|||
}); |
@ -0,0 +1,42 @@ |
|||
# AngularJS (Performance Optimized) TodoMVC Example |
|||
|
|||
> HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop. |
|||
|
|||
> _[AngularJS - angularjs.org](http://angularjs.org)_ |
|||
|
|||
|
|||
## Learning AngularJS |
|||
The [AngularJS website](http://angularjs.org) is a great resource for getting started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [Tutorial](http://docs.angularjs.org/tutorial) |
|||
* [API Reference](http://docs.angularjs.org/api) |
|||
* [Developer Guide](http://docs.angularjs.org/guide) |
|||
* [Applications built with AngularJS](http://builtwith.angularjs.org) |
|||
* [Blog](http://blog.angularjs.org) |
|||
* [FAQ](http://docs.angularjs.org/misc/faq) |
|||
* [AngularJS Meetups](http://www.youtube.com/angularjs) |
|||
|
|||
Articles and guides from the community: |
|||
|
|||
* [Code School AngularJS course](http://www.codeschool.com/code_tv/angularjs-part-1) |
|||
* [5 Awesome AngularJS Features](http://net.tutsplus.com/tutorials/javascript-ajax/5-awesome-angularjs-features) |
|||
* [Using Yeoman with AngularJS](http://briantford.com/blog/angular-yeoman.html) |
|||
* [me&ngular - an introduction to MVW](http://stephenplusplus.github.io/meangular) |
|||
|
|||
Get help from other AngularJS users: |
|||
|
|||
* [Walkthroughs and Tutorials on YouTube](http://www.youtube.com/playlist?list=PL1w1q3fL4pmgqpzb-XhG7Clgi67d_OHXz) |
|||
* [Google Groups mailing list](https://groups.google.com/forum/?fromgroups#!forum/angular) |
|||
* [angularjs on Stack Overflow](http://stackoverflow.com/questions/tagged/angularjs) |
|||
* [AngularJS on Twitter](https://twitter.com/angularjs) |
|||
* [AngularjS on Google +](https://plus.google.com/+AngularJS/posts) |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
|||
|
|||
|
|||
## Implementation |
|||
The normal AngularJS TodoMVC implementation performs deep watching of the todos array object. This means that it keeps an in-memory copy of the complete array that is used for dirty checking in order to detect model mutations. For smaller applications such as TodoMVC, this is completely fine as one trades off a little memory and performance for the sake of simplicity. |
|||
|
|||
In larger more complex applications however, where one might be working with 100s or 1000s of large objects one definitely should avoid using this approach. This implementation of the AngularJS app demonstrates the correct way to approach this problem when working in larger apps. |
@ -0,0 +1,12 @@ |
|||
{ |
|||
"name": "todomvc-angular", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"angular": "1.2.16", |
|||
"todomvc-common": "~0.1.4" |
|||
}, |
|||
"devDependencies": { |
|||
"angular-mocks": "1.2.16", |
|||
"angular-route": "1.2.16" |
|||
} |
|||
} |
@ -0,0 +1,927 @@ |
|||
/** |
|||
* @license AngularJS v1.2.16 |
|||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
|||
* License: MIT |
|||
*/ |
|||
(function(window, angular, undefined) {'use strict'; |
|||
|
|||
/** |
|||
* @ngdoc module |
|||
* @name ngRoute |
|||
* @description |
|||
* |
|||
* # ngRoute |
|||
* |
|||
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps. |
|||
* |
|||
* ## Example |
|||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. |
|||
* |
|||
* |
|||
* <div doc-module-components="ngRoute"></div> |
|||
*/ |
|||
/* global -ngRouteModule */ |
|||
var ngRouteModule = angular.module('ngRoute', ['ng']). |
|||
provider('$route', $RouteProvider); |
|||
|
|||
/** |
|||
* @ngdoc provider |
|||
* @name $routeProvider |
|||
* @function |
|||
* |
|||
* @description |
|||
* |
|||
* Used for configuring routes. |
|||
* |
|||
* ## Example |
|||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. |
|||
* |
|||
* ## Dependencies |
|||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
|||
*/ |
|||
function $RouteProvider(){ |
|||
function inherit(parent, extra) { |
|||
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); |
|||
} |
|||
|
|||
var routes = {}; |
|||
|
|||
/** |
|||
* @ngdoc method |
|||
* @name $routeProvider#when |
|||
* |
|||
* @param {string} path Route path (matched against `$location.path`). If `$location.path` |
|||
* contains redundant trailing slash or is missing one, the route will still match and the |
|||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the |
|||
* route definition. |
|||
* |
|||
* * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up |
|||
* to the next slash are matched and stored in `$routeParams` under the given `name` |
|||
* when the route matches. |
|||
* * `path` can contain named groups starting with a colon and ending with a star: |
|||
* e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` |
|||
* when the route matches. |
|||
* * `path` can contain optional named groups with a question mark: e.g.`:name?`. |
|||
* |
|||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match |
|||
* `/color/brown/largecode/code/with/slashes/edit` and extract: |
|||
* |
|||
* * `color: brown` |
|||
* * `largecode: code/with/slashes`. |
|||
* |
|||
* |
|||
* @param {Object} route Mapping information to be assigned to `$route.current` on route |
|||
* match. |
|||
* |
|||
* Object properties: |
|||
* |
|||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with |
|||
* newly created scope or the name of a {@link angular.Module#controller registered |
|||
* controller} if passed as a string. |
|||
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be |
|||
* published to scope under the `controllerAs` name. |
|||
* - `template` – `{string=|function()=}` – html template as a string or a function that |
|||
* returns an html template as a string which should be used by {@link |
|||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. |
|||
* This property takes precedence over `templateUrl`. |
|||
* |
|||
* If `template` is a function, it will be called with the following parameters: |
|||
* |
|||
* - `{Array.<Object>}` - route parameters extracted from the current |
|||
* `$location.path()` by applying the current route |
|||
* |
|||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html |
|||
* template that should be used by {@link ngRoute.directive:ngView ngView}. |
|||
* |
|||
* If `templateUrl` is a function, it will be called with the following parameters: |
|||
* |
|||
* - `{Array.<Object>}` - route parameters extracted from the current |
|||
* `$location.path()` by applying the current route |
|||
* |
|||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should |
|||
* be injected into the controller. If any of these dependencies are promises, the router |
|||
* will wait for them all to be resolved or one to be rejected before the controller is |
|||
* instantiated. |
|||
* If all the promises are resolved successfully, the values of the resolved promises are |
|||
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is |
|||
* fired. If any of the promises are rejected the |
|||
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object |
|||
* is: |
|||
* |
|||
* - `key` – `{string}`: a name of a dependency to be injected into the controller. |
|||
* - `factory` - `{string|function}`: If `string` then it is an alias for a service. |
|||
* Otherwise if function, then it is {@link auto.$injector#invoke injected} |
|||
* and the return value is treated as the dependency. If the result is a promise, it is |
|||
* resolved before its value is injected into the controller. Be aware that |
|||
* `ngRoute.$routeParams` will still refer to the previous route within these resolve |
|||
* functions. Use `$route.current.params` to access the new route parameters, instead. |
|||
* |
|||
* - `redirectTo` – {(string|function())=} – value to update |
|||
* {@link ng.$location $location} path with and trigger route redirection. |
|||
* |
|||
* If `redirectTo` is a function, it will be called with the following parameters: |
|||
* |
|||
* - `{Object.<string>}` - route parameters extracted from the current |
|||
* `$location.path()` by applying the current route templateUrl. |
|||
* - `{string}` - current `$location.path()` |
|||
* - `{Object}` - current `$location.search()` |
|||
* |
|||
* The custom `redirectTo` function is expected to return a string which will be used |
|||
* to update `$location.path()` and `$location.search()`. |
|||
* |
|||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` |
|||
* or `$location.hash()` changes. |
|||
* |
|||
* If the option is set to `false` and url in the browser changes, then |
|||
* `$routeUpdate` event is broadcasted on the root scope. |
|||
* |
|||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive |
|||
* |
|||
* If the option is set to `true`, then the particular route can be matched without being |
|||
* case sensitive |
|||
* |
|||
* @returns {Object} self |
|||
* |
|||
* @description |
|||
* Adds a new route definition to the `$route` service. |
|||
*/ |
|||
this.when = function(path, route) { |
|||
routes[path] = angular.extend( |
|||
{reloadOnSearch: true}, |
|||
route, |
|||
path && pathRegExp(path, route) |
|||
); |
|||
|
|||
// create redirection for trailing slashes
|
|||
if (path) { |
|||
var redirectPath = (path[path.length-1] == '/') |
|||
? path.substr(0, path.length-1) |
|||
: path +'/'; |
|||
|
|||
routes[redirectPath] = angular.extend( |
|||
{redirectTo: path}, |
|||
pathRegExp(redirectPath, route) |
|||
); |
|||
} |
|||
|
|||
return this; |
|||
}; |
|||
|
|||
/** |
|||
* @param path {string} path |
|||
* @param opts {Object} options |
|||
* @return {?Object} |
|||
* |
|||
* @description |
|||
* Normalizes the given path, returning a regular expression |
|||
* and the original path. |
|||
* |
|||
* Inspired by pathRexp in visionmedia/express/lib/utils.js. |
|||
*/ |
|||
function pathRegExp(path, opts) { |
|||
var insensitive = opts.caseInsensitiveMatch, |
|||
ret = { |
|||
originalPath: path, |
|||
regexp: path |
|||
}, |
|||
keys = ret.keys = []; |
|||
|
|||
path = path |
|||
.replace(/([().])/g, '\\$1') |
|||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option){ |
|||
var optional = option === '?' ? option : null; |
|||
var star = option === '*' ? option : null; |
|||
keys.push({ name: key, optional: !!optional }); |
|||
slash = slash || ''; |
|||
return '' |
|||
+ (optional ? '' : slash) |
|||
+ '(?:' |
|||
+ (optional ? slash : '') |
|||
+ (star && '(.+?)' || '([^/]+)') |
|||
+ (optional || '') |
|||
+ ')' |
|||
+ (optional || ''); |
|||
}) |
|||
.replace(/([\/$\*])/g, '\\$1'); |
|||
|
|||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); |
|||
return ret; |
|||
} |
|||
|
|||
/** |
|||
* @ngdoc method |
|||
* @name $routeProvider#otherwise |
|||
* |
|||
* @description |
|||
* Sets route definition that will be used on route change when no other route definition |
|||
* is matched. |
|||
* |
|||
* @param {Object} params Mapping information to be assigned to `$route.current`. |
|||
* @returns {Object} self |
|||
*/ |
|||
this.otherwise = function(params) { |
|||
this.when(null, params); |
|||
return this; |
|||
}; |
|||
|
|||
|
|||
this.$get = ['$rootScope', |
|||
'$location', |
|||
'$routeParams', |
|||
'$q', |
|||
'$injector', |
|||
'$http', |
|||
'$templateCache', |
|||
'$sce', |
|||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { |
|||
|
|||
/** |
|||
* @ngdoc service |
|||
* @name $route |
|||
* @requires $location |
|||
* @requires $routeParams |
|||
* |
|||
* @property {Object} current Reference to the current route definition. |
|||
* The route definition contains: |
|||
* |
|||
* - `controller`: The controller constructor as define in route definition. |
|||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for |
|||
* controller instantiation. The `locals` contain |
|||
* the resolved values of the `resolve` map. Additionally the `locals` also contain: |
|||
* |
|||
* - `$scope` - The current route scope. |
|||
* - `$template` - The current route template HTML. |
|||
* |
|||
* @property {Object} routes Object with all route configuration Objects as its properties. |
|||
* |
|||
* @description |
|||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials). |
|||
* It watches `$location.url()` and tries to map the path to an existing route definition. |
|||
* |
|||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
|||
* |
|||
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. |
|||
* |
|||
* The `$route` service is typically used in conjunction with the |
|||
* {@link ngRoute.directive:ngView `ngView`} directive and the |
|||
* {@link ngRoute.$routeParams `$routeParams`} service. |
|||
* |
|||
* @example |
|||
* This example shows how changing the URL hash causes the `$route` to match a route against the |
|||
* URL, and the `ngView` pulls in the partial. |
|||
* |
|||
* Note that this example is using {@link ng.directive:script inlined templates} |
|||
* to get it working on jsfiddle as well. |
|||
* |
|||
* <example name="$route-service" module="ngRouteExample" |
|||
* deps="angular-route.js" fixBase="true"> |
|||
* <file name="index.html"> |
|||
* <div ng-controller="MainController"> |
|||
* Choose: |
|||
* <a href="Book/Moby">Moby</a> | |
|||
* <a href="Book/Moby/ch/1">Moby: Ch1</a> | |
|||
* <a href="Book/Gatsby">Gatsby</a> | |
|||
* <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | |
|||
* <a href="Book/Scarlet">Scarlet Letter</a><br/> |
|||
* |
|||
* <div ng-view></div> |
|||
* |
|||
* <hr /> |
|||
* |
|||
* <pre>$location.path() = {{$location.path()}}</pre> |
|||
* <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> |
|||
* <pre>$route.current.params = {{$route.current.params}}</pre> |
|||
* <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> |
|||
* <pre>$routeParams = {{$routeParams}}</pre> |
|||
* </div> |
|||
* </file> |
|||
* |
|||
* <file name="book.html"> |
|||
* controller: {{name}}<br /> |
|||
* Book Id: {{params.bookId}}<br /> |
|||
* </file> |
|||
* |
|||
* <file name="chapter.html"> |
|||
* controller: {{name}}<br /> |
|||
* Book Id: {{params.bookId}}<br /> |
|||
* Chapter Id: {{params.chapterId}} |
|||
* </file> |
|||
* |
|||
* <file name="script.js"> |
|||
* angular.module('ngRouteExample', ['ngRoute']) |
|||
* |
|||
* .controller('MainController', function($scope, $route, $routeParams, $location) { |
|||
* $scope.$route = $route; |
|||
* $scope.$location = $location; |
|||
* $scope.$routeParams = $routeParams; |
|||
* }) |
|||
* |
|||
* .controller('BookController', function($scope, $routeParams) { |
|||
* $scope.name = "BookController"; |
|||
* $scope.params = $routeParams; |
|||
* }) |
|||
* |
|||
* .controller('ChapterController', function($scope, $routeParams) { |
|||
* $scope.name = "ChapterController"; |
|||
* $scope.params = $routeParams; |
|||
* }) |
|||
* |
|||
* .config(function($routeProvider, $locationProvider) { |
|||
* $routeProvider |
|||
* .when('/Book/:bookId', { |
|||
* templateUrl: 'book.html', |
|||
* controller: 'BookController', |
|||
* resolve: { |
|||
* // I will cause a 1 second delay
|
|||
* delay: function($q, $timeout) { |
|||
* var delay = $q.defer(); |
|||
* $timeout(delay.resolve, 1000); |
|||
* return delay.promise; |
|||
* } |
|||
* } |
|||
* }) |
|||
* .when('/Book/:bookId/ch/:chapterId', { |
|||
* templateUrl: 'chapter.html', |
|||
* controller: 'ChapterController' |
|||
* }); |
|||
* |
|||
* // configure html5 to get links working on jsfiddle
|
|||
* $locationProvider.html5Mode(true); |
|||
* }); |
|||
* |
|||
* </file> |
|||
* |
|||
* <file name="protractor.js" type="protractor"> |
|||
* it('should load and compile correct template', function() { |
|||
* element(by.linkText('Moby: Ch1')).click(); |
|||
* var content = element(by.css('[ng-view]')).getText(); |
|||
* expect(content).toMatch(/controller\: ChapterController/); |
|||
* expect(content).toMatch(/Book Id\: Moby/); |
|||
* expect(content).toMatch(/Chapter Id\: 1/); |
|||
* |
|||
* element(by.partialLinkText('Scarlet')).click(); |
|||
* |
|||
* content = element(by.css('[ng-view]')).getText(); |
|||
* expect(content).toMatch(/controller\: BookController/); |
|||
* expect(content).toMatch(/Book Id\: Scarlet/); |
|||
* }); |
|||
* </file> |
|||
* </example> |
|||
*/ |
|||
|
|||
/** |
|||
* @ngdoc event |
|||
* @name $route#$routeChangeStart |
|||
* @eventType broadcast on root scope |
|||
* @description |
|||
* Broadcasted before a route change. At this point the route services starts |
|||
* resolving all of the dependencies needed for the route change to occur. |
|||
* Typically this involves fetching the view template as well as any dependencies |
|||
* defined in `resolve` route property. Once all of the dependencies are resolved |
|||
* `$routeChangeSuccess` is fired. |
|||
* |
|||
* @param {Object} angularEvent Synthetic event object. |
|||
* @param {Route} next Future route information. |
|||
* @param {Route} current Current route information. |
|||
*/ |
|||
|
|||
/** |
|||
* @ngdoc event |
|||
* @name $route#$routeChangeSuccess |
|||
* @eventType broadcast on root scope |
|||
* @description |
|||
* Broadcasted after a route dependencies are resolved. |
|||
* {@link ngRoute.directive:ngView ngView} listens for the directive |
|||
* to instantiate the controller and render the view. |
|||
* |
|||
* @param {Object} angularEvent Synthetic event object. |
|||
* @param {Route} current Current route information. |
|||
* @param {Route|Undefined} previous Previous route information, or undefined if current is |
|||
* first route entered. |
|||
*/ |
|||
|
|||
/** |
|||
* @ngdoc event |
|||
* @name $route#$routeChangeError |
|||
* @eventType broadcast on root scope |
|||
* @description |
|||
* Broadcasted if any of the resolve promises are rejected. |
|||
* |
|||
* @param {Object} angularEvent Synthetic event object |
|||
* @param {Route} current Current route information. |
|||
* @param {Route} previous Previous route information. |
|||
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. |
|||
*/ |
|||
|
|||
/** |
|||
* @ngdoc event |
|||
* @name $route#$routeUpdate |
|||
* @eventType broadcast on root scope |
|||
* @description |
|||
* |
|||
* The `reloadOnSearch` property has been set to false, and we are reusing the same |
|||
* instance of the Controller. |
|||
*/ |
|||
|
|||
var forceReload = false, |
|||
$route = { |
|||
routes: routes, |
|||
|
|||
/** |
|||
* @ngdoc method |
|||
* @name $route#reload |
|||
* |
|||
* @description |
|||
* Causes `$route` service to reload the current route even if |
|||
* {@link ng.$location $location} hasn't changed. |
|||
* |
|||
* As a result of that, {@link ngRoute.directive:ngView ngView} |
|||
* creates new scope, reinstantiates the controller. |
|||
*/ |
|||
reload: function() { |
|||
forceReload = true; |
|||
$rootScope.$evalAsync(updateRoute); |
|||
} |
|||
}; |
|||
|
|||
$rootScope.$on('$locationChangeSuccess', updateRoute); |
|||
|
|||
return $route; |
|||
|
|||
/////////////////////////////////////////////////////
|
|||
|
|||
/** |
|||
* @param on {string} current url |
|||
* @param route {Object} route regexp to match the url against |
|||
* @return {?Object} |
|||
* |
|||
* @description |
|||
* Check if the route matches the current url. |
|||
* |
|||
* Inspired by match in |
|||
* visionmedia/express/lib/router/router.js. |
|||
*/ |
|||
function switchRouteMatcher(on, route) { |
|||
var keys = route.keys, |
|||
params = {}; |
|||
|
|||
if (!route.regexp) return null; |
|||
|
|||
var m = route.regexp.exec(on); |
|||
if (!m) return null; |
|||
|
|||
for (var i = 1, len = m.length; i < len; ++i) { |
|||
var key = keys[i - 1]; |
|||
|
|||
var val = 'string' == typeof m[i] |
|||
? decodeURIComponent(m[i]) |
|||
: m[i]; |
|||
|
|||
if (key && val) { |
|||
params[key.name] = val; |
|||
} |
|||
} |
|||
return params; |
|||
} |
|||
|
|||
function updateRoute() { |
|||
var next = parseRoute(), |
|||
last = $route.current; |
|||
|
|||
if (next && last && next.$$route === last.$$route |
|||
&& angular.equals(next.pathParams, last.pathParams) |
|||
&& !next.reloadOnSearch && !forceReload) { |
|||
last.params = next.params; |
|||
angular.copy(last.params, $routeParams); |
|||
$rootScope.$broadcast('$routeUpdate', last); |
|||
} else if (next || last) { |
|||
forceReload = false; |
|||
$rootScope.$broadcast('$routeChangeStart', next, last); |
|||
$route.current = next; |
|||
if (next) { |
|||
if (next.redirectTo) { |
|||
if (angular.isString(next.redirectTo)) { |
|||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params) |
|||
.replace(); |
|||
} else { |
|||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) |
|||
.replace(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
$q.when(next). |
|||
then(function() { |
|||
if (next) { |
|||
var locals = angular.extend({}, next.resolve), |
|||
template, templateUrl; |
|||
|
|||
angular.forEach(locals, function(value, key) { |
|||
locals[key] = angular.isString(value) ? |
|||
$injector.get(value) : $injector.invoke(value); |
|||
}); |
|||
|
|||
if (angular.isDefined(template = next.template)) { |
|||
if (angular.isFunction(template)) { |
|||
template = template(next.params); |
|||
} |
|||
} else if (angular.isDefined(templateUrl = next.templateUrl)) { |
|||
if (angular.isFunction(templateUrl)) { |
|||
templateUrl = templateUrl(next.params); |
|||
} |
|||
templateUrl = $sce.getTrustedResourceUrl(templateUrl); |
|||
if (angular.isDefined(templateUrl)) { |
|||
next.loadedTemplateUrl = templateUrl; |
|||
template = $http.get(templateUrl, {cache: $templateCache}). |
|||
then(function(response) { return response.data; }); |
|||
} |
|||
} |
|||
if (angular.isDefined(template)) { |
|||
locals['$template'] = template; |
|||
} |
|||
return $q.all(locals); |
|||
} |
|||
}). |
|||
// after route change
|
|||
then(function(locals) { |
|||
if (next == $route.current) { |
|||
if (next) { |
|||
next.locals = locals; |
|||
angular.copy(next.params, $routeParams); |
|||
} |
|||
$rootScope.$broadcast('$routeChangeSuccess', next, last); |
|||
} |
|||
}, function(error) { |
|||
if (next == $route.current) { |
|||
$rootScope.$broadcast('$routeChangeError', next, last, error); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* @returns {Object} the current active route, by matching it against the URL |
|||
*/ |
|||
function parseRoute() { |
|||
// Match a route
|
|||
var params, match; |
|||
angular.forEach(routes, function(route, path) { |
|||
if (!match && (params = switchRouteMatcher($location.path(), route))) { |
|||
match = inherit(route, { |
|||
params: angular.extend({}, $location.search(), params), |
|||
pathParams: params}); |
|||
match.$$route = route; |
|||
} |
|||
}); |
|||
// No route matched; fallback to "otherwise" route
|
|||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); |
|||
} |
|||
|
|||
/** |
|||
* @returns {string} interpolation of the redirect path with the parameters |
|||
*/ |
|||
function interpolate(string, params) { |
|||
var result = []; |
|||
angular.forEach((string||'').split(':'), function(segment, i) { |
|||
if (i === 0) { |
|||
result.push(segment); |
|||
} else { |
|||
var segmentMatch = segment.match(/(\w+)(.*)/); |
|||
var key = segmentMatch[1]; |
|||
result.push(params[key]); |
|||
result.push(segmentMatch[2] || ''); |
|||
delete params[key]; |
|||
} |
|||
}); |
|||
return result.join(''); |
|||
} |
|||
}]; |
|||
} |
|||
|
|||
ngRouteModule.provider('$routeParams', $RouteParamsProvider); |
|||
|
|||
|
|||
/** |
|||
* @ngdoc service |
|||
* @name $routeParams |
|||
* @requires $route |
|||
* |
|||
* @description |
|||
* The `$routeParams` service allows you to retrieve the current set of route parameters. |
|||
* |
|||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
|||
* |
|||
* The route parameters are a combination of {@link ng.$location `$location`}'s |
|||
* {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. |
|||
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. |
|||
* |
|||
* In case of parameter name collision, `path` params take precedence over `search` params. |
|||
* |
|||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged |
|||
* (but its properties will likely change) even when a route change occurs. |
|||
* |
|||
* Note that the `$routeParams` are only updated *after* a route change completes successfully. |
|||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions. |
|||
* Instead you can use `$route.current.params` to access the new route's parameters. |
|||
* |
|||
* @example |
|||
* ```js
|
|||
* // Given:
|
|||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
|||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
|||
* //
|
|||
* // Then
|
|||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'} |
|||
* ``` |
|||
*/ |
|||
function $RouteParamsProvider() { |
|||
this.$get = function() { return {}; }; |
|||
} |
|||
|
|||
ngRouteModule.directive('ngView', ngViewFactory); |
|||
ngRouteModule.directive('ngView', ngViewFillContentFactory); |
|||
|
|||
|
|||
/** |
|||
* @ngdoc directive |
|||
* @name ngView |
|||
* @restrict ECA |
|||
* |
|||
* @description |
|||
* # Overview |
|||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by |
|||
* including the rendered template of the current route into the main layout (`index.html`) file. |
|||
* Every time the current route changes, the included view changes with it according to the |
|||
* configuration of the `$route` service. |
|||
* |
|||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
|||
* |
|||
* @animations |
|||
* enter - animation is used to bring new content into the browser. |
|||
* leave - animation is used to animate existing content away. |
|||
* |
|||
* The enter and leave animation occur concurrently. |
|||
* |
|||
* @scope |
|||
* @priority 400 |
|||
* @param {string=} onload Expression to evaluate whenever the view updates. |
|||
* |
|||
* @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll |
|||
* $anchorScroll} to scroll the viewport after the view is updated. |
|||
* |
|||
* - If the attribute is not set, disable scrolling. |
|||
* - If the attribute is set without value, enable scrolling. |
|||
* - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated |
|||
* as an expression yields a truthy value. |
|||
* @example |
|||
<example name="ngView-directive" module="ngViewExample" |
|||
deps="angular-route.js;angular-animate.js" |
|||
animations="true" fixBase="true"> |
|||
<file name="index.html"> |
|||
<div ng-controller="MainCtrl as main"> |
|||
Choose: |
|||
<a href="Book/Moby">Moby</a> | |
|||
<a href="Book/Moby/ch/1">Moby: Ch1</a> | |
|||
<a href="Book/Gatsby">Gatsby</a> | |
|||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | |
|||
<a href="Book/Scarlet">Scarlet Letter</a><br/> |
|||
|
|||
<div class="view-animate-container"> |
|||
<div ng-view class="view-animate"></div> |
|||
</div> |
|||
<hr /> |
|||
|
|||
<pre>$location.path() = {{main.$location.path()}}</pre> |
|||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> |
|||
<pre>$route.current.params = {{main.$route.current.params}}</pre> |
|||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre> |
|||
<pre>$routeParams = {{main.$routeParams}}</pre> |
|||
</div> |
|||
</file> |
|||
|
|||
<file name="book.html"> |
|||
<div> |
|||
controller: {{book.name}}<br /> |
|||
Book Id: {{book.params.bookId}}<br /> |
|||
</div> |
|||
</file> |
|||
|
|||
<file name="chapter.html"> |
|||
<div> |
|||
controller: {{chapter.name}}<br /> |
|||
Book Id: {{chapter.params.bookId}}<br /> |
|||
Chapter Id: {{chapter.params.chapterId}} |
|||
</div> |
|||
</file> |
|||
|
|||
<file name="animations.css"> |
|||
.view-animate-container { |
|||
position:relative; |
|||
height:100px!important; |
|||
position:relative; |
|||
background:white; |
|||
border:1px solid black; |
|||
height:40px; |
|||
overflow:hidden; |
|||
} |
|||
|
|||
.view-animate { |
|||
padding:10px; |
|||
} |
|||
|
|||
.view-animate.ng-enter, .view-animate.ng-leave { |
|||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; |
|||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; |
|||
|
|||
display:block; |
|||
width:100%; |
|||
border-left:1px solid black; |
|||
|
|||
position:absolute; |
|||
top:0; |
|||
left:0; |
|||
right:0; |
|||
bottom:0; |
|||
padding:10px; |
|||
} |
|||
|
|||
.view-animate.ng-enter { |
|||
left:100%; |
|||
} |
|||
.view-animate.ng-enter.ng-enter-active { |
|||
left:0; |
|||
} |
|||
.view-animate.ng-leave.ng-leave-active { |
|||
left:-100%; |
|||
} |
|||
</file> |
|||
|
|||
<file name="script.js"> |
|||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) |
|||
.config(['$routeProvider', '$locationProvider', |
|||
function($routeProvider, $locationProvider) { |
|||
$routeProvider |
|||
.when('/Book/:bookId', { |
|||
templateUrl: 'book.html', |
|||
controller: 'BookCtrl', |
|||
controllerAs: 'book' |
|||
}) |
|||
.when('/Book/:bookId/ch/:chapterId', { |
|||
templateUrl: 'chapter.html', |
|||
controller: 'ChapterCtrl', |
|||
controllerAs: 'chapter' |
|||
}); |
|||
|
|||
// configure html5 to get links working on jsfiddle
|
|||
$locationProvider.html5Mode(true); |
|||
}]) |
|||
.controller('MainCtrl', ['$route', '$routeParams', '$location', |
|||
function($route, $routeParams, $location) { |
|||
this.$route = $route; |
|||
this.$location = $location; |
|||
this.$routeParams = $routeParams; |
|||
}]) |
|||
.controller('BookCtrl', ['$routeParams', function($routeParams) { |
|||
this.name = "BookCtrl"; |
|||
this.params = $routeParams; |
|||
}]) |
|||
.controller('ChapterCtrl', ['$routeParams', function($routeParams) { |
|||
this.name = "ChapterCtrl"; |
|||
this.params = $routeParams; |
|||
}]); |
|||
|
|||
</file> |
|||
|
|||
<file name="protractor.js" type="protractor"> |
|||
it('should load and compile correct template', function() { |
|||
element(by.linkText('Moby: Ch1')).click(); |
|||
var content = element(by.css('[ng-view]')).getText(); |
|||
expect(content).toMatch(/controller\: ChapterCtrl/); |
|||
expect(content).toMatch(/Book Id\: Moby/); |
|||
expect(content).toMatch(/Chapter Id\: 1/); |
|||
|
|||
element(by.partialLinkText('Scarlet')).click(); |
|||
|
|||
content = element(by.css('[ng-view]')).getText(); |
|||
expect(content).toMatch(/controller\: BookCtrl/); |
|||
expect(content).toMatch(/Book Id\: Scarlet/); |
|||
}); |
|||
</file> |
|||
</example> |
|||
*/ |
|||
|
|||
|
|||
/** |
|||
* @ngdoc event |
|||
* @name ngView#$viewContentLoaded |
|||
* @eventType emit on the current ngView scope |
|||
* @description |
|||
* Emitted every time the ngView content is reloaded. |
|||
*/ |
|||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; |
|||
function ngViewFactory( $route, $anchorScroll, $animate) { |
|||
return { |
|||
restrict: 'ECA', |
|||
terminal: true, |
|||
priority: 400, |
|||
transclude: 'element', |
|||
link: function(scope, $element, attr, ctrl, $transclude) { |
|||
var currentScope, |
|||
currentElement, |
|||
previousElement, |
|||
autoScrollExp = attr.autoscroll, |
|||
onloadExp = attr.onload || ''; |
|||
|
|||
scope.$on('$routeChangeSuccess', update); |
|||
update(); |
|||
|
|||
function cleanupLastView() { |
|||
if(previousElement) { |
|||
previousElement.remove(); |
|||
previousElement = null; |
|||
} |
|||
if(currentScope) { |
|||
currentScope.$destroy(); |
|||
currentScope = null; |
|||
} |
|||
if(currentElement) { |
|||
$animate.leave(currentElement, function() { |
|||
previousElement = null; |
|||
}); |
|||
previousElement = currentElement; |
|||
currentElement = null; |
|||
} |
|||
} |
|||
|
|||
function update() { |
|||
var locals = $route.current && $route.current.locals, |
|||
template = locals && locals.$template; |
|||
|
|||
if (angular.isDefined(template)) { |
|||
var newScope = scope.$new(); |
|||
var current = $route.current; |
|||
|
|||
// Note: This will also link all children of ng-view that were contained in the original
|
|||
// html. If that content contains controllers, ... they could pollute/change the scope.
|
|||
// However, using ng-view on an element with additional content does not make sense...
|
|||
// Note: We can't remove them in the cloneAttchFn of $transclude as that
|
|||
// function is called before linking the content, which would apply child
|
|||
// directives to non existing elements.
|
|||
var clone = $transclude(newScope, function(clone) { |
|||
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { |
|||
if (angular.isDefined(autoScrollExp) |
|||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) { |
|||
$anchorScroll(); |
|||
} |
|||
}); |
|||
cleanupLastView(); |
|||
}); |
|||
|
|||
currentElement = clone; |
|||
currentScope = current.scope = newScope; |
|||
currentScope.$emit('$viewContentLoaded'); |
|||
currentScope.$eval(onloadExp); |
|||
} else { |
|||
cleanupLastView(); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
} |
|||
|
|||
// This directive is called during the $transclude call of the first `ngView` directive.
|
|||
// It will replace and compile the content of the element with the loaded template.
|
|||
// We need this directive so that the element content is already filled when
|
|||
// the link function of another directive on the same element as ngView
|
|||
// is called.
|
|||
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; |
|||
function ngViewFillContentFactory($compile, $controller, $route) { |
|||
return { |
|||
restrict: 'ECA', |
|||
priority: -400, |
|||
link: function(scope, $element) { |
|||
var current = $route.current, |
|||
locals = current.locals; |
|||
|
|||
$element.html(locals.$template); |
|||
|
|||
var link = $compile($element.contents()); |
|||
|
|||
if (current.controller) { |
|||
locals.$scope = scope; |
|||
var controller = $controller(current.controller, locals); |
|||
if (current.controllerAs) { |
|||
scope[current.controllerAs] = controller; |
|||
} |
|||
$element.data('$ngControllerController', controller); |
|||
$element.children().data('$ngControllerController', controller); |
|||
} |
|||
|
|||
link(scope); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
|
|||
})(window, window.angular); |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,79 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="angularjs"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>AngularJS • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
<style>[ng-cloak] { display: none; }</style> |
|||
</head> |
|||
<body ng-app="todomvc"> |
|||
<ng-view /> |
|||
|
|||
<script type="text/ng-template" id="todomvc-index.html"> |
|||
<section id="todoapp" ng-controller="TodoCtrl"> |
|||
<header id="header"> |
|||
<h1>todos</h1> |
|||
<form id="todo-form" ng-submit="addTodo()"> |
|||
<input id="new-todo" placeholder="What needs to be done?" ng-model="newTodo" autofocus> |
|||
</form> |
|||
</header> |
|||
<section id="main" ng-show="todos.length" ng-cloak> |
|||
<input id="toggle-all" type="checkbox" ng-model="allChecked" ng-click="markAll(allChecked)"> |
|||
<label for="toggle-all">Mark all as complete</label> |
|||
<ul id="todo-list"> |
|||
<li ng-repeat="todo in todos | filter:statusFilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedTodo}"> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" ng-model="todo.completed"> |
|||
<label ng-dblclick="editTodo(todo)">{{todo.title}}</label> |
|||
<button class="destroy" ng-click="removeTodo(todo)"></button> |
|||
</div> |
|||
<form ng-submit="doneEditing(todo)"> |
|||
<input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertEditing(todo)" ng-blur="doneEditing(todo)" todo-focus="todo == editedTodo"> |
|||
</form> |
|||
</li> |
|||
</ul> |
|||
</section> |
|||
<footer id="footer" ng-show="todos.length" ng-cloak> |
|||
<span id="todo-count"><strong>{{remainingCount}}</strong> |
|||
<ng-pluralize count="remainingCount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize> |
|||
</span> |
|||
<ul id="filters"> |
|||
<li> |
|||
<a ng-class="{selected: status == ''} " href="#/">All</a> |
|||
</li> |
|||
<li> |
|||
<a ng-class="{selected: status == 'active'}" href="#/active">Active</a> |
|||
</li> |
|||
<li> |
|||
<a ng-class="{selected: status == 'completed'}" href="#/completed">Completed</a> |
|||
</li> |
|||
</ul> |
|||
<button id="clear-completed" ng-click="clearCompletedTodos()" ng-show="completedCount">Clear completed ({{completedCount}})</button> |
|||
</footer> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Credits: |
|||
<a href="http://twitter.com/cburgdorf">Christoph Burgdorf</a>, |
|||
<a href="http://ericbidelman.com">Eric Bidelman</a>, |
|||
<a href="http://jacobmumm.com">Jacob Mumm</a> and |
|||
<a href="http://igorminar.com">Igor Minar</a> |
|||
</p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
</script> |
|||
|
|||
<!-- import hoodie and jquery. jquery is a dependency only for promises. --> |
|||
<script src="http://127.0.0.1:6013/assets/vendor/jquery-2.1.0.min.js"></script> |
|||
<script src="http://127.0.0.1:6013/_api/_files/hoodie.js"></script> |
|||
|
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script src="bower_components/angular/angular.js"></script> |
|||
<script src="bower_components/angular-route/angular-route.js"></script> |
|||
<script src="js/app.js"></script> |
|||
<script src="js/controllers/todoCtrl.js"></script> |
|||
<script src="js/services/todoStorage.js"></script> |
|||
<script src="js/directives/todoFocus.js"></script> |
|||
<script src="js/directives/todoEscape.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,21 @@ |
|||
/*global angular */ |
|||
|
|||
/** |
|||
* The main TodoMVC app module |
|||
* |
|||
* @type {angular.Module} |
|||
*/ |
|||
angular.module('todomvc', ['ngRoute']) |
|||
.config(function ($routeProvider) { |
|||
'use strict'; |
|||
|
|||
$routeProvider.when('/', { |
|||
controller: 'TodoCtrl', |
|||
templateUrl: 'todomvc-index.html' |
|||
}).when('/:status', { |
|||
controller: 'TodoCtrl', |
|||
templateUrl: 'todomvc-index.html' |
|||
}).otherwise({ |
|||
redirectTo: '/' |
|||
}); |
|||
}); |
@ -0,0 +1,105 @@ |
|||
/*global angular */ |
|||
|
|||
/** |
|||
* The main controller for the app. The controller: |
|||
* - retrieves and persists the model via the todoStorage service |
|||
* - exposes the model to the template and provides event handlers |
|||
*/ |
|||
angular.module('todomvc') |
|||
.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, $filter, todoStorage) { |
|||
'use strict'; |
|||
|
|||
// var todos = $scope.todos = todoStorage.get();
|
|||
|
|||
// start empty and update if ready
|
|||
var todos = $scope.todos = []; |
|||
|
|||
$scope.newTodo = ''; |
|||
$scope.editedTodo = null; |
|||
|
|||
$scope.$watch('todos', function (newTodoList, oldTodoList) { |
|||
$scope.remainingCount = $filter('filter')(todos, { completed: false }).length; |
|||
$scope.completedCount = todos.length - $scope.remainingCount; |
|||
$scope.allChecked = !$scope.remainingCount; |
|||
|
|||
// this will just forcepush everything
|
|||
// if (newValue !== oldValue) { // This prevents unneeded calls to the local storage
|
|||
// todoStorage.put(todos);
|
|||
// }
|
|||
|
|||
// since we work with read entity data, we need to improve here
|
|||
todoStorage |
|||
.performDataSync(newTodoList, oldTodoList) |
|||
.done(function(storedTodos) { |
|||
todos = $scope.todos = storedTodos; |
|||
}); |
|||
}, true); |
|||
|
|||
// Monitor the current route for changes and adjust the filter accordingly.
|
|||
$scope.$on('$routeChangeSuccess', function () { |
|||
var status = $scope.status = $routeParams.status || ''; |
|||
|
|||
$scope.statusFilter = (status === 'active') ? |
|||
{ completed: false } : (status === 'completed') ? |
|||
{ completed: true } : null; |
|||
}); |
|||
|
|||
$scope.addTodo = function () { |
|||
var newTodo = $scope.newTodo.trim(); |
|||
if (!newTodo.length) { |
|||
return; |
|||
} |
|||
|
|||
todos.push({ |
|||
title: newTodo, |
|||
completed: false |
|||
}); |
|||
|
|||
$scope.newTodo = ''; |
|||
}; |
|||
|
|||
$scope.updateTodoList = function updateTodoList() { |
|||
todoStorage.get().done(function(storedTodos) { |
|||
todos = $scope.todos = storedTodos; |
|||
}); |
|||
}; |
|||
$scope.updateTodoList(); |
|||
|
|||
|
|||
$scope.editTodo = function (todo) { |
|||
$scope.editedTodo = todo; |
|||
// Clone the original todo to restore it on demand.
|
|||
$scope.originalTodo = angular.extend({}, todo); |
|||
}; |
|||
|
|||
$scope.doneEditing = function (todo) { |
|||
$scope.editedTodo = null; |
|||
todo.title = todo.title.trim(); |
|||
|
|||
if (!todo.title) { |
|||
$scope.removeTodo(todo); |
|||
} |
|||
}; |
|||
|
|||
$scope.revertEditing = function (todo) { |
|||
todos[todos.indexOf(todo)] = $scope.originalTodo; |
|||
$scope.doneEditing($scope.originalTodo); |
|||
}; |
|||
|
|||
$scope.removeTodo = function (todo) { |
|||
todos.splice(todos.indexOf(todo), 1); |
|||
}; |
|||
|
|||
$scope.clearCompletedTodos = function () { |
|||
$scope.todos = todos = todos.filter(function (val) { |
|||
return !val.completed; |
|||
}); |
|||
}; |
|||
|
|||
$scope.markAll = function (completed) { |
|||
todos.forEach(function (todo) { |
|||
todo.completed = !completed; |
|||
}); |
|||
}; |
|||
|
|||
}); |
@ -0,0 +1,20 @@ |
|||
/*global angular */ |
|||
|
|||
/** |
|||
* Directive that executes an expression when the element it is applied to gets |
|||
* an `escape` keydown event. |
|||
*/ |
|||
angular.module('todomvc') |
|||
.directive('todoEscape', function () { |
|||
'use strict'; |
|||
|
|||
var ESCAPE_KEY = 27; |
|||
|
|||
return function (scope, elem, attrs) { |
|||
elem.bind('keydown', function (event) { |
|||
if (event.keyCode === ESCAPE_KEY) { |
|||
scope.$apply(attrs.todoEscape); |
|||
} |
|||
}); |
|||
}; |
|||
}); |
@ -0,0 +1,20 @@ |
|||
/*global angular */ |
|||
|
|||
/** |
|||
* Directive that places focus on the element it is applied to when the |
|||
* expression it binds to evaluates to true |
|||
*/ |
|||
angular.module('todomvc') |
|||
.directive('todoFocus', function todoFocus($timeout) { |
|||
'use strict'; |
|||
|
|||
return function (scope, elem, attrs) { |
|||
scope.$watch(attrs.todoFocus, function (newVal) { |
|||
if (newVal) { |
|||
$timeout(function () { |
|||
elem[0].focus(); |
|||
}, 0, false); |
|||
} |
|||
}); |
|||
}; |
|||
}); |
@ -0,0 +1,104 @@ |
|||
/*global angular */ |
|||
|
|||
/** |
|||
* Services that persists and retrieves TODOs from localStorage |
|||
*/ |
|||
angular.module('todomvc') |
|||
.factory('todoStorage', function () { |
|||
'use strict'; |
|||
|
|||
var STORAGE_ID = 'todos-angularjs'; |
|||
|
|||
// fake login, for syncing
|
|||
var hoodie = new Hoodie(); |
|||
|
|||
|
|||
return { |
|||
get: function () { |
|||
return hoodie.store.findAll('todo'); |
|||
|
|||
// todomvc original
|
|||
// return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]');
|
|||
}, |
|||
|
|||
put: function (todo) { |
|||
return hoodie.store.add('todo', todo); |
|||
|
|||
// todomvc original
|
|||
// localStorage.setItem(STORAGE_ID, JSON.stringify(todos));
|
|||
}, |
|||
|
|||
remove: function (todo) { |
|||
console.log('remove', todo); |
|||
return hoodie.store.remove('todo', todo.id); |
|||
|
|||
// return JSON.parse(localStorage.getItem(STORAGE_ID) || '[]');
|
|||
}, |
|||
|
|||
performDataSync: function(newList, oldList) { |
|||
var that = this, |
|||
newItems, |
|||
updatedItems, |
|||
deletedItems; |
|||
|
|||
newItems = newList.filter(function(todo) { |
|||
// filter the new items. new items dont
|
|||
// have an id yet
|
|||
|
|||
return todo.id === undefined; |
|||
}); |
|||
|
|||
updatedItems = newList.filter(function(todo, idx) { |
|||
// filter updated items.
|
|||
// since the completed tad is eveything we
|
|||
// can currently edit on an existig todo,
|
|||
// we'take this as "changed/dirty" criteria
|
|||
|
|||
return oldList[idx] != undefined && todo.completed != oldList[idx].completed; |
|||
}); |
|||
|
|||
deletedItems = oldList.filter(function(todo, idx) { |
|||
// filter deleted items
|
|||
// since angular creates copies the array contents
|
|||
// We need can't use Array.indexOf here.
|
|||
var isDeleted = true, |
|||
idx; |
|||
|
|||
for(idx = 0; idx < newList.length && isDeleted; ++idx) { |
|||
if(newList[idx].id == todo.id) { |
|||
isDeleted = false; |
|||
} |
|||
} |
|||
|
|||
return isDeleted; |
|||
}); |
|||
|
|||
// console.clear();
|
|||
// console.log('newItems', newItems);
|
|||
// console.log('updateItems', updatedItems);
|
|||
// console.log('deletedItems', deletedItems);
|
|||
|
|||
newItems.forEach(function(todo) { |
|||
that.put(todo).done(function(writtenTodo) { |
|||
// console.log('newItem', writtenTodo);
|
|||
}); |
|||
}); |
|||
|
|||
updatedItems.forEach(function(todo) { |
|||
that.put(todo).done(function(updatedTodo){ |
|||
// console.log('updatedItem', updatedTodo);
|
|||
}); |
|||
}); |
|||
|
|||
deletedItems.forEach(function(todo) { |
|||
that.remove(todo).done(function(doneTodo) { |
|||
// console.log('doneTodo', doneTodo);
|
|||
}); |
|||
}) |
|||
|
|||
// since we use localstorage it doesn't
|
|||
// hurt to pull the whole list again. \o/
|
|||
return that.get(); |
|||
} |
|||
}; |
|||
}); |
@ -0,0 +1,36 @@ |
|||
# AngularJS TodoMVC Example |
|||
|
|||
> HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop. |
|||
|
|||
> _[AngularJS - angularjs.org](http://angularjs.org)_ |
|||
|
|||
|
|||
## Learning AngularJS |
|||
The [AngularJS website](http://angularjs.org) is a great resource for getting started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [Tutorial](http://docs.angularjs.org/tutorial) |
|||
* [API Reference](http://docs.angularjs.org/api) |
|||
* [Developer Guide](http://docs.angularjs.org/guide) |
|||
* [Applications built with AngularJS](http://builtwith.angularjs.org) |
|||
* [Blog](http://blog.angularjs.org) |
|||
* [FAQ](http://docs.angularjs.org/misc/faq) |
|||
* [AngularJS Meetups](http://www.youtube.com/angularjs) |
|||
|
|||
Articles and guides from the community: |
|||
|
|||
* [Code School AngularJS course](http://www.codeschool.com/code_tv/angularjs-part-1) |
|||
* [5 Awesome AngularJS Features](http://net.tutsplus.com/tutorials/javascript-ajax/5-awesome-angularjs-features) |
|||
* [Using Yeoman with AngularJS](http://briantford.com/blog/angular-yeoman.html) |
|||
* [me&ngular - an introduction to MVW](http://stephenplusplus.github.io/meangular) |
|||
|
|||
Get help from other AngularJS users: |
|||
|
|||
* [Walkthroughs and Tutorials on YouTube](http://www.youtube.com/playlist?list=PL1w1q3fL4pmgqpzb-XhG7Clgi67d_OHXz) |
|||
* [Google Groups mailing list](https://groups.google.com/forum/?fromgroups#!forum/angular) |
|||
* [angularjs on Stack Overflow](http://stackoverflow.com/questions/tagged/angularjs) |
|||
* [AngularJS on Twitter](https://twitter.com/angularjs) |
|||
* [AngularjS on Google +](https://plus.google.com/+AngularJS/posts) |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
@ -0,0 +1,18 @@ |
|||
module.exports = function (config) { |
|||
'use strict'; |
|||
|
|||
config.set({ |
|||
basePath: '../../', |
|||
frameworks: ['jasmine'], |
|||
files: [ |
|||
'bower_components/angular/angular.js', |
|||
'bower_components/angular-route/angular-route.js', |
|||
'bower_components/angular-mocks/angular-mocks.js', |
|||
'js/**/*.js', |
|||
'test/unit/**/*.js' |
|||
], |
|||
autoWatch: true, |
|||
singleRun: false, |
|||
browsers: ['Chrome', 'Firefox'] |
|||
}); |
|||
}; |
@ -0,0 +1,13 @@ |
|||
{ |
|||
"name": "todomvc-angular-tests", |
|||
"description": "Unit tests for the AngularJS example of TodoMVC", |
|||
"author": "Pascal Hartig <phartig@rdrei.net>", |
|||
"version": "1.0.0", |
|||
"devDependencies": { |
|||
"karma": "~0.10.0" |
|||
}, |
|||
"scripts": { |
|||
"test": "karma start config/karma.conf.js" |
|||
}, |
|||
"dependencies": {} |
|||
} |
@ -0,0 +1,7 @@ |
|||
Angular Unit Tests |
|||
================== |
|||
|
|||
To run the test suite, run these commands: |
|||
|
|||
npm install |
|||
npm test |
@ -0,0 +1,30 @@ |
|||
/*global describe, it, beforeEach, inject, expect, angular*/ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
beforeEach(module('todomvc')); |
|||
|
|||
describe('todoFocus directive', function () { |
|||
var scope, compile, browser; |
|||
|
|||
beforeEach(inject(function ($rootScope, $compile, $browser) { |
|||
scope = $rootScope.$new(); |
|||
compile = $compile; |
|||
browser = $browser; |
|||
})); |
|||
|
|||
it('should focus on truthy expression', function () { |
|||
var el = angular.element('<input todo-focus="focus">'); |
|||
scope.focus = false; |
|||
|
|||
compile(el)(scope); |
|||
expect(browser.deferredFns.length).toBe(0); |
|||
|
|||
scope.$apply(function () { |
|||
scope.focus = true; |
|||
}); |
|||
|
|||
expect(browser.deferredFns.length).toBe(1); |
|||
}); |
|||
}); |
|||
}()); |
@ -0,0 +1,189 @@ |
|||
/*global describe, it, beforeEach, inject, expect*/ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
describe('Todo Controller', function () { |
|||
var ctrl, scope; |
|||
var todoList; |
|||
var todoStorage = { |
|||
storage: {}, |
|||
get: function () { |
|||
return this.storage; |
|||
}, |
|||
put: function (value) { |
|||
this.storage = value; |
|||
} |
|||
}; |
|||
|
|||
// Load the module containing the app, only 'ng' is loaded by default.
|
|||
beforeEach(module('todomvc')); |
|||
|
|||
beforeEach(inject(function ($controller, $rootScope) { |
|||
scope = $rootScope.$new(); |
|||
ctrl = $controller('TodoCtrl', { $scope: scope }); |
|||
})); |
|||
|
|||
it('should not have an edited Todo on start', function () { |
|||
expect(scope.editedTodo).toBeNull(); |
|||
}); |
|||
|
|||
it('should not have any Todos on start', function () { |
|||
expect(scope.todos.length).toBe(0); |
|||
}); |
|||
|
|||
it('should have all Todos completed', function () { |
|||
scope.$digest(); |
|||
expect(scope.allChecked).toBeTruthy(); |
|||
}); |
|||
|
|||
describe('the filter', function () { |
|||
it('should default to ""', function () { |
|||
scope.$emit('$routeChangeSuccess'); |
|||
|
|||
expect(scope.status).toBe(''); |
|||
expect(scope.statusFilter).toBeNull(); |
|||
}); |
|||
|
|||
describe('being at /active', function () { |
|||
it('should filter non-completed', inject(function ($controller) { |
|||
ctrl = $controller('TodoCtrl', { |
|||
$scope: scope, |
|||
$routeParams: { |
|||
status: 'active' |
|||
} |
|||
}); |
|||
|
|||
scope.$emit('$routeChangeSuccess'); |
|||
expect(scope.statusFilter.completed).toBeFalsy(); |
|||
})); |
|||
}); |
|||
|
|||
describe('being at /completed', function () { |
|||
it('should filter completed', inject(function ($controller) { |
|||
ctrl = $controller('TodoCtrl', { |
|||
$scope: scope, |
|||
$routeParams: { |
|||
status: 'completed' |
|||
} |
|||
}); |
|||
|
|||
scope.$emit('$routeChangeSuccess'); |
|||
expect(scope.statusFilter.completed).toBeTruthy(); |
|||
})); |
|||
}); |
|||
}); |
|||
|
|||
describe('having no Todos', function () { |
|||
var ctrl; |
|||
|
|||
beforeEach(inject(function ($controller) { |
|||
todoStorage.storage = []; |
|||
ctrl = $controller('TodoCtrl', { |
|||
$scope: scope, |
|||
todoStorage: todoStorage |
|||
}); |
|||
scope.$digest(); |
|||
})); |
|||
|
|||
it('should not add empty Todos', function () { |
|||
scope.newTodo = ''; |
|||
scope.addTodo(); |
|||
scope.$digest(); |
|||
expect(scope.todos.length).toBe(0); |
|||
}); |
|||
|
|||
it('should not add items consisting only of whitespaces', function () { |
|||
scope.newTodo = ' '; |
|||
scope.addTodo(); |
|||
scope.$digest(); |
|||
expect(scope.todos.length).toBe(0); |
|||
}); |
|||
|
|||
|
|||
it('should trim whitespace from new Todos', function () { |
|||
scope.newTodo = ' buy some unicorns '; |
|||
scope.addTodo(); |
|||
scope.$digest(); |
|||
expect(scope.todos.length).toBe(1); |
|||
expect(scope.todos[0].title).toBe('buy some unicorns'); |
|||
}); |
|||
}); |
|||
|
|||
describe('having some saved Todos', function () { |
|||
var ctrl; |
|||
|
|||
beforeEach(inject(function ($controller) { |
|||
todoList = [{ |
|||
'title': 'Uncompleted Item 0', |
|||
'completed': false |
|||
}, { |
|||
'title': 'Uncompleted Item 1', |
|||
'completed': false |
|||
}, { |
|||
'title': 'Uncompleted Item 2', |
|||
'completed': false |
|||
}, { |
|||
'title': 'Completed Item 0', |
|||
'completed': true |
|||
}, { |
|||
'title': 'Completed Item 1', |
|||
'completed': true |
|||
}]; |
|||
|
|||
todoStorage.storage = todoList; |
|||
ctrl = $controller('TodoCtrl', { |
|||
$scope: scope, |
|||
todoStorage: todoStorage |
|||
}); |
|||
scope.$digest(); |
|||
})); |
|||
|
|||
it('should count Todos correctly', function () { |
|||
expect(scope.todos.length).toBe(5); |
|||
expect(scope.remainingCount).toBe(3); |
|||
expect(scope.completedCount).toBe(2); |
|||
expect(scope.allChecked).toBeFalsy(); |
|||
}); |
|||
|
|||
it('should save Todos to local storage', function () { |
|||
expect(todoStorage.storage.length).toBe(5); |
|||
}); |
|||
|
|||
it('should remove Todos w/o title on saving', function () { |
|||
var todo = todoList[2]; |
|||
todo.title = ''; |
|||
|
|||
scope.doneEditing(todo); |
|||
expect(scope.todos.length).toBe(4); |
|||
}); |
|||
|
|||
it('should trim Todos on saving', function () { |
|||
var todo = todoList[0]; |
|||
todo.title = ' buy moar unicorns '; |
|||
|
|||
scope.doneEditing(todo); |
|||
expect(scope.todos[0].title).toBe('buy moar unicorns'); |
|||
}); |
|||
|
|||
it('clearCompletedTodos() should clear completed Todos', function () { |
|||
scope.clearCompletedTodos(); |
|||
expect(scope.todos.length).toBe(3); |
|||
}); |
|||
|
|||
it('markAll() should mark all Todos completed', function () { |
|||
scope.markAll(); |
|||
scope.$digest(); |
|||
expect(scope.completedCount).toBe(5); |
|||
}); |
|||
|
|||
it('revertTodo() get a Todo to its previous state', function () { |
|||
var todo = todoList[0]; |
|||
scope.editTodo(todo); |
|||
todo.title = 'Unicorn sparkly skypuffles.'; |
|||
scope.revertEditing(todo); |
|||
scope.$digest(); |
|||
expect(scope.todos[0].title).toBe('Uncompleted Item 0'); |
|||
}); |
|||
}); |
|||
}); |
|||
}()); |
@ -0,0 +1,11 @@ |
|||
{ |
|||
"name": "todomvc-backbone", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"backbone": "~1.1.1", |
|||
"underscore": "~1.6.0", |
|||
"jquery": "~2.0.0", |
|||
"todomvc-common": "~0.1.4", |
|||
"backbone.localStorage": "~1.1.0" |
|||
} |
|||
} |
@ -0,0 +1,222 @@ |
|||
/** |
|||
* Backbone localStorage Adapter |
|||
* Version 1.1.7 |
|||
* |
|||
* https://github.com/jeromegn/Backbone.localStorage
|
|||
*/ |
|||
(function (root, factory) { |
|||
if (typeof exports === 'object' && typeof require === 'function') { |
|||
module.exports = factory(require("underscore"), require("backbone")); |
|||
} else if (typeof define === "function" && define.amd) { |
|||
// AMD. Register as an anonymous module.
|
|||
define(["underscore","backbone"], function(_, Backbone) { |
|||
// Use global variables if the locals are undefined.
|
|||
return factory(_ || root._, Backbone || root.Backbone); |
|||
}); |
|||
} else { |
|||
// RequireJS isn't being used. Assume underscore and backbone are loaded in <script> tags
|
|||
factory(_, Backbone); |
|||
} |
|||
}(this, function(_, Backbone) { |
|||
// A simple module to replace `Backbone.sync` with *localStorage*-based
|
|||
// persistence. Models are given GUIDS, and saved into a JSON object. Simple
|
|||
// as that.
|
|||
|
|||
// Hold reference to Underscore.js and Backbone.js in the closure in order
|
|||
// to make things work even if they are removed from the global namespace
|
|||
|
|||
// Generate four random hex digits.
|
|||
function S4() { |
|||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1); |
|||
}; |
|||
|
|||
// Generate a pseudo-GUID by concatenating random hexadecimal.
|
|||
function guid() { |
|||
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); |
|||
}; |
|||
|
|||
// Our Store is represented by a single JS object in *localStorage*. Create it
|
|||
// with a meaningful name, like the name you'd give a table.
|
|||
// window.Store is deprectated, use Backbone.LocalStorage instead
|
|||
Backbone.LocalStorage = window.Store = function(name) { |
|||
if( !this.localStorage ) { |
|||
throw "Backbone.localStorage: Environment does not support localStorage." |
|||
} |
|||
this.name = name; |
|||
var store = this.localStorage().getItem(this.name); |
|||
this.records = (store && store.split(",")) || []; |
|||
}; |
|||
|
|||
_.extend(Backbone.LocalStorage.prototype, { |
|||
|
|||
// Save the current state of the **Store** to *localStorage*.
|
|||
save: function() { |
|||
this.localStorage().setItem(this.name, this.records.join(",")); |
|||
}, |
|||
|
|||
// Add a model, giving it a (hopefully)-unique GUID, if it doesn't already
|
|||
// have an id of it's own.
|
|||
create: function(model) { |
|||
if (!model.id) { |
|||
model.id = guid(); |
|||
model.set(model.idAttribute, model.id); |
|||
} |
|||
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); |
|||
this.records.push(model.id.toString()); |
|||
this.save(); |
|||
return this.find(model); |
|||
}, |
|||
|
|||
// Update a model by replacing its copy in `this.data`.
|
|||
update: function(model) { |
|||
this.localStorage().setItem(this.name+"-"+model.id, JSON.stringify(model)); |
|||
if (!_.include(this.records, model.id.toString())) |
|||
this.records.push(model.id.toString()); this.save(); |
|||
return this.find(model); |
|||
}, |
|||
|
|||
// Retrieve a model from `this.data` by id.
|
|||
find: function(model) { |
|||
return this.jsonData(this.localStorage().getItem(this.name+"-"+model.id)); |
|||
}, |
|||
|
|||
// Return the array of all models currently in storage.
|
|||
findAll: function() { |
|||
// Lodash removed _#chain in v1.0.0-rc.1
|
|||
return (_.chain || _)(this.records) |
|||
.map(function(id){ |
|||
return this.jsonData(this.localStorage().getItem(this.name+"-"+id)); |
|||
}, this) |
|||
.compact() |
|||
.value(); |
|||
}, |
|||
|
|||
// Delete a model from `this.data`, returning it.
|
|||
destroy: function(model) { |
|||
if (model.isNew()) |
|||
return false |
|||
this.localStorage().removeItem(this.name+"-"+model.id); |
|||
this.records = _.reject(this.records, function(id){ |
|||
return id === model.id.toString(); |
|||
}); |
|||
this.save(); |
|||
return model; |
|||
}, |
|||
|
|||
localStorage: function() { |
|||
return localStorage; |
|||
}, |
|||
|
|||
// fix for "illegal access" error on Android when JSON.parse is passed null
|
|||
jsonData: function (data) { |
|||
return data && JSON.parse(data); |
|||
}, |
|||
|
|||
// Clear localStorage for specific collection.
|
|||
_clear: function() { |
|||
var local = this.localStorage(), |
|||
itemRe = new RegExp("^" + this.name + "-"); |
|||
|
|||
// Remove id-tracking item (e.g., "foo").
|
|||
local.removeItem(this.name); |
|||
|
|||
// Lodash removed _#chain in v1.0.0-rc.1
|
|||
// Match all data items (e.g., "foo-ID") and remove.
|
|||
(_.chain || _)(local).keys() |
|||
.filter(function (k) { return itemRe.test(k); }) |
|||
.each(function (k) { local.removeItem(k); }); |
|||
|
|||
this.records.length = 0; |
|||
}, |
|||
|
|||
// Size of localStorage.
|
|||
_storageSize: function() { |
|||
return this.localStorage().length; |
|||
} |
|||
|
|||
}); |
|||
|
|||
// localSync delegate to the model or collection's
|
|||
// *localStorage* property, which should be an instance of `Store`.
|
|||
// window.Store.sync and Backbone.localSync is deprecated, use Backbone.LocalStorage.sync instead
|
|||
Backbone.LocalStorage.sync = window.Store.sync = Backbone.localSync = function(method, model, options) { |
|||
var store = model.localStorage || model.collection.localStorage; |
|||
|
|||
var resp, errorMessage, syncDfd = Backbone.$.Deferred && Backbone.$.Deferred(); //If $ is having Deferred - use it.
|
|||
|
|||
try { |
|||
|
|||
switch (method) { |
|||
case "read": |
|||
resp = model.id != undefined ? store.find(model) : store.findAll(); |
|||
break; |
|||
case "create": |
|||
resp = store.create(model); |
|||
break; |
|||
case "update": |
|||
resp = store.update(model); |
|||
break; |
|||
case "delete": |
|||
resp = store.destroy(model); |
|||
break; |
|||
} |
|||
|
|||
} catch(error) { |
|||
if (error.code === 22 && store._storageSize() === 0) |
|||
errorMessage = "Private browsing is unsupported"; |
|||
else |
|||
errorMessage = error.message; |
|||
} |
|||
|
|||
if (resp) { |
|||
if (options && options.success) { |
|||
if (Backbone.VERSION === "0.9.10") { |
|||
options.success(model, resp, options); |
|||
} else { |
|||
options.success(resp); |
|||
} |
|||
} |
|||
if (syncDfd) { |
|||
syncDfd.resolve(resp); |
|||
} |
|||
|
|||
} else { |
|||
errorMessage = errorMessage ? errorMessage |
|||
: "Record Not Found"; |
|||
|
|||
if (options && options.error) |
|||
if (Backbone.VERSION === "0.9.10") { |
|||
options.error(model, errorMessage, options); |
|||
} else { |
|||
options.error(errorMessage); |
|||
} |
|||
|
|||
if (syncDfd) |
|||
syncDfd.reject(errorMessage); |
|||
} |
|||
|
|||
// add compatibility with $.ajax
|
|||
// always execute callback for success and error
|
|||
if (options && options.complete) options.complete(resp); |
|||
|
|||
return syncDfd && syncDfd.promise(); |
|||
}; |
|||
|
|||
Backbone.ajaxSync = Backbone.sync; |
|||
|
|||
Backbone.getSyncMethod = function(model) { |
|||
if(model.localStorage || (model.collection && model.collection.localStorage)) { |
|||
return Backbone.localSync; |
|||
} |
|||
|
|||
return Backbone.ajaxSync; |
|||
}; |
|||
|
|||
// Override 'Backbone.sync' to default to localSync,
|
|||
// the original 'Backbone.sync' is still available in 'Backbone.ajaxSync'
|
|||
Backbone.sync = function(method, model, options) { |
|||
return Backbone.getSyncMethod(model).apply(this, [method, model, options]); |
|||
}; |
|||
|
|||
return Backbone.LocalStorage; |
|||
})); |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,63 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="backbonejs"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Backbone.js • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp"> |
|||
<header id="header"> |
|||
<h1>todos</h1> |
|||
<input id="new-todo" placeholder="What needs to be done?" autofocus> |
|||
</header> |
|||
<section id="main"> |
|||
<input id="toggle-all" type="checkbox"> |
|||
<label for="toggle-all">Mark all as complete</label> |
|||
<ul id="todo-list"></ul> |
|||
</section> |
|||
<footer id="footer"></footer> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Written by <a href="https://github.com/addyosmani">Addy Osmani</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
<script type="text/template" id="item-template"> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" <%= completed ? 'checked' : '' %>> |
|||
<label><%- title %></label> |
|||
<button class="destroy"></button> |
|||
</div> |
|||
<input class="edit" value="<%- title %>"> |
|||
</script> |
|||
<script type="text/template" id="stats-template"> |
|||
<span id="todo-count"><strong><%= remaining %></strong> <%= remaining === 1 ? 'item' : 'items' %> left</span> |
|||
<ul id="filters"> |
|||
<li> |
|||
<a class="selected" href="#/">All</a> |
|||
</li> |
|||
<li> |
|||
<a href="#/active">Active</a> |
|||
</li> |
|||
<li> |
|||
<a href="#/completed">Completed</a> |
|||
</li> |
|||
</ul> |
|||
<% if (completed) { %> |
|||
<button id="clear-completed">Clear completed (<%= completed %>)</button> |
|||
<% } %> |
|||
</script> |
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script src="bower_components/jquery/jquery.js"></script> |
|||
<script src="bower_components/underscore/underscore.js"></script> |
|||
<script src="bower_components/backbone/backbone.js"></script> |
|||
<script src="bower_components/backbone.localStorage/backbone.localStorage.js"></script> |
|||
<script src="js/models/todo.js"></script> |
|||
<script src="js/collections/todos.js"></script> |
|||
<script src="js/views/todo-view.js"></script> |
|||
<script src="js/views/app-view.js"></script> |
|||
<script src="js/routers/router.js"></script> |
|||
<script src="js/app.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,12 @@ |
|||
/*global $ */ |
|||
/*jshint unused:false */ |
|||
var app = app || {}; |
|||
var ENTER_KEY = 13; |
|||
var ESC_KEY = 27; |
|||
|
|||
$(function () { |
|||
'use strict'; |
|||
|
|||
// kick things off by creating the `App`
|
|||
new app.AppView(); |
|||
}); |
@ -0,0 +1,48 @@ |
|||
/*global Backbone */ |
|||
var app = app || {}; |
|||
|
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Todo Collection
|
|||
// ---------------
|
|||
|
|||
// The collection of todos is backed by *localStorage* instead of a remote
|
|||
// server.
|
|||
var Todos = Backbone.Collection.extend({ |
|||
// Reference to this collection's model.
|
|||
model: app.Todo, |
|||
|
|||
// Save all of the todo items under the `"todos"` namespace.
|
|||
localStorage: new Backbone.LocalStorage('todos-backbone'), |
|||
|
|||
// Filter down the list of all todo items that are finished.
|
|||
completed: function () { |
|||
return this.filter(function (todo) { |
|||
return todo.get('completed'); |
|||
}); |
|||
}, |
|||
|
|||
// Filter down the list to only todo items that are still not finished.
|
|||
remaining: function () { |
|||
return this.without.apply(this, this.completed()); |
|||
}, |
|||
|
|||
// We keep the Todos in sequential order, despite being saved by unordered
|
|||
// GUID in the database. This generates the next order number for new items.
|
|||
nextOrder: function () { |
|||
if (!this.length) { |
|||
return 1; |
|||
} |
|||
return this.last().get('order') + 1; |
|||
}, |
|||
|
|||
// Todos are sorted by their original insertion order.
|
|||
comparator: function (todo) { |
|||
return todo.get('order'); |
|||
} |
|||
}); |
|||
|
|||
// Create our global collection of **Todos**.
|
|||
app.todos = new Todos(); |
|||
})(); |
@ -0,0 +1,26 @@ |
|||
/*global Backbone */ |
|||
var app = app || {}; |
|||
|
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Todo Model
|
|||
// ----------
|
|||
|
|||
// Our basic **Todo** model has `title`, `order`, and `completed` attributes.
|
|||
app.Todo = Backbone.Model.extend({ |
|||
// Default attributes for the todo
|
|||
// and ensure that each todo created has `title` and `completed` keys.
|
|||
defaults: { |
|||
title: '', |
|||
completed: false |
|||
}, |
|||
|
|||
// Toggle the `completed` state of this todo item.
|
|||
toggle: function () { |
|||
this.save({ |
|||
completed: !this.get('completed') |
|||
}); |
|||
} |
|||
}); |
|||
})(); |
@ -0,0 +1,26 @@ |
|||
/*global Backbone */ |
|||
var app = app || {}; |
|||
|
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Todo Router
|
|||
// ----------
|
|||
var TodoRouter = Backbone.Router.extend({ |
|||
routes: { |
|||
'*filter': 'setFilter' |
|||
}, |
|||
|
|||
setFilter: function (param) { |
|||
// Set the current filter to be used
|
|||
app.TodoFilter = param || ''; |
|||
|
|||
// Trigger a collection filter event, causing hiding/unhiding
|
|||
// of Todo view items
|
|||
app.todos.trigger('filter'); |
|||
} |
|||
}); |
|||
|
|||
app.TodoRouter = new TodoRouter(); |
|||
Backbone.history.start(); |
|||
})(); |
@ -0,0 +1,131 @@ |
|||
/*global Backbone, jQuery, _, ENTER_KEY */ |
|||
var app = app || {}; |
|||
|
|||
(function ($) { |
|||
'use strict'; |
|||
|
|||
// The Application
|
|||
// ---------------
|
|||
|
|||
// Our overall **AppView** is the top-level piece of UI.
|
|||
app.AppView = Backbone.View.extend({ |
|||
|
|||
// Instead of generating a new element, bind to the existing skeleton of
|
|||
// the App already present in the HTML.
|
|||
el: '#todoapp', |
|||
|
|||
// Our template for the line of statistics at the bottom of the app.
|
|||
statsTemplate: _.template($('#stats-template').html()), |
|||
|
|||
// Delegated events for creating new items, and clearing completed ones.
|
|||
events: { |
|||
'keypress #new-todo': 'createOnEnter', |
|||
'click #clear-completed': 'clearCompleted', |
|||
'click #toggle-all': 'toggleAllComplete' |
|||
}, |
|||
|
|||
// At initialization we bind to the relevant events on the `Todos`
|
|||
// collection, when items are added or changed. Kick things off by
|
|||
// loading any preexisting todos that might be saved in *localStorage*.
|
|||
initialize: function () { |
|||
this.allCheckbox = this.$('#toggle-all')[0]; |
|||
this.$input = this.$('#new-todo'); |
|||
this.$footer = this.$('#footer'); |
|||
this.$main = this.$('#main'); |
|||
this.$list = $('#todo-list'); |
|||
|
|||
this.listenTo(app.todos, 'add', this.addOne); |
|||
this.listenTo(app.todos, 'reset', this.addAll); |
|||
this.listenTo(app.todos, 'change:completed', this.filterOne); |
|||
this.listenTo(app.todos, 'filter', this.filterAll); |
|||
this.listenTo(app.todos, 'all', this.render); |
|||
|
|||
// Suppresses 'add' events with {reset: true} and prevents the app view
|
|||
// from being re-rendered for every model. Only renders when the 'reset'
|
|||
// event is triggered at the end of the fetch.
|
|||
app.todos.fetch({reset: true}); |
|||
}, |
|||
|
|||
// Re-rendering the App just means refreshing the statistics -- the rest
|
|||
// of the app doesn't change.
|
|||
render: function () { |
|||
var completed = app.todos.completed().length; |
|||
var remaining = app.todos.remaining().length; |
|||
|
|||
if (app.todos.length) { |
|||
this.$main.show(); |
|||
this.$footer.show(); |
|||
|
|||
this.$footer.html(this.statsTemplate({ |
|||
completed: completed, |
|||
remaining: remaining |
|||
})); |
|||
|
|||
this.$('#filters li a') |
|||
.removeClass('selected') |
|||
.filter('[href="#/' + (app.TodoFilter || '') + '"]') |
|||
.addClass('selected'); |
|||
} else { |
|||
this.$main.hide(); |
|||
this.$footer.hide(); |
|||
} |
|||
|
|||
this.allCheckbox.checked = !remaining; |
|||
}, |
|||
|
|||
// Add a single todo item to the list by creating a view for it, and
|
|||
// appending its element to the `<ul>`.
|
|||
addOne: function (todo) { |
|||
var view = new app.TodoView({ model: todo }); |
|||
this.$list.append(view.render().el); |
|||
}, |
|||
|
|||
// Add all items in the **Todos** collection at once.
|
|||
addAll: function () { |
|||
this.$list.html(''); |
|||
app.todos.each(this.addOne, this); |
|||
}, |
|||
|
|||
filterOne: function (todo) { |
|||
todo.trigger('visible'); |
|||
}, |
|||
|
|||
filterAll: function () { |
|||
app.todos.each(this.filterOne, this); |
|||
}, |
|||
|
|||
// Generate the attributes for a new Todo item.
|
|||
newAttributes: function () { |
|||
return { |
|||
title: this.$input.val().trim(), |
|||
order: app.todos.nextOrder(), |
|||
completed: false |
|||
}; |
|||
}, |
|||
|
|||
// If you hit return in the main input field, create new **Todo** model,
|
|||
// persisting it to *localStorage*.
|
|||
createOnEnter: function (e) { |
|||
if (e.which === ENTER_KEY && this.$input.val().trim()) { |
|||
app.todos.create(this.newAttributes()); |
|||
this.$input.val(''); |
|||
} |
|||
}, |
|||
|
|||
// Clear all completed todo items, destroying their models.
|
|||
clearCompleted: function () { |
|||
_.invoke(app.todos.completed(), 'destroy'); |
|||
return false; |
|||
}, |
|||
|
|||
toggleAllComplete: function () { |
|||
var completed = this.allCheckbox.checked; |
|||
|
|||
app.todos.each(function (todo) { |
|||
todo.save({ |
|||
completed: completed |
|||
}); |
|||
}); |
|||
} |
|||
}); |
|||
})(jQuery); |
@ -0,0 +1,134 @@ |
|||
/*global Backbone, jQuery, _, ENTER_KEY, ESC_KEY */ |
|||
var app = app || {}; |
|||
|
|||
(function ($) { |
|||
'use strict'; |
|||
|
|||
// Todo Item View
|
|||
// --------------
|
|||
|
|||
// The DOM element for a todo item...
|
|||
app.TodoView = Backbone.View.extend({ |
|||
//... is a list tag.
|
|||
tagName: 'li', |
|||
|
|||
// Cache the template function for a single item.
|
|||
template: _.template($('#item-template').html()), |
|||
|
|||
// The DOM events specific to an item.
|
|||
events: { |
|||
'click .toggle': 'toggleCompleted', |
|||
'dblclick label': 'edit', |
|||
'click .destroy': 'clear', |
|||
'keypress .edit': 'updateOnEnter', |
|||
'keydown .edit': 'revertOnEscape', |
|||
'blur .edit': 'close' |
|||
}, |
|||
|
|||
// The TodoView listens for changes to its model, re-rendering. Since
|
|||
// there's a one-to-one correspondence between a **Todo** and a
|
|||
// **TodoView** in this app, we set a direct reference on the model for
|
|||
// convenience.
|
|||
initialize: function () { |
|||
this.listenTo(this.model, 'change', this.render); |
|||
this.listenTo(this.model, 'destroy', this.remove); |
|||
this.listenTo(this.model, 'visible', this.toggleVisible); |
|||
}, |
|||
|
|||
// Re-render the titles of the todo item.
|
|||
render: function () { |
|||
// Backbone LocalStorage is adding `id` attribute instantly after
|
|||
// creating a model. This causes our TodoView to render twice. Once
|
|||
// after creating a model and once on `id` change. We want to
|
|||
// filter out the second redundant render, which is caused by this
|
|||
// `id` change. It's known Backbone LocalStorage bug, therefore
|
|||
// we've to create a workaround.
|
|||
// https://github.com/tastejs/todomvc/issues/469
|
|||
if (this.model.changed.id !== undefined) { |
|||
return; |
|||
} |
|||
|
|||
this.$el.html(this.template(this.model.toJSON())); |
|||
this.$el.toggleClass('completed', this.model.get('completed')); |
|||
this.toggleVisible(); |
|||
this.$input = this.$('.edit'); |
|||
return this; |
|||
}, |
|||
|
|||
toggleVisible: function () { |
|||
this.$el.toggleClass('hidden', this.isHidden()); |
|||
}, |
|||
|
|||
isHidden: function () { |
|||
var isCompleted = this.model.get('completed'); |
|||
return (// hidden cases only
|
|||
(!isCompleted && app.TodoFilter === 'completed') || |
|||
(isCompleted && app.TodoFilter === 'active') |
|||
); |
|||
}, |
|||
|
|||
// Toggle the `"completed"` state of the model.
|
|||
toggleCompleted: function () { |
|||
this.model.toggle(); |
|||
}, |
|||
|
|||
// Switch this view into `"editing"` mode, displaying the input field.
|
|||
edit: function () { |
|||
this.$el.addClass('editing'); |
|||
this.$input.focus(); |
|||
}, |
|||
|
|||
// Close the `"editing"` mode, saving changes to the todo.
|
|||
close: function () { |
|||
var value = this.$input.val(); |
|||
var trimmedValue = value.trim(); |
|||
|
|||
// We don't want to handle blur events from an item that is no
|
|||
// longer being edited. Relying on the CSS class here has the
|
|||
// benefit of us not having to maintain state in the DOM and the
|
|||
// JavaScript logic.
|
|||
if (!this.$el.hasClass('editing')) { |
|||
return; |
|||
} |
|||
|
|||
if (trimmedValue) { |
|||
this.model.save({ title: trimmedValue }); |
|||
|
|||
if (value !== trimmedValue) { |
|||
// Model values changes consisting of whitespaces only are
|
|||
// not causing change to be triggered Therefore we've to
|
|||
// compare untrimmed version with a trimmed one to check
|
|||
// whether anything changed
|
|||
// And if yes, we've to trigger change event ourselves
|
|||
this.model.trigger('change'); |
|||
} |
|||
} else { |
|||
this.clear(); |
|||
} |
|||
|
|||
this.$el.removeClass('editing'); |
|||
}, |
|||
|
|||
// If you hit `enter`, we're through editing the item.
|
|||
updateOnEnter: function (e) { |
|||
if (e.which === ENTER_KEY) { |
|||
this.close(); |
|||
} |
|||
}, |
|||
|
|||
// If you're pressing `escape` we revert your change by simply leaving
|
|||
// the `editing` state.
|
|||
revertOnEscape: function (e) { |
|||
if (e.which === ESC_KEY) { |
|||
this.$el.removeClass('editing'); |
|||
// Also reset the hidden input back to the original value.
|
|||
this.$input.val(this.model.get('title')); |
|||
} |
|||
}, |
|||
|
|||
// Remove the item, destroy the model from *localStorage* and delete its view.
|
|||
clear: function () { |
|||
this.model.destroy(); |
|||
} |
|||
}); |
|||
})(jQuery); |
@ -0,0 +1,29 @@ |
|||
# Backbone.js TodoMVC Example |
|||
|
|||
> Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. |
|||
|
|||
> _[Backbone.js - backbonejs.org](http://backbonejs.org)_ |
|||
|
|||
|
|||
## Learning Backbone.js |
|||
|
|||
The [Backbone.js website](http://backbonejs.org) is a great resource for getting started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [Annotated source code](http://backbonejs.org/docs/backbone.html) |
|||
* [Applications built with Backbone.js](http://backbonejs.org/#examples) |
|||
* [FAQ](http://backbonejs.org/#faq) |
|||
|
|||
Articles and guides from the community: |
|||
|
|||
* [Developing Backbone.js Applications](http://addyosmani.github.io/backbone-fundamentals) |
|||
* [Collection of tutorials, blog posts, and example sites](https://github.com/documentcloud/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites) |
|||
|
|||
Get help from other Backbone.js users: |
|||
|
|||
* [Backbone.js on StackOverflow](http://stackoverflow.com/questions/tagged/backbone.js) |
|||
* [Google Groups mailing list](https://groups.google.com/forum/#!forum/backbonejs) |
|||
* [Backbone.js on Twitter](http://twitter.com/documentcloud) |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
@ -0,0 +1,10 @@ |
|||
{ |
|||
"name": "todomvc-canjs", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"jquery": "~2.0.0", |
|||
"canjs": "~2.0.0", |
|||
"canjs-localstorage": "~0.2.0", |
|||
"todomvc-common": "~0.1.6" |
|||
} |
|||
} |
@ -0,0 +1,76 @@ |
|||
(function(define) { |
|||
if (typeof define == "undefined") { |
|||
define = function(deps, fn) { |
|||
can.Model.LocalStorage = fn(can.Model); |
|||
} |
|||
} |
|||
|
|||
define(['can/model'], function(Model) { |
|||
return Model.extend({ |
|||
// Implement local storage handling
|
|||
localStore: function(cb) { |
|||
var name = this.name, |
|||
data = JSON.parse(window.localStorage[name] || (window.localStorage[name] = '[]')), |
|||
res = cb.call(this, data); |
|||
if (res !== false) { |
|||
can.each(data, function(todo) { |
|||
delete todo.editing; |
|||
}); |
|||
window.localStorage[name] = JSON.stringify(data); |
|||
} |
|||
}, |
|||
|
|||
findAll: function(params) { |
|||
var def = new can.Deferred(); |
|||
this.localStore(function(todos) { |
|||
var instances = [], |
|||
self = this; |
|||
can.each(todos, function(todo) { |
|||
instances.push(new self(todo)); |
|||
}); |
|||
def.resolve({data: instances}); |
|||
}); |
|||
return def; |
|||
}, |
|||
|
|||
destroy: function(id) { |
|||
var def = new can.Deferred(); |
|||
this.localStore(function(todos) { |
|||
for (var i = 0; i < todos.length; i++) { |
|||
if (todos[i].id === id) { |
|||
todos.splice(i, 1); |
|||
break; |
|||
} |
|||
} |
|||
def.resolve({}); |
|||
}); |
|||
return def; |
|||
}, |
|||
|
|||
create: function(attrs) { |
|||
var def = new can.Deferred(); |
|||
this.localStore(function(todos) { |
|||
attrs.id = attrs.id || parseInt(100000 * Math.random(), 10); |
|||
todos.push(attrs); |
|||
}); |
|||
def.resolve({id: attrs.id}); |
|||
return def; |
|||
}, |
|||
|
|||
update: function(id, attrs) { |
|||
var def = new can.Deferred(), todo; |
|||
this.localStore(function(todos) { |
|||
for (var i = 0; i < todos.length; i++) { |
|||
if (todos[i].id === id) { |
|||
todo = todos[i]; |
|||
break; |
|||
} |
|||
} |
|||
can.extend(todo, attrs); |
|||
}); |
|||
def.resolve({}); |
|||
return def; |
|||
} |
|||
}, {}); |
|||
}); |
|||
})(window.define); |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,63 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="canjs"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>CanJS • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp"> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Written by <a href="http://bitovi.com">Bitovi</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
<script type="text/mustache" id="app-template"> |
|||
<todo-app> |
|||
<header id="header"> |
|||
<h1>todos</h1> |
|||
<input id="new-todo" placeholder="What needs to be done?" autofocus="" can-enter="createTodo"> |
|||
</header> |
|||
<section id="main" class="{{^if todos.length}}hidden{{/if}}"> |
|||
<input id="toggle-all" type="checkbox" {{#if todos.allComplete}}checked="checked"{{/if}} can-click="toggleAll"> |
|||
<label for="toggle-all">Mark all as complete</label> |
|||
<ul id="todo-list"> |
|||
{{#each displayList}} |
|||
<li class="todo{{#if complete}} completed{{/if}}{{#if editing}} editing{{/if}}"> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" can-value="complete"> |
|||
<label can-dblclick="edit">{{text}}</label> |
|||
<button class="destroy" can-click="destroy"></button> |
|||
</div> |
|||
<input class="edit" type="text" value="{{text}}" can-blur="updateTodo" |
|||
can-keyup="cancelEditing" can-enter="updateTodo"> |
|||
</li> |
|||
{{/each}} |
|||
</ul> |
|||
</section> |
|||
|
|||
<footer id="footer" class="{{^if todos.length}}hidden{{/if}}"> |
|||
<span id="todo-count"> |
|||
<strong>{{todos.remaining}}</strong> {{plural "item" todos.remaining}} left |
|||
</span> |
|||
<ul id="filters"> |
|||
<li>{{{link "All" undefined}}}</li> |
|||
<li>{{{link "Active" "active"}}}</li> |
|||
<li>{{{link "Completed" "completed"}}}</li> |
|||
</ul> |
|||
<button id="clear-completed" class="{{^if todos.completed.length}}hidden{{/if}}" can-click="clearCompleted"> |
|||
Clear completed ({{todos.completed.length}}) |
|||
</button> |
|||
</footer> |
|||
</todo-app> |
|||
</script> |
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script src="bower_components/jquery/jquery.js"></script> |
|||
<script src="bower_components/canjs/can.jquery.js"></script> |
|||
<script src="bower_components/canjs-localstorage/can.localstorage.js"></script> |
|||
<script src="js/models/todo.js"></script> |
|||
<script src="js/components/todo-app.js"></script> |
|||
<script src="js/app.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,15 @@ |
|||
/* global $, can */ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
$(function () { |
|||
// Set up a route that maps to the `filter` attribute
|
|||
can.route(':filter'); |
|||
|
|||
// Render #app-template
|
|||
$('#todoapp').html(can.view('app-template', {})); |
|||
|
|||
// Start the router
|
|||
can.route.ready(); |
|||
}); |
|||
})(); |
@ -0,0 +1,97 @@ |
|||
/* global can */ |
|||
(function (namespace) { |
|||
'use strict'; |
|||
|
|||
var ESCAPE_KEY = 27; |
|||
|
|||
can.Component.extend({ |
|||
// Create this component on a tag like `<todo-app>`.
|
|||
tag: 'todo-app', |
|||
scope: { |
|||
// Store the Todo model in the scope
|
|||
Todo: namespace.Models.Todo, |
|||
// A list of all Todos retrieved from LocalStorage
|
|||
todos: new namespace.Models.Todo.List({}), |
|||
// Edit a Todo
|
|||
edit: function (todo, el) { |
|||
todo.attr('editing', true); |
|||
el.parents('.todo').find('.edit').focus(); |
|||
}, |
|||
cancelEditing: function (todo, el, ev) { |
|||
if (ev.which === ESCAPE_KEY) { |
|||
el.val(todo.attr('text')); |
|||
todo.attr('editing', false); |
|||
} |
|||
}, |
|||
// Returns a list of Todos filtered based on the route
|
|||
displayList: function () { |
|||
var filter = can.route.attr('filter'); |
|||
return this.todos.filter(function (todo) { |
|||
if (filter === 'completed') { |
|||
return todo.attr('complete'); |
|||
} |
|||
|
|||
if (filter === 'active') { |
|||
return !todo.attr('complete'); |
|||
} |
|||
|
|||
return true; |
|||
}); |
|||
}, |
|||
updateTodo: function (todo, el) { |
|||
var value = can.trim(el.val()); |
|||
|
|||
if (value === '') { |
|||
todo.destroy(); |
|||
} else { |
|||
todo.attr({ |
|||
editing: false, |
|||
text: value |
|||
}); |
|||
} |
|||
}, |
|||
createTodo: function (context, el) { |
|||
var value = can.trim(el.val()); |
|||
var TodoModel = this.Todo; |
|||
|
|||
if (value !== '') { |
|||
new TodoModel({ |
|||
text: value, |
|||
complete: false |
|||
}).save(); |
|||
|
|||
can.route.removeAttr('filter'); |
|||
el.val(''); |
|||
} |
|||
}, |
|||
toggleAll: function (scope, el) { |
|||
var toggle = el.prop('checked'); |
|||
this.attr('todos').each(function (todo) { |
|||
todo.attr('complete', toggle); |
|||
}); |
|||
}, |
|||
clearCompleted: function () { |
|||
this.attr('todos').completed().forEach(function (todo) { |
|||
todo.destroy(); |
|||
}); |
|||
} |
|||
}, |
|||
events: { |
|||
// When a new Todo has been created, add it to the todo list
|
|||
'{Todo} created': function (Construct, ev, todo) { |
|||
this.scope.attr('todos').push(todo); |
|||
} |
|||
}, |
|||
helpers: { |
|||
link: function (name, filter) { |
|||
var data = filter ? { filter: filter } : {}; |
|||
return can.route.link(name, data, { |
|||
className: can.route.attr('filter') === filter ? 'selected' : '' |
|||
}); |
|||
}, |
|||
plural: function (singular, num) { |
|||
return num() === 1 ? singular : singular + 's'; |
|||
} |
|||
} |
|||
}); |
|||
})(this); |
@ -0,0 +1,50 @@ |
|||
/*global can */ |
|||
(function (namespace) { |
|||
'use strict'; |
|||
|
|||
// Basic Todo entry model
|
|||
var Todo = can.Model.LocalStorage.extend({ |
|||
storageName: 'todos-canjs' |
|||
}, { |
|||
init: function () { |
|||
// Autosave when changing the text or completing the todo
|
|||
this.on('change', function (ev, prop) { |
|||
if (prop === 'text' || prop === 'complete') { |
|||
ev.target.save(); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
// List for Todos
|
|||
Todo.List = Todo.List.extend({ |
|||
filter: function (check) { |
|||
var list = []; |
|||
|
|||
this.each(function (todo) { |
|||
if (check(todo)) { |
|||
list.push(todo); |
|||
} |
|||
}); |
|||
|
|||
return list; |
|||
}, |
|||
|
|||
completed: function () { |
|||
return this.filter(function (todo) { |
|||
return todo.attr('complete'); |
|||
}); |
|||
}, |
|||
|
|||
remaining: function () { |
|||
return this.attr('length') - this.completed().length; |
|||
}, |
|||
|
|||
allComplete: function () { |
|||
return this.attr('length') === this.completed().length; |
|||
} |
|||
}); |
|||
|
|||
namespace.Models = namespace.Models || {}; |
|||
namespace.Models.Todo = Todo; |
|||
})(this); |
@ -0,0 +1,48 @@ |
|||
# CanJS TodoMVC Example |
|||
|
|||
> CanJS is a MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy. |
|||
|
|||
> _[CanJS - canjs.com](http://canjs.com)_ |
|||
|
|||
## Learning CanJS |
|||
|
|||
The [CanJS website](http://canjs.com) is a great resource for getting started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [CanJS guides documentation](http://canjs.com/guides/index.html) |
|||
* [API documentation](http://canjs.com/docs/index.html) |
|||
* [Blog](http://bitovi.com/blog/tag/canjs) |
|||
* [Getting started video](http://www.youtube.com/watch?v=GdT4Oq6ZQ68) |
|||
|
|||
Get help from other CanJS users: |
|||
|
|||
* [CanJS on StackOverflow](http://stackoverflow.com/questions/tagged/canjs) |
|||
* [CanJS Forums](http://forum.javascriptmvc.com/#Forum/canjs) |
|||
* [CanJS on Twitter](http://twitter.com/canjs) |
|||
* [#canjs](http://webchat.freenode.net/?channels=canjs) IRC channel on Freenode |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
|||
|
|||
|
|||
## Implementation |
|||
|
|||
The CanJS TodoMVC example uses [can.Component](http://canjs.com/guides/Components.html) introduced in CanJS 2.0. |
|||
can.Component supports declarative view bindings using Mustache/Handlebars as the template syntax. |
|||
|
|||
Version 2 is mostly backwards compatible with previous 1.1.x version. For alternative architecture examples have a look at |
|||
the [TodoMVC 1.2.0 CanJS example](https://github.com/tastejs/todomvc/tree/1.2.0/architecture-examples/canjs). |
|||
|
|||
### CanJS and JavaScriptMVC |
|||
|
|||
CanJS is the extracted, more modern and more library-like MVC parts of [JavaScriptMVC](http://javascriptmvc.com), formerly known as jQueryMX. |
|||
|
|||
JavaScriptMVC 3.3 uses CanJS for the MVC structure so this TodoMVC example applies to JavaScriptMVC as well. |
|||
|
|||
Additionally, JavaScriptMVC contains: |
|||
|
|||
- [CanJS](http://canjs.com) - For the MVC parts |
|||
- [jQuery++](http://jquerypp.com) - jQuery's missing utils and special events |
|||
- [StealJS](http://javascriptmvc.com/docs.html#!stealjs) - A JavaScript package manager |
|||
- [DocumentJS](http://javascriptmvc.com/docs.html#!DocumentJS) - A documentation engine |
|||
- [FuncUnit](http://funcunit.com) - jQuery style functional unit testing |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"name": "todomvc-closure", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"todomvc-common": "~0.1.4" |
|||
} |
|||
} |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1 @@ |
|||
2.3.5 |
@ -0,0 +1 @@ |
|||
plovr-4b3caf2b7d84 |
@ -0,0 +1,47 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="closure"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Closure • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp"> |
|||
<header id="header"> |
|||
<h1>todos</h1> |
|||
<input id="new-todo" placeholder="What needs to be done?" autofocus> |
|||
</header> |
|||
<section id="main"> |
|||
<input id="toggle-all" type="checkbox"> |
|||
<label for="toggle-all">Mark all as complete</label> |
|||
<ul id="todo-list"> |
|||
</ul> |
|||
</section> |
|||
<footer id="footer"> |
|||
<ul id="filters"> |
|||
<li> |
|||
<a class="selected" href="#/">All</a> |
|||
</li> |
|||
<li> |
|||
<a href="#/active">Active</a> |
|||
</li> |
|||
<li> |
|||
<a href="#/completed">Completed</a> |
|||
</li> |
|||
</ul> |
|||
</footer> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Created by <a href="http://www.scottlogic.co.uk/blog/chris/">Chris Price</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<!-- The compiled version (to update run java -jar build/plovr.jar build plovr.json > js/compiled.js) --> |
|||
<script type="text/javascript" src="js/compiled.js"></script> |
|||
<!-- The RAW development version (to serve the files run java -jar build/plovr.jar serve plovr.json) --> |
|||
<!-- <script type="text/javascript" src="http://localhost:9810/compile?id=todomvc&mode=RAW"></script> --> |
|||
<!-- The COMPILED development version (to serve the files run java -jar build/plovr.jar serve plovr.json) --> |
|||
<!-- <script type="text/javascript" src="http://localhost:9810/compile?id=todomvc&mode=ADVANCED"></script> --> |
|||
</body> |
|||
</html> |
@ -0,0 +1,245 @@ |
|||
goog.provide('todomvc'); |
|||
|
|||
goog.require('goog.History'); |
|||
goog.require('goog.array'); |
|||
goog.require('goog.dom.query'); |
|||
goog.require('goog.events.EventType'); |
|||
goog.require('goog.events.KeyCodes'); |
|||
goog.require('goog.storage.Storage'); |
|||
goog.require('goog.storage.mechanism.mechanismfactory'); |
|||
goog.require('goog.string'); |
|||
goog.require('goog.ui.Component'); |
|||
goog.require('goog.ui.Control'); |
|||
goog.require('todomvc.model.ToDoItem'); |
|||
goog.require('todomvc.model.ToDoItemStore'); |
|||
goog.require('todomvc.view'); |
|||
goog.require('todomvc.view.ClearCompletedControlRenderer'); |
|||
goog.require('todomvc.view.ItemCountControlRenderer'); |
|||
goog.require('todomvc.view.ToDoItemControl'); |
|||
goog.require('todomvc.view.ToDoListContainer'); |
|||
|
|||
/** |
|||
* @fileoverview The controller/business logic for the application. |
|||
* |
|||
* This file creates the interface and marshals changes from the interface |
|||
* to the model and back. |
|||
*/ |
|||
|
|||
/** |
|||
* @type {todomvc.model.ToDoItemStore} |
|||
*/ |
|||
var itemStore = new todomvc.model.ToDoItemStore(); |
|||
itemStore.addEventListener(todomvc.model.ToDoItemStore.ChangeEventType, |
|||
redraw); |
|||
|
|||
/** |
|||
* @type {todomvc.view.ToDoListContainer} |
|||
*/ |
|||
var container = new todomvc.view.ToDoListContainer(); |
|||
container.decorate(document.getElementById('todo-list')); |
|||
|
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var main = document.getElementById('main'); |
|||
|
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var footer = document.getElementById('footer'); |
|||
|
|||
/** |
|||
* @type {goog.ui.Control} |
|||
*/ |
|||
var itemCountControl = new goog.ui.Control(null, |
|||
todomvc.view.ItemCountControlRenderer.getInstance()); |
|||
itemCountControl.render(footer); |
|||
|
|||
/** |
|||
* @type {goog.ui.Control} |
|||
*/ |
|||
var clearCompletedControl = new goog.ui.Control(null, |
|||
todomvc.view.ClearCompletedControlRenderer.getInstance()); |
|||
clearCompletedControl.render(footer); |
|||
|
|||
goog.events.listen(clearCompletedControl, |
|||
goog.ui.Component.EventType.ACTION, function(e) { |
|||
// go backwards to avoid collection modification problems
|
|||
goog.array.forEachRight(itemStore.getAll(), function(model) { |
|||
if (model.isDone()) { |
|||
itemStore.remove(model); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var toggleAll = document.getElementById('toggle-all'); |
|||
goog.events.listen(toggleAll, goog.events.EventType.CLICK, function(e) { |
|||
/** |
|||
* @type {boolean} |
|||
*/ |
|||
var state = toggleAll.checked; |
|||
goog.array.forEach(itemStore.getAll(), function(model) { |
|||
/** |
|||
* @type {!todomvc.model.ToDoItem} |
|||
*/ |
|||
var updatedModel = new todomvc.model.ToDoItem( |
|||
model.getNote(), state, model.getId()); |
|||
|
|||
itemStore.addOrUpdate(updatedModel); |
|||
}); |
|||
}); |
|||
|
|||
/** |
|||
* Enum for the three possible route values |
|||
* @enum {!string} |
|||
*/ |
|||
todomvc.Route = { |
|||
ALL: '/', |
|||
ACTIVE: '/active', |
|||
COMPLETED: '/completed' |
|||
}; |
|||
|
|||
/** |
|||
* @type {!todomvc.Route} |
|||
*/ |
|||
var currentRoute = todomvc.Route.ALL; |
|||
|
|||
/** |
|||
* @type {!goog.History} |
|||
*/ |
|||
var history = new goog.History(); |
|||
goog.events.listen(history, goog.history.EventType.NAVIGATE, |
|||
function(e) { |
|||
// constrain the route to be one of the enum values
|
|||
switch (e.token) { |
|||
case todomvc.Route.ALL: |
|||
case todomvc.Route.ACTIVE: |
|||
case todomvc.Route.COMPLETED: |
|||
if (e.token !== currentRoute) { |
|||
currentRoute = e.token; |
|||
redraw(); |
|||
} |
|||
break; |
|||
default: |
|||
history.replaceToken(todomvc.Route.ALL); |
|||
break; |
|||
} |
|||
}); |
|||
|
|||
function redraw() { |
|||
container.removeChildren(true); |
|||
/** |
|||
* @type {Array.<todomvc.model.ToDoItem>} |
|||
*/ |
|||
var items = itemStore.getAll(); |
|||
goog.array.forEach(items, function(item) { |
|||
// filter based on current route
|
|||
if ((currentRoute === todomvc.Route.ACTIVE && item.isDone()) || |
|||
(currentRoute === todomvc.Route.COMPLETED && !item.isDone())) { |
|||
return; |
|||
} |
|||
|
|||
/** |
|||
* @type {todomvc.view.ToDoItemControl} |
|||
*/ |
|||
var control = new todomvc.view.ToDoItemControl(); |
|||
|
|||
control.setContent(item.getNote()); |
|||
control.setChecked(item.isDone()); |
|||
control.setModel(item); |
|||
|
|||
container.addChild(control, true); |
|||
}); |
|||
|
|||
var doneCount = /** @type {number} */ |
|||
(goog.array.reduce(items, function(count, model) { |
|||
return model.isDone() ? count + 1 : count; |
|||
}, 0)); |
|||
var remainingCount = items.length - (doneCount); |
|||
toggleAll.checked = remainingCount === 0; |
|||
itemCountControl.setContent(remainingCount.toString()); |
|||
clearCompletedControl.setContent(doneCount.toString()); |
|||
clearCompletedControl.setVisible(doneCount > 0); |
|||
goog.style.showElement(main, items.length > 0); |
|||
goog.style.showElement(footer, items.length > 0); |
|||
|
|||
/** |
|||
* @type {Array.<Element>} |
|||
*/ |
|||
var routeLinks = /** @type {Array.<Element>} */ |
|||
(goog.dom.query('#filters a')); |
|||
goog.array.forEach(routeLinks, function(link, i) { |
|||
if ((currentRoute === todomvc.Route.ALL && i === 0) || |
|||
(currentRoute === todomvc.Route.ACTIVE && i === 1) || |
|||
(currentRoute === todomvc.Route.COMPLETED && i === 2)) { |
|||
link.className = 'selected'; |
|||
} else { |
|||
link.className = ''; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
goog.events.listen(container, |
|||
todomvc.view.ToDoItemControl.EventType.EDIT, function(e) { |
|||
/** |
|||
* @type {todomvc.view.ToDoItemControl} |
|||
*/ |
|||
var control = e.target; |
|||
|
|||
/** |
|||
* @type {todomvc.model.ToDoItem} |
|||
*/ |
|||
var originalModel = /**@type {todomvc.model.ToDoItem} */ |
|||
(control.getModel()); |
|||
|
|||
/** |
|||
* @type {!todomvc.model.ToDoItem} |
|||
*/ |
|||
var updatedModel = new todomvc.model.ToDoItem( |
|||
(/**@type {!string} */ control.getContent()), |
|||
(/**@type {!boolean} */ control.isChecked()), |
|||
originalModel.getId()); |
|||
|
|||
itemStore.addOrUpdate(updatedModel); |
|||
}); |
|||
|
|||
goog.events.listen(container, |
|||
todomvc.view.ToDoItemControl.EventType.DESTROY, function(e) { |
|||
/** |
|||
* @type {todomvc.view.ToDoItemControl} |
|||
*/ |
|||
var control = e.target; |
|||
|
|||
/** |
|||
* @type {todomvc.model.ToDoItem} |
|||
*/ |
|||
var model = (/**@type {todomvc.model.ToDoItem} */ control.getModel()); |
|||
if (model !== null) { |
|||
itemStore.remove(model); |
|||
} |
|||
}); |
|||
|
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var newToDo = document.getElementById('new-todo'); |
|||
goog.events.listen(newToDo, goog.events.EventType.KEYUP, function(e) { |
|||
if (e.keyCode !== goog.events.KeyCodes.ENTER) { |
|||
return; |
|||
} |
|||
// get the text
|
|||
var value = goog.string.trim(newToDo.value); |
|||
if (value === '') { |
|||
return; |
|||
} |
|||
// clear the input box
|
|||
newToDo.value = ''; |
|||
// create the item
|
|||
itemStore.addOrUpdate(new todomvc.model.ToDoItem(value)); |
|||
}); |
|||
|
|||
itemStore.load(); |
|||
history.setEnabled(true); |
@ -0,0 +1,138 @@ |
|||
(function(){function g(a){throw a;}var i=void 0,j=!0,k=null,p=!1;function r(a){return function(){return this[a]}}function s(a){return function(){return a}}var v,w=this;function aa(a,b){var c=a.split("."),d=w;!(c[0]in d)&&d.execScript&&d.execScript("var "+c[0]);for(var f;c.length&&(f=c.shift());)!c.length&&b!==i?d[f]=b:d=d[f]?d[f]:d[f]={}}function ba(){}function x(a){a.K=function(){return a.Fb?a.Fb:a.Fb=new a}} |
|||
function ca(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; |
|||
else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function z(a){return"array"==ca(a)}function da(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length}function A(a){return"string"==typeof a}function ea(a){return"function"==ca(a)}function fa(a){var b=typeof a;return"object"==b&&a!=k||"function"==b}function ga(a){return a[ha]||(a[ha]=++ia)}var ha="closure_uid_"+Math.floor(2147483648*Math.random()).toString(36),ia=0; |
|||
function ja(a,b,c){return a.call.apply(a.bind,arguments)}function ka(a,b,c){a||g(Error());if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}}function la(a,b,c){la=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?ja:ka;return la.apply(k,arguments)} |
|||
function ma(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=Array.prototype.slice.call(arguments);b.unshift.apply(b,c);return a.apply(this,b)}}var na=Date.now||function(){return+new Date};function C(a,b){function c(){}c.prototype=b.prototype;a.f=b.prototype;a.prototype=new c;a.prototype.constructor=a};function D(){0!=oa&&(this.oc=Error().stack,ga(this))}var oa=0;D.prototype.Ub=p;function pa(a,b){for(var c=1;c<arguments.length;c++)var d=String(arguments[c]).replace(/\$/g,"$$$$"),a=a.replace(/\%s/,d);return a}function qa(a){return a.replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")}function ra(a){if(!sa.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(ta,"&"));-1!=a.indexOf("<")&&(a=a.replace(ua,"<"));-1!=a.indexOf(">")&&(a=a.replace(va,">"));-1!=a.indexOf('"')&&(a=a.replace(wa,"""));return a}var ta=/&/g,ua=/</g,va=/>/g,wa=/\"/g,sa=/[&<>\"]/;var E=Array.prototype,xa=E.indexOf?function(a,b,c){return E.indexOf.call(a,b,c)}:function(a,b,c){c=c==k?0:0>c?Math.max(0,a.length+c):c;if(A(a))return!A(b)||1!=b.length?-1:a.indexOf(b,c);for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},F=E.forEach?function(a,b,c){E.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,f=A(a)?a.split(""):a,e=0;e<d;e++)e in f&&b.call(c,f[e],e,a)},ya=E.filter?function(a,b,c){return E.filter.call(a,b,c)}:function(a,b,c){for(var d=a.length,f=[],e=0,h=A(a)?a.split(""): |
|||
a,l=0;l<d;l++)if(l in h){var m=h[l];b.call(c,m,l,a)&&(f[e++]=m)}return f},za=E.every?function(a,b,c){return E.every.call(a,b,c)}:function(a,b,c){for(var d=a.length,f=A(a)?a.split(""):a,e=0;e<d;e++)if(e in f&&!b.call(c,f[e],e,a))return p;return j};function Aa(a,b,c){for(var d=a.length,f=A(a)?a.split(""):a,e=0;e<d;e++)if(e in f&&b.call(c,f[e],e,a))return e;return-1}function Ba(a,b){return 0<=xa(a,b)}function Ca(a,b){var c=xa(a,b);0<=c&&E.splice.call(a,c,1)} |
|||
function Da(a){var b=a.length;if(0<b){for(var c=Array(b),d=0;d<b;d++)c[d]=a[d];return c}return[]}function Ea(a,b,c,d){E.splice.apply(a,Fa(arguments,1))}function Fa(a,b,c){return 2>=arguments.length?E.slice.call(a,b):E.slice.call(a,b,c)};var Ga,Ha,Ia,Ja;function Ka(){return w.navigator?w.navigator.userAgent:k}Ja=Ia=Ha=Ga=p;var La;if(La=Ka()){var Ma=w.navigator;Ga=0==La.indexOf("Opera");Ha=!Ga&&-1!=La.indexOf("MSIE");Ia=!Ga&&-1!=La.indexOf("WebKit");Ja=!Ga&&!Ia&&"Gecko"==Ma.product}var Na=Ga,H=Ha,I=Ja,J=Ia,Oa=w.navigator,Pa=-1!=(Oa&&Oa.platform||"").indexOf("Mac"),Qa; |
|||
a:{var Ra="",Sa;if(Na&&w.opera)var Ta=w.opera.version,Ra="function"==typeof Ta?Ta():Ta;else if(I?Sa=/rv\:([^\);]+)(\)|;)/:H?Sa=/MSIE\s+([^\);]+)(\)|;)/:J&&(Sa=/WebKit\/(\S+)/),Sa)var Ua=Sa.exec(Ka()),Ra=Ua?Ua[1]:"";if(H){var Va,Wa=w.document;Va=Wa?Wa.documentMode:i;if(Va>parseFloat(Ra)){Qa=String(Va);break a}}Qa=Ra}var Xa={}; |
|||
function K(a){var b;if(!(b=Xa[a])){b=0;for(var c=qa(String(Qa)).split("."),d=qa(String(a)).split("."),f=Math.max(c.length,d.length),e=0;0==b&&e<f;e++){var h=c[e]||"",l=d[e]||"",m=RegExp("(\\d*)(\\D*)","g"),n=RegExp("(\\d*)(\\D*)","g");do{var q=m.exec(h)||["","",""],u=n.exec(l)||["","",""];if(0==q[0].length&&0==u[0].length)break;b=((0==q[1].length?0:parseInt(q[1],10))<(0==u[1].length?0:parseInt(u[1],10))?-1:(0==q[1].length?0:parseInt(q[1],10))>(0==u[1].length?0:parseInt(u[1],10))?1:0)||((0==q[2].length)< |
|||
(0==u[2].length)?-1:(0==q[2].length)>(0==u[2].length)?1:0)||(q[2]<u[2]?-1:q[2]>u[2]?1:0)}while(0==b)}b=Xa[a]=0<=b}return b}var Ya={};function Za(a){return Ya[a]||(Ya[a]=H&&!!document.documentMode&&document.documentMode>=a)};var $a=!H||Za(9),ab=!H||Za(9),bb=H&&!K("9");!J||K("528");I&&K("1.9b")||H&&K("8")||Na&&K("9.5")||J&&K("528");I&&!K("8")||H&&K("9");function L(a,b){this.type=a;this.currentTarget=this.target=b}v=L.prototype;v.Z=p;v.defaultPrevented=p;v.Ya=j;v.stopPropagation=function(){this.Z=j};v.preventDefault=function(){this.defaultPrevented=j;this.Ya=p};function cb(a){cb[" "](a);return a}cb[" "]=ba;function db(a,b){a&&this.ya(a,b)}C(db,L);var eb=[1,4,2];v=db.prototype;v.target=k;v.relatedTarget=k;v.offsetX=0;v.offsetY=0;v.clientX=0;v.clientY=0;v.screenX=0;v.screenY=0;v.button=0;v.keyCode=0;v.charCode=0;v.ctrlKey=p;v.altKey=p;v.shiftKey=p;v.metaKey=p;v.vb=p;v.I=k; |
|||
v.ya=function(a,b){var c=this.type=a.type;L.call(this,c);this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(I){var f;a:{try{cb(d.nodeName);f=j;break a}catch(e){}f=p}f||(d=k)}}else"mouseover"==c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=J||a.offsetX!==i?a.offsetX:a.layerX;this.offsetY=J||a.offsetY!==i?a.offsetY:a.layerY;this.clientX=a.clientX!==i?a.clientX:a.pageX;this.clientY=a.clientY!==i?a.clientY:a.pageY;this.screenX=a.screenX|| |
|||
0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=a.metaKey;this.vb=Pa?a.metaKey:a.ctrlKey;this.state=a.state;this.I=a;a.defaultPrevented&&this.preventDefault();delete this.Z};function fb(a){return $a?0==a.I.button:"click"==a.type?j:!!(a.I.button&eb[0])} |
|||
v.stopPropagation=function(){db.f.stopPropagation.call(this);this.I.stopPropagation?this.I.stopPropagation():this.I.cancelBubble=j};v.preventDefault=function(){db.f.preventDefault.call(this);var a=this.I;if(a.preventDefault)a.preventDefault();else if(a.returnValue=p,bb)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};function gb(){}var hb=0;v=gb.prototype;v.key=0;v.aa=p;v.zb=p;v.ya=function(a,b,c,d,f,e){ea(a)?this.Gb=j:a&&a.handleEvent&&ea(a.handleEvent)?this.Gb=p:g(Error("Invalid listener argument"));this.ma=a;this.Lb=b;this.src=c;this.type=d;this.capture=!!f;this.Pa=e;this.zb=p;this.key=++hb;this.aa=p};v.handleEvent=function(a){return this.Gb?this.ma.call(this.Pa||this.src,a):this.ma.handleEvent.call(this.ma,a)};function ib(a,b,c){b in a&&g(Error('The object already contains the key "'+b+'"'));a[b]=c}var jb="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" ");function kb(a,b){for(var c,d,f=1;f<arguments.length;f++){d=arguments[f];for(c in d)a[c]=d[c];for(var e=0;e<jb.length;e++)c=jb[e],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};var lb={},N={},mb={},nb={}; |
|||
function O(a,b,c,d,f){if(b){if(z(b)){for(var e=0;e<b.length;e++)O(a,b[e],c,d,f);return k}var d=!!d,h=N;b in h||(h[b]={A:0,z:0});h=h[b];d in h||(h[d]={A:0,z:0},h.A++);var h=h[d],l=ga(a),m;h.z++;if(h[l]){m=h[l];for(e=0;e<m.length;e++)if(h=m[e],h.ma==c&&h.Pa==f){if(h.aa)break;return m[e].key}}else m=h[l]=[],h.A++;var n=ob,q=ab?function(a){return n.call(q.src,q.key,a)}:function(a){a=n.call(q.src,q.key,a);if(!a)return a},e=q;e.src=a;h=new gb;h.ya(c,e,a,b,d,f);c=h.key;e.key=c;m.push(h);lb[c]=h;mb[l]||(mb[l]= |
|||
[]);mb[l].push(h);a.addEventListener?(a==w||!a.Bb)&&a.addEventListener(b,e,d):a.attachEvent(b in nb?nb[b]:nb[b]="on"+b,e);return c}g(Error("Invalid event type"))}function pb(a,b,c,d,f){if(z(b))for(var e=0;e<b.length;e++)pb(a,b[e],c,d,f);else if(d=!!d,a=qb(a,b,d))for(e=0;e<a.length;e++)if(a[e].ma==c&&a[e].capture==d&&a[e].Pa==f){P(a[e].key);break}} |
|||
function P(a){if(!lb[a])return p;var b=lb[a];if(b.aa)return p;var c=b.src,d=b.type,f=b.Lb,e=b.capture;c.removeEventListener?(c==w||!c.Bb)&&c.removeEventListener(d,f,e):c.detachEvent&&c.detachEvent(d in nb?nb[d]:nb[d]="on"+d,f);c=ga(c);mb[c]&&(f=mb[c],Ca(f,b),0==f.length&&delete mb[c]);b.aa=j;if(b=N[d][e][c])b.Jb=j,rb(d,e,c,b);delete lb[a];return j} |
|||
function rb(a,b,c,d){if(!d.Ua&&d.Jb){for(var f=0,e=0;f<d.length;f++)d[f].aa?d[f].Lb.src=k:(f!=e&&(d[e]=d[f]),e++);d.length=e;d.Jb=p;0==e&&(delete N[a][b][c],N[a][b].A--,0==N[a][b].A&&(delete N[a][b],N[a].A--),0==N[a].A&&delete N[a])}}function qb(a,b,c){var d=N;return b in d&&(d=d[b],c in d&&(d=d[c],a=ga(a),d[a]))?d[a]:k} |
|||
function sb(a,b,c,d,f){var e=1,b=ga(b);if(a[b]){a.z--;a=a[b];a.Ua?a.Ua++:a.Ua=1;try{for(var h=a.length,l=0;l<h;l++){var m=a[l];m&&!m.aa&&(e&=tb(m,f)!==p)}}finally{a.Ua--,rb(c,d,b,a)}}return Boolean(e)}function tb(a,b){a.zb&&P(a.key);return a.handleEvent(b)} |
|||
function ob(a,b){if(!lb[a])return j;var c=lb[a],d=c.type,f=N;if(!(d in f))return j;var f=f[d],e,h;if(!ab){var l;if(!(l=b))a:{l=["window","event"];for(var m=w;e=l.shift();)if(m[e]!=k)m=m[e];else{l=k;break a}l=m}e=l;l=j in f;m=p in f;if(l){if(0>e.keyCode||e.returnValue!=i)return j;a:{var n=p;if(0==e.keyCode)try{e.keyCode=-1;break a}catch(q){n=j}if(n||e.returnValue==i)e.returnValue=j}}n=new db;n.ya(e,this);e=j;try{if(l){for(var u=[],B=n.currentTarget;B;B=B.parentNode)u.push(B);h=f[j];h.z=h.A;for(var M= |
|||
u.length-1;!n.Z&&0<=M&&h.z;M--)n.currentTarget=u[M],e&=sb(h,u[M],d,j,n);if(m){h=f[p];h.z=h.A;for(M=0;!n.Z&&M<u.length&&h.z;M++)n.currentTarget=u[M],e&=sb(h,u[M],d,p,n)}}else e=tb(c,n)}finally{u&&(u.length=0)}return e}d=new db(b,this);return e=tb(c,d)};function ub(){D.call(this)}C(ub,D);v=ub.prototype;v.Bb=j;v.ub=k;v.wb=function(a){this.ub=a};v.addEventListener=function(a,b,c,d){O(this,a,b,c,d)};v.removeEventListener=function(a,b,c,d){pb(this,a,b,c,d)}; |
|||
v.dispatchEvent=function(a){var b=a.type||a,c=N;if(b in c){if(A(a))a=new L(a,this);else if(a instanceof L)a.target=a.target||this;else{var d=a,a=new L(b,this);kb(a,d)}var d=1,f,c=c[b],b=j in c,e;if(b){f=[];for(e=this;e;e=e.ub)f.push(e);e=c[j];e.z=e.A;for(var h=f.length-1;!a.Z&&0<=h&&e.z;h--)a.currentTarget=f[h],d&=sb(e,f[h],a.type,j,a)&&a.Ya!=p}if(p in c)if(e=c[p],e.z=e.A,b)for(h=0;!a.Z&&h<f.length&&e.z;h++)a.currentTarget=f[h],d&=sb(e,f[h],a.type,p,a)&&a.Ya!=p;else for(f=this;!a.Z&&f&&e.z;f=f.ub)a.currentTarget= |
|||
f,d&=sb(e,f,a.type,p,a)&&a.Ya!=p;a=Boolean(d)}else a=j;return a};function vb(a,b){D.call(this);this.za=a||1;this.Za=b||wb;this.cb=la(this.lc,this);this.ob=na()}C(vb,ub);vb.prototype.enabled=p;var wb=w.window;v=vb.prototype;v.l=k;v.setInterval=function(a){this.za=a;this.l&&this.enabled?(this.stop(),this.start()):this.l&&this.stop()};v.lc=function(){if(this.enabled){var a=na()-this.ob;0<a&&a<0.8*this.za?this.l=this.Za.setTimeout(this.cb,this.za-a):(this.dispatchEvent(xb),this.enabled&&(this.l=this.Za.setTimeout(this.cb,this.za),this.ob=na()))}}; |
|||
v.start=function(){this.enabled=j;this.l||(this.l=this.Za.setTimeout(this.cb,this.za),this.ob=na())};v.stop=function(){this.enabled=p;this.l&&(this.Za.clearTimeout(this.l),this.l=k)};var xb="tick";var yb,zb=!H||Za(9);!I&&!H||H&&Za(9)||I&&K("1.9.1");H&&K("9");function Ab(a){a=a.className;return A(a)&&a.match(/\S+/g)||[]}function Bb(a,b){for(var c=Ab(a),d=Fa(arguments,1),f=c.length+d.length,e=c,h=0;h<d.length;h++)Ba(e,d[h])||e.push(d[h]);a.className=c.join(" ");return c.length==f}function Cb(a,b){var c=Ab(a),d=Fa(arguments,1),f,e=d;f=ya(c,function(a){return!Ba(e,a)});a.className=f.join(" ");return f.length==c.length-d.length};function Db(a){return a?new Eb(Q(a)):yb||(yb=new Eb)}var Fb={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"}; |
|||
function Gb(a){var b=document,c=b.createElement("div");H?(c.innerHTML="<br>"+a,c.removeChild(c.firstChild)):c.innerHTML=a;if(1==c.childNodes.length)return c.removeChild(c.firstChild);for(a=b.createDocumentFragment();c.firstChild;)a.appendChild(c.firstChild);return a}function Hb(a){for(var b;b=a.firstChild;)a.removeChild(b)} |
|||
function Ib(a,b){if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||Boolean(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}function Q(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function Jb(a){var b=a.getAttributeNode("tabindex");return b&&b.specified?(a=a.tabIndex,"number"==typeof a&&0<=a&&32768>a):p}function Eb(a){this.B=a||w.document||document}v=Eb.prototype;v.Ma=Db; |
|||
v.a=function(a){return A(a)?this.B.getElementById(a):a}; |
|||
v.j=function(a,b,c){var d=this.B,f=arguments,e=f[0],h=f[1];if(!zb&&h&&(h.name||h.type)){e=["<",e];h.name&&e.push(' name="',ra(h.name),'"');if(h.type){e.push(' type="',ra(h.type),'"');var l={};kb(l,h);delete l.type;h=l}e.push(">");e=e.join("")}e=d.createElement(e);if(h)if(A(h))e.className=h;else if(z(h))Bb.apply(k,[e].concat(h));else{var m=e,l=function(a,b){"style"==b?m.style.cssText=a:"class"==b?m.className=a:"for"==b?m.htmlFor=a:b in Fb?m.setAttribute(Fb[b],a):0==b.lastIndexOf("aria-",0)||0==b.lastIndexOf("data-", |
|||
0)?m.setAttribute(b,a):m[b]=a},n;for(n in h)l.call(i,h[n],n)}if(2<f.length){var q=d,u=e,d=f,f=function(a){a&&u.appendChild(A(a)?q.createTextNode(a):a)};for(n=2;n<d.length;n++)if(h=d[n],da(h)&&!(fa(h)&&0<h.nodeType)){a:{if(h&&"number"==typeof h.length){if(fa(h)){l="function"==typeof h.item||"string"==typeof h.item;break a}if(ea(h)){l="function"==typeof h.item;break a}}l=p}F(l?Da(h):h,f)}else f(h)}return e};v.createElement=function(a){return this.B.createElement(a)};v.createTextNode=function(a){return this.B.createTextNode(a)}; |
|||
v.appendChild=function(a,b){a.appendChild(b)};v.Mb=Hb;v.contains=Ib;function Kb(a){D.call(this);this.Db=a;this.ka=[]}C(Kb,D);var Lb=[];function R(a,b,c,d){z(c)||(Lb[0]=c,c=Lb);for(var f=0;f<c.length;f++){var e=O(b,c[f],d||a,p,a.Db||a);a.ka.push(e)}return a}function S(a,b,c,d,f,e){if(z(c))for(var h=0;h<c.length;h++)S(a,b,c[h],d,f,e);else{a:{d=d||a;e=e||a.Db||a;f=!!f;if(b=qb(b,c,f))for(c=0;c<b.length;c++)if(!b[c].aa&&b[c].ma==d&&b[c].capture==f&&b[c].Pa==e){b=b[c];break a}b=k}b&&(b=b.key,P(b),Ca(a.ka,b))}return a}Kb.prototype.handleEvent=function(){g(Error("EventHandler.handleEvent not implemented"))};function Mb(a,b){L.call(this,"navigate");this.xb=a;this.pc=b}C(Mb,L);function Nb(a,b,c,d){D.call(this);a&&!b&&g(Error("Can't use invisible history without providing a blank page."));var f;c?f=c:(f="history_state"+Ob,document.write(pa(Pb,f,f)),f=A(f)?document.getElementById(f):f);this.xa=f;this.N=c?Q(c)?Q(c).parentWindow||Q(c).defaultView:window:window;this.Sb=this.N.location.href.split("#")[0];this.Qa=b;H&&!b&&(this.Qa="https"==window.location.protocol?"https:///":'javascript:""');this.l=new vb(Qb);this.ba=!a;this.fa=new Kb(this);if(a||Rb)d?a=d:(a="history_iframe"+ |
|||
Ob,b=this.Qa?'src="'+ra(this.Qa)+'"':"",document.write(pa(Sb,a,b)),a=A(a)?document.getElementById(a):a),this.ia=a,this.Qb=j;Rb&&(R(this.fa,this.N,"load",this.fc),this.Ob=this.fb=p);this.ba?Tb(this,Ub(this),j):Vb(this,this.xa.value);Ob++}C(Nb,ub);Nb.prototype.q=p;Nb.prototype.oa=p;Nb.prototype.la=k;var Wb=H&&Za(8)||I&&K("1.9.2")||J&&K("532.1"),Rb=H&&!Za(8),Xb=Rb;v=Nb.prototype;v.na=k; |
|||
v.L=function(a){if(a!=this.q)if(Rb&&!this.fb)this.Ob=a;else if(a)if(Na?R(this.fa,this.N.document,Yb,this.jc):I&&R(this.fa,this.N,"pageshow",this.hc),Wb&&this.ba)R(this.fa,this.N,"hashchange",this.gc),this.q=j,this.dispatchEvent(new Mb(Ub(this),p));else{if(!H||this.fb)R(this.fa,this.l,xb,la(this.Ab,this,j)),this.q=j,Rb||(this.la=Ub(this),this.dispatchEvent(new Mb(Ub(this),p))),this.l.start()}else this.q=p,a=this.fa,F(a.ka,P),a.ka.length=0,this.l.stop()}; |
|||
v.fc=function(){this.fb=j;this.xa.value&&Vb(this,this.xa.value,j);this.L(this.Ob)};v.hc=function(a){a.I.persisted&&(this.L(p),this.L(j))};v.gc=function(){var a=Zb(this.N);a!=this.la&&$b(this,a,j)};function Ub(a){return a.na!=k?a.na:a.ba?Zb(a.N):ac(a)||""}function Zb(a){var a=a.location.href,b=a.indexOf("#");return 0>b?"":a.substring(b+1)}function Tb(a,b,c){var d=a.N.location,a=a.Sb,f=-1!=d.href.indexOf("#");if(Xb||f||b)a+="#"+b;a!=d.href&&(c?d.replace(a):d.href=a)} |
|||
function Vb(a,b,c,d){if(a.Qb||b!=ac(a))if(a.Qb=p,b=encodeURIComponent(String(b)),H){var f=a.ia.contentDocument||a.ia.contentWindow.document;f.open("text/html",c?"replace":i);f.write(pa(bc,ra(d||a.N.document.title),b));f.close()}else if(b=a.Qa+"#"+b,a=a.ia.contentWindow)c?a.location.replace(b):a.location.href=b} |
|||
function ac(a){if(H)return a=a.ia.contentDocument||a.ia.contentWindow.document,a.body?decodeURIComponent(a.body.innerHTML.replace(/\+/g," ")):k;var b=a.ia.contentWindow;if(b){var c;try{c=decodeURIComponent(Zb(b).replace(/\+/g," "))}catch(d){return a.oa||(a.oa!=j&&a.l.setInterval(cc),a.oa=j),k}a.oa&&(a.oa!=p&&a.l.setInterval(Qb),a.oa=p);return c||k}return k} |
|||
v.Ab=function(a){if(this.ba){var b=Zb(this.N);b!=this.la&&$b(this,b,a)}if(!this.ba||Rb)if(b=ac(this)||"",this.na==k||b==this.na)this.na=k,b!=this.la&&$b(this,b,a)};function $b(a,b,c){a.la=a.xa.value=b;a.ba?(Rb&&Vb(a,b),Tb(a,b)):Vb(a,b);a.dispatchEvent(new Mb(Ub(a),c))}v.jc=function(){this.l.stop();this.l.start()}; |
|||
var Yb=["mousedown","keydown","mousemove"],bc="<title>%s</title><body>%s</body>",Sb='<iframe id="%s" style="display:none" %s></iframe>',Pb='<input type="text" name="%s" id="%s" style="display:none">',Ob=0,Qb=150,cc=1E4;var dc;dc=s(j);/* |
|||
Portions of this code are from the Dojo Toolkit, received by |
|||
The Closure Library Authors under the BSD license. All other code is |
|||
Copyright 2005-2009 The Closure Library Authors. All Rights Reserved. |
|||
|
|||
The "New" BSD License: |
|||
|
|||
Copyright (c) 2005-2009, The Dojo Foundation |
|||
All rights reserved. |
|||
|
|||
Redistribution and use in source and binary forms, with or without |
|||
modification, are permitted provided that the following conditions are met: |
|||
|
|||
Redistributions of source code must retain the above copyright notice, this |
|||
list of conditions and the following disclaimer. |
|||
Redistributions in binary form must reproduce the above copyright notice, |
|||
this list of conditions and the following disclaimer in the documentation |
|||
and/or other materials provided with the distribution. |
|||
Neither the name of the Dojo Foundation nor the names of its contributors |
|||
may be used to endorse or promote products derived from this software |
|||
without specific prior written permission. |
|||
|
|||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
|||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
|||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|||
*/ |
|||
var ec;function fc(a,b){var c=b||[];a&&c.push(a);return c}var gc=J&&"BackCompat"==document.compatMode,hc=document.firstChild.children?"children":"childNodes",ic=p; |
|||
function jc(a){function b(){0<=n&&(t.id=c(n,y).replace(/\\/g,""),n=-1);if(0<=q){var a=q==y?k:c(q,y);0>">~+".indexOf(a)?t.t=a:t.Wa=a;q=-1}0<=m&&(t.O.push(c(m+1,y).replace(/\\/g,"")),m=-1)}function c(b,c){return qa(a.slice(b,c))}for(var a=0<=">~+".indexOf(a.slice(-1))?a+" * ":a+" ",d=[],f=-1,e=-1,h=-1,l=-1,m=-1,n=-1,q=-1,u="",B="",M,y=0,ce=a.length,t=k,G=k;u=B,B=a.charAt(y),y<ce;y++)if("\\"!=u)if(t||(M=y,t={pa:k,$:[],Ka:[],O:[],t:k,Wa:k,id:k,ib:function(){return ic?this.kc:this.t}},q=y),0<=f)if("]"== |
|||
B){G.bb?G.pb=c(h||f+1,y):G.bb=c(f+1,y);if((f=G.pb)&&('"'==f.charAt(0)||"'"==f.charAt(0)))G.pb=f.slice(1,-1);t.Ka.push(G);G=k;f=h=-1}else"="==B&&(h=0<="|~^$*".indexOf(u)?u:"",G.type=h+B,G.bb=c(f+1,y-h.length),h=y+1);else 0<=e?")"==B&&(0<=l&&(G.value=c(e+1,y)),l=e=-1):"#"==B?(b(),n=y+1):"."==B?(b(),m=y):":"==B?(b(),l=y):"["==B?(b(),f=y,G={}):"("==B?(0<=l&&(G={name:c(l+1,y),value:k},t.$.push(G)),e=y):" "==B&&u!=B&&(b(),0<=l&&t.$.push({name:c(l+1,y)}),t.Ib=t.$.length||t.Ka.length||t.O.length,t.qc=t.pa= |
|||
c(M,y),t.kc=t.t=t.Wa?k:t.t||"*",t.t&&(t.t=t.t.toUpperCase()),d.length&&d[d.length-1].Wa&&(t.Eb=d.pop(),t.pa=t.Eb.pa+" "+t.pa),d.push(t),t=k);return d}function kc(a,b){return!a?b:!b?a:function(){return a.apply(window,arguments)&&b.apply(window,arguments)}}function lc(a){return 1==a.nodeType}function mc(a,b){return!a?"":"class"==b?a.className||"":"for"==b?a.htmlFor||"":"style"==b?a.style.cssText||"":(ic?a.getAttribute(b):a.getAttribute(b,2))||""} |
|||
var nc={"*=":function(a,b){return function(c){return 0<=mc(c,a).indexOf(b)}},"^=":function(a,b){return function(c){return 0==mc(c,a).indexOf(b)}},"$=":function(a,b){return function(c){c=" "+mc(c,a);return c.lastIndexOf(b)==c.length-b.length}},"~=":function(a,b){var c=" "+b+" ";return function(b){return 0<=(" "+mc(b,a)+" ").indexOf(c)}},"|=":function(a,b){b=" "+b;return function(c){c=" "+mc(c,a);return c==b||0==c.indexOf(b+"-")}},"=":function(a,b){return function(c){return mc(c,a)==b}}},oc="undefined"== |
|||
typeof document.firstChild.nextElementSibling,pc=!oc?"nextElementSibling":"nextSibling",qc=!oc?"previousElementSibling":"previousSibling",rc=oc?lc:dc;function sc(a){for(;a=a[qc];)if(rc(a))return p;return j}function tc(a){for(;a=a[pc];)if(rc(a))return p;return j}function uc(a){var b=a.parentNode,c=0,d=b[hc],f=a._i||-1,e=b._l||-1;if(!d)return-1;d=d.length;if(e==d&&0<=f&&0<=e)return f;b._l=d;f=-1;for(b=b.firstElementChild||b.firstChild;b;b=b[pc])rc(b)&&(b._i=++c,a===b&&(f=c));return f} |
|||
function vc(a){return!(uc(a)%2)}function wc(a){return uc(a)%2} |
|||
var yc={checked:function(){return function(a){return a.checked||a.attributes.checked}},"first-child":function(){return sc},"last-child":function(){return tc},"only-child":function(){return function(a){return!sc(a)||!tc(a)?p:j}},empty:function(){return function(a){for(var b=a.childNodes,a=a.childNodes.length-1;0<=a;a--){var c=b[a].nodeType;if(1===c||3==c)return p}return j}},contains:function(a,b){var c=b.charAt(0);if('"'==c||"'"==c)b=b.slice(1,-1);return function(a){return 0<=a.innerHTML.indexOf(b)}}, |
|||
not:function(a,b){var c=jc(b)[0],d={da:1};"*"!=c.t&&(d.t=1);c.O.length||(d.O=1);var f=xc(c,d);return function(a){return!f(a)}},"nth-child":function(a,b){if("odd"==b)return wc;if("even"==b)return vc;if(-1!=b.indexOf("n")){var c=b.split("n",2),d=c[0]?"-"==c[0]?-1:parseInt(c[0],10):1,f=c[1]?parseInt(c[1],10):0,e=0,h=-1;0<d?0>f?f=f%d&&d+f%d:0<f&&(f>=d&&(e=f-f%d),f%=d):0>d&&(d*=-1,0<f&&(h=f,f%=d));if(0<d)return function(a){a=uc(a);return a>=e&&(0>h||a<=h)&&a%d==f};b=f}var l=parseInt(b,10);return function(a){return uc(a)== |
|||
l}}},zc=H?function(a){var b=a.toLowerCase();"class"==b&&(a="className");return function(c){return ic?c.getAttribute(a):c[a]||c[b]}}:function(a){return function(b){return b&&b.getAttribute&&b.hasAttribute(a)}}; |
|||
function xc(a,b){if(!a)return dc;var b=b||{},c=k;b.da||(c=kc(c,lc));b.t||"*"!=a.t&&(c=kc(c,function(b){return b&&b.tagName==a.ib()}));b.O||F(a.O,function(a,b){var e=RegExp("(?:^|\\s)"+a+"(?:\\s|$)");c=kc(c,function(a){return e.test(a.className)});c.count=b});b.$||F(a.$,function(a){var b=a.name;yc[b]&&(c=kc(c,yc[b](b,a.value)))});b.Ka||F(a.Ka,function(a){var b,e=a.bb;a.type&&nc[a.type]?b=nc[a.type](e,a.pb):e.length&&(b=zc(e));b&&(c=kc(c,b))});b.id||a.id&&(c=kc(c,function(b){return!!b&&b.id==a.id})); |
|||
c||"default"in b||(c=dc);return c}var Ac={}; |
|||
function Bc(a){var b=Ac[a.pa];if(b)return b;var c=a.Eb,c=c?c.Wa:"",d=xc(a,{da:1}),f="*"==a.t,e=document.getElementsByClassName;if(c)if(e={da:1},f&&(e.t=1),d=xc(a,e),"+"==c)var h=d,b=function(a,b,c){for(;a=a[pc];)if(!oc||lc(a)){(!c||Cc(a,c))&&h(a)&&b.push(a);break}return b};else if("~"==c)var l=d,b=function(a,b,c){for(a=a[pc];a;){if(rc(a)){if(c&&!Cc(a,c))break;l(a)&&b.push(a)}a=a[pc]}return b};else{if(">"==c)var m=d,m=m||dc,b=function(a,b,c){for(var d=0,f=a[hc];a=f[d++];)rc(a)&&((!c||Cc(a,c))&&m(a, |
|||
d))&&b.push(a);return b}}else if(a.id)d=!a.Ib&&f?dc:xc(a,{da:1,id:1}),b=function(b,c){var f=Db(b).a(a.id),e;if(e=f&&d(f))if(!(e=9==b.nodeType)){for(e=f.parentNode;e&&e!=b;)e=e.parentNode;e=!!e}if(e)return fc(f,c)};else if(e&&/\{\s*\[native code\]\s*\}/.test(String(e))&&a.O.length&&!gc)var d=xc(a,{da:1,O:1,id:1}),n=a.O.join(" "),b=function(a,b){for(var c=fc(0,b),e,f=0,h=a.getElementsByClassName(n);e=h[f++];)d(e,a)&&c.push(e);return c};else!f&&!a.Ib?b=function(b,c){for(var d=fc(0,c),e,f=0,h=b.getElementsByTagName(a.ib());e= |
|||
h[f++];)d.push(e);return d}:(d=xc(a,{da:1,t:1,id:1}),b=function(b,c){for(var e=fc(0,c),f,h=0,l=b.getElementsByTagName(a.ib());f=l[h++];)d(f,b)&&e.push(f);return e});return Ac[a.pa]=b}var Dc={},Ec={};function Fc(a){var b=jc(qa(a));if(1==b.length){var c=Bc(b[0]);return function(a){if(a=c(a,[]))a.Va=j;return a}}return function(a){for(var a=fc(a),c,e,h=b.length,l,m,n=0;n<h;n++){m=[];c=b[n];e=a.length-1;0<e&&(l={},m.Va=j);e=Bc(c);for(var q=0;c=a[q];q++)e(c,m,l);if(!m.length)break;a=m}return m}} |
|||
var Gc=!!document.querySelectorAll&&(!J||K("526")); |
|||
function Hc(a,b){if(Gc){var c=Ec[a];if(c&&!b)return c}if(c=Dc[a])return c;var c=a.charAt(0),d=-1==a.indexOf(" ");0<=a.indexOf("#")&&d&&(b=j);if(Gc&&!b&&-1==">~+".indexOf(c)&&(!H||-1==a.indexOf(":"))&&!(gc&&0<=a.indexOf("."))&&-1==a.indexOf(":contains")&&-1==a.indexOf("|=")){var f=0<=">~+".indexOf(a.charAt(a.length-1))?a+" *":a;return Ec[a]=function(b){try{9==b.nodeType||d||g("");var c=b.querySelectorAll(f);H?c.Tb=j:c.Va=j;return c}catch(e){return Hc(a,j)(b)}}}var e=a.split(/\s*,\s*/);return Dc[a]= |
|||
2>e.length?Fc(a):function(a){for(var b=0,c=[],d;d=e[b++];)c=c.concat(Fc(d)(a));return c}}var Ic=0,Jc=H?function(a){return ic?a.getAttribute("_uid")||a.setAttribute("_uid",++Ic)||Ic:a.uniqueID}:function(a){return a._uid||(a._uid=++Ic)};function Cc(a,b){if(!b)return 1;var c=Jc(a);return!b[c]?b[c]=1:0} |
|||
function Kc(a){if(a&&a.Va)return a;var b=[];if(!a||!a.length)return b;a[0]&&b.push(a[0]);if(2>a.length)return b;Ic++;if(H&&ic){var c=Ic+"";a[0].setAttribute("_zipIdx",c);for(var d=1,f;f=a[d];d++)a[d].getAttribute("_zipIdx")!=c&&b.push(f),f.setAttribute("_zipIdx",c)}else if(H&&a.Tb)try{for(d=1;f=a[d];d++)lc(f)&&b.push(f)}catch(e){}else{a[0]&&(a[0]._zipIdx=Ic);for(d=1;f=a[d];d++)a[d]._zipIdx!=Ic&&b.push(f),f._zipIdx=Ic}return b} |
|||
function Lc(a,b){if(!a)return[];if(a.constructor==Array)return a;if(!A(a))return[a];if(A(b)&&(b=A(b)?document.getElementById(b):b,!b))return[];var b=b||document,c=b.ownerDocument||b.documentElement;ic=b.contentType&&"application/xml"==b.contentType||Na&&(b.doctype||"[object XMLDocument]"==c.toString())||!!c&&(H?c.xml:b.xmlVersion||c.xmlVersion);return(c=Hc(a)(b))&&c.Va?c:Kc(c)}Lc.$=yc;ec=Lc;aa("goog.dom.query",ec);aa("goog.dom.query.pseudos",ec.$);function Mc(a,b,c,d,f){if(!H&&(!J||!K("525")))return j;if(Pa&&f)return Nc(a);if(f&&!d||!c&&(17==b||18==b)||H&&d&&b==a)return p;switch(a){case 13:return!(H&&Za(9));case 27:return!J}return Nc(a)}function Nc(a){if(48<=a&&57>=a||96<=a&&106>=a||65<=a&&90>=a||J&&0==a)return j;switch(a){case 32:case 63:case 107:case 109:case 110:case 111:case 186:case 59:case 189:case 187:case 61:case 188:case 190:case 191:case 192:case 222:case 219:case 220:case 221:return j;default:return p}} |
|||
function Oc(a){switch(a){case 61:return 187;case 59:return 186;case 224:return 91;case 0:return 224;default:return a}};function Pc(a){a=String(a);if(/^\s*$/.test(a)?0:/^[\],:{}\s\u2028\u2029]*$/.test(a.replace(/\\["\\\/bfnrtu]/g,"@").replace(/"[^"\\\n\r\u2028\u2029\x00-\x08\x10-\x1f\x80-\x9f]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:[\s\u2028\u2029]*\[)+/g,"")))try{return eval("("+a+")")}catch(b){}g(Error("Invalid JSON string: "+a))}function Qc(){this.Xa=i}function Rc(a,b){var c=[];Sc(a,b,c);return c.join("")} |
|||
function Sc(a,b,c){switch(typeof b){case "string":Tc(b,c);break;case "number":c.push(isFinite(b)&&!isNaN(b)?b:"null");break;case "boolean":c.push(b);break;case "undefined":c.push("null");break;case "object":if(b==k){c.push("null");break}if(z(b)){var d=b.length;c.push("[");for(var f="",e=0;e<d;e++)c.push(f),f=b[e],Sc(a,a.Xa?a.Xa.call(b,String(e),f):f,c),f=",";c.push("]");break}c.push("{");d="";for(e in b)Object.prototype.hasOwnProperty.call(b,e)&&(f=b[e],"function"!=typeof f&&(c.push(d),Tc(e,c),c.push(":"), |
|||
Sc(a,a.Xa?a.Xa.call(b,e,f):f,c),d=","));c.push("}");break;case "function":break;default:g(Error("Unknown type: "+typeof b))}}var Uc={'"':'\\"',"\\":"\\\\","/":"\\/","\b":"\\b","\f":"\\f","\n":"\\n","\r":"\\r","\t":"\\t","\x0B":"\\u000b"},Vc=/\uffff/.test("\uffff")?/[\\\"\x00-\x1f\x7f-\uffff]/g:/[\\\"\x00-\x1f\x7f-\xff]/g; |
|||
function Tc(a,b){b.push('"',a.replace(Vc,function(a){if(a in Uc)return Uc[a];var b=a.charCodeAt(0),f="\\u";16>b?f+="000":256>b?f+="00":4096>b&&(f+="0");return Uc[a]=f+b.toString(16)}),'"')};function Wc(){};function Xc(a){this.Aa=a;this.Nb=new Qc}v=Xc.prototype;v.Aa=k;v.Nb=k;v.set=function(a,b){b!==i?this.Aa.set(a,Rc(this.Nb,b)):this.Aa.remove(a)};v.get=function(a){a=this.Aa.get(a);if(a!==k)try{return Pc(a)}catch(b){g("Storage: Invalid value was encountered")}};v.remove=function(a){this.Aa.remove(a)};function Yc(){}C(Yc,Wc);function Zc(a){this.F=a}C(Zc,Yc);Zc.prototype.set=function(a,b){try{this.F.setItem(a,b)}catch(c){g("Storage mechanism: Quota exceeded")}};Zc.prototype.get=function(a){a=this.F.getItem(a);!A(a)&&a!==k&&g("Storage mechanism: Invalid value was encountered");return a};Zc.prototype.remove=function(a){this.F.removeItem(a)};function $c(){var a=k;try{a=window.localStorage||k}catch(b){}this.F=a}C($c,Zc);function ad(a,b){a.style.display=b?"":"none"}var bd=I?"MozUserSelect":J?"WebkitUserSelect":k;function cd(a,b,c){c=!c?a.getElementsByTagName("*"):k;if(bd){if(b=b?"none":"",a.style[bd]=b,c)for(var a=0,d;d=c[a];a++)d.style[bd]=b}else if(H||Na)if(b=b?"on":"",a.setAttribute("unselectable",b),c)for(a=0;d=c[a];a++)d.setAttribute("unselectable",b)};function dd(){}x(dd);dd.prototype.ec=0;dd.K();function T(a){D.call(this);this.C=a||Db();this.Ba=ed}C(T,ub);T.prototype.dc=dd.K();var ed=k;function fd(a,b){switch(a){case 1:return b?"disable":"enable";case 2:return b?"highlight":"unhighlight";case 4:return b?"activate":"deactivate";case 8:return b?"select":"unselect";case 16:return b?"check":"uncheck";case 32:return b?"focus":"blur";case 64:return b?"open":"close"}g(Error("Invalid component state"))}v=T.prototype;v.D=k;v.d=p;v.b=k;v.Ba=k;v.rb=k;v.p=k;v.n=k;v.i=k;v.mc=p; |
|||
v.o=function(){return this.D||(this.D=":"+(this.dc.ec++).toString(36))};v.Ea=function(a){if(this.p&&this.p.i){var b=this.p.i,c=this.D;c in b&&delete b[c];ib(this.p.i,a,this)}this.D=a};v.a=r("b");function gd(a){return a.jb||(a.jb=new Kb(a))}function hd(a,b){a==b&&g(Error("Unable to set parent component"));b&&(a.p&&a.D&&a.p.i&&a.D&&(a.D in a.p.i&&a.p.i[a.D])&&a.p!=b)&&g(Error("Unable to set parent component"));a.p=b;T.f.wb.call(a,b)}v.getParent=r("p"); |
|||
v.wb=function(a){this.p&&this.p!=a&&g(Error("Method not supported"));T.f.wb.call(this,a)};v.Ma=r("C");v.j=function(){this.b=this.C.createElement("div")};function id(a,b,c){a.d&&g(Error("Component already rendered"));a.b||a.j();b?b.insertBefore(a.b,c||k):a.C.B.body.appendChild(a.b);(!a.p||a.p.d)&&a.u()}v.V=function(a){this.d&&g(Error("Component already rendered"));if(a&&this.G(a)){this.mc=j;if(!this.C||this.C.B!=Q(a))this.C=Db(a);this.eb(a);this.u()}else g(Error("Invalid element to decorate"))}; |
|||
v.G=s(j);v.eb=function(a){this.b=a};v.u=function(){this.d=j;jd(this,function(a){!a.d&&a.a()&&a.u()})};v.ga=function(){jd(this,function(a){a.d&&a.ga()});if(this.jb){var a=this.jb;F(a.ka,P);a.ka.length=0}this.d=p};v.Ia=function(a,b){this.$a(a,kd(this),b)}; |
|||
v.$a=function(a,b,c){a.d&&(c||!this.d)&&g(Error("Component already rendered"));(0>b||b>kd(this))&&g(Error("Child component index out of bounds"));if(!this.i||!this.n)this.i={},this.n=[];if(a.getParent()==this){var d=a.o();this.i[d]=a;Ca(this.n,a)}else ib(this.i,a.o(),a);hd(a,this);Ea(this.n,b,0,a);a.d&&this.d&&a.getParent()==this?(c=this.v(),c.insertBefore(a.a(),c.childNodes[b]||k)):c?(this.b||this.j(),b=U(this,b+1),id(a,this.v(),b?b.b:k)):this.d&&(!a.d&&a.b&&a.b.parentNode)&&a.u()};v.v=r("b"); |
|||
function ld(a){if(a.Ba==k){var b;a:{b=a.d?a.b:a.C.B.body;var c=Q(b);if(c.defaultView&&c.defaultView.getComputedStyle&&(b=c.defaultView.getComputedStyle(b,k))){b=b.direction||b.getPropertyValue("direction")||"";break a}b=""}a.Ba="rtl"==(b||((a.d?a.b:a.C.B.body).currentStyle?(a.d?a.b:a.C.B.body).currentStyle.direction:k)||(a.d?a.b:a.C.B.body).style&&(a.d?a.b:a.C.B.body).style.direction)}return a.Ba}v.Fa=function(a){this.d&&g(Error("Component already rendered"));this.Ba=a}; |
|||
function kd(a){return a.n?a.n.length:0}function U(a,b){return a.n?a.n[b]||k:k}function jd(a,b,c){a.n&&F(a.n,b,c)}function md(a,b){return a.n&&b?xa(a.n,b):-1}v.removeChild=function(a,b){if(a){var c=A(a)?a:a.o(),a=this.i&&c?(c in this.i?this.i[c]:i)||k:k;if(c&&a){var d=this.i;c in d&&delete d[c];Ca(this.n,a);b&&(a.ga(),a.b&&(c=a.b)&&c.parentNode&&c.parentNode.removeChild(c));hd(a,k)}}a||g(Error("Child is not in parent component"));return a}; |
|||
v.Mb=function(a){for(var b=[];this.n&&0!=this.n.length;)b.push(this.removeChild(U(this,0),a));return b};function nd(a,b){D.call(this);a&&od(this,a,b)}C(nd,ub);v=nd.prototype;v.b=k;v.Sa=k;v.nb=k;v.Ta=k;v.U=-1;v.T=-1;v.ab=p; |
|||
var pd={3:13,12:144,63232:38,63233:40,63234:37,63235:39,63236:112,63237:113,63238:114,63239:115,63240:116,63241:117,63242:118,63243:119,63244:120,63245:121,63246:122,63247:123,63248:44,63272:46,63273:36,63275:35,63276:33,63277:34,63289:144,63302:45},qd={Up:38,Down:40,Left:37,Right:39,Enter:13,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"U+007F":46,Home:36,End:35,PageUp:33,PageDown:34,Insert:45},rd=H||J&&K("525"),sd=Pa&&I;v=nd.prototype; |
|||
v.$b=function(a){if(J&&(17==this.U&&!a.ctrlKey||18==this.U&&!a.altKey))this.T=this.U=-1;rd&&!Mc(a.keyCode,this.U,a.shiftKey,a.ctrlKey,a.altKey)?this.handleEvent(a):(this.T=I?Oc(a.keyCode):a.keyCode,sd&&(this.ab=a.altKey))};v.ac=function(a){this.T=this.U=-1;this.ab=a.altKey}; |
|||
v.handleEvent=function(a){var b=a.I,c,d,f=b.altKey;H&&"keypress"==a.type?(c=this.T,d=13!=c&&27!=c?b.keyCode:0):J&&"keypress"==a.type?(c=this.T,d=0<=b.charCode&&63232>b.charCode&&Nc(c)?b.charCode:0):Na?(c=this.T,d=Nc(c)?b.keyCode:0):(c=b.keyCode||this.T,d=b.charCode||0,sd&&(f=this.ab),Pa&&(63==d&&224==c)&&(c=191));var e=c,h=b.keyIdentifier;c?63232<=c&&c in pd?e=pd[c]:25==c&&a.shiftKey&&(e=9):h&&h in qd&&(e=qd[h]);a=e==this.U;this.U=e;b=new td(e,d,a,b);b.altKey=f;this.dispatchEvent(b)};v.a=r("b"); |
|||
function od(a,b,c){a.Ta&&a.detach();a.b=b;a.Sa=O(a.b,"keypress",a,c);a.nb=O(a.b,"keydown",a.$b,c,a);a.Ta=O(a.b,"keyup",a.ac,c,a)}v.detach=function(){this.Sa&&(P(this.Sa),P(this.nb),P(this.Ta),this.Ta=this.nb=this.Sa=k);this.b=k;this.T=this.U=-1};function td(a,b,c,d){d&&this.ya(d,i);this.type="key";this.keyCode=a;this.charCode=b;this.repeat=c}C(td,db);function V(){}var ud;x(V);v=V.prototype;v.j=function(a){var b=a.Ma().j("div",this.hb(a).join(" "),a.H);vd(a,b);return b};v.v=function(a){return a};v.ea=function(a,b,c){if(a=a.a?a.a():a)if(H&&!K("7")){var d=wd(Ab(a),b);d.push(b);ma(c?Bb:Cb,a).apply(k,d)}else c?Bb(a,b):Cb(a,b)};v.G=s(j); |
|||
v.V=function(a,b){b.id&&a.Ea(b.id);var c=this.v(b);c&&c.firstChild?(c=c.firstChild.nextSibling?Da(c.childNodes):c.firstChild,a.H=c):a.H=k;var d=0,f=this.w(),e=this.w(),h=p,l=p,c=p,m=Ab(b);F(m,function(a){if(!h&&a==f)h=j,e==f&&(l=j);else if(!l&&a==e)l=j;else{var b=d;if(!this.Pb){this.La||xd(this);var c=this.La,m={},n;for(n in c)m[c[n]]=n;this.Pb=m}a=parseInt(this.Pb[a],10);d=b|(isNaN(a)?0:a)}},this);a.e=d;h||(m.push(f),e==f&&(l=j));l||m.push(e);var n=a.J;n&&m.push.apply(m,n);if(H&&!K("7")){var q=wd(m); |
|||
0<q.length&&(m.push.apply(m,q),c=j)}if(!h||!l||n||c)b.className=m.join(" ");vd(a,b);return b};v.Ra=function(a){ld(a)&&this.Fa(a.a(),j);a.isEnabled()&&this.qa(a,a.g)};function vd(a,b){a.isEnabled()||yd(b,1,j);a.e&8&&yd(b,8,j);a.s&16&&yd(b,16,!!(a.e&16));a.s&64&&yd(b,64,!!(a.e&64))}v.Ca=function(a,b){cd(a,!b,!H&&!Na)};v.Fa=function(a,b){this.ea(a,this.w()+"-rtl",b)};v.W=function(a){var b;return a.s&32&&(b=a.k())?Jb(b):p}; |
|||
v.qa=function(a,b){var c;if(a.s&32&&(c=a.k())){if(!b&&a.e&32){try{c.blur()}catch(d){}a.e&32&&a.ta(k)}Jb(c)!=b&&(b?c.tabIndex=0:(c.tabIndex=-1,c.removeAttribute("tabIndex")))}};v.ra=function(a,b){ad(a,b)};v.r=function(a,b,c){var d=a.a();if(d){var f=zd(this,b);f&&this.ea(a,f,c);yd(d,b,c)}};function yd(a,b,c){ud||(ud={1:"disabled",8:"selected",16:"checked",64:"expanded"});(b=ud[b])&&a.setAttribute("aria-"+b,c)} |
|||
v.P=function(a,b){var c=this.v(a);if(c&&(Hb(c),b))if(A(b))if("textContent"in c)c.textContent=b;else if(c.firstChild&&3==c.firstChild.nodeType){for(;c.lastChild!=c.firstChild;)c.removeChild(c.lastChild);c.firstChild.data=b}else Hb(c),c.appendChild(Q(c).createTextNode(b));else{var d=function(a){if(a){var b=Q(c);c.appendChild(A(a)?b.createTextNode(a):a)}};z(b)?F(b,d):da(b)&&!("nodeType"in b)?F(Da(b),d):d(b)}};v.k=function(a){return a.a()};v.w=s("goog-control"); |
|||
v.hb=function(a){var b=this.w(),c=[b],d=this.w();d!=b&&c.push(d);b=a.e;for(d=[];b;){var f=b&-b;d.push(zd(this,f));b&=~f}c.push.apply(c,d);(a=a.J)&&c.push.apply(c,a);H&&!K("7")&&c.push.apply(c,wd(c));return c};function wd(a,b){var c=[];b&&(a=a.concat([b]));F([],function(d){za(d,ma(Ba,a))&&(!b||Ba(d,b))&&c.push(d.join("_"))});return c}function zd(a,b){a.La||xd(a);return a.La[b]} |
|||
function xd(a){var b=a.w();a.La={1:b+"-disabled",2:b+"-hover",4:b+"-active",8:b+"-selected",16:b+"-checked",32:b+"-focused",64:b+"-open"}};function Ad(a,b){a||g(Error("Invalid class name "+a));ea(b)||g(Error("Invalid decorator function "+b));Bd[a]=b}var Cd={},Bd={};function W(a,b,c){T.call(this,c);if(!b){for(var b=this.constructor,d;b;){d=ga(b);if(d=Cd[d])break;b=b.f?b.f.constructor:k}b=d?ea(d.K)?d.K():new d:k}this.c=b;this.H=a}C(W,T);v=W.prototype;v.H=k;v.e=0;v.s=39;v.sa=255;v.Ha=0;v.g=j;v.J=k;v.va=j;v.Ja=p;v.Kb=k;v.k=function(){return this.c.k(this)};v.Na=function(){return this.ja||(this.ja=new nd)}; |
|||
v.ea=function(a,b){b?a&&(this.J?Ba(this.J,a)||this.J.push(a):this.J=[a],this.c.ea(this,a,j)):a&&this.J&&(Ca(this.J,a),0==this.J.length&&(this.J=k),this.c.ea(this,a,p))};v.j=function(){var a=this.c.j(this);this.b=a;var b=this.Kb||i;b&&a.setAttribute("role",b);this.Ja||this.c.Ca(a,p);this.g||this.c.ra(a,p)};v.v=function(){return this.c.v(this.a())};v.G=function(a){return this.c.G(a)}; |
|||
v.eb=function(a){this.b=a=this.c.V(this,a);var b=this.Kb||i;b&&a.setAttribute("role",b);this.Ja||this.c.Ca(a,p);this.g="none"!=a.style.display};v.u=function(){W.f.u.call(this);this.c.Ra(this);if(this.s&-2&&(this.va&&Dd(this,j),this.s&32)){var a=this.k();if(a){var b=this.Na();od(b,a);R(R(R(gd(this),b,"key",this.R),a,"focus",this.Oa),a,"blur",this.ta)}}}; |
|||
function Dd(a,b){var c=gd(a),d=a.a();b?(R(R(R(R(c,d,"mouseover",a.mb),d,"mousedown",a.ha),d,"mouseup",a.wa),d,"mouseout",a.lb),a.ua!=ba&&R(c,d,"contextmenu",a.ua),H&&R(c,d,"dblclick",a.Cb)):(S(S(S(S(c,d,"mouseover",a.mb),d,"mousedown",a.ha),d,"mouseup",a.wa),d,"mouseout",a.lb),a.ua!=ba&&S(c,d,"contextmenu",a.ua),H&&S(c,d,"dblclick",a.Cb))}v.ga=function(){W.f.ga.call(this);this.ja&&this.ja.detach();this.g&&this.isEnabled()&&this.c.qa(this,p)};v.P=function(a){this.c.P(this.a(),a);this.H=a}; |
|||
v.Fa=function(a){W.f.Fa.call(this,a);var b=this.a();b&&this.c.Fa(b,a)};v.Ca=function(a){this.Ja=a;var b=this.a();b&&this.c.Ca(b,a)};v.ra=function(a,b){if(b||this.g!=a&&this.dispatchEvent(a?"show":"hide")){var c=this.a();c&&this.c.ra(c,a);this.isEnabled()&&this.c.qa(this,a);this.g=a;return j}return p};v.isEnabled=function(){return!(this.e&1)}; |
|||
v.L=function(a){var b=this.getParent();if((!b||"function"!=typeof b.isEnabled||b.isEnabled())&&Ed(this,1,!a))a||(this.setActive(p),this.M(p)),this.g&&this.c.qa(this,a),this.r(1,!a)};v.M=function(a){Ed(this,2,a)&&this.r(2,a)};v.setActive=function(a){Ed(this,4,a)&&this.r(4,a)};v.Ga=function(a){Ed(this,8,a)&&this.r(8,a)};function Fd(a,b){Ed(a,16,b)&&a.r(16,b)}v.Da=function(a){Ed(this,32,a)&&this.r(32,a)};function Gd(a,b){Ed(a,64,b)&&a.r(64,b)} |
|||
v.r=function(a,b){this.s&a&&b!=!!(this.e&a)&&(this.c.r(this,a,b),this.e=b?this.e|a:this.e&~a)};function Hd(a,b,c){a.d&&(a.e&b&&!c)&&g(Error("Component already rendered"));!c&&a.e&b&&a.r(b,p);a.s=c?a.s|b:a.s&~b}function X(a,b){return!!(a.sa&b)&&!!(a.s&b)}function Ed(a,b,c){return!!(a.s&b)&&!!(a.e&b)!=c&&(!(a.Ha&b)||a.dispatchEvent(fd(b,c)))&&!a.Ub}v.mb=function(a){(!a.relatedTarget||!Ib(this.a(),a.relatedTarget))&&(this.dispatchEvent("enter")&&this.isEnabled()&&X(this,2))&&this.M(j)}; |
|||
v.lb=function(a){if((!a.relatedTarget||!Ib(this.a(),a.relatedTarget))&&this.dispatchEvent("leave"))X(this,4)&&this.setActive(p),X(this,2)&&this.M(p)};v.ua=ba;v.ha=function(a){if(this.isEnabled()&&(X(this,2)&&this.M(j),fb(a)&&(!J||!Pa||!a.ctrlKey)))X(this,4)&&this.setActive(j),this.c.W(this)&&this.k().focus();!this.Ja&&(fb(a)&&(!J||!Pa||!a.ctrlKey))&&a.preventDefault()};v.wa=function(a){this.isEnabled()&&(X(this,2)&&this.M(j),this.e&4&&(Id(this,a)&&X(this,4))&&this.setActive(p))}; |
|||
v.Cb=function(a){this.isEnabled()&&Id(this,a)};function Id(a,b){X(a,16)&&Fd(a,!(a.e&16));X(a,8)&&a.Ga(j);X(a,64)&&Gd(a,!(a.e&64));var c=new L("action",a);b&&(c.altKey=b.altKey,c.ctrlKey=b.ctrlKey,c.metaKey=b.metaKey,c.shiftKey=b.shiftKey,c.vb=b.vb);return a.dispatchEvent(c)}v.Oa=function(){X(this,32)&&this.Da(j)};v.ta=function(){X(this,4)&&this.setActive(p);X(this,32)&&this.Da(p)};v.R=function(a){return this.g&&this.isEnabled()&&this.kb(a)?(a.preventDefault(),a.stopPropagation(),j):p}; |
|||
v.kb=function(a){return 13==a.keyCode&&Id(this,a)};ea(W)||g(Error("Invalid component class "+W));ea(V)||g(Error("Invalid renderer class "+V));var Jd=ga(W);Cd[Jd]=V;Ad("goog-control",function(){return new W(k)});function Kd(a,b,c){this.sb=a;this.ca=b||p;this.D=c||0}Kd.prototype.o=r("D");Kd.prototype.Ea=function(a){this.D=a};function Ld(){var a;a=new $c;var b;a:{try{b=!!a.F&&!!a.F.getItem;break a}catch(c){}b=p}this.F=(a=b?a:k)?new Xc(a):k;this.S=[];this.qb=0}C(Ld,ub);Ld.prototype.load=function(){if(this.F){var a=this.S;if(!z(a))for(var b=a.length-1;0<=b;b--)delete a[b];a.length=0;(a=this.F.get("todos-closure"))&&F(a,function(a){a=new Kd(a.title,a.completed,a.id);a.o()>this.qb&&(this.qb=a.o());this.S.push(a)},this)}Md(this,p)}; |
|||
function Nd(a){var b=Od,c=Aa(b.S,function(b){return a.o()===b.o()});-1===c?(0===a.o()&&a.Ea(++b.qb),b.S.push(a)):b.S[c]=a;Md(b)}Ld.prototype.remove=function(a){var b=this.S,c=Aa(b,function(b){return a.o()===b.o()},i);0<=c&&E.splice.call(b,c,1);Md(this)};function Md(a,b){if((b===i||b)&&a.F){var c=[];F(a.S,function(a){c.push({completed:a.ca,title:a.sb,id:a.o()})});a.F.set("todos-closure",c)}a.dispatchEvent(new Pd(a))}Ld.prototype.getAll=r("S");function Pd(a){L.call(this,"change",a)}C(Pd,L);H&&K(8);function Qd(a){return"object"===typeof a&&a&&0===a.nc?a.content:String(a).replace(Rd,Sd)}var Td={"\x00":"�",'"':""","&":"&","'":"'","<":"<",">":">","\t":"	","\n":" ","\x0B":"","\f":"","\r":" "," ":" ","-":"-","/":"/","=":"=","`":"`","\u0085":"…","\u00a0":" ","\u2028":"
","\u2029":"
"};function Sd(a){return Td[a]}var Rd=/[\x00\x22\x26\x27\x3c\x3e]/g;function Ud(a){return"<strong>"+Qd(a.tb)+"</strong> "+(1==a.tb?"item":"items")+" left"};function Vd(){}C(Vd,V);x(Vd);Vd.prototype.j=function(a){var b='<button id="clear-completed">'+("Clear completed ("+Qd(a.H)+")")+"</button>",b=Gb(b);vd(a,b);return b};Vd.prototype.G=s(p);Vd.prototype.P=function(a,b){a.innerHTML="Clear completed ("+Qd(b)+")"};Vd.prototype.r=function(a,b,c){(a=a.a())&&yd(a,b,c)};function Wd(){}C(Wd,V);x(Wd);Wd.prototype.j=function(a){var b='<span id="todo-count">'+Ud({tb:a.H})+"</span>",b=Gb(b);vd(a,b);return b};Wd.prototype.G=s(p);Wd.prototype.P=function(a,b){a.innerHTML=Ud({tb:b})};Wd.prototype.r=function(a,b,c){(a=a.a())&&yd(a,b,c)};function Xd(){}C(Xd,V);x(Xd);Xd.prototype.j=function(a){var b;b=!!(a.e&16);b="<li "+(b?'class="completed"':"")+'><div class="view"><input class="toggle" type="checkbox" '+(b?"checked":"")+"><label>"+Qd(a.H)+'</label><button class="destroy"></button></div><input class="edit" value="Rule the web"></li>';b=Gb(b);vd(a,b);this.r(a,a.e,j);return b}; |
|||
Xd.prototype.r=function(a,b,c){var d=a.a();if(d){switch(b){case 16:(d?(d?d.childNodes[0]:k).childNodes[0]:k).checked=c;break;case 8:this.ea(a,"editing",c)}yd(d,b,c)}};Xd.prototype.k=function(a){return a.a()?a.a().childNodes[1]:k};Xd.prototype.v=function(a){return a?(a?a.childNodes[0]:k).childNodes[1]:k};function Y(a){W.call(this,"",Xd.K(),a);Hd(this,16,j);Hd(this,8,j);this.sa&=-17;this.sa&=-9;this.Ca(j)}C(Y,W);Y.prototype.u=function(){Y.f.u.call(this);R(gd(this),this.a(),"click",function(a){a.preventDefault()});R(gd(this),this.a(),"dblclick",function(){this.Ga(j)});var a=this.a()?this.a().childNodes[1]:k;R(gd(this),a,"keyup",function(a){13===a.I.keyCode&&this.Da(p)})}; |
|||
Y.prototype.wa=function(a){Y.f.wa.call(this,a);this.isEnabled()&&(a.target===(this.a()?(this.a()?this.a().childNodes[0]:k).childNodes[0]:k)?(Fd(this,!(this.e&16)),this.dispatchEvent("edit")):a.target===(this.a()?(this.a()?this.a().childNodes[0]:k).childNodes[2]:k)&&this.dispatchEvent("destroy"))};Y.prototype.Da=function(a){Y.f.Da.call(this,a);!a&&this.e&8&&(a=qa((this.a()?this.a().childNodes[1]:k).value),""===a?this.dispatchEvent("destroy"):(this.P(a),this.Ga(p),this.dispatchEvent("edit")))}; |
|||
Y.prototype.Ga=function(a){Y.f.Ga.call(this,a);a&&(a=this.a()?this.a().childNodes[1]:k,a.value=this.H,a.focus())};function Yd(){}C(Yd,V);x(Yd);Yd.prototype.j=function(a){return a.Ma().j("div",this.w())};Yd.prototype.V=function(a,b){b.id&&a.Ea(b.id);if("HR"==b.tagName){var c=b,b=this.j(a);c.parentNode&&c.parentNode.insertBefore(b,c);c&&c.parentNode&&c.parentNode.removeChild(c)}else Bb(b,this.w());return b};Yd.prototype.P=function(){};Yd.prototype.w=s("goog-menuseparator");function Zd(a,b){W.call(this,k,a||Yd.K(),b);Hd(this,1,p);Hd(this,2,p);Hd(this,4,p);Hd(this,32,p);this.e=1}C(Zd,W);Zd.prototype.u=function(){Zd.f.u.call(this);this.a().setAttribute("role","separator")};Ad("goog-menuseparator",function(){return new Zd});function $d(){}x($d);v=$d.prototype;v.j=function(a){return a.Ma().j("div",this.hb(a).join(" "))};v.v=function(a){return a};v.G=function(a){return"DIV"==a.tagName}; |
|||
v.V=function(a,b){b.id&&a.Ea(b.id);var c=this.w(),d=p,f=Ab(b);f&&F(f,function(b){b==c?d=j:b&&(b==c+"-disabled"?a.L(p):b==c+"-horizontal"?ae(a,be):b==c+"-vertical"&&ae(a,de))},this);d||Bb(b,c);if(f=this.v(b))for(var e=f.firstChild,h;e&&e.parentNode==f;){h=e.nextSibling;if(1==e.nodeType){var l;a:{l=i;for(var m=Ab(e),n=0,q=m.length;n<q;n++)if(l=m[n]in Bd?Bd[m[n]]():k)break a;l=k}l&&(l.b=e,a.isEnabled()||l.L(p),a.Ia(l),l.V(e))}else(!e.nodeValue||""==qa(e.nodeValue))&&f.removeChild(e);e=h}return b}; |
|||
v.Ra=function(a){a=a.a();cd(a,j,I);H&&(a.hideFocus=j)};v.k=function(a){return a.a()};v.w=s("goog-container");v.hb=function(a){var b=this.w(),c=[b,a.Y==be?b+"-horizontal":b+"-vertical"];a.isEnabled()||c.push(b+"-disabled");return c};function Z(a,b,c){T.call(this,c);this.c=b||$d.K();this.Y=a||de}C(Z,T);var be="horizontal",de="vertical";v=Z.prototype;v.Hb=k;v.ja=k;v.c=k;v.Y=k;v.g=j;v.q=j;v.gb=j;v.m=-1;v.h=k;v.X=p;v.yb=p;v.ic=j;v.Q=k;v.k=function(){return this.Hb||this.c.k(this)};v.Na=function(){return this.ja||(this.ja=new nd(this.k()))};v.j=function(){this.b=this.c.j(this)};v.v=function(){return this.c.v(this.a())};v.G=function(a){return this.c.G(a)};v.eb=function(a){this.b=this.c.V(this,a);"none"==a.style.display&&(this.g=p)}; |
|||
v.u=function(){Z.f.u.call(this);jd(this,function(a){a.d&&ee(this,a)},this);var a=this.a();this.c.Ra(this);this.ra(this.g,j);R(R(R(R(R(R(R(R(gd(this),this,"enter",this.Yb),this,"highlight",this.Zb),this,"unhighlight",this.cc),this,"open",this.bc),this,"close",this.Wb),a,"mousedown",this.ha),Q(a),"mouseup",this.Xb),a,["mousedown","mouseup","mouseover","mouseout","contextmenu"],this.Vb);this.W()&&fe(this,j)}; |
|||
function fe(a,b){var c=gd(a),d=a.k();b?R(R(R(c,d,"focus",a.Oa),d,"blur",a.ta),a.Na(),"key",a.R):S(S(S(c,d,"focus",a.Oa),d,"blur",a.ta),a.Na(),"key",a.R)}v.ga=function(){ge(this,-1);this.h&&Gd(this.h,p);this.X=p;Z.f.ga.call(this)};v.Yb=s(j);v.Zb=function(a){var b=md(this,a.target);if(-1<b&&b!=this.m){var c=U(this,this.m);c&&c.M(p);this.m=b;c=U(this,this.m);this.X&&c.setActive(j);this.ic&&(this.h&&c!=this.h)&&(c.s&64?Gd(c,j):Gd(this.h,p))}this.a().setAttribute("aria-activedescendant",a.target.a().id)}; |
|||
v.cc=function(a){a.target==U(this,this.m)&&(this.m=-1);this.a().setAttribute("aria-activedescendant","")};v.bc=function(a){if((a=a.target)&&a!=this.h&&a.getParent()==this)this.h&&Gd(this.h,p),this.h=a};v.Wb=function(a){a.target==this.h&&(this.h=k)};v.ha=function(a){this.q&&(this.X=j);var b=this.k();b&&Jb(b)?b.focus():a.preventDefault()};v.Xb=function(){this.X=p}; |
|||
v.Vb=function(a){var b;a:{b=a.target;if(this.Q)for(var c=this.a();b&&b!==c;){var d=b.id;if(d in this.Q){b=this.Q[d];break a}b=b.parentNode}b=k}if(b)switch(a.type){case "mousedown":b.ha(a);break;case "mouseup":b.wa(a);break;case "mouseover":b.mb(a);break;case "mouseout":b.lb(a);break;case "contextmenu":b.ua(a)}};v.Oa=function(){};v.ta=function(){ge(this,-1);this.X=p;this.h&&Gd(this.h,p)}; |
|||
v.R=function(a){return this.isEnabled()&&this.g&&(0!=kd(this)||this.Hb)&&this.kb(a)?(a.preventDefault(),a.stopPropagation(),j):p}; |
|||
v.kb=function(a){var b=U(this,this.m);if(b&&"function"==typeof b.R&&b.R(a)||this.h&&this.h!=b&&"function"==typeof this.h.R&&this.h.R(a))return j;if(a.shiftKey||a.ctrlKey||a.metaKey||a.altKey)return p;switch(a.keyCode){case 27:if(this.W())this.k().blur();else return p;break;case 36:he(this,function(a,b){return(a+1)%b},kd(this)-1);break;case 35:he(this,function(a,b){a--;return 0>a?b-1:a},0);break;case 38:if(this.Y==de)ie(this);else return p;break;case 37:if(this.Y==be)ld(this)?je(this):ie(this);else return p; |
|||
break;case 40:if(this.Y==de)je(this);else return p;break;case 39:if(this.Y==be)ld(this)?ie(this):je(this);else return p;break;default:return p}return j};function ee(a,b){var c=b.a(),c=c.id||(c.id=b.o());a.Q||(a.Q={});a.Q[c]=b}v.Ia=function(a,b){Z.f.Ia.call(this,a,b)};v.$a=function(a,b,c){a.Ha|=2;a.Ha|=64;(this.W()||!this.yb)&&Hd(a,32,p);a.d&&p!=a.va&&Dd(a,p);a.va=p;Z.f.$a.call(this,a,b,c);a.d&&this.d&&ee(this,a);b<=this.m&&this.m++}; |
|||
v.removeChild=function(a,b){if(a=A(a)?this.i&&a?(a in this.i?this.i[a]:i)||k:k:a){var c=md(this,a);-1!=c&&(c==this.m?a.M(p):c<this.m&&this.m--);var d=a.a();d&&(d.id&&this.Q)&&(c=this.Q,d=d.id,d in c&&delete c[d])}c=a=Z.f.removeChild.call(this,a,b);c.d&&j!=c.va&&Dd(c,j);c.va=j;return a};function ae(a,b){a.a()&&g(Error("Component already rendered"));a.Y=b} |
|||
v.ra=function(a,b){if(b||this.g!=a&&this.dispatchEvent(a?"show":"hide")){this.g=a;var c=this.a();if(c){ad(c,a);if(this.W()&&(c=this.k()))c.tabIndex=this.q&&this.g?0:-1;b||this.dispatchEvent(this.g?"aftershow":"afterhide")}return j}return p};v.isEnabled=r("q"); |
|||
v.L=function(a){if(this.q!=a&&this.dispatchEvent(a?"enable":"disable"))if(a?(this.q=j,jd(this,function(a){a.Rb?delete a.Rb:a.L(j)})):(jd(this,function(a){a.isEnabled()?a.L(p):a.Rb=j}),this.X=this.q=p),this.W()){var b=this.k();b&&(b.tabIndex=a&&this.g?0:-1)}};v.W=r("gb");v.qa=function(a){a!=this.gb&&this.d&&fe(this,a);this.gb=a;if(this.q&&this.g){var b=this.k();b&&(b.tabIndex=a?0:-1)}};function ge(a,b){var c=U(a,b);c?c.M(j):-1<a.m&&U(a,a.m).M(p)}v.M=function(a){ge(this,md(this,a))}; |
|||
function je(a){he(a,function(a,c){return(a+1)%c},a.m)}function ie(a){he(a,function(a,c){a--;return 0>a?c-1:a},a.m)}function he(a,b,c){for(var c=0>c?md(a,a.h):c,d=kd(a),c=b.call(a,c,d),f=0;f<=d;){var e=U(a,c);if(e&&e.g&&e.isEnabled()&&e.s&2){ge(a,c);break}f++;c=b.call(a,c,d)}};function ke(){}C(ke,$d);x(ke);ke.prototype.G=function(a){return"UL"==a.tagName};ke.prototype.Ra=function(){};function le(a){Z.call(this,de,ke.K(),a);this.qa(p);this.yb=j}C(le,Z);le.prototype.ha=function(){this.q&&(this.X=j)};var Od=new Ld;Od.addEventListener("change",me);var ne=new le;ne.V(document.getElementById("todo-list"));var oe=document.getElementById("main"),pe=document.getElementById("footer"),qe=new W(k,Wd.K());id(qe,pe);var re=new W(k,Vd.K());id(re,pe);O(re,"action",function(){function a(a){a.ca&&Od.remove(a)}for(var b=Od.getAll(),c=A(b)?b.split(""):b,b=b.length-1;0<=b;--b)b in c&&a.call(i,c[b])});var se=document.getElementById("toggle-all"); |
|||
O(se,"click",function(){var a=se.checked;F(Od.getAll(),function(b){b=new Kd(b.sb,a,b.o());Nd(b)})});var te="/",ue="/active",ve="/completed",we=te,$=new Nb;O($,"navigate",function(a){switch(a.xb){case te:case ue:case ve:a.xb!==we&&(we=a.xb,me());break;default:a=te,Ub($)!=a&&($.ba?(Tb($,a,j),Wb||H&&Vb($,a,j,i),$.q&&$.Ab(p)):(Vb($,a,j),$.na=$.la=$.xa.value=a,$.dispatchEvent(new Mb(a,p))))}}); |
|||
function me(){ne.Mb(j);var a=Od.getAll();F(a,function(a){if(!(we===ue&&a.ca||we===ve&&!a.ca)){var b=new Y;b.P(a.sb);Fd(b,a.ca);b.rb=a;ne.Ia(b,j)}});var b;var c=function(a,b){return b.ca?a+1:a};if(a.reduce)b=a.reduce(c,0);else{var d=0;F(a,function(a){d=c.call(i,d,a)});b=d}var f=a.length-b;se.checked=0===f;qe.P(f.toString());re.P(b.toString());re.ra(0<b);ad(oe,0<a.length);ad(pe,0<a.length);a=ec("#filters a");F(a,function(a,b){a.className=we===te&&0===b||we===ue&&1===b||we===ve&&2===b?"selected":""})} |
|||
O(ne,"edit",function(a){a=a.target;a=new Kd(a.H,!!(a.e&16),a.rb.o());Nd(a)});O(ne,"destroy",function(a){a=a.target.rb;a!==k&&Od.remove(a)});var xe=document.getElementById("new-todo");O(xe,"keyup",function(a){13===a.keyCode&&(a=qa(xe.value),""!==a&&(xe.value="",Nd(new Kd(a))))});Od.load();$.L(j);})(); |
@ -0,0 +1,74 @@ |
|||
goog.provide('todomvc.model.ToDoItem'); |
|||
|
|||
/** |
|||
* The model object representing a todo item. |
|||
* |
|||
* @param {!string} note the text associated with this item. |
|||
* @param {!boolean=} opt_done is this item complete? defaults to false. |
|||
* @param {!number=} opt_id the id for the item defaults to 0 meaning undefined. |
|||
* @constructor |
|||
*/ |
|||
todomvc.model.ToDoItem = function(note, opt_done, opt_id) { |
|||
/** |
|||
* note the text associated with this item |
|||
* @private |
|||
* @type {!string} |
|||
*/ |
|||
this.note_ = note; |
|||
|
|||
/** |
|||
* is this item complete? |
|||
* @private |
|||
* @type {!boolean} |
|||
*/ |
|||
this.done_ = opt_done || false; |
|||
|
|||
/** |
|||
* the id for the item, or 0 if it is not yet defined |
|||
* @private |
|||
* @type {!number} |
|||
*/ |
|||
this.id_ = opt_id || 0; |
|||
}; |
|||
|
|||
/** |
|||
* @return {!string} the text associated with this item. |
|||
*/ |
|||
todomvc.model.ToDoItem.prototype.getNote = function() { |
|||
return this.note_; |
|||
}; |
|||
|
|||
/** |
|||
* @return {!boolean} is this item complete? |
|||
*/ |
|||
todomvc.model.ToDoItem.prototype.isDone = function() { |
|||
return this.done_; |
|||
}; |
|||
|
|||
/** |
|||
* @return {!number} the id for the item, or 0 if it is not yet defined. |
|||
*/ |
|||
todomvc.model.ToDoItem.prototype.getId = function() { |
|||
return this.id_; |
|||
}; |
|||
|
|||
/** |
|||
* @param {!string} note the text associated with this item. |
|||
*/ |
|||
todomvc.model.ToDoItem.prototype.setNote = function(note) { |
|||
this.note_ = note; |
|||
}; |
|||
|
|||
/** |
|||
* @param {!boolean} done is this item complete? |
|||
*/ |
|||
todomvc.model.ToDoItem.prototype.setDone = function(done) { |
|||
this.done_ = done; |
|||
}; |
|||
|
|||
/** |
|||
* @param {!number} id the id for the item, or 0 if it is not yet defined. |
|||
*/ |
|||
todomvc.model.ToDoItem.prototype.setId = function(id) { |
|||
this.id_ = id; |
|||
}; |
@ -0,0 +1,153 @@ |
|||
goog.provide('todomvc.model.ToDoItemStore'); |
|||
|
|||
goog.require('goog.array'); |
|||
goog.require('goog.events.Event'); |
|||
goog.require('goog.events.EventTarget'); |
|||
goog.require('goog.storage.Storage'); |
|||
goog.require('goog.storage.mechanism.mechanismfactory'); |
|||
goog.require('goog.string'); |
|||
goog.require('goog.ui.Component'); |
|||
goog.require('goog.ui.Control'); |
|||
goog.require('todomvc.model.ToDoItem'); |
|||
|
|||
|
|||
/** |
|||
* @constructor |
|||
* @extends {goog.events.EventTarget} |
|||
*/ |
|||
todomvc.model.ToDoItemStore = function() { |
|||
var mechanism = goog.storage.mechanism.mechanismfactory |
|||
.createHTML5LocalStorage(); |
|||
/** |
|||
* @type {goog.storage.Storage} |
|||
* @private |
|||
*/ |
|||
this.storage_ = mechanism ? new goog.storage.Storage(mechanism) : null; |
|||
|
|||
/** |
|||
* @type {!Array.<todomvc.model.ToDoItem>} |
|||
* @private |
|||
*/ |
|||
this.items_ = []; |
|||
|
|||
/** |
|||
* Fundamentally flawed approach to ID-ing but fine for demo |
|||
* @type {!number} |
|||
* @private |
|||
*/ |
|||
this.maxId_ = 0; |
|||
}; |
|||
goog.inherits(todomvc.model.ToDoItemStore, goog.events.EventTarget); |
|||
|
|||
/** |
|||
* Load item list from storage |
|||
*/ |
|||
todomvc.model.ToDoItemStore.prototype.load = function() { |
|||
if (!this.storage_) { |
|||
this.notify_(false); |
|||
return; // no storage = no loading!
|
|||
} |
|||
goog.array.clear(this.items_); |
|||
/** |
|||
* @type {Array.<*>} |
|||
*/ |
|||
var serializedItems = /** @type {Array.<*>} */ |
|||
(this.storage_.get('todos-closure')); |
|||
if (!serializedItems) { |
|||
this.notify_(false); |
|||
return; // nothing in storage
|
|||
} |
|||
goog.array.forEach(serializedItems, function(serializedItem) { |
|||
var item = new todomvc.model.ToDoItem(serializedItem['title'], |
|||
serializedItem['completed'], serializedItem['id']); |
|||
if (item.getId() > this.maxId_) { |
|||
this.maxId_ = item.getId(); |
|||
} |
|||
this.items_.push(item); |
|||
}, this); |
|||
this.notify_(false); |
|||
}; |
|||
|
|||
/** |
|||
* @param {!todomvc.model.ToDoItem} updatedItem A prototype model to update. |
|||
*/ |
|||
todomvc.model.ToDoItemStore.prototype.addOrUpdate = function(updatedItem) { |
|||
var idx = goog.array.findIndex(this.items_, function(item) { |
|||
return updatedItem.getId() === item.getId(); |
|||
}); |
|||
if (idx === -1) { |
|||
if (updatedItem.getId() === 0) { |
|||
updatedItem.setId(++this.maxId_); |
|||
} |
|||
this.items_.push(updatedItem); |
|||
} else { |
|||
this.items_[idx] = updatedItem; |
|||
} |
|||
this.notify_(); |
|||
}; |
|||
|
|||
/** |
|||
* @param {!todomvc.model.ToDoItem} itemToRemove A prototype model to remove. |
|||
*/ |
|||
todomvc.model.ToDoItemStore.prototype.remove = function(itemToRemove) { |
|||
goog.array.removeIf(this.items_, function(item) { |
|||
return itemToRemove.getId() === item.getId(); |
|||
}); |
|||
this.notify_(); |
|||
}; |
|||
|
|||
/** |
|||
* @param {boolean=} opt_save whether to save to storage, defaults to true. |
|||
* @private |
|||
*/ |
|||
todomvc.model.ToDoItemStore.prototype.notify_ = function(opt_save) { |
|||
// TODO delay until all changes have been made
|
|||
if (!goog.isDef(opt_save) || opt_save) { |
|||
this.save_(); |
|||
} |
|||
this.dispatchEvent(new todomvc.model.ToDoItemStore.ChangeEvent(this)); |
|||
}; |
|||
|
|||
/** |
|||
* @return {Array.<todomvc.model.ToDoItem>} All of the stored items. |
|||
*/ |
|||
todomvc.model.ToDoItemStore.prototype.getAll = function() { |
|||
return this.items_; |
|||
}; |
|||
|
|||
/** |
|||
* @private |
|||
*/ |
|||
todomvc.model.ToDoItemStore.prototype.save_ = function() { |
|||
if (!this.storage_) { |
|||
return; // no storage = no saving!
|
|||
} |
|||
/** |
|||
* @type {Array.<*>} |
|||
*/ |
|||
var serializedItems = []; |
|||
goog.array.forEach(this.items_, function(item) { |
|||
serializedItems.push({ |
|||
'completed' : item.isDone(), |
|||
'title': item.getNote(), |
|||
'id' : item.getId() |
|||
}); |
|||
}); |
|||
this.storage_.set('todos-closure', serializedItems); |
|||
}; |
|||
|
|||
/** |
|||
* @const |
|||
*/ |
|||
todomvc.model.ToDoItemStore.ChangeEventType = 'change'; |
|||
|
|||
/** |
|||
* @constructor |
|||
* @extends {goog.events.Event} |
|||
* @param {todomvc.model.ToDoItemStore} target The item store. |
|||
*/ |
|||
todomvc.model.ToDoItemStore.ChangeEvent = function(target) { |
|||
goog.events.Event.call(this, |
|||
todomvc.model.ToDoItemStore.ChangeEventType, target); |
|||
}; |
|||
goog.inherits(todomvc.model.ToDoItemStore.ChangeEvent, goog.events.Event); |
@ -0,0 +1,69 @@ |
|||
goog.provide('todomvc.view.ClearCompletedControlRenderer'); |
|||
|
|||
goog.require('goog.dom'); |
|||
goog.require('goog.ui.Component.State'); |
|||
goog.require('goog.ui.ControlRenderer'); |
|||
|
|||
/** |
|||
* A renderer for the clear completed control. |
|||
* |
|||
* @constructor |
|||
* @extends {goog.ui.ControlRenderer} |
|||
*/ |
|||
todomvc.view.ClearCompletedControlRenderer = function() { |
|||
goog.ui.ControlRenderer.call(this); |
|||
}; |
|||
goog.inherits(todomvc.view.ClearCompletedControlRenderer, |
|||
goog.ui.ControlRenderer); |
|||
|
|||
// add getInstance method to todomvc.view.ClearCompletedControlRenderer
|
|||
goog.addSingletonGetter(todomvc.view.ClearCompletedControlRenderer); |
|||
|
|||
/** |
|||
* @param {goog.ui.Control} control Control to render. |
|||
* @return {Element} Root element for the control. |
|||
*/ |
|||
todomvc.view.ClearCompletedControlRenderer.prototype.createDom = |
|||
function(control) { |
|||
var html = todomvc.view.clearCompleted({ |
|||
number: control.getContent() |
|||
}); |
|||
var element = (/**@type {!Element}*/ goog.dom.htmlToDocumentFragment(html)); |
|||
this.setAriaStates(control, element); |
|||
return element; |
|||
}; |
|||
|
|||
/** |
|||
* @param {Element} element Element to decorate. |
|||
* @return {boolean} Whether the renderer can decorate the element. |
|||
*/ |
|||
todomvc.view.ClearCompletedControlRenderer.prototype.canDecorate = |
|||
function(element) { |
|||
return false; |
|||
}; |
|||
|
|||
/** |
|||
* @param {Element} element Element to populate. |
|||
* @param {goog.ui.ControlContent} content Text caption or DOM. |
|||
*/ |
|||
todomvc.view.ClearCompletedControlRenderer.prototype.setContent = |
|||
function(element, content) { |
|||
element.innerHTML = todomvc.view.clearCompletedInner({ |
|||
number: content |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* Updates the appearance of the control in response to a state change. |
|||
* |
|||
* @param {goog.ui.Control} control Control instance to update. |
|||
* @param {goog.ui.Component.State} state State to enable or disable. |
|||
* @param {boolean} enable Whether the control is entering or exiting the state. |
|||
*/ |
|||
todomvc.view.ClearCompletedControlRenderer.prototype.setState = |
|||
function(control, state, enable) { |
|||
var element = control.getElement(); |
|||
if (element) { |
|||
this.updateAriaState(element, state, enable); |
|||
} |
|||
}; |
@ -0,0 +1,67 @@ |
|||
goog.provide('todomvc.view.ItemCountControlRenderer'); |
|||
|
|||
goog.require('goog.dom'); |
|||
goog.require('goog.ui.Component.State'); |
|||
goog.require('goog.ui.ControlRenderer'); |
|||
|
|||
/** |
|||
* A renderer for the item count control. |
|||
* |
|||
* @constructor |
|||
* @extends {goog.ui.ControlRenderer} |
|||
*/ |
|||
todomvc.view.ItemCountControlRenderer = function() { |
|||
goog.ui.ControlRenderer.call(this); |
|||
}; |
|||
goog.inherits(todomvc.view.ItemCountControlRenderer, goog.ui.ControlRenderer); |
|||
|
|||
// add getInstance method to todomvc.view.ItemCountControlRenderer
|
|||
goog.addSingletonGetter(todomvc.view.ItemCountControlRenderer); |
|||
|
|||
/** |
|||
* @param {goog.ui.Control} control Control to render. |
|||
* @return {Element} Root element for the control. |
|||
*/ |
|||
todomvc.view.ItemCountControlRenderer.prototype.createDom = function(control) { |
|||
var html = todomvc.view.itemCount({ |
|||
number: control.getContent() |
|||
}); |
|||
var element = (/**@type {!Element}*/ goog.dom.htmlToDocumentFragment(html)); |
|||
this.setAriaStates(control, element); |
|||
return element; |
|||
}; |
|||
|
|||
/** |
|||
* @param {Element} element Element to decorate. |
|||
* @return {boolean} Whether the renderer can decorate the element. |
|||
*/ |
|||
todomvc.view.ItemCountControlRenderer.prototype.canDecorate = |
|||
function(element) { |
|||
return false; |
|||
}; |
|||
|
|||
/** |
|||
* @param {Element} element Element to populate. |
|||
* @param {goog.ui.ControlContent} content Text caption or DOM. |
|||
*/ |
|||
todomvc.view.ItemCountControlRenderer.prototype.setContent = |
|||
function(element, content) { |
|||
element.innerHTML = todomvc.view.itemCountInner({ |
|||
number: content |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* Updates the appearance of the control in response to a state change. |
|||
* |
|||
* @param {goog.ui.Control} control Control instance to update. |
|||
* @param {goog.ui.Component.State} state State to enable or disable. |
|||
* @param {boolean} enable Whether the control is entering or exiting the state. |
|||
*/ |
|||
todomvc.view.ItemCountControlRenderer.prototype.setState = |
|||
function(control, state, enable) { |
|||
var element = control.getElement(); |
|||
if (element) { |
|||
this.updateAriaState(element, state, enable); |
|||
} |
|||
}; |
@ -0,0 +1,150 @@ |
|||
goog.provide('todomvc.view.ToDoItemControl'); |
|||
|
|||
goog.require('goog.dom'); |
|||
goog.require('goog.events'); |
|||
goog.require('goog.events.KeyCodes'); |
|||
goog.require('goog.string'); |
|||
goog.require('goog.ui.Component.State'); |
|||
goog.require('goog.ui.Control'); |
|||
|
|||
goog.require('todomvc.view.ToDoItemControlRenderer'); |
|||
|
|||
/** |
|||
* A control representing each item in the todo list. It makes use of the |
|||
* CHECKED and SELECTED states to represent being done and being in edit mode. |
|||
* |
|||
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, |
|||
* used for document interaction. |
|||
* @constructor |
|||
* @extends {goog.ui.Control} |
|||
*/ |
|||
todomvc.view.ToDoItemControl = function(opt_domHelper) { |
|||
goog.ui.Control.call(this, '', todomvc.view.ToDoItemControlRenderer |
|||
.getInstance(), opt_domHelper); |
|||
|
|||
// enable CHECKED and SELECTED states
|
|||
this.setSupportedState(goog.ui.Component.State.CHECKED, true); |
|||
this.setSupportedState(goog.ui.Component.State.SELECTED, true); |
|||
|
|||
// disable auto handling of CHECKED and SELECTED states
|
|||
this.setAutoStates(goog.ui.Component.State.CHECKED, false); |
|||
this.setAutoStates(goog.ui.Component.State.SELECTED, false); |
|||
|
|||
// allow text selection
|
|||
this.setAllowTextSelection(true); |
|||
}; |
|||
goog.inherits(todomvc.view.ToDoItemControl, goog.ui.Control); |
|||
|
|||
/** |
|||
* The event types this control dispatches. |
|||
*/ |
|||
todomvc.view.ToDoItemControl.EventType = { |
|||
EDIT: 'edit', |
|||
DESTROY: 'destroy' |
|||
}; |
|||
|
|||
|
|||
/** |
|||
* Configures the component after its DOM has been rendered, and sets up event |
|||
* handling. Overrides {@link goog.ui.Component#enterDocument}. |
|||
* |
|||
* @override |
|||
*/ |
|||
todomvc.view.ToDoItemControl.prototype.enterDocument = function() { |
|||
todomvc.view.ToDoItemControl.superClass_.enterDocument.call(this); |
|||
// prevent clicking the checkbox (or anything within the root element)
|
|||
// from having any default behaviour. This stops the checkbox being set
|
|||
// by the browser.
|
|||
this.getHandler().listen(this.getElement(), goog.events.EventType.CLICK, |
|||
function(e) { |
|||
e.preventDefault(); |
|||
}); |
|||
this.getHandler().listen(this.getElement(), goog.events.EventType.DBLCLICK, |
|||
function(e) { |
|||
this.setSelected(true); |
|||
}); |
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var inputElement = this.getRenderer().getInputElement( |
|||
this.getElement()); |
|||
this.getHandler().listen(inputElement, goog.events.EventType.KEYUP, |
|||
function(e) { |
|||
var be = e.getBrowserEvent(); |
|||
if (be.keyCode === goog.events.KeyCodes.ENTER) { |
|||
this.setFocused(false); |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
/** |
|||
* Returns the renderer used by this component to render itself or to decorate |
|||
* an existing element. |
|||
* |
|||
* @return {todomvc.view.ToDoItemControlRenderer} Renderer used by the |
|||
* component. |
|||
*/ |
|||
todomvc.view.ToDoItemControl.prototype.getRenderer = function() { |
|||
return (/**@type {todomvc.view.ToDoItemControlRenderer}*/ this.renderer_); |
|||
}; |
|||
|
|||
/** |
|||
* Specialised handling of mouse events when clicking on the checkbox, label, |
|||
* textbox or remove link. |
|||
* |
|||
* @param {goog.events.Event} e Mouse event to handle. |
|||
*/ |
|||
todomvc.view.ToDoItemControl.prototype.handleMouseUp = function(e) { |
|||
todomvc.view.ToDoItemControl.superClass_.handleMouseUp.call(this, e); |
|||
if (this.isEnabled()) { |
|||
if (e.target === this.getRenderer().getCheckboxElement( |
|||
this.getElement())) { |
|||
this.setChecked(!this.isChecked()); |
|||
this.dispatchEvent(todomvc.view.ToDoItemControl.EventType.EDIT); |
|||
} else if (e.target === this.getRenderer().getDestroyElement( |
|||
this.getElement())) { |
|||
this.dispatchEvent(todomvc.view.ToDoItemControl.EventType.DESTROY); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Override the behaviour when the control is unfocused. |
|||
* @param {boolean} focused is focused? |
|||
*/ |
|||
todomvc.view.ToDoItemControl.prototype.setFocused = function(focused) { |
|||
todomvc.view.ToDoItemControl.superClass_.setFocused.call(this, focused); |
|||
if (!focused && this.isSelected()) { |
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var inputElement = this.getRenderer().getInputElement( |
|||
this.getElement()); |
|||
var value = goog.string.trim(inputElement.value); |
|||
if (value === '') { |
|||
this.dispatchEvent(todomvc.view.ToDoItemControl.EventType.DESTROY); |
|||
} else { |
|||
this.setContent(value); |
|||
this.setSelected(false); |
|||
this.dispatchEvent(todomvc.view.ToDoItemControl.EventType.EDIT); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Override the behaviour to switch to editing mode when the control is selected |
|||
* @param {boolean} selected is selected? |
|||
*/ |
|||
todomvc.view.ToDoItemControl.prototype.setSelected = function(selected) { |
|||
todomvc.view.ToDoItemControl.superClass_.setSelected.call(this, selected); |
|||
// populate the input box when selected
|
|||
if (selected) { |
|||
/** |
|||
* @type {Element} |
|||
*/ |
|||
var inputElement = this.getRenderer().getInputElement( |
|||
this.getElement()); |
|||
inputElement.value = this.getContent(); |
|||
inputElement.focus(); |
|||
} |
|||
}; |
@ -0,0 +1,133 @@ |
|||
goog.provide('todomvc.view.ToDoItemControlRenderer'); |
|||
|
|||
goog.require('goog.ui.Component.State'); |
|||
goog.require('goog.ui.ControlRenderer'); |
|||
|
|||
/** |
|||
* The renderer for the ToDoItemControl which has knowledge of the DOM |
|||
* structure of the Control and the applicable CSS classes. |
|||
* |
|||
* @constructor |
|||
* @extends {goog.ui.ControlRenderer} |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer = function() { |
|||
goog.ui.ControlRenderer.call(this); |
|||
}; |
|||
goog.inherits(todomvc.view.ToDoItemControlRenderer, goog.ui.ControlRenderer); |
|||
|
|||
// add getInstance method to todomvc.view.ToDoItemControlRenderer
|
|||
goog.addSingletonGetter(todomvc.view.ToDoItemControlRenderer); |
|||
|
|||
/** |
|||
* @param {goog.ui.Control} control Control to render. |
|||
* @return {Element} Root element for the control. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.createDom = function(control) { |
|||
var html = todomvc.view.toDoItem({ |
|||
content: control.getContent(), |
|||
checked: control.isChecked() |
|||
}); |
|||
var element = (/**@type {!Element}*/ goog.dom.htmlToDocumentFragment(html)); |
|||
this.setAriaStates(control, element); |
|||
this.setState(control, /** @type {goog.ui.Component.State} */ |
|||
(control.getState()), true); |
|||
return element; |
|||
}; |
|||
|
|||
/** |
|||
* Updates the appearance of the control in response to a state change. |
|||
* |
|||
* @param {goog.ui.Control} control Control instance to update. |
|||
* @param {goog.ui.Component.State} state State to enable or disable. |
|||
* @param {boolean} enable Whether the control is entering or exiting the state. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.setState = |
|||
function(control, state, enable) { |
|||
var element = control.getElement(); |
|||
if (element) { |
|||
switch (state) { |
|||
case goog.ui.Component.State.CHECKED: |
|||
this.getCheckboxElement(element).checked = enable; |
|||
break; |
|||
case goog.ui.Component.State.SELECTED: |
|||
this.enableClassName(control, 'editing', enable); |
|||
break; |
|||
} |
|||
|
|||
this.updateAriaState(element, state, enable); |
|||
} |
|||
}; |
|||
|
|||
/** |
|||
* Returns the element within the component's DOM that should receive keyboard |
|||
* focus (null if none). The default implementation returns the control's root |
|||
* element. |
|||
* @param {goog.ui.Control} control Control whose key event target is to be |
|||
* returned. |
|||
* @return {Element} The key event target. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.getKeyEventTarget = |
|||
function(control) { |
|||
return this.getInputElement(control.getElement()); |
|||
}; |
|||
|
|||
/** |
|||
* Takes the control's root element and returns the display element |
|||
* |
|||
* @param {Element} element Root element of the control whose display element is |
|||
* to be returned. |
|||
* @return {Element} The control's display element. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.getDisplayElement = function( |
|||
element) { |
|||
return element ? element.childNodes[0] : null; |
|||
}; |
|||
|
|||
/** |
|||
* Takes the control's root element and returns the parent element of the |
|||
* control's contents. |
|||
* |
|||
* @param {Element} element Root element of the control whose content element is |
|||
* to be returned. |
|||
* @return {Element} The control's content element. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.getContentElement = function( |
|||
element) { |
|||
return element ? this.getDisplayElement(element).childNodes[1] : null; |
|||
}; |
|||
|
|||
/** |
|||
* Takes the control's root element and returns the checkbox element |
|||
* |
|||
* @param {Element} element Root element of the control whose checkbox element |
|||
* is to be returned. |
|||
* @return {Element} The control's checkbox element. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.getCheckboxElement = function( |
|||
element) { |
|||
return element ? this.getDisplayElement(element).childNodes[0] : null; |
|||
}; |
|||
|
|||
/** |
|||
* Takes the control's root element and returns the destroy element |
|||
* |
|||
* @param {Element} element Root element of the control whose destroy element is |
|||
* to be returned. |
|||
* @return {Element} The control's destroy element. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.getDestroyElement = function( |
|||
element) { |
|||
return element ? this.getDisplayElement(element).childNodes[2] : null; |
|||
}; |
|||
|
|||
/** |
|||
* Takes the control's root element and returns the input element |
|||
* |
|||
* @param {Element} element Root element of the control whose input element is |
|||
* to be returned. |
|||
* @return {Element} The control's input element. |
|||
*/ |
|||
todomvc.view.ToDoItemControlRenderer.prototype.getInputElement = function( |
|||
element) { |
|||
return element ? element.childNodes[1] : null; |
|||
}; |
@ -0,0 +1,37 @@ |
|||
goog.provide('todomvc.view.ToDoListContainer'); |
|||
|
|||
goog.require('goog.ui.Container'); |
|||
|
|||
goog.require('todomvc.view.ToDoListContainerRenderer'); |
|||
|
|||
/** |
|||
* A container for the ToDoItemControls, overridden to support keyboard focus |
|||
* on child controls. |
|||
* |
|||
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper, used for |
|||
* document interaction. |
|||
* @constructor |
|||
* @extends {goog.ui.Container} |
|||
*/ |
|||
todomvc.view.ToDoListContainer = function(opt_domHelper) { |
|||
goog.ui.Container |
|||
.call(this, goog.ui.Container.Orientation.VERTICAL, |
|||
todomvc.view.ToDoListContainerRenderer.getInstance(), |
|||
opt_domHelper); |
|||
|
|||
// allow focus on children
|
|||
this.setFocusable(false); |
|||
this.setFocusableChildrenAllowed(true); |
|||
}; |
|||
goog.inherits(todomvc.view.ToDoListContainer, goog.ui.Container); |
|||
|
|||
/** |
|||
* Override this method to allow text selection in children. |
|||
* |
|||
* @param {goog.events.BrowserEvent} e Mousedown event to handle. |
|||
*/ |
|||
todomvc.view.ToDoListContainer.prototype.handleMouseDown = function(e) { |
|||
if (this.enabled_) { |
|||
this.setMouseButtonPressed(true); |
|||
} |
|||
}; |
@ -0,0 +1,44 @@ |
|||
goog.provide('todomvc.view.ToDoListContainerRenderer'); |
|||
|
|||
goog.require('goog.ui.Component.State'); |
|||
goog.require('goog.ui.Container'); |
|||
goog.require('goog.ui.ContainerRenderer'); |
|||
|
|||
/** |
|||
* A renderer for the container, overridden to support keyboard focus |
|||
* on child controls. |
|||
* @constructor |
|||
* @extends {goog.ui.ContainerRenderer} |
|||
*/ |
|||
todomvc.view.ToDoListContainerRenderer = function() { |
|||
goog.ui.ContainerRenderer.call(this); |
|||
}; |
|||
goog.inherits(todomvc.view.ToDoListContainerRenderer, |
|||
goog.ui.ContainerRenderer); |
|||
goog.addSingletonGetter(todomvc.view.ToDoListContainerRenderer); |
|||
|
|||
/** |
|||
* @param {Element} element Element to decorate. |
|||
* @return {boolean} Whether the renderer can decorate the element. |
|||
*/ |
|||
todomvc.view.ToDoListContainerRenderer.prototype.canDecorate = |
|||
function(element) { |
|||
return element.tagName == 'UL'; |
|||
}; |
|||
|
|||
/** |
|||
* Override this method to allow text selection in children |
|||
* |
|||
* @param {goog.ui.Container} container Container whose DOM is to be initialized |
|||
* as it enters the document. |
|||
*/ |
|||
todomvc.view.ToDoListContainerRenderer.prototype.initializeDom = |
|||
function(container) { |
|||
var elem = (/**@type {!Element}*/ container.getElement()); |
|||
|
|||
// Set the ARIA role.
|
|||
var ariaRole = this.getAriaRole(); |
|||
if (ariaRole) { |
|||
goog.dom.a11y.setRole(elem, ariaRole); |
|||
} |
|||
}; |
@ -0,0 +1,49 @@ |
|||
{namespace todomvc.view} |
|||
|
|||
/** |
|||
* A todo list item template |
|||
* @param content the label for this item |
|||
* @param checked whether the item is checked |
|||
*/ |
|||
{template .toDoItem} |
|||
<li {if $checked}class="completed"{/if}> |
|||
<div class="view"> |
|||
<input class="toggle" type="checkbox" {if $checked}checked{/if}> |
|||
<label>{$content}</label> |
|||
<button class="destroy"></button> |
|||
</div> |
|||
<input class="edit" value="Rule the web"> |
|||
</li> |
|||
{/template} |
|||
|
|||
/** |
|||
* A todo list item count template |
|||
* @param number the count of items |
|||
*/ |
|||
{template .itemCount} |
|||
<span id="todo-count">{call .itemCountInner data="all"/}</span> |
|||
{/template} |
|||
|
|||
/** |
|||
* A todo list item count template |
|||
* @param number the count of items |
|||
*/ |
|||
{template .itemCountInner} |
|||
<strong>{$number}</strong> {if $number == 1}item{else}items{/if} left |
|||
{/template} |
|||
|
|||
/** |
|||
* A todo list clear completed template |
|||
* @param number the count of items |
|||
*/ |
|||
{template .clearCompleted} |
|||
<button id="clear-completed">{call .clearCompletedInner data="all"/}</button> |
|||
{/template} |
|||
|
|||
/** |
|||
* A todo list clear completed template |
|||
* @param number the count of items |
|||
*/ |
|||
{template .clearCompletedInner} |
|||
Clear completed ({$number}) |
|||
{/template} |
@ -0,0 +1,25 @@ |
|||
{ |
|||
"id" : "todomvc", |
|||
"inputs" : "js/app.js", |
|||
"paths" : "js/", |
|||
"output-wrapper" : "(function(){%output%})();", |
|||
"mode" : "ADVANCED", |
|||
"define" : { |
|||
"goog.LOCALE": "en_GB" |
|||
}, |
|||
"checks": { |
|||
// Unfortunately, the Closure Library violates these in many places. |
|||
// "accessControls": "ERROR", |
|||
// "visibility": "ERROR" |
|||
|
|||
"checkRegExp": "WARNING", |
|||
"checkTypes": "WARNING", |
|||
"checkVars": "WARNING", |
|||
"deprecated": "WARNING", |
|||
"fileoverviewTags": "WARNING", |
|||
"invalidCasts": "WARNING", |
|||
"missingProperties": "WARNING", |
|||
"nonStandardJsDocs": "WARNING", |
|||
"undefinedVars": "WARNING" |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
# Closure Tools TodoMVC Example |
|||
|
|||
> The Closure Tools project is an effort by Google engineers to open source the tools used in many of Google's sites and web applications for use by the wider Web development community. |
|||
|
|||
> _[Closure Tools - developers.google.com/closure](https://developers.google.com/closure)_ |
|||
|
|||
|
|||
## Learning Closure Tools |
|||
|
|||
The [Closure Tools website](https://developers.google.com/closure) is a great resource for getting started. |
|||
|
|||
Here are some links you may find helpful: |
|||
|
|||
* [Documentation](https://developers.google.com/closure/library/docs/overview) |
|||
* [API Reference](http://docs.closure-library.googlecode.com/git/index.html) |
|||
* [Blog](http://closuretools.blogspot.com) |
|||
* [FAQ](https://developers.google.com/closure/faq) |
|||
|
|||
Articles and guides from the community: |
|||
|
|||
* [Examples, walkthroughs, and articles](http://www.googleclosure.com) |
|||
* [First Adventure in Google Closure](http://www.codeproject.com/Articles/265364/First-Adventures-in-Google-Closure) |
|||
|
|||
Get help from other Closure Tools users: |
|||
|
|||
* [Google Groups mailing list](https://groups.google.com/group/closure-library-discuss) |
|||
* [Closure Tools on Twitter](http://twitter.com/closuretools) |
|||
* [Closure Tools on Google +](https://plus.google.com/communities/113969319608324762672) |
|||
|
|||
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ |
|||
|
|||
|
|||
## Implementation |
|||
|
|||
Note this project breaks with the convention of the others and uses spaces in place of tabs within JavaScript files. This is to comply with the Google style guidelines which the Closure Linter enforces (see [Linting](#linting) below). |
|||
|
|||
|
|||
## Running |
|||
|
|||
A third party build tool called [Plovr](http://plovr.com/) is used to make running and compiling the code easier. To serve the code for development purposes (the example should run in compiled mode without using Plovr), first download the latest stable version from the [Plovr Google Code project](http://code.google.com/p/plovr/downloads/list) (at the time of writing plovr-eba786b34df9.jar). Copy the file into the build folder, rename it plovr.jar and run the following command from this folder - |
|||
|
|||
`java -jar build/plovr.jar serve plovr.json` |
|||
|
|||
You'll also need to change the HTML file so that it references the served files instead of the compiled version (**make sure you comment out the compiled version otherwise it will not work**), to do this remove the compiled script reference and add the following - |
|||
|
|||
`<script type="text/javascript" src="http://localhost:9810/compile?id=todomvc&mode=RAW"></script>` |
|||
|
|||
This will serve up the javascript files in RAW mode which is ideal for rapid development and debugging. To run the compiler, and therefore all the associated type checks etc., change RAW for ADVANCED - |
|||
|
|||
`<script type="text/javascript" src="http://localhost:9810/compile?id=todomvc&mode=ADVANCED"></script>` |
|||
|
|||
|
|||
## Linting |
|||
|
|||
Whilst Plovr features many of the tools from the Closure toolkit, one very useful one that's missing is the linter. The linter checks for common mistakes in your code, e.g. unused dependencies, whitespace errors. One restriction with the linter is that it will only permit code that adheres to the [Google JavaScript style guide](http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml). In this case that means that we break with the project conventions and use space indentation instead of tabs. |
|||
|
|||
The linter must be installed before use, the installation package is included in the build folder and the instructions are available on the [linter homepage](https://developers.google.com/closure/utilities/). Once installed run the following to check for errors - |
|||
|
|||
`find . -name *.js | xargs gjslint` |
|||
|
|||
(or whatever floats your OSs boat) |
|||
|
|||
|
|||
## Compiling |
|||
|
|||
To compile the code from the command line run Plovr like so - |
|||
|
|||
`java -jar build/plovr.jar build plovr.json > js/compiled.js` |
|||
|
|||
This will overwrite the js/compiled.js file with the new version, be sure to change the script tag reference in the HTML page. |
|||
|
|||
|
|||
## Credits |
|||
|
|||
This TodoMVC application was created by [Chris Price](http://www.scottlogic.co.uk/blog/chris/). |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"name": "todmvc-dojo", |
|||
"version": "0.0.0", |
|||
"dependencies": { |
|||
"todomvc-common": "~0.1.6" |
|||
} |
|||
} |
@ -0,0 +1,556 @@ |
|||
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; |
|||
-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; |
|||
} |
|||
|
|||
button, |
|||
input[type="checkbox"] { |
|||
outline: none; |
|||
} |
|||
|
|||
#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: 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); |
|||
-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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
} |
|||
|
|||
#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; |
|||
/* Mobile Safari */ |
|||
border: none; |
|||
-webkit-appearance: none; |
|||
-ms-appearance: none; |
|||
-o-appearance: none; |
|||
appearance: none; |
|||
} |
|||
|
|||
#todo-list li .toggle:after { |
|||
content: '✔'; |
|||
/* 40 + a couple of pixels visual adjustment */ |
|||
line-height: 43px; |
|||
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 { |
|||
white-space: pre; |
|||
word-break: break-word; |
|||
padding: 15px 60px 15px 15px; |
|||
margin-left: 45px; |
|||
display: block; |
|||
line-height: 1.2; |
|||
-webkit-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; |
|||
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); |
|||
-ms-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); |
|||
-ms-transform: rotate(90deg); |
|||
transform: rotate(90deg); |
|||
-webkit-appearance: none; |
|||
appearance: none; |
|||
} |
|||
} |
|||
|
|||
.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
hr { |
|||
margin: 20px 0; |
|||
border: 0; |
|||
border-top: 1px dashed #C5C5C5; |
|||
border-bottom: 1px dashed #F7F7F7; |
|||
} |
|||
|
|||
.learn a { |
|||
font-weight: normal; |
|||
text-decoration: none; |
|||
color: #b83f45; |
|||
} |
|||
|
|||
.learn a:hover { |
|||
text-decoration: underline; |
|||
color: #787e7e; |
|||
} |
|||
|
|||
.learn h3, |
|||
.learn h4, |
|||
.learn h5 { |
|||
margin: 10px 0; |
|||
font-weight: 500; |
|||
line-height: 1.2; |
|||
color: #000; |
|||
} |
|||
|
|||
.learn h3 { |
|||
font-size: 24px; |
|||
} |
|||
|
|||
.learn h4 { |
|||
font-size: 18px; |
|||
} |
|||
|
|||
.learn h5 { |
|||
margin-bottom: 0; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.learn ul { |
|||
padding: 0; |
|||
margin: 0 0 30px 25px; |
|||
} |
|||
|
|||
.learn li { |
|||
line-height: 20px; |
|||
} |
|||
|
|||
.learn p { |
|||
font-size: 15px; |
|||
font-weight: 300; |
|||
line-height: 1.3; |
|||
margin-top: 0; |
|||
margin-bottom: 0; |
|||
} |
|||
|
|||
.quote { |
|||
border: none; |
|||
margin: 20px 0 60px 0; |
|||
} |
|||
|
|||
.quote p { |
|||
font-style: italic; |
|||
} |
|||
|
|||
.quote p:before { |
|||
content: '“'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
top: -20px; |
|||
left: 3px; |
|||
} |
|||
|
|||
.quote p:after { |
|||
content: '”'; |
|||
font-size: 50px; |
|||
opacity: .15; |
|||
position: absolute; |
|||
bottom: -42px; |
|||
right: 3px; |
|||
} |
|||
|
|||
.quote footer { |
|||
position: absolute; |
|||
bottom: -40px; |
|||
right: 0; |
|||
} |
|||
|
|||
.quote footer img { |
|||
border-radius: 3px; |
|||
} |
|||
|
|||
.quote footer a { |
|||
margin-left: 5px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.speech-bubble { |
|||
position: relative; |
|||
padding: 10px; |
|||
background: rgba(0, 0, 0, .04); |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
.speech-bubble:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: 100%; |
|||
right: 30px; |
|||
border: 13px solid transparent; |
|||
border-top-color: rgba(0, 0, 0, .04); |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
position: absolute; |
|||
width: 272px; |
|||
top: 8px; |
|||
left: -300px; |
|||
padding: 10px; |
|||
border-radius: 5px; |
|||
background-color: rgba(255, 255, 255, .6); |
|||
-webkit-transition-property: left; |
|||
transition-property: left; |
|||
-webkit-transition-duration: 500ms; |
|||
transition-duration: 500ms; |
|||
} |
|||
|
|||
@media (min-width: 899px) { |
|||
.learn-bar { |
|||
width: auto; |
|||
margin: 0 0 0 300px; |
|||
} |
|||
|
|||
.learn-bar > .learn { |
|||
left: 8px; |
|||
} |
|||
|
|||
.learn-bar #todoapp { |
|||
width: 550px; |
|||
margin: 130px auto 40px auto; |
|||
} |
|||
} |
@ -0,0 +1,209 @@ |
|||
(function () { |
|||
'use strict'; |
|||
|
|||
// Underscore's Template Module
|
|||
// Courtesy of underscorejs.org
|
|||
var _ = (function (_) { |
|||
_.defaults = function (object) { |
|||
if (!object) { |
|||
return object; |
|||
} |
|||
for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { |
|||
var iterable = arguments[argsIndex]; |
|||
if (iterable) { |
|||
for (var key in iterable) { |
|||
if (object[key] == null) { |
|||
object[key] = iterable[key]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
return object; |
|||
} |
|||
|
|||
// By default, Underscore uses ERB-style template delimiters, change the
|
|||
// following template settings to use alternative delimiters.
|
|||
_.templateSettings = { |
|||
evaluate : /<%([\s\S]+?)%>/g, |
|||
interpolate : /<%=([\s\S]+?)%>/g, |
|||
escape : /<%-([\s\S]+?)%>/g |
|||
}; |
|||
|
|||
// When customizing `templateSettings`, if you don't want to define an
|
|||
// interpolation, evaluation or escaping regex, we need one that is
|
|||
// guaranteed not to match.
|
|||
var noMatch = /(.)^/; |
|||
|
|||
// Certain characters need to be escaped so that they can be put into a
|
|||
// string literal.
|
|||
var escapes = { |
|||
"'": "'", |
|||
'\\': '\\', |
|||
'\r': 'r', |
|||
'\n': 'n', |
|||
'\t': 't', |
|||
'\u2028': 'u2028', |
|||
'\u2029': 'u2029' |
|||
}; |
|||
|
|||
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; |
|||
|
|||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|||
// and correctly escapes quotes within interpolated code.
|
|||
_.template = function(text, data, settings) { |
|||
var render; |
|||
settings = _.defaults({}, settings, _.templateSettings); |
|||
|
|||
// Combine delimiters into one regular expression via alternation.
|
|||
var matcher = new RegExp([ |
|||
(settings.escape || noMatch).source, |
|||
(settings.interpolate || noMatch).source, |
|||
(settings.evaluate || noMatch).source |
|||
].join('|') + '|$', 'g'); |
|||
|
|||
// Compile the template source, escaping string literals appropriately.
|
|||
var index = 0; |
|||
var source = "__p+='"; |
|||
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { |
|||
source += text.slice(index, offset) |
|||
.replace(escaper, function(match) { return '\\' + escapes[match]; }); |
|||
|
|||
if (escape) { |
|||
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; |
|||
} |
|||
if (interpolate) { |
|||
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; |
|||
} |
|||
if (evaluate) { |
|||
source += "';\n" + evaluate + "\n__p+='"; |
|||
} |
|||
index = offset + match.length; |
|||
return match; |
|||
}); |
|||
source += "';\n"; |
|||
|
|||
// If a variable is not specified, place data values in local scope.
|
|||
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; |
|||
|
|||
source = "var __t,__p='',__j=Array.prototype.join," + |
|||
"print=function(){__p+=__j.call(arguments,'');};\n" + |
|||
source + "return __p;\n"; |
|||
|
|||
try { |
|||
render = new Function(settings.variable || 'obj', '_', source); |
|||
} catch (e) { |
|||
e.source = source; |
|||
throw e; |
|||
} |
|||
|
|||
if (data) return render(data, _); |
|||
var template = function(data) { |
|||
return render.call(this, data, _); |
|||
}; |
|||
|
|||
// Provide the compiled function source as a convenience for precompilation.
|
|||
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; |
|||
|
|||
return template; |
|||
}; |
|||
|
|||
return _; |
|||
})({}); |
|||
|
|||
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 redirect() { |
|||
if (location.hostname === 'tastejs.github.io') { |
|||
location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); |
|||
} |
|||
} |
|||
|
|||
function findRoot() { |
|||
var base; |
|||
|
|||
[/labs/, /\w*-examples/].forEach(function (href) { |
|||
var match = location.href.match(href); |
|||
|
|||
if (!base && match) { |
|||
base = location.href.indexOf(match); |
|||
} |
|||
}); |
|||
|
|||
return location.href.substr(0, base); |
|||
} |
|||
|
|||
function getFile(file, callback) { |
|||
if (!location.host) { |
|||
return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); |
|||
} |
|||
|
|||
var xhr = new XMLHttpRequest(); |
|||
|
|||
xhr.open('GET', findRoot() + file, true); |
|||
xhr.send(); |
|||
|
|||
xhr.onload = function () { |
|||
if (xhr.status === 200 && callback) { |
|||
callback(xhr.responseText); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
function Learn(learnJSON, config) { |
|||
if (!(this instanceof Learn)) { |
|||
return new Learn(learnJSON, config); |
|||
} |
|||
|
|||
var template, framework; |
|||
|
|||
if (typeof learnJSON !== 'object') { |
|||
try { |
|||
learnJSON = JSON.parse(learnJSON); |
|||
} catch (e) { |
|||
return; |
|||
} |
|||
} |
|||
|
|||
if (config) { |
|||
template = config.template; |
|||
framework = config.framework; |
|||
} |
|||
|
|||
if (!template && learnJSON.templates) { |
|||
template = learnJSON.templates.todomvc; |
|||
} |
|||
|
|||
if (!framework && document.querySelector('[data-framework]')) { |
|||
framework = document.querySelector('[data-framework]').getAttribute('data-framework'); |
|||
} |
|||
|
|||
|
|||
if (template && learnJSON[framework]) { |
|||
this.frameworkJSON = learnJSON[framework]; |
|||
this.template = template; |
|||
|
|||
this.append(); |
|||
} |
|||
} |
|||
|
|||
Learn.prototype.append = function () { |
|||
var aside = document.createElement('aside'); |
|||
aside.innerHTML = _.template(this.template, this.frameworkJSON); |
|||
aside.className = 'learn'; |
|||
|
|||
// Localize demo links
|
|||
var demoLinks = aside.querySelectorAll('.demo-link'); |
|||
Array.prototype.forEach.call(demoLinks, function (demoLink) { |
|||
demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); |
|||
}); |
|||
|
|||
document.body.className = (document.body.className + ' learn-bar').trim(); |
|||
document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); |
|||
}; |
|||
|
|||
redirect(); |
|||
getFile('learn.json', Learn); |
|||
})(); |
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,95 @@ |
|||
#clear-completed, #footer, #main, |
|||
.plural { |
|||
display: none; |
|||
} |
|||
|
|||
#todoapp.todos_selected #clear-completed, |
|||
#todoapp.todos_present #footer, |
|||
#todoapp.todos_present #main, |
|||
.multiple .plural { |
|||
display: inherit; |
|||
} |
|||
|
|||
#todo-list li.hidden { |
|||
display: none; |
|||
} |
|||
|
|||
#todo-list li .toggle.dijitChecked:after { |
|||
color: #85ada7; |
|||
text-shadow: 0 1px 0 #669991; |
|||
bottom: 1px; |
|||
position: relative; |
|||
} |
|||
|
|||
/* When checkbox is selected, score through todo item content */ |
|||
.dijitCheckBoxChecked + .todo-content { |
|||
color: #666; |
|||
text-decoration: line-through; |
|||
} |
|||
|
|||
/* When checkbox is selected, score through todo item content after an edit */ |
|||
.dijitCheckBoxChecked + .dijitInline + .todo-content { |
|||
color: #666; |
|||
text-decoration: line-through; |
|||
} |
|||
|
|||
#todo-list .dijitCheckBoxInput { |
|||
opacity: 0; |
|||
position: absolute; |
|||
top: 14px; |
|||
z-index: 10; |
|||
} |
|||
|
|||
/** Match up inline edit box with styling */ |
|||
.dijitInputInner { |
|||
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; |
|||
} |
|||
|
|||
#todo-list li .dijitInputInner { |
|||
display: block; |
|||
width: 506px; |
|||
padding: 13px 17px 12px 17px; |
|||
margin: 0 0 0 43px; |
|||
z-index: 10; |
|||
} |
|||
|
|||
/** Ugh, force override of edit container margin */ |
|||
#todo-list li span.dijitInline { |
|||
margin: 0 !important; |
|||
} |
|||
|
|||
/** |
|||
* Inline edit box doesn't provide indication via class names |
|||
* when a box is 'live'. Style values are set manually. Use the |
|||
* opacity change as indicator... :( */ |
|||
.inline_edit[style~='0;'] ~ .toggle { |
|||
visibility: hidden !important; |
|||
} |
|||
|
|||
.dijitOffScreen { /* For 1.8 in-line edit box */ |
|||
position: absolute !important; |
|||
left: 50% !important; |
|||
top: -10000px !important; |
|||
} |
|||
|
@ -0,0 +1,20 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="dojo"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Dojo • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
<link rel="stylesheet" href="css/app.css"> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp" data-dojo-type="todo.app"> |
|||
</section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Created by <a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script data-dojo-config="async:true, parseOnLoad:true, locale:'en', paths:{'todo':'../todo/'}, deps:['dojo/parser', 'todo/app']" src="js/lib/dojo/dojo.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,32 @@ |
|||
<!doctype html> |
|||
<html lang="en" data-framework="dojo"> |
|||
<head> |
|||
<meta charset="utf-8"> |
|||
<title>Dojo • TodoMVC</title> |
|||
<link rel="stylesheet" href="bower_components/todomvc-common/base.css"> |
|||
<link rel="stylesheet" href="css/app.css"> |
|||
</head> |
|||
<body> |
|||
<section id="todoapp" data-dojo-type="todo/app18"></section> |
|||
<footer id="info"> |
|||
<p>Double-click to edit a todo</p> |
|||
<p>Created by <a href="http://jamesthom.as/">James Thomas</a> and <a href="https://github.com/edchat">Ed Chatelain</a></p> |
|||
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p> |
|||
</footer> |
|||
|
|||
<script src="bower_components/todomvc-common/base.js"></script> |
|||
<script> |
|||
require = { |
|||
async: true, |
|||
parseOnLoad: true, |
|||
locale: 'en', |
|||
paths: { |
|||
dijit: '../dijit-1.8' |
|||
}, |
|||
deps: ['dojo/parser', 'dojo/domReady!'], |
|||
mvc: { debugBindings: true } |
|||
}; |
|||
</script> |
|||
<script src="js/lib/dojo-1.8/dojo.js"></script> |
|||
</body> |
|||
</html> |
@ -0,0 +1,42 @@ |
|||
define("dijit/nls/common", { root: |
|||
//begin v1.x content
|
|||
({ |
|||
buttonOk: "OK", |
|||
buttonCancel: "Cancel", |
|||
buttonSave: "Save", |
|||
itemClose: "Close" |
|||
}) |
|||
//end v1.x content
|
|||
, |
|||
"zh": true, |
|||
"zh-tw": true, |
|||
"tr": true, |
|||
"th": true, |
|||
"sv": true, |
|||
"sl": true, |
|||
"sk": true, |
|||
"ru": true, |
|||
"ro": true, |
|||
"pt": true, |
|||
"pt-pt": true, |
|||
"pl": true, |
|||
"nl": true, |
|||
"nb": true, |
|||
"ko": true, |
|||
"kk": true, |
|||
"ja": true, |
|||
"it": true, |
|||
"hu": true, |
|||
"hr": true, |
|||
"he": true, |
|||
"fr": true, |
|||
"fi": true, |
|||
"es": true, |
|||
"el": true, |
|||
"de": true, |
|||
"da": true, |
|||
"cs": true, |
|||
"ca": true, |
|||
"az": true, |
|||
"ar": true |
|||
}); |
@ -0,0 +1,21 @@ |
|||
define( |
|||
//begin v1.x content
|
|||
{ |
|||
"decimal": ".", |
|||
"group": ",", |
|||
"list": ";", |
|||
"percentSign": "%", |
|||
"plusSign": "+", |
|||
"minusSign": "-", |
|||
"exponential": "E", |
|||
"perMille": "‰", |
|||
"infinity": "∞", |
|||
"nan": "NaN", |
|||
"decimalFormat": "#,##0.###", |
|||
"decimalFormat-short": "000T", |
|||
"scientificFormat": "#E0", |
|||
"percentFormat": "#,##0%", |
|||
"currencyFormat": "¤#,##0.00;(¤#,##0.00)" |
|||
} |
|||
//end v1.x content
|
|||
); |