Commit b7d1832a authored by TasteBot's avatar TasteBot

update the build files for gh-pages [ci skip]

parent ca4e845e
# editorconfig.org
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
node_modules
/bower_components
/dist
{
"requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"],
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireLeftStickedOperators": [","],
"requireParenthesesAroundIIFE": true,
"requireSpacesInFunctionExpression": {
"beforeOpeningCurlyBrace": true
},
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"disallowEmptyBlocks": true,
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"disallowQuotedKeysInObjects": true,
"disallowSpaceAfterObjectKeys": true,
"requireCommaBeforeLineBreak": true,
"requireSpaceBeforeBinaryOperators": [
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!=="
],
"requireSpaceAfterBinaryOperators": [
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!=="
],
"requireCamelCaseOrUpperCaseIdentifiers": true,
"requireOperatorBeforeLineBreak": [
"?",
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="
],
"disallowLeftStickedOperators": [
"?",
"+",
"-",
"/",
"*",
"=",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="
],
"disallowRightStickedOperators": [
"?",
"+",
"/",
"*",
":",
"=",
"==",
"===",
"!=",
"!==",
">",
">=",
"<",
"<="
],
"requireRightStickedOperators": ["!"],
"disallowImplicitTypeConversion": ["string"],
"disallowKeywords": ["with"],
"disallowMultipleLineStrings": true,
"disallowMultipleLineBreaks": true,
"validateLineBreaks": "LF",
"validateQuoteMarks": true,
"disallowMixedSpacesAndTabs": true,
"requireMultipleVarDecl": true,
"disallowTrailingWhitespace": true,
"disallowKeywordsOnNewLine": ["else"],
"maximumLineLength": 100,
"requireCapitalizedConstructors": true,
"safeContextKeyword": "self",
"requireDotNotation": true,
"requireLineFeedAtFileEnd": true,
"excludeFiles": [],
"validateJSDoc": {
"checkParamNames": true,
"requireParamTypes": true
}
}
{
"node": true,
"browser": true,
"esnext": true,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 4,
"latedef": true,
"newcap": true,
"noarg": true,
"quotmark": "single",
"regexp": true,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"smarttabs": true,
"white": true
}
# You must sign into travis-ci.org and set the commit hook on your project for travis to
# run on your project. The secure: variable must be generated by running `travis encrypt`
# on a github oauth key that you can generate using curl.
language:
node_js
node_js:
- 0.10
notifications:
irc: "irc.freenode.org#tastejs"
branches:
only:
- master
env:
global:
# GH_OAUTH_TOKEN is the oauth token generated as described at
# https://help.github.com/articles/creating-an-oauth-token-for-command-line-use
#
# curl -u 'username' -d '{"scopes":["repo"],"note":"push to gh-pages from travis"}' https://api.github.com/authorizations
#
# It must be encrypted using the travis gem
# http://about.travis-ci.org/docs/user/build-configuration/#Secure-environment-variables
#
# travis encrypt GH_OAUTH_TOKEN=XXXXXXXXXXXXXXX
#
# User specific env variables
- secure: "fHgfjMpYuliwMr2QLnjYZExIViNrxprf9dhXRBLZ6P9Hz7P6m1BMYrI/xEG8X+fFbCi0+n3AXh8SEMHi9ou/Pty/cx12z4w/z3B2BHMxh4XBwpZHs+AB4IXkLiwwWoP4QFy4vTipgYnMDMq9CRhlRbhZEpenQBmaTEc472By1uM="
- GH_OWNER: tastejs
- GH_PROJECT_NAME: todomvc
before_script:
# install dependencies
- npm install -g gulp
script:
# We want to gate on passing tests and a successful build
- gulp
after_success:
# Any command that using GH_OAUTH_TOKEN must pipe the output to /dev/null to not expose your oauth token
- git submodule add -b gh-pages https://${GH_OAUTH_TOKEN}@github.com/${GH_OWNER}/${GH_PROJECT_NAME} site > /dev/null 2>&1
- cd site
- if git checkout gh-pages; then git checkout -b gh-pages; fi
- git rm -r .
- cp -R ../dist/* .
- cp ../dist/.* .
- git add -f .
- git config user.email "travis@rdrei.net"
- git config user.name "TasteBot"
- git commit -am "update the build files for gh-pages [ci skip]"
# Any command that using GH_OAUTH_TOKEN must pipe the output to /dev/null to not expose your oauth token
- git push https://${GH_OAUTH_TOKEN}@github.com/${GH_OWNER}/${GH_PROJECT_NAME} HEAD:gh-pages > /dev/null 2>&1
# Application Specification
We have created this short spec to help you create awesome and consistent todo apps. Make sure to not only read it, but also understand.
## Template Application
Our [template](https://github.com/tastejs/todomvc/tree/gh-pages/template) should be used as the base when implementing a todo app. Before implementing, we recommend you interact with some of the other apps to see how they're built and how they behave. Check out the [Backbone app](http://todomvc.com/architecture-examples/backbone) if you need a reference implementation. If something is unclear or could be improved, [let us know](https://github.com/tastejs/todomvc/issues).
## Structure
### Directory Structure
Recommended file structure:
```
index.html
bower.json
bower_components/
css
└── app.css
js/
├── app.js
├── controllers/
└── models/
readme.md
```
Try to follow this structure as close a possible while still keeping to the framework’s best practices.
Components should be split up into separate files and placed into folders where it makes the most sense.
Example:
```
js/
├── app.js
├── controllers/
│ └── todos.js
└── models/
└── todo.js
```
Keep in mind that framework best practices on how to structure your app comes first.
### README
All examples should include a README describing the framework, the general implementation and the build process if required. There is an [example readme](https://github.com/tastejs/todomvc/blob/gh-pages/template/readme.md) included in the [template](https://github.com/tastejs/todomvc/tree/gh-pages/template).
### Dependency Management
Unless it conflicts with the project's best practices, your example should use [bower](http://bower.io/) for package management. Specify your dependencies in a `bower.json` file in the root directory of your app. The name of the component must follow the schema `todomvc-[framework]` and must include `todomvc-common` as dependency. An example `bower.json` could look like this:
```json
{
"name": "todomvc-sample",
"version": "0.0.0",
"dependencies": {
"underscore": "~1.5.0",
"jquery": "~2.0.0",
"todomvc-common": "~0.1.0"
}
}
```
The files in `bower_components/` should be limited to the files actually used by your example. That means documentation, READMEs and tests should not be included in the pull request.
### Code
Please try to keep the HTML as close to the template as possible. Remove the comments from the HTML when done. The `base.css` file should be referenced from the assets folder and not be touched. If you need to change some styles, use the `app.css` file, but try to keep it to a minimum. Remember to update the relative paths when using the template.
Make sure to follow these:
- Follow our [code style](https://github.com/tastejs/todomvc/blob/gh-pages/contributing.md#code-style)
- Use double-quotes in HTML and single-quotes in JS and CSS.
- Use bower components for your third-party dependencies and manually remove files that aren't required for your app to run.
- Use a constant instead of the keyCode directly: `var ENTER_KEY = 13;`
- Apps should be written without any preprocessors (Sass/CoffeeScript/..) to reach the largest audience.
- To make it easy to compare frameworks, the app should look and behave exactly like the template and the other examples.
- We require apps to work in every browser we [support](https://github.com/tastejs/todomvc/blob/gh-pages/contributing.md#browser-compatibility).
## Functionality
### No todos
When there are no todos, `#main` and `#footer` should be hidden.
### New todo
New todos are entered in the input at the top of the app. Pressing Enter creates the todo, appends it to the todo list and clears the input. Make sure to `.trim()` the input and then check that it's not empty before creating a new todo.
### Mark all as complete
This checkbox toggles all the todos to the same state as itself. Make sure to clear the checked state after the the "Clear completed" button is clicked. The "Mark all as complete" checkbox should also be updated when single todo items are checked/unchecked. Eg. When all the todos are checked it should also get checked.
### Item
A todo item has three possible interactions:
1. Clicking the checkbox marks the todo as complete by updating its `completed` value and toggling the class `completed` on its parent `<li>`
2. Double-clicking the `<label>` activates editing mode, by toggling the `.editing` class on its `<li>`
3. Hovering over the todo shows the remove button (`.destroy`)
### Editing
When editing mode is activated it will hide the other controls and bring forward an input that contains the todo title, which should be focused (`.focus()`). The edit should be saved on both blur and enter, and the `editing` class should be removed. Make sure to `.trim()` the input and then check that it's not empty. If it's empty the todo should instead be destroyed. If escape is pressed during the edit, the edit state should be left and any changes be discarded.
### Counter
Displays the number of active todos in a pluralized form. Make sure the number is wrapped by a `<strong>` tag. Also make sure to pluralize the `item` word correctly: `0 items`, `1 item`, `2 items`. Example: **2** items left
### Clear completed button
Displays the number of completed todos, and when clicked, removes them. Should be hidden when there are no completed todos.
### Persistence
Your app should dynamically persist the todos to localStorage. If the framework has capabilities for persisting data (i.e. Backbone.sync), use that, otherwise vanilla localStorage. If possible, use the keys `id`, `title`, `completed` for each item. Make sure to use this format for the localStorage name: `todos-[framework]`. Editing mode should not be persisted.
### Routing
Routing is required for all frameworks. Use the built-in capabilities if supported, otherwise use the [Flatiron Director](https://github.com/flatiron/director) routing library located in the `/assets` folder. The following routes should be implemented: `#/` (all - default), `#/active` and `#/completed` (`#!/` is also allowed). When the route changes the todo list should be filtered on a model level and the `selected` class on the filter links should be toggled. When an item is updated while in a filtered state, it should be updated accordingly. E.g. if the filter is `Active` and the item is checked, it should be hidden. Make sure the active filter is persisted on reload.
{
"name": "todomvc",
"dependencies": {
"jquery": "~2.1.0",
"bootstrap": "~3.2.0"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
module.exports = function (grunt) {
'use strict';
grunt.loadNpmTasks('grunt-simple-mocha');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.initConfig({
simplemocha: {
options: {
reporter: 'mocha-known-issues-reporter',
enableTimeouts: false,
},
files: {
src: 'allTests.js'
}
}
});
// build tasks
grunt.registerTask('test', ['simplemocha']);
grunt.registerTask('dev', ['jshint']);
};
# TodoMVC Browser Tests
The TodoMVC project has a great many implementations of exactly the same app using different MV* frameworks. Each app should be functionally identical. The goal of these tests is to provide a fully automated browser-based test that can be used to ensure that the specification is being followed by each and every TodoMVC app.
## Todo
- [ ] Complete the test implementation (27 out of 28 are now complete). The only test that I am struggling with is to test that the delete button becomes visible on hover.
- [ ] Find a more elegant solution for TodoMVC apps that use RequireJS, currently there is a short 'sleep' statement in order to give the browser time to load dependencies. Yuck!
- [ ] Run JSHint over my code ;-)
- [ ] Make it work with PhantomJS. In practice, Phantom is only a little bit faster, but it should work. Currently there are a few Phantom specific failures.
- [ ] Allow testing of apps that require a server (see: https://github.com/tastejs/todomvc/pull/821/files#r9377070)
## Running the tests
These tests use Selenium 2 (WebDriver), via the JavaScript API (WebdriverJS). In order to run the tests you will need to install the dependencies. Run the following command from within the `browser-tests` folder:
```sh
$ npm install
```
The tests use Mocha, which must be installed as a command line module:
```sh
$ npm install -g mocha
```
You need to run a local server at the root of the TodoMVC project. On Mac OSX, you can do the following:
```sh
$ python -m SimpleHTTPServer 8000
```
or its Ruby equivalent:
```sh
$ ruby -run -e httpd . -p 8000
```
And for Windows, the node [http-server](https://github.com/nodeapps/http-server) is a simple, zero-configuration alternative.
To run the tests for all TodoMVC implementations, run the following:
```sh
$ mocha allTests.js --no-timeouts --reporter spec
```
Note that `--reporter spec` uses the mocha 'spec' reporter, which is quite informative. You can of course specify any other reporter.
In order to run tests for a single TodoMVC implementation, supply a framework argument as follows:
```sh
$ mocha allTests.js --no-timeouts --reporter spec --framework=angularjs
```
In order to run a specific test, use the mocha 'grep' function. For example:
```
$ mocha allTests.js --no-timeouts --reporter spec --framework=jquery \
--grep 'should trim entered text'
TodoMVC - jquery
Editing
✓ should trim entered text (1115ms)
1 passing (3s)
```
### Specifying the browser
You can also specify the browser that will be used to execute the tests via the `---browser` argument. The tests default to using Chrome (see the instructions below for installing ChromeDriver). For example, to run against phantomjs, use the following:
```sh
$ mocha allTests.js --no-timeouts --reporter spec --browser=phantomjs
```
You must install phantomjs first of course!
Valid browser names can be found within webdriver via the `webdriver.Browser` enumeration.
## Reporting against known issues
The `knownIssues.js` file details the currently known issues with the TodoMVC implementations. You can run the tests and compare against these issues using the `mocha-known-issues-reporter`. This reporter is a separate npm module, as a result the easiest way to run it using the supplied gruntfile:
```sh
$ grunt test --framework=angularjs
```
When run via grunt the suite supports exactly the same command line arguments.
An example output with the known issues reporter is shown below:
```
$ grunt test --framework=jquery
Running "simplemocha:files" (simplemocha) task
(1 of 27) pass: TodoMVC - jquery, No Todos, should hide #main and #footer
[...]
(17 of 27) pass: TodoMVC - jquery, Editing, should remove the item if an empty text string was entered
(18 of 27) known issue: TodoMVC - jquery, Editing, should cancel edits on escape -- error: undefined
(19 of 27) pass: TodoMVC - jquery, Counter, should display the current number of todo items
(20 of 27) pass: TodoMVC - jquery, Clear completed button, should display the number of completed items
(21 of 27) pass: TodoMVC - jquery, Clear completed button, should remove completed items when clicked
(22 of 27) pass: TodoMVC - jquery, Clear completed button, should be hidden when there are no items that are completed
(23 of 27) pass: TodoMVC - jquery, Persistence, should persist its data
(24 of 27) known issue: TodoMVC - jquery, Routing, should allow me to display active items -- error: Cannot call method 'click' of undefined
(25 of 27) known issue: TodoMVC - jquery, Routing, should allow me to display completed items -- error: Cannot call method 'click' of undefined
(26 of 27) known issue: TodoMVC - jquery, Routing, should allow me to display all items -- error: Cannot call method 'click' of undefined
(27 of 27) known issue: TodoMVC - jquery, Routing, should highlight the currently applied filter -- error: Cannot call method 'getAttribute' of undefined
passed: 22/27
failed: 5/27
new issues: 0
resolved issues: 0
```
The reporter indicates the number of passes, failed, new and resolved issues. This makes it ideal for regression testing.
### Chrome
In order to run the tests using the Chrome browser, you need to install ChromeDriver. Instructions for download and installation can be found on the [ChromeDriver homepage](http://code.google.com/p/selenium/wiki/ChromeDriver), or a simpler set of instructions is available [here](http://damien.co/resources/how-to-install-chromedriver-mac-os-x-selenium-python-7406).
### Example output
A test run with the 'spec' reporter looks something like the following:
```
$ mocha allTests.js --no-timeouts --reporter spec --framework=angularjs
angularjs
TodoMVC
No Todos
✓ should hide #main and #footer (201ms)
New Todo
✓ should allow me to add todo items (548ms)
✓ should clear text input field when an item is added (306ms)
✓ should trim text input (569ms)
✓ should show #main and #footer when items added (405ms)
Mark all as completed
✓ should allow me to mark all items as completed (1040ms)
✓ should allow me to clear the completion state of all items (1014ms)
✓ complete all checkbox should update state when items are completed (1413ms)
Item
✓ should allow me to mark items as complete (843ms)
✓ should allow me to un-mark items as complete (978ms)
✓ should allow me to edit an item (1155ms)
✓ should show the remove button on hover
Editing
✓ should hide other controls when editing (718ms)
✓ should save edits on enter (1093ms)
✓ should save edits on blur (1256ms)
✓ should trim entered text (1163ms)
✓ should remove the item if an empty text string was entered (1033ms)
✓ should cancel edits on escape (1115ms)
Counter
✓ should display the current number of todo items (462ms)
Clear completed button
✓ should display the number of completed items (873ms)
✓ should remove completed items when clicked (898ms)
✓ should be hidden when there are no items that are completed (893ms)
Persistence
✓ should persist its data (3832ms)
Routing
✓ should allow me to display active items (871ms)
✓ should allow me to display completed items (960ms)
✓ should allow me to display all items (1192ms)
✓ should highlight the currently applied filter (1095ms)
27 passing (1m)
```
## Speed mode
In order to keep each test case fully isolated, the browser is closed then re-opened in between each test. This does mean that the tests can take quite a long time to run. If you don't mind the risk of side-effects you can run the tests in speed mode by adding the `--speedMode` argument.
```sh
$ mocha allTests.js --no-timeouts --reporter spec --speedMode
```
Before each test all the todo items are checked as completed and the 'clear complete' button pressed. This make the tests run in around half the time, but with the obvious risk that the tear-down code may fail.
## Lax mode
There are certain implementations (e.g. GWT and Dojo) where the constraints of the framework mean that it is not possible to match exactly the HTML specification for TodoMVC. In these cases the tests can be run in a 'lax' mode where the XPath queries used to locate DOM elements are more general. For example, rather than looking for a checkbox `input` element with a class of `toggle`, in lax mode it simply looks for any `input` elements of type `checkbox`. To run the tests in lax mode, simply use the `--laxMode` argument:
```sh
$ mocha allTests.js --no-timeouts --reporter spec --laxMode
```
## Test design
Very briefly, the tests are designed as follows:
- `page.js` - provides an abstraction layer for the HTML template. All the code required to access elements from the DOM is found within this file. The XPaths used to locate elements are based on the TodoMVC specification, using the required element classes / ids.
- `pageLaxMode.js` - extends the above in order to relax the XPath constraints.
- `testOperations.js` - provides common assertions and operations.
- `test.js` - Erm … the tests! These are written to closely match the TodoMVC spec.
- `allTest.js` - A simple file that locates all of the framework examples, and runs the tests for each.
**NOTE:** All of the WebdriverJS methods return promises and are executed asynchronously. However, you do not have to 'chain' them using `then`, they are instead automagically added to a queue, then executed. This means that if you add non-WebdriverJS operations (asserts, log messages) these will not be executed at the point you might expect. This is why `TestOperations.js` uses an explicit `then` each time it asserts.
'use strict';
var testSuite = require('./test.js');
var fs = require('fs');
var argv = require('optimist').default('laxMode', false).default('browser', 'chrome').argv;
var rootUrl = 'http://localhost:8000/';
var frameworkNamePattern = /^[a-z-_]+$/;
var excludedFrameworks = [
// this implementation deviates from the specification to such an extent that they are
// not worth testing via a generic mechanism
'gwt',
// selenium webdriver cannot see the shadow dom
'polymer',
// these implementations cannot be run offline, because they are hosted
'derby', 'firebase-angular', 'meteor', 'socketstream',
// YUI is a special case here, it is not hosted, but fetches JS files dynamically
'yui',
// these frameworks take a long time to start-up, and there is no easy way to determine when they are ready
'cujo', 'montage',
// sammyjs fails intermittently, it would appear that its state is sometimes updated asynchronously?
'sammyjs',
// these are examples that have been removed or are empty folders
'emberjs_require', 'dermis'
];
// collect together the framework names from each of the subfolders
var list = fs.readdirSync('../architecture-examples/')
.map(function (folderName) {
return { name: folderName, path: 'architecture-examples/' + folderName };
})
.concat(fs.readdirSync('../labs/architecture-examples/')
.map(function (folderName) {
return { name: folderName, path: 'labs/architecture-examples/' + folderName };
})
)
.concat(fs.readdirSync('../labs/dependency-examples/')
.map(function (folderName) {
return { name: folderName, path: 'labs/dependency-examples/' + folderName };
})
)
.concat(fs.readdirSync('../dependency-examples/')
.map(function (folderName) {
return { name: folderName, path: 'dependency-examples/' + folderName };
})
);
// apps that are not hosted at the root of their folder need to be handled explicitly
var exceptions = [
{ name: 'chaplin-brunch', path: 'labs/dependency-examples/chaplin-brunch/public' },
{ name: 'angular-dart', path: 'labs/architecture-examples/angular-dart/web' },
{ name: 'duel', path: 'labs/architecture-examples/duel/www' },
{ name: 'dart', path: 'vanilla-examples/vanilladart/build/' },
{ name: 'canjs_require', path: 'labs/dependency-examples/canjs_require/' },
{ name: 'troopjs', path: 'labs/dependency-examples/troopjs_require/' },
{ name: 'thorax_lumbar', path: 'labs/dependency-examples/thorax_lumbar/public' },
];
list = list.map(function (framework) {
var exception = exceptions.filter(function (exFramework) {
return exFramework.name === framework.name;
});
return exception.length > 0 ? exception[0] : framework;
});
// filter out any folders that are not frameworks (.e.g hidden files)
list = list.filter(function (framework) {
return frameworkNamePattern.test(framework.name);
});
// filter out un-supported implementations
list = list.filter(function (framework) {
return excludedFrameworks.indexOf(framework.name) === -1;
});
// if a specific framework has been named, just run this one
if (argv.framework) {
list = list.filter(function (framework) {
return framework.name === argv.framework;
});
if (list.length === 0) {
console.log('You have either requested an unknown or an un-supported framework');
}
}
// run the tests for each framework
list.forEach(function (framework) {
testSuite.todoMVCTest(
framework.name,
rootUrl + framework.path + '/index.html', argv.speedMode,
argv.laxMode, argv.browser);
});
This diff is collapsed.
{
"name": "todomvc-browser-tests",
"description": "An automated test suite for TodoMVC",
"private": true,
"devDependencies": {
"grunt": "^0.4.5",
"grunt-simple-mocha": "^0.4.0",
"mocha": "*",
"optimist": "^0.6.1",
"q": "^1.0.1",
"selenium-webdriver": "^2.42.1",
"mocha-known-issues-reporter" : "git+https://github.com/ColinEberhardt/mocha-known-issues-reporter.git#v0.0.0"
}
}
'use strict';
var webdriver = require('selenium-webdriver');
module.exports = function Page(browser) {
// ----------------- utility methods
this.tryFindByXpath = function (xpath) {
return browser.findElements(webdriver.By.xpath(xpath));
};
this.findByXpath = function (xpath) {
return browser.findElement(webdriver.By.xpath(xpath));
};
this.getTodoListXpath = function () {
return '//ul[@id="todo-list"]';
};
this.xPathForItemAtIndex = function (index) {
// why is XPath the only language silly enough to be 1-indexed?
return this.getTodoListXpath() + '/li[' + (index + 1) + ']';
};
// ----------------- navigation methods
this.back = function() {
return browser.navigate().back();
}
// ----------------- try / get methods
// unfortunately webdriver does not have a decent API for determining if an
// element exists. The standard approach is to obtain an array of elements
// and test that the length is zero. These methods are used to obtain
// elements which *might* be present in the DOM, hence the try/get name.
this.tryGetMainSectionElement = function () {
return this.tryFindByXpath('//section[@id="main"]');
};
this.tryGetFooterElement = function () {
return this.tryFindByXpath('//footer[@id="footer"]');
};
this.tryGetClearCompleteButton = function () {
return this.tryFindByXpath('//button[@id="clear-completed"]');
};
this.tryGetToggleForItemAtIndex = function (index) {
var xpath = this.xPathForItemAtIndex(index) + '//input[contains(@class,"toggle")]';
return this.tryFindByXpath(xpath);
};
this.tryGetItemLabelAtIndex = function (index) {
return this.tryFindByXpath(this.xPathForItemAtIndex(index) + '//label');
};
// ----------------- DOM element access methods
this.getEditInputForItemAtIndex = function (index) {
var xpath = this.xPathForItemAtIndex(index) + '//input[contains(@class,"edit")]';
return this.findByXpath(xpath);
};
this.getItemInputField = function () {
return this.findByXpath('//input[@id="new-todo"]');
};
this.getMarkAllCompletedCheckBox = function () {
return this.findByXpath('//input[@id="toggle-all"]');
};
this.getItemElements = function () {
return this.tryFindByXpath(this.getTodoListXpath() + '/li');
};
this.getNonCompletedItemElements = function () {
return this.tryFindByXpath(this.getTodoListXpath() + '/li[not(contains(@class,"completed"))]');
};
this.getItemsCountElement = function () {
return this.findByXpath('//span[@id="todo-count"]');
};
this.getItemLabelAtIndex = function (index) {
return this.findByXpath(this.xPathForItemAtIndex(index) + '//label');
};
this.getFilterElements = function () {
return this.tryFindByXpath('//ul[@id="filters"]//a');
};
this.getItemLabels = function () {
var xpath = this.getTodoListXpath() + '/li//label';
return this.tryFindByXpath(xpath);
};
// ----------------- page actions
this.clickMarkAllCompletedCheckBox = function () {
return this.getMarkAllCompletedCheckBox().then(function (checkbox) {
checkbox.click();
});
};
this.clickClearCompleteButton = function () {
return this.tryGetClearCompleteButton().then(function (elements) {
var button = elements[0];
button.click();
});
};
this.enterItem = function (itemText) {
var textField = this.getItemInputField();
textField.sendKeys(itemText);
textField.sendKeys(webdriver.Key.ENTER);
};
this.toggleItemAtIndex = function (index) {
return this.tryGetToggleForItemAtIndex(index).then(function (elements) {
var toggleElement = elements[0];
toggleElement.click();
});
};
this.editItemAtIndex = function (index, itemText) {
return this.getEditInputForItemAtIndex(index)
.then(function (itemEditField) {
// send 50 delete keypresses, just to be sure the item text is deleted
var deleteKeyPresses = '';
for (var i = 0; i < 50; i++) {
deleteKeyPresses += webdriver.Key.BACK_SPACE;
}
itemEditField.sendKeys(deleteKeyPresses);
// update the item with the new text.
itemEditField.sendKeys(itemText);
});
};
this.doubleClickItemAtIndex = function (index) {
return this.getItemLabelAtIndex(index).then(function (itemLabel) {
// double click is not 'natively' supported, so we need to send the
// event direct to the element see:
// http://stackoverflow.com/questions/3982442/selenium-2-webdriver-how-to-double-click-a-table-row-which-opens-a-new-window
browser.executeScript('var evt = document.createEvent("MouseEvents");' +
'evt.initMouseEvent("dblclick",true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0,null);' +
'arguments[0].dispatchEvent(evt);', itemLabel);
});
};
this.filterByActiveItems = function () {
return this.getFilterElements().then(function (filters) {
filters[1].click();
});
};
this.filterByCompletedItems = function () {
return this.getFilterElements().then(function (filters) {
filters[2].click();
});
};
this.filterByAllItems = function () {
return this.getFilterElements().then(function (filters) {
filters[0].click();
});
};
};
'use strict';
var webdriver = require('selenium-webdriver');
var Page = require('./page');
module.exports = function PageLaxMode(browser) {
Page.apply(this, [browser]);
this.tryGetMainSectionElement = function () {
return this.tryFindByXpath('//section//section');
};
this.tryGetFooterElement = function () {
return this.tryFindByXpath('//section//footer');
};
this.getTodoListXpath = function () {
return '(//section/ul | //section/div/ul | //ul[@id="todo-list"])';
};
this.getMarkAllCompletedCheckBox = function () {
var xpath = '(//section/input[@type="checkbox"] | //section/*/input[@type="checkbox"] | //input[@id="toggle-all"])';
return browser.findElement(webdriver.By.xpath(xpath));
};
this.tryGetClearCompleteButton = function () {
var xpath = '(//footer/button | //footer/*/button | //button[@id="clear-completed"])';
return browser.findElements(webdriver.By.xpath(xpath));
};
this.getItemsCountElement = function () {
var xpath = '(//footer/span | //footer/*/span)';
return browser.findElement(webdriver.By.xpath(xpath));
};
this.getFilterElements = function () {
return browser.findElements(webdriver.By.xpath('//footer//ul//a'));
};
this.getItemInputField = function () {
// allow a more generic method for locating the text getItemInputField
var xpath = '(//header/input | //header/*/input | //input[@id="new-todo"])';
return browser.findElement(webdriver.By.xpath(xpath));
};
this.tryGetToggleForItemAtIndex = function (index) {
// the specification dictates that the checkbox should have the 'toggle' CSS class. Some implementations deviate from
// this, hence in lax mode we simply look for any checkboxes within the specified 'li'.
var xpath = this.xPathForItemAtIndex(index) + '//input[@type="checkbox"]';
return browser.findElements(webdriver.By.xpath(xpath));
};
this.getEditInputForItemAtIndex = function (index) {
// the specification dictates that the input element that allows the user to edit a todo item should have a CSS
// class of 'edit'. In lax mode, we also look for an input of type 'text'.
var xpath = '(' + this.xPathForItemAtIndex(index) + '//input[@type="text"]' + '|' +
this.xPathForItemAtIndex(index) + '//input[contains(@class,"edit")]' + ')';
return browser.findElement(webdriver.By.xpath(xpath));
};
};
This diff is collapsed.
'use strict';
var assert = require('assert');
var Q = require('q');
function TestOperations(page) {
// unfortunately webdriver does not have a decent API for determining if an
// element exists. The standard approach is to obtain an array of elements
// and test that the length is zero. In this case the item is hidden if
// it is either not in the DOM, or is in the DOM but not visible.
function testIsHidden(elements, name) {
if (elements.length === 1) {
elements[0].isDisplayed().then(function (isDisplayed) {
assert(!isDisplayed, 'the ' + name + ' element should be hidden');
});
}
}
function testIsVisible(elements, name) {
assert.equal(1, elements.length);
elements[0].isDisplayed().then(function (isDisplayed) {
assert(isDisplayed, 'the ' + name + ' element should be displayed');
});
}
this.assertClearCompleteButtonIsHidden = function () {
page.tryGetClearCompleteButton().then(function (element) {
testIsHidden(element, 'clear completed items button');
});
};
this.assertClearCompleteButtonIsVisible = function () {
page.tryGetClearCompleteButton().then(function (element) {
testIsVisible(element, 'clear completed items button');
});
};
this.assertItemCount = function (itemCount) {
page.getItemElements().then(function (toDoItems) {
assert.equal(itemCount, toDoItems.length,
itemCount + ' items expected in the todo list, ' + toDoItems.length + ' items observed');
});
};
this.assertClearCompleteButtonText = function (buttonText) {
page.tryGetClearCompleteButton().then(function (elements) {
var button = elements[0];
button.getText().then(function (text) {
assert.equal(buttonText, text);
});
});
};
this.assertMainSectionIsHidden = function () {
page.tryGetMainSectionElement().then(function (mainSection) {
testIsHidden(mainSection, 'main');
});
};
this.assertFooterIsHidden = function () {
page.tryGetFooterElement().then(function (footer) {
testIsHidden(footer, 'footer');
});
};
this.assertMainSectionIsVisible = function () {
page.tryGetMainSectionElement().then(function (mainSection) {
testIsVisible(mainSection, 'main');
});
};
//TODO: fishy!
this.assertItemToggleIsHidden = function (index) {
page.tryGetToggleForItemAtIndex(index).then(function (toggleItem) {
testIsHidden(toggleItem, 'item-toggle');
});
};
this.assertItemLabelIsHidden = function (index) {
page.tryGetItemLabelAtIndex(index).then(function (toggleItem) {
testIsHidden(toggleItem, 'item-label');
});
};
this.assertFooterIsVisible = function () {
page.tryGetFooterElement().then(function (footer) {
testIsVisible(footer, 'footer');
});
};
this.assertItemInputFieldText = function (text) {
page.getItemInputField().getText().then(function (inputFieldText) {
assert.equal(text, inputFieldText);
});
};
this.assertItemText = function (itemIndex, textToAssert) {
page.getItemLabelAtIndex(itemIndex).getText().then(function (text) {
assert.equal(textToAssert, text,
'A todo item with text \'' + textToAssert + '\' was expected at index ' +
itemIndex + ', the text \'' + text + '\' was observed');
});
};
// tests that the list contains the following items, independant of order
this.assertItems = function (textArray) {
page.getItemLabels().then(function (labels) {
// obtain all the visible items
var visibleLabels = [];
var tests = [];
for (var i = 0; i < labels.length; i++) {
(function (index) {
// suppressing JSHint - the loop variable is not being used in the function.
/* jshint -W083 */
tests.push(labels[index].isDisplayed().then(function (isDisplayed) {
if (isDisplayed) {
visibleLabels.push(labels[index]);
}
}));
})(i);
}
// check that they match the supplied text
return Q.all(tests).then(function () {
assert.equal(textArray.length, visibleLabels.length,
textArray.length + ' items expected in the todo list, ' + visibleLabels.length + ' items observed');
// create an array of promises which check the presence of the
// label text within the 'textArray'
tests = [];
for (var i = 0; i < visibleLabels.length; i++) {
// suppressing JSHint - the loop variable is not being used in the function.
/* jshint -W083 */
tests.push(visibleLabels[i].getText().then(function (text) {
var index = textArray.indexOf(text);
assert(index !== -1, 'A todo item with text \'' + text + '\' was not expected');
// remove this item when found
textArray.splice(index, 1);
}));
}
// execute all the tests
return Q.all(tests);
})
});
};
this.assertItemCountText = function (textToAssert) {
page.getItemsCountElement().getText().then(function (text) {
assert.equal(textToAssert, text.trim(), 'the item count text was incorrect');
});
};
// tests for the presence of the 'completed' CSS class for the item at the given index
this.assertItemAtIndexIsCompleted = function (index) {
page.getItemElements().then(function (toDoItems) {
toDoItems[index].getAttribute('class').then(function (cssClass) {
assert(cssClass.indexOf('completed') !== -1,
'the item at index ' + index + ' should have been marked as completed');
});
});
};
this.assertItemAtIndexIsNotCompleted = function (index) {
page.getItemElements().then(function (toDoItems) {
toDoItems[index].getAttribute('class').then(function (cssClass) {
// the maria implementation uses an 'incompleted' CSS class which is redundant
// TODO: this should really be moved into the pageLaxMode
assert(cssClass.indexOf('completed') === -1 || cssClass.indexOf('incompleted') !== -1,
'the item at index ' + index + ' should not have been marked as completed');
});
});
};
function isSelected(cssClass) {
return cssClass.indexOf('selected') !== -1;
}
this.assertFilterAtIndexIsSelected = function (selectedIndex) {
page.getFilterElements().then(function (filterElements) {
// create an array of promises, each one holding a test
var tests = [];
// push a test into the array, avoiding the classic JS for loops + closures issue!
function pushTest(itemIndex) {
tests.push(filterElements[itemIndex].getAttribute('class').then(function (cssClass) {
assert(selectedIndex === itemIndex ? isSelected(cssClass) : !isSelected(cssClass),
'the filter / route at index ' + selectedIndex + ' should have been selected');
}));
}
for (var i = 0; i < 3; i++) {
pushTest(i);
}
// execute all the tests
return Q.all(tests);
});
};
this.assertCompleteAllIsClear = function () {
page.getMarkAllCompletedCheckBox().then(function (markAllCompleted) {
markAllCompleted.isSelected().then(function (isSelected) {
assert(!isSelected, 'the mark-all-completed checkbox should be clear');
});
});
};
this.assertCompleteAllIsChecked = function () {
page.getMarkAllCompletedCheckBox().then(function (markAllCompleted) {
markAllCompleted.isSelected().then(function (isSelected) {
assert(isSelected, 'the mark-all-completed checkbox should be checked');
});
});
};
}
module.exports = TestOperations;
# Changelog
## 1.3 - TBD
- New since 1.2:
- Durandal
- Exoskeleton
- Atma.js
- ComponentJS
- AngularDart
- Updates since 1.2:
- CanJS 2.0
- CanJS 2.0 + RequireJS
- VanillaJS refactored, tests, bug fixes
- knockoutjs_classBindingProvider example has been removed
- Backbone 1.1
- Backbone 1.1 + RequireJS
- Dart 1.0
- React graduated from Labs
- Removed since 1.2:
- Dermis.js
- ExtJS
## 1.2 - 2013-08-06
- New since 1.1:
- Polymer
- Flight
- DeftJS + ExtJS
- Aria Templates
- Enyo + Backbone.js
- React
- SAPUI5
- AngularJS + Firebase
- Updates since 1.1:
- Backbone 1.0
- cujoJS got updated
- Kendo UI Spring 2013 release
- Maria graduated from labs
- TroopJS 2.0
- The GWT example implemented routing
- The sammy.js example implemented routing
- Removed Ember.js + require.js example
## 1.1 - 2013-02-14
- We now have 18 stable apps and 36 in labs. New since 1.0.1:
- Dart
- TypeScript + Backbone.js
- TypeScript + AngularJS
- Serenade.js
- CanJS + RequireJS
- Chaplin + Brunch
- Thorax + Lumbar
- Kendo UI
- CanJS replaced the JavaScriptMVC app
- Many app frameworks and libraries have been upgraded to the latest version
- XSS issues in several apps have been resolved
- The homepage got reorganized with new categories
- Various consistency fixes across all apps
## 1.0.1 - 2012-10-09
- All main apps have been completely rewritten for consistency
- Routing has been added to many of these
- We now have 30+ apps being worked on in Labs
- We're using a kick-ass new template
- Framework authors and contributors have been consulted to ensure our apps adhere to best practices
- We're helping AMD users by adding AMD versions of many apps
# Code Style
We think it's best for the project if the code you write looks like the code the last developer wrote. Please read this document in its entirety, and be sure to refer back to it throughout the development of your contribution. We greatly appreciate your cooperation.
## General Rules
- Tab indentation
- Single-quotes
- Semicolon
- Strict mode
- No trailing whitespace
- Variables at the top of the scope
- Multiple variable statements
- Space after keywords and between arguments and operators
- Return early
- JSHint valid
- Consistency
Example:
```js
'use strict';
function foo(bar, fum) {
var i, l, ret;
var hello = 'Hello';
if (!bar) {
return;
}
for (i = 0, l = bar.length; i < l; i++) {
if (bar[i] === hello) {
ret += fum(bar[i]);
}
}
return ret;
}
```
Read [idiomatic.js](https://github.com/rwldrn/idiomatic.js) for general JavaScript code style best practices.
## Anonymous Functions
When using anonymous functions, leave a space between the function name and opening parenthesis.
Example:
```js
(function () {
'use strict';
var thanks = 'mate';
})();
```
## Strict mode
Strict mode should be used wherever possible, but must never be globally
applied. Instead, use it inside an IIFE as shown above.
## Comments
Inline comments are a great way of giving new users a better understanding of what you're doing and why.
It's also helpful to let your functions breathe, by leaving additional lines between statements.
Example:
```js
// Ok.
var removeTodo = function (todoItem) {
var todoModel = todoItem.getModel(); // Grab the model from the todoItem.
todoItem.find('.destroy').click(); // Trigger a click to remove the element from the <ul>.
todoModel.remove(); // Removes the todo model from localStorage.
};
// Better.
var removeTodo = function (todoItem) {
// Grab the model from the todoItem.
var todoModel = todoItem.getModel();
// Trigger a click to remove the element from the <ul>.
todoItem.find('.destroy').click();
// Removes the todo model from localStorage.
todoModel.remove();
};
```
## RequireJS
When using RequireJS, please format your code to these specifications:
```js
define('Block', [
'jQuery',
'Handlebars'
], function ($, Handlebars) {
'use strict';
// Code here.
});
```
## JSHint
When you submit your pull request, one of the first things we will do is run JSHint against your code.
You can help speed the process by running it yourself:
```
jshint path/to/your/app/js
```
Your JSHint code blocks must follow this style:
```js
/*global define, App */
/*jshint unused:false */
```
# Contribute
We're happy to accept contributions in the form of new apps, bug fixes, issues and so on. If you want to help out, add a comment on the issue you want to work on and hack way!.
Note: Before starting work on an app intended for submission, please open an issue to discuss it with the team. This will allow us to review the framework being used to determine if a spec-compatible app is likely to be accepted.
## Code Style
We think it's best for the project if the code you write looks like the code the last developer wrote, so we've put together [some guidelines we ask that you follow](https://github.com/tastejs/todomvc/tree/gh-pages/codestyle.md). We greatly appreciate your cooperation and contribution.
## Pull Request Guidelines
- Develop in a topic branch (not `master`) and submit against the `labs` folder in the default `gh-pages` branch
- Squash your commits
- Write a convincing description of your PR and why we should land it
## Submitting a New App
- **Read the [App Specification](app-spec.md) thoroughly**
- Use the [automated browser tests](/browser-tests) to ensure that your app meets the app specification requirements. For bonus points add the test output to your pull request!
- Make sure it hasn't already been submitted or declined by searching the issue tracker
- Looking at our most recent [reference app](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/backbone)
One of us will be happy to review your submission and discuss any changes that may be required before it can be included. Apps will typically land first in Labs, reaching the 'stable' mark once we and the community are happy with it.
## Browser Compatibility
Modern browser (latest: Chrome, Firefox, Opera, Safari, IE9)
## Unit Tests
At present, due to the large number of apps in the TodoMVC suite we haven't been mandating that unit tests be written in order for an application to be accepted.
We do however plan on addressing this in a future release as we feel it would both help further ensure consistency and provide developers with a reference for writing tests for each framework.
If you are a library author or contributor wishing to start work on writing tests for an implementation, we'll happily consider including them in the future. This may change based on how we specify unit tests must be structured and so on post 1.0.
## A Final Note
Note that due to the current number of MVC/MVVM/MV* frameworks in circulation, it's not always possible to include each one in TodoMVC, but we'll definitely discuss the merits of any framework prior to making a decision :)
For applications that we feel don't quite match the goals of the project, but which we feel still offer value, we're happy to include references to them in our official [wiki](https://github.com/tastejs/todomvc/wiki/Other-implementations).
'use strict';
// Include Gulp & Tools We'll Use
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var del = require('del');
var runSequence = require('run-sequence');
var pagespeed = require('psi');
var AUTOPREFIXER_BROWSERS = [
'ie >= 10',
'ie_mob >= 10',
'ff >= 30',
'chrome >= 34',
'safari >= 7',
'opera >= 23',
'ios >= 7',
'android >= 4.4',
'bb >= 10'
];
// Lint JavaScript
gulp.task('jshint', function () {
return gulp.src('site-assets/*.js')
.pipe($.jshint())
.pipe($.jshint.reporter('jshint-stylish'));
});
// Optimize Images
gulp.task('images', function () {
return gulp.src('site-assets/*.{png,jpg,svg}')
.pipe($.cache($.imagemin({
progressive: true,
interlaced: true
})))
.pipe(gulp.dest('dist/site-assets'))
.pipe($.size({title: 'images'}));
});
// Copy All Files At The Root Level (app)
gulp.task('copy', function () {
return gulp.src([
'architecture-examples/**',
'dependency-examples/**',
'vanilla-examples/**',
'labs/**',
'learn.json',
'CNAME',
'.nojekyll'
], {
dot: true,
base: './'
}).pipe(gulp.dest('dist'))
.pipe($.size({title: 'copy'}));
});
// Compile and Automatically Prefix Stylesheets
gulp.task('styles', function () {
// For best performance, don't add Sass partials to `gulp.src`
return gulp.src([
'site-assets/*.css',
])
.pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
.pipe(gulp.dest('dist/site-assets'))
.pipe($.size({title: 'styles'}))
.pipe(gulp.dest('.tmp/site-assets'));
});
// Scan Your HTML For Assets & Optimize Them
gulp.task('html', function () {
var assets = $.useref.assets({searchPath: '{.tmp,.}'});
return gulp.src('index.html')
.pipe(assets)
// Concatenate And Minify JavaScript
.pipe($.if('*.js', $.uglify({preserveComments: 'some'})))
// Concatenate And Minify Styles
.pipe($.if('*.css', $.csso()))
.pipe(assets.restore())
.pipe($.useref())
// Minify Any HTML
.pipe($.if('*.html', $.minifyHtml()))
// Output Files
.pipe(gulp.dest('dist'))
.pipe($.size({title: 'html'}));
});
// Clean Output Directory
gulp.task('clean', del.bind(null, ['.tmp', 'dist']));
// Build Production Files, the Default Task
gulp.task('default', ['clean'], function (cb) {
runSequence('styles', ['jshint', 'html', 'images', 'copy'], cb);
});
// Run PageSpeed Insights
// Update `url` below to the public URL for your site
gulp.task('pagespeed', pagespeed.bind(null, {
// By default, we use the PageSpeed Insights
// free (no API key) tier. You can use a Google
// Developer API key if you have one. See
// http://goo.gl/RkN0vE for info key: 'YOUR_API_KEY'
url: 'https://todomvc.com',
strategy: 'mobile'
}));
This diff is collapsed.
define(['./system', './viewEngine', './composition', './widget', './modalDialog', './events'],
define(['./system', './viewEngine', './composition', './widget', './modalDialog', './events'],
function(system, viewEngine, composition, widget, modalDialog, Events) {
var app = {
......
define(['./viewLocator', './viewModelBinder', './viewEngine', './system', './viewModel'],
define(['./viewLocator', './viewModelBinder', './viewEngine', './system', './viewModel'],
function (viewLocator, viewModelBinder, viewEngine, system, viewModel) {
var dummyModel = {},
......
//heavily borrowed from backbone events, augmented by signals.js, added a little of my own code, cleaned up for better readability
//heavily borrowed from backbone events, augmented by signals.js, added a little of my own code, cleaned up for better readability
define(['./system'], function (system) {
var eventSplitter = /\s+/;
var Events = function() { };
......
define(['./composition', './system', './viewModel'],
define(['./composition', './system', './viewModel'],
function (composition, system, viewModel) {
var contexts = {},
......
define(['../system', '../viewModel', '../app'], function (system, viewModel, app) {
define(['../system', '../viewModel', '../app'], function (system, viewModel, app) {
//NOTE: Sammy.js is not required by the core of Durandal.
//However, this plugin leverages it to enable navigation.
......
define(['require'], function (require) {
define(['require'], function (require) {
var isDebugging = false,
nativeKeys = Object.keys,
hasOwnProperty = Object.prototype.hasOwnProperty,
......
define(['./system'], function (system) {
define(['./system'], function (system) {
var parseMarkupCore;
if ($.parseHTML) {
......
define(['./system', './viewEngine'],
define(['./system', './viewEngine'],
function (system, viewEngine) {
function findInElements(nodes, url) {
......
define(['./system'], function (system) {
define(['./system'], function (system) {
var viewModel;
function ensureSettings(settings) {
......
define(['./system'], function (system) {
define(['./system'], function (system) {
var viewModelBinder;
var insufficientInfoMessage = 'Insufficient Information to Bind';
var unexpectedViewMessage = 'Unexpected View Type';
......
define(['./system', './composition'], function (system, composition) {
define(['./system', './composition'], function (system, composition) {
var widgetPartAttribute = 'data-part',
widgetPartSelector = '[' + widgetPartAttribute + ']';
......
/*!
/*!
* jQuery JavaScript Library v1.9.1
* http://jquery.com/
*
......
.splash {
.splash {
text-align: center;
margin: 10% 0 0 0;
}
......
/*global requirejs, define, ko */
/*global requirejs, define, ko */
(function () {
'use strict';
......
/*global define */
/*global define */
define([
'bower_components/durandal/plugins/router',
], function (router) {
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false, ToDo:false, Backbone: false */
enyo.ready(function () {
ToDo.TaskCollection = Backbone.Collection.extend({
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false, $:false */
/*exported ENTER_KEY, ESC_KEY */
// This is based on Enyo 2.1.1. The next version (2.3) of Enyo will more tightly integrate MVC and should require less custom code.
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false, ToDo:false */
enyo.kind({
kind: 'enyo.Router',
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false, ToDo:false, Backbone:false */
enyo.ready(function () {
ToDo.TaskModel = Backbone.Model.extend({
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false */
// The footer section
enyo.kind({
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false */
enyo.kind({
name: 'ToDo.NotepadFooterView',
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false, ENTER_KEY:false */
// Header section for adding a new task.
enyo.kind({
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false, ENTER_KEY:false, ESC_KEY:false */
// The main task list view
enyo.kind({
......
/*jshint strict:false */
/*jshint strict:false */
/*global enyo:false */
// This is the notepad area
enyo.kind({
......
{
"framework": {
"name": "Framework Name",
"description": "",
"homepage": "",
"source_path": [{
"name": "__ Example",
"url": ""
}],
"link_groups": [{
"heading": "Official Resources",
"links": [{
"name": "Documentation",
"url": ""
}, {
"name": "API Reference",
"url": ""
}, {
"name": "Applications built with Framework Name",
"url": ""
}, {
"name": "Blog",
"url": ""
}, {
"name": "FAQ",
"url": ""
}, {
"name": "Framework Name on GitHub",
"url": "https://github.com/__"
}]
}, {
"heading": "Articles and Guides",
// Search blogs on google to help dig up articles.
// https://www.google.com/search?q=Framework Name&tbm=blg
"links": [{
"name": "Article 1",
"url": ""
}, {
"name": "Article 2",
"url": ""
}]
}, {
"heading": "Community",
"links": [{
"name": "Framework Name on StackOverflow",
"url": "http://stackoverflow.com/questions/tagged/__"
}, {
"name": "Mailing list on Google Groups",
"url": ""
}, {
"name": "Framework Name on Twitter",
"url": "http://twitter.com/__"
}, {
"name": "Framework Name on Google +",
"url": ""
}]
}]
}
}
Everything in this repo is MIT License unless otherwise specified.
Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="85.529" viewBox="0 0 500 85.529"><g fill="#2D2D2D"><path d="M115.936 74.479v-6.708l8.744-.594c1.188-.086 1.358-.425 1.358-1.698v-43.213h-8.743c-.935 0-1.189.083-1.359.933l-1.358 7.388h-9.339v-17.577h54.677v17.576h-9.255l-1.357-7.388c-.17-.85-.425-.933-1.358-.933h-8.746v43.214c0 1.188.085 1.527 1.359 1.612l9.34.68v6.708h-33.963zM151.076 75.659h-36.319v-8.989l9.843-.669.244-.026.015-.495v-42.036h-7.563l-.188.003-1.549 8.317h-11.499v-19.933h57.034v19.934h-11.417l-1.528-8.317-.204-.003h-7.566v42.035l.006.413.252.022 10.44.76v8.984zm-33.961-2.359h31.603v-4.433l-8.246-.599c-2.264-.151-2.453-1.46-2.453-2.789v-44.393h9.926c1.082 0 2.168.146 2.516 1.88l1.184 6.44h7.092v-15.217h-52.317v15.217h7.178l1.179-6.422c.354-1.753 1.438-1.898 2.521-1.898h9.924v44.394c0 1.307-.189 2.711-2.454 2.876l-7.649.517v4.427z"/></g><g fill="#2D2D2D"><path d="M158.563 52.659c0-14.095 7.385-23.263 22.923-23.263 15.705 0 21.817 9.338 21.817 22.33 0 14.433-7.302 23.77-22.923 23.77-15.533 0-21.817-9.508-21.817-22.837zm31.329-.509c0-10.021-3.057-13.586-8.574-13.586-5.604 0-9.34 3.482-9.34 13.67 0 10.273 3.311 14.35 8.997 14.35 5.52 0 8.917-3.82 8.917-14.434zM180.381 76.677c-14.829 0-22.997-8.53-22.997-24.018 0-15.532 8.785-24.441 24.103-24.441 14.829 0 22.998 8.35 22.998 23.509-.001 16.089-8.561 24.95-24.104 24.95zm1.105-46.101c-14.021 0-21.744 7.843-21.744 22.083 0 9.882 3.579 21.659 20.639 21.659 17.972 0 21.745-12.287 21.745-22.592 0-9.649-3.58-21.15-20.64-21.15zm-.511 37.187c-6.753 0-10.177-5.224-10.177-15.528 0-9.854 3.54-14.849 10.52-14.849 8.742 0 9.754 8.425 9.754 14.765-.001 10.505-3.304 15.612-10.097 15.612zm.342-28.019c-3.505 0-8.16 1.294-8.16 12.49 0 11.803 4.463 13.17 7.817 13.17 3.322 0 7.738-1.373 7.738-13.254.001-10.932-3.737-12.406-7.395-12.406z"/></g><g fill="#2D2D2D"><path d="M240.416 74.479l-.852-4.754-.594-.087c-3.565 3.482-7.726 5.857-13.922 5.857-10.019 0-16.475-6.364-16.475-22.582 0-17.065 8.578-23.518 18.003-23.518 4.922 0 8.745 1.697 11.97 4.075v-12.905c0-.848-.34-1.441-1.104-1.697l-4.668-1.612.933-6.878h17.066v55.187c0 1.271.081 1.442 1.357 1.613l4.072.594v6.708h-15.786zm-2.378-32.687c-2.122-1.445-4.84-2.546-7.303-2.546-6.281 0-8.745 5.855-8.745 13.413 0 8.233 2.376 12.82 7.725 12.82 2.89 0 6.031-1.697 8.323-3.65v-20.037zM225.049 76.677c-11.714 0-17.653-7.995-17.653-23.763 0-15.464 7.17-24.696 19.182-24.696 3.869 0 7.419.997 10.79 3.042v-10.695c0-.479-.139-.524-.3-.58l-5.591-1.931 1.201-8.856h19.275v56.366l.004.395.333.05 5.095.742v8.908h-17.957l-.741-4.147c-4.051 3.562-8.315 5.165-13.638 5.165zm1.528-46.101c-10.69 0-16.823 8.143-16.823 22.338 0 14.403 5.003 21.404 15.295 21.404 5.146 0 9.187-1.702 13.098-5.522l.417-.406 2.014.289.827 4.621h13.622v-4.507l-3.064-.448c-2.011-.269-2.367-1.11-2.367-2.78v-54.008h-14.858l-.662 4.899 3.753 1.297c1.203.403 1.898 1.431 1.898 2.812v15.239l-1.878-1.384c-3.562-2.623-7.141-3.844-11.272-3.844zm3.138 36.083c-5.906 0-8.905-4.711-8.905-14 0-9.274 3.62-14.593 9.926-14.593 2.495 0 5.399 1.003 7.967 2.751l.516.35v21.206l-.415.354c-1.388 1.182-5.05 3.932-9.089 3.932zm1.02-26.233c-6.582 0-7.565 7.666-7.565 12.233 0 11.642 4.928 11.642 6.545 11.642 2.131 0 4.771-1.124 7.145-3.027v-18.844c-2.029-1.263-4.278-2.004-6.125-2.004z"/></g><g fill="#2D2D2D"><path d="M259.098 52.659c0-14.095 7.388-23.263 22.925-23.263 15.707 0 21.821 9.338 21.821 22.33 0 14.433-7.303 23.77-22.926 23.77-15.536 0-21.82-9.508-21.82-22.837zm31.331-.509c0-10.021-3.059-13.586-8.578-13.586-5.602 0-9.336 3.482-9.336 13.67 0 10.273 3.31 14.35 8.997 14.35 5.519 0 8.917-3.82 8.917-14.434zM280.918 76.677c-14.832 0-22.999-8.53-22.999-24.018 0-15.532 8.786-24.441 24.104-24.441 14.831 0 22.999 8.35 22.999 23.509-.001 16.089-8.56 24.95-24.104 24.95zm1.104-46.101c-14.022 0-21.746 7.843-21.746 22.083 0 9.882 3.583 21.659 20.642 21.659 17.971 0 21.745-12.287 21.745-22.592 0-9.649-3.581-21.15-20.641-21.15zm-.51 37.187c-6.754 0-10.178-5.224-10.178-15.528 0-9.854 3.538-14.849 10.517-14.849 8.747 0 9.758 8.425 9.758 14.765-.001 10.505-3.303 15.612-10.097 15.612zm.339-28.019c-3.503 0-8.159 1.294-8.159 12.49 0 11.803 4.466 13.17 7.82 13.17 3.322 0 7.739-1.373 7.739-13.254 0-10.932-3.741-12.406-7.4-12.406z"/></g><g fill="#AF2F2F"><path d="M354.554 74.479v-6.623l6.963-.594c1.188-.171 1.358-.51 1.358-1.698v-32.262h-.339l-12.141 28.951h-13.843l-11.968-29.036h-.427v32.263c0 1.356.086 1.527 1.358 1.698l7.303.679v6.623h-25.81v-6.623l5.347-.594c1.191-.171 1.359-.427 1.359-1.698v-43.555c0-1.274-.168-1.527-1.359-1.697l-5.347-.595v-6.708h22.753l14.774 38.461h.339l15.536-38.461h22.076v6.623l-5.69.68c-1.272.17-1.358.338-1.358 1.611v43.641c0 1.104.254 1.442 1.358 1.613l5.69.679v6.623h-27.932zM383.668 75.659h-30.292v-8.885l8.04-.688.289-.06c-.019-.047-.011-.198-.011-.463v-27.21l-10.517 25.077h-15.414l-10.426-25.294v27.342l.008.479.328.05 8.325.771v8.879h-28.168v-8.857l6.396-.712.295-.053.017-.473v-43.552l-.017-.473-.332-.057-6.359-.707v-8.942h24.742l14.159 36.853 14.886-36.853h24.051v8.848l-6.729.804-.316.048-.004.394v43.641l.021.395.339.054 6.688.799v8.845zm-27.935-2.359h25.575v-4.396l-4.65-.556c-1.691-.261-2.397-1.093-2.397-2.784v-43.64c0-1.667.356-2.51 2.381-2.779l4.667-.558v-4.397h-20.103l-15.536 38.46h-1.944l-14.773-38.461h-20.764v4.473l4.301.478c2.021.29 2.406 1.271 2.406 2.87v43.555c0 1.599-.386 2.58-2.368 2.866l-4.339.481v4.388h23.452v-4.367l-6.231-.581c-2.088-.278-2.43-1.11-2.43-2.872v-33.443h2.395l11.968 29.037h12.269l12.144-28.953h2.3v33.443c0 1.56-.396 2.582-2.367 2.866l-5.953.508v4.362z"/></g><g fill="#AF2F2F"><path d="M411.176 74.479l-17.066-52.469c-.34-1.021-1.017-1.189-2.038-1.444l-3.821-.933.85-6.623h25.728v6.623l-6.879.763 12.733 42.451h.682l12.311-42.451-6.79-.763.848-6.623h21.988v6.623l-3.566.933c-1.019.255-1.865.595-2.12 1.612l-15.537 52.302h-17.323zM429.377 75.659h-19.059l-17.329-53.285c-.116-.342-.131-.396-1.198-.663l-4.845-1.182 1.118-8.698h27.943v8.856l-6.521.724 11.523 38.423 11.142-38.423-6.589-.74 1.129-8.84h24.208v8.711l-4.445 1.164c-1.077.271-1.21.485-1.275.76l-15.802 53.193zm-17.345-2.359h15.584l15.286-51.458c.45-1.788 2.148-2.217 2.965-2.421l2.675-.698v-4.533h-19.771l-.565 4.403 6.991.786-12.945 44.647h-2.446l-13.392-44.647 7.235-.801v-4.389h-23.513l-.582 4.547 2.797.685c1.102.273 2.334.582 2.879 2.216l16.802 51.663z"/></g><g fill="#AF2F2F"><path d="M498.821 70.829c-5.011 3.057-13.502 4.667-21.228 4.667-21.141 0-29.801-12.308-29.801-32.007 0-18.083 10.442-31.585 31.583-31.585 7.047 0 14.603 1.783 19.273 4.842v16.64l-9.338-.764-1.358-8.32c-.171-.848-.512-1.273-1.36-1.53-1.867-.594-4.753-1.018-7.554-1.018-10.785 0-17.664 7.302-17.664 21.735 0 15.027 6.54 22.329 17.069 22.329 2.802 0 6.281-.425 8.489-1.103 1.02-.256 1.273-.51 1.528-1.783l1.273-7.896h9.086v15.793zM477.594 76.677c-20.558 0-30.979-11.167-30.979-33.188 0-20.21 12.553-32.765 32.762-32.765 7.545 0 15.179 1.929 19.919 5.036l.533.348v18.559l-11.532-.945-1.507-9.229c-.088-.427-.15-.475-.537-.593-1.854-.589-4.68-.967-7.213-.967-10.629 0-16.482 7.3-16.482 20.556 0 13.836 5.492 21.149 15.888 21.149 2.783 0 6.132-.433 8.142-1.05.415-.105.502-.157.524-.171.005-.013.079-.15.194-.718l1.426-8.843h11.268v17.632l-.564.346c-4.896 2.987-13.265 4.843-21.842 4.843zm1.782-63.593c-19.037 0-30.404 11.367-30.404 30.405 0 20.457 9.63 30.829 28.622 30.829 7.718 0 15.475-1.625 20.047-4.166v-13.936h-6.901l-1.115 6.906c-.344 1.731-.905 2.359-2.401 2.737-2.188.673-5.74 1.138-8.778 1.138-11.769 0-18.247-8.35-18.247-23.508 0-14.562 6.867-22.915 18.842-22.915 2.798 0 5.829.413 7.911 1.074 1.226.368 1.896 1.116 2.158 2.421l1.22 7.451 7.143.585v-14.708c-4.368-2.632-11.375-4.313-18.097-4.313z"/></g><g fill="#2D2D2D"><path d="M12.83 5.701h8.781v-5.701h-8.781c-2.433 0-4.801.683-6.849 1.979l3.046 4.815c1.136-.715 2.448-1.093 3.803-1.093"/><path d="M5.704 12.827c0-1.042.216-2.042.646-2.979l-5.178-2.389c-.778 1.692-1.172 3.498-1.172 5.368v10.095h5.704v-10.095zM0 28.68h5.704v14.37h-5.704zM76.782 6.979c1.718-1.091 3.432-2.121 5.13-3.079-.97-1-2.104-1.842-3.336-2.478-1.803-.93-3.837-1.422-5.875-1.422h-5.048v5.701h5.048c1.494 0 2.904.449 4.081 1.278"/><path d="M0 48.81h5.704v14.397h-5.704zM27.369 0h14.371v5.701h-14.371zM47.497 0h14.396v5.701h-14.396zM53.855 79.824h14.367v5.705h-14.367zM79.827 32.387h5.701v14.398h-5.701zM79.827 52.542h5.701v14.372h-5.701zM5.704 72.699v-3.738h-5.704v3.738c0 4.668 2.54 8.976 6.631 11.236l2.758-4.99c-2.274-1.259-3.685-3.65-3.685-6.246M79.827 22.847v3.778h5.701v-8.467c-1.875 1.399-3.772 2.96-5.701 4.689"/><path d="M79.827 72.699c0 3.575-2.667 6.614-6.203 7.07l.726 5.655c3.07-.396 5.897-1.894 7.956-4.22 2.078-2.347 3.223-5.366 3.223-8.506v-.028h-5.701v.029zM13.542 79.824h14.398v5.705h-14.398zM33.696 79.824h14.399v5.705h-14.399z"/></g><path fill="#AF2F2F" d="M13.059 45.396l26.744 26.744s26.745-51.453 55.662-62.994l-1.084-8.144c-15.749 6.244-39.648 22.673-57.294 47.787l-18.6-10.996-5.428 7.603z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="3348.477" height="3000" viewBox="0 0 3348.477 3000"><g fill="#383838"><path d="M450 200h308v-200h-308.001c-85.296 0-168.349 24-240.179 69.403l106.86 169.059c39.807-25.162 85.909-38.462 133.32-38.462zM200 450c0-36.534 7.671-71.695 22.799-104.509l-181.626-83.737c-27.321 59.258-41.173 122.593-41.173 188.246v354h200v-354zM0 1006h200v504h-200zM2693.184 244.82c60.299-38.185 120.381-74.26 179.885-108.002-34.01-35.025-73.675-64.589-116.941-86.915-63.27-32.647-134.548-49.903-206.128-49.903h-177v200h177c52.391 0 101.865 15.829 143.184 44.82zM0 1712h200v505h-200zM960 0h504v200h-504zM1666 0h505v200h-505zM1889 2800h504v200h-504zM2800 1136h200v505h-200zM2800 1843h200v504h-200zM200 2550v-131h-200v131c0 163.793 89.126 314.793 232.597 394.074l96.732-175.051c-79.773-44.083-129.329-128.006-129.329-219.023zM2800 801.374v132.626h200v-297.03c-65.743 48.947-132.47 103.807-200 164.404zM2800 2550c0 125.372-93.562 231.961-217.635 247.936l25.539 198.363c107.655-13.861 206.741-66.413 279.005-147.977 72.928-82.313 113.091-188.26 113.091-298.322v-1h-200v1zM475 2800h505v200h-505zM1182 2800h505v200h-505z"/></g><path fill="#AF2F2F" d="M458 1592.333l938.096 938.095s938.096-1804.761 1952.381-2209.523l-38.096-285.715c-552.381 219.048-1390.477 795.238-2009.524 1676.191l-652.382-385.714-190.475 266.666z"/></svg>
\ No newline at end of file
{
"devDependencies": {
"del": "^0.1.1",
"gulp": "^3.8.5",
"gulp-autoprefixer": "^0.0.8",
"gulp-cache": "^0.2.0",
"gulp-csso": "^0.2.9",
"gulp-if": "^1.2.1",
"gulp-imagemin": "^1.0.0",
"gulp-jshint": "^1.6.3",
"gulp-load-plugins": "^0.5.3",
"gulp-minify-html": "^0.1.4",
"gulp-rename": "^1.2.0",
"gulp-replace": "^0.4.0",
"gulp-size": "^1.0.0",
"gulp-uglify": "^0.3.1",
"gulp-uncss": "^0.4.4",
"gulp-useref": "^0.6.0",
"jshint-stylish": "^0.4.0",
"psi": "^0.1.1",
"run-sequence": "^0.3.6"
},
"engines": {
"node": ">=0.10.0"
},
"private": true
}
# ![TodoMVC](media/logo.png)
> Helping you select an MV\* framework
### [Website](http://todomvc.com)&nbsp;&nbsp;&nbsp;&nbsp;[Blog](http://blog.tastejs.com)&nbsp;&nbsp;&nbsp;&nbsp;[TasteJS](http://tastejs.com)
Developers these days are spoiled with choice when it comes to selecting an MV\* framework for structuring and organizing JavaScript web apps.
Backbone, Ember, AngularJS... the list of new and stable solutions goes on and on, but just how do you decide on which to use in a sea of so many options?
To help solve this problem, we created TodoMVC - a project which offers the same Todo application implemented using MV* concepts in most of the popular JavaScript MV\* frameworks of today.
## View & Run in Web IDE
Click on the button below to view the code in a Web IDE. Feel free to edit the code and then run it all from your browser.
[![IDE](site-assets/editcloud9.png)](https://c9.io/open/git/?url=git://github.com/tastejs/todomvc.git)
[![IDE](https://codio-public.s3.amazonaws.com/sharing/demo-in-ide.png)](https://codio.com/p/create/?from_github=tastejs/todomvc)
## Team
TodoMVC would not be possible without a strong team of [contributors](https://github.com/tastejs/todomvc/contributors) helping push the project forward each day. In addition, we have a core project team composed of:
#### [Addy Osmani](http://github.com/addyosmani) - Founder/Lead
<img align="left" width="40" height="40" src="http://www.gravatar.com/avatar/96270e4c3e5e9806cf7245475c00b275.png?s=40">
Addy is a Developer Platform Engineer at Google who originally created TodoMVC. He oversees the project direction, drives expansion and helps lead core development with Sindre Sorhus (by far our most active contributor!).
#### [Sindre Sorhus](https://github.com/sindresorhus) - Lead Developer
<img align="left" width="40" height="40" src="http://www.gravatar.com/avatar/d36a92237c75c5337c17b60d90686bf9.png?s=40">
Sindre is a Web Developer who drives core development, quality control and application design for the project. His contributions have helped us ensure consistency and best practices are enforced wherever possible.
#### [Pascal Hartig](https://github.com/passy) - Developer
<img align="left" width="40" height="40" src="http://www.gravatar.com/avatar/be451fcdbf0e5ff07f23ed16cb5c90a3.png?s=40">
Pascal is a Front-end Engineer at Twitter with a deep passion for consistency. He watches pull requests and helps developers getting their contributions integrated with TodoMVC.
#### [Stephen Sawchuk](https://github.com/stephenplusplus) - Developer
<img align="left" width="40" height="40" src="https://secure.gravatar.com/avatar/098cfe2d360e77c3229f2cd5298354c4?s=40">
Stephen is a Front-end Engineer at Quicken Loans that cares about improving the maintainability and developer experience of open-source projects. His recent contributions include helping us move all apps over to using Bower and implementing the new information bar.
#### [Colin Eberhardt](https://github.com/colineberhardt) - Developer
<img align="left" width="40" height="40" src="https://secure.gravatar.com/avatar/73bba00b41ff1c9ecc3ee29487bace7d?s=40">
Colin is a software consultant at Scott Logic who is passionate about all software - from JavaScript to Java, and C# to Objective-C. His recent contribution to the project has been a fully automated test suite.
#### [Gianni Chiappetta](https://github.com/gf3) - Logo designer
<img align="left" width="40" height="40" src="http://www.gravatar.com/avatar/4b0209ae3652cc5a7d53545e759fbe39.png?s=40">
Gianni is a programmer and designer currently working as the Chief Rigger at MetaLab.
## Disclaimer
<img align="right" width="230" height="230" src="media/icon-small.png">
TodoMVC has been called many things including the 'Speed-dating' and 'Rosetta Stone' of MV* frameworks. Whilst we hope that this project is able to offer assistance in deciding what frameworks are worth spending more time looking at, remember that the Todo application offers a limited view of what a framework may be capable of.
It is meant to be used as a gateway to reviewing how a basic application using a framework may be structured and we heavily recommend investing time researching a solution in more depth before opting to use it.
## Getting Involved
Whilst we enjoy implementing and improving existing Todo apps, we're always interested in speaking to framework authors (and users) wishing to share Todo app implementations in their framework/solution of choice.
Check out our [contribution docs](contributing.md) for more info.
## License
Everything in this repo is MIT License unless otherwise specified.
MIT © Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk.
This diff is collapsed.
......@@ -122,6 +122,8 @@ header nav a:not(:last-child) {
list-style: none;
margin: 0;
padding: 0;
-webkit-columns: 1;
-moz-columns: 1;
columns: 1;
font-size: 17px;
}
......@@ -182,6 +184,7 @@ header nav a:not(:last-child) {
.collapsed {
overflow: hidden;
max-height: 0;
-webkit-transition: max-height 0.7s ease-out;
transition: max-height 0.7s ease-out;
}
......@@ -191,6 +194,7 @@ header nav a:not(:last-child) {
#news-expander:checked ~ .collapsed {
max-height: 999px;
-webkit-transition: max-height 0.7s ease-in;
transition: max-height 0.7s ease-in;
}
......@@ -318,6 +322,9 @@ a.zocial {
text-align: center;
text-decoration: none;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.5);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: relative;
border-radius: .3em;
......@@ -325,6 +332,8 @@ a.zocial {
@media (max-width: 480px) {
body .applist {
-webkit-columns: auto !important;
-moz-columns: auto !important;
columns: auto !important;
}
......@@ -356,12 +365,16 @@ a.zocial {
@media (min-width: 480px) {
.applist {
-webkit-columns: 2;
-moz-columns: 2;
columns: 2;
}
}
@media (min-width: 640px) and (max-width: 770px) {
.applist {
-webkit-columns: 3;
-moz-columns: 3;
columns: 3;
}
}
......@@ -370,6 +383,8 @@ a.zocial {
@media (min-width: 771px) {
.js,
.ctojs {
-webkit-columns: 4;
-moz-columns: 4;
columns: 4;
}
}
......
/*global $ */
(function () {
'use strict';
var date = new Date();
if (date.getDate() === 1 && date.getMonth() === 3) {
['', '-webkit-', '-moz-'].forEach(function (prefix) {
document.body.style[prefix + 'filter'] = 'blur(1px)';
});
}
$.fn.persistantPopover = function () {
var popoverTimeout;
function delay() {
popoverTimeout = setTimeout(function () {
$('.popover').hide();
}, 100);
}
return this.each(function () {
var $this = $(this);
$this.popover({
trigger: 'manual',
placement: 'right',
animation: false,
html: true,
title: this.firstChild.textContent + '<a href="' + $this.data('source') + '">Website</a>'
});
})
.mouseenter(function () {
clearTimeout(popoverTimeout);
$('.popover').remove();
$(this).popover('show');
})
.mouseleave(function () {
delay();
$('.popover').mouseenter(function () {
clearTimeout(popoverTimeout);
}).mouseleave(function () {
delay();
});
});
};
function redirect() {
if (location.hostname === 'addyosmani.github.io') {
location.href = location.href.replace('addyosmani.github.io/todomvc', 'todomvc.com');
}
}
var Quotes = {};
Quotes.build = function (quotes, template) {
var quoteContainer = document.createElement('q');
var quoteElemCount = 0;
var quoteCount = quotes.length;
var createQuoteElems = function () {
var quote = quotes[quoteElemCount];
var el = $(template).hide();
el.children('p').text(quote.quote);
el.find('a').text(quote.person.name).attr('href', quote.person.link);
el.find('img').attr('src', quote.person.gravatar);
quoteContainer.appendChild(el[0]);
if (quoteCount > ++quoteElemCount) {
createQuoteElems();
}
return quoteContainer.innerHTML;
};
return createQuoteElems();
};
Quotes.random = function (quotes) {
var quoteCount = quotes.length;
var randomQuotes = [];
var randomQuote = function () {
var randomQuoteIndex = Math.floor(Math.random() * quoteCount);
if ($.inArray(randomQuoteIndex, randomQuotes) > -1) {
return randomQuote();
}
if (randomQuotes.length === quoteCount - 1) {
randomQuotes = [];
}
randomQuotes.push(randomQuoteIndex);
return randomQuoteIndex;
};
return randomQuote;
};
Quotes.animate = function (container, animSpeed) {
var fader = function (fadeOut, fadeIn) {
var fadeOutCallback = function () {
fadeIn.fadeIn(500, fadeInCallback);
};
var fadeInCallback = function () {
window.setTimeout(swap, animSpeed);
};
fadeOut.fadeOut(500, fadeOutCallback);
};
var quotes = container.children();
var selectRandomQuoteIndex = Quotes.random(quotes);
var quoteElems = {};
var activeQuoteIndex = selectRandomQuoteIndex();
var prevQuoteElem = $(quotes[activeQuoteIndex]);
var swap = function () {
if (!quoteElems[activeQuoteIndex]) {
quoteElems[activeQuoteIndex] = $(quotes[activeQuoteIndex]);
}
var activeQuoteElem = quoteElems[activeQuoteIndex];
fader(prevQuoteElem, activeQuoteElem);
activeQuoteIndex = selectRandomQuoteIndex();
prevQuoteElem = activeQuoteElem;
};
return swap();
};
Quotes.init = function (quotes) {
var container = $(this);
var template = $(this).html();
var quotesHTML = Quotes.build(quotes, template);
container.html(quotesHTML);
Quotes.animate(container, 25000);
};
$.fn.quote = function (quotes) {
return this.each(function () {
Quotes.init.call(this, quotes);
});
};
// Redirect if not on main site.
redirect();
// Apps popover
$('.applist a').persistantPopover();
// Quotes
$('.quotes').quote([{
quote: 'TodoMVC is a godsend for helping developers find what well-developed frameworks match their mental model of application architecture.',
person: {
name: 'Paul Irish',
gravatar: 'http://gravatar.com/avatar/ffe68d6f71b225f7661d33f2a8908281?s=40',
link: 'https://github.com/paulirish'
}
}, {
quote: 'Modern JavaScript developers realise an MVC framework is essential for managing the complexity of their apps. TodoMVC is a fabulous community contribution that helps developers compare frameworks on the basis of actual project code, not just claims and anecdotes.',
person: {
name: 'Michael Mahemoff',
gravatar: 'http://gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=40',
link: 'https://github.com/mahemoff'
}
}, {
quote: 'TodoMVC is an immensely valuable attempt at a difficult problem - providing a structured way of comparing JS libraries and frameworks. TodoMVC is a lone data point in a sea of conjecture and opinion.',
person: {
name: 'Justin Meyer',
gravatar: 'http://gravatar.com/avatar/70ee60f32937b52758869488d5753259?s=40',
link: 'https://github.com/justinbmeyer'
}
}, {
quote: 'It can be hard to make the leap from hacking together code that works to writing code that`s organized, maintainable, reusable, and a joy to work on. The TodoMVC project does a great job of introducing developers to different approaches to code organization, and to the various libraries that can help them along the way. If you`re trying to get your bearings in the world of client-side application development, the TodoMVC project is a great place to get started.',
person: {
name: 'Rebecca Murphey',
gravatar: 'http://gravatar.com/avatar/0177cdce6af15e10db15b6bf5dc4e0b0?s=40',
link: 'https://github.com/rmurphey'
}
}]);
}());
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
module.exports = function (grunt) {
var bower = require('bower');
var wrench = require('wrench');
grunt.registerTask('todomvc-common', function () {
// For some reason, I began trusting Batman to be my test directory for
// `todomvc-common`. If you work on changes in ...
//
// labs/architecture-examples/batman/bower_components/todomvc-common
//
// ... or even just drop the latest `todomvc-common` there, this task will
// find all of the other `bower_components/todomvc-common` directories, and
// update them with what Batman has.
//
// I also added Bower up top for in the future, as that might come in handy
// to correctly fetch and install the latest todomvc-common, without relying
// on this weird Batman system.
var sourceTodoMvcCommon = 'labs/architecture-examples/batman/bower_components/todomvc-common';
var sourceIdentifierRegex = /batman/;
grunt.file.setBase('../');
var directories = grunt.file.expand({
filter: function (src) {
return grunt.file.isDir(src) && src.substr(-14) === 'todomvc-common' && !src.match(sourceIdentifierRegex);
}
}, ['*/**']);
directories.forEach(function (destPath) {
wrench.copyDirSyncRecursive(
sourceTodoMvcCommon,
destPath,
{
forceDelete: true,
preserveFiles: false
},
function () {
console.log(arguments);
});
});
});
};
{
"name": "TodoMVC-Testing-Grounds",
"private": true,
"version": "0.0.0",
"devDependencies": {
"grunt": "~0.4.1",
"bower": "~0.9.2",
"wrench": "~1.5.1"
}
}
{
"name": "todomvc-template",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.4"
}
}
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;
}
}
(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);
})();
/* base.css overrides */
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Template • TodoMVC</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<!-- CSS overrides - remove if you don't need it -->
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<input id="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<!-- This section should be hidden by default and shown when there are todos -->
<section id="main">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<!-- These are here just to show the structure of the list items -->
<!-- List items should get the class `editing` when editing and `completed` when marked as completed -->
<li class="completed">
<div class="view">
<input class="toggle" type="checkbox" checked>
<label>Create a TodoMVC template</label>
<button class="destroy"></button>
</div>
<input class="edit" value="Create a TodoMVC template">
</li>
<li>
<div class="view">
<input class="toggle" type="checkbox">
<label>Rule the web</label>
<button class="destroy"></button>
</div>
<input class="edit" value="Rule the web">
</li>
</ul>
</section>
<!-- This footer should hidden by default and shown when there are todos -->
<footer id="footer">
<!-- This should be `0 items left` by default -->
<span id="todo-count"><strong>1</strong> item left</span>
<!-- Remove this if you don't implement routing -->
<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>
<!-- Hidden if no completed items are left ↓ -->
<button id="clear-completed">Clear completed (1)</button>
</footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<!-- Remove the below line ↓ -->
<p>Template by <a href="http://github.com/sindresorhus">Sindre Sorhus</a></p>
<!-- Change this out with your name and url ↓ -->
<p>Created by <a href="http://todomvc.com">you</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<!-- Scripts here. Don't remove this ↓ -->
<script src="bower_components/todomvc-common/base.js"></script>
<script src="js/app.js"></script>
</body>
</html>
(function( window ) {
'use strict';
// Your starting point. Enjoy the ride!
})( window );
# Framework Name TodoMVC Example
> Short description of the framework provided by the official website.
> _[Framework Name - framework.com](link-to-framework)_
## Learning Framework Name
The [Framework Name website]() is a great resource for getting started.
Here are some links you may find helpful:
* [Documentation]()
* [API Reference]()
* [Applications built with Framework Name]()
* [Blog]()
* [FAQ]()
* [Framework Name on GitHub]()
Articles and guides from the community:
* [Article 1]()
* [Article 2]()
Get help from other Framework Name users:
* [Framework Name on StackOverflow](http://stackoverflow.com/questions/tagged/____)
* [Mailing list on Google Groups]()
* [Framework Name on Twitter](http://twitter.com/____)
* [Framework Name on Google +]()
_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
How is the app structured? Are there deviations from the spec? If so, why?
## Running
If there is a build step required to get the example working, explain it here.
To run the app, spin up an HTTP server and visit http://localhost/.../myexample/.
## Credit
This TodoMVC application was created by [you]().
# Template • [TodoMVC](http://todomvc.com)
## Getting Started
Read the [App Specification](https://github.com/tastejs/todomvc/wiki/App-Specification) before touching the template.
## Need help?
Feel free to contact [Sindre](https://github.com/sindresorhus) or [Pascal](https://github.com/passy) if you have any questions or need help with the template.
## Credit
Created by [Sindre Sorhus](http://sindresorhus.com)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment