Commit 89590766 authored by Addy Osmani's avatar Addy Osmani

Fixes #576 - adds new Polymer TodoMVC implementation. Add entry to the homepage

parent e82d6e4f
...@@ -80,6 +80,9 @@ ...@@ -80,6 +80,9 @@
<li class="routing"> <li class="routing">
<a href="architecture-examples/maria/" data-source="https://github.com/petermichaux/maria" data-content="An MVC framework for JavaScript applications. The real MVC. The Smalltalk MVC. The Gang of Four MVC. The three core design patterns of MVC (observer, composite, and strategy) are embedded in Maria's Model, View, and Controller objects. Other patterns traditionally included in MVC implementations (e.g. factory method and template) make appearances too.">Maria</a> <a href="architecture-examples/maria/" data-source="https://github.com/petermichaux/maria" data-content="An MVC framework for JavaScript applications. The real MVC. The Smalltalk MVC. The Gang of Four MVC. The three core design patterns of MVC (observer, composite, and strategy) are embedded in Maria's Model, View, and Controller objects. Other patterns traditionally included in MVC implementations (e.g. factory method and template) make appearances too.">Maria</a>
</li> </li>
<li class="labs routing">
<a href="labs/architecture-examples/polymer/index.html" data-source="http://polymer-project.org" data-content="Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform on modern browsers. It is comprised of core platform features (e.g Shadow DOM, Custom Elements, MDV) enabled with polyfills and a next generation web application framework built on these technologies.">Polymer</a>
</li>
<li class="labs"> <li class="labs">
<a href="labs/architecture-examples/cujo/index.html" data-source="http://cujojs.com" data-content="cujoJS is an architectural framework for building highly modular, scalable, maintainable applications in Javascript. It provides architectural plumbing, such as modules (AMD and CommonJS), declarative application composition, declarative connections, and aspect oriented programming.">cujoJS</a> <a href="labs/architecture-examples/cujo/index.html" data-source="http://cujojs.com" data-content="cujoJS is an architectural framework for building highly modular, scalable, maintainable applications in Javascript. It provides architectural plumbing, such as modules (AMD and CommonJS), declarative application composition, declarative connections, and aspect oriented programming.">cujoJS</a>
</li> </li>
......
# Names should be added to this file with this pattern:
#
# For individuals:
# Name <email address>
#
# For organizations:
# Organization <fnmatch pattern>
#
Google Inc. <*@google.com>
# Contributing
Want to contribute to Polymer? Great!
We are more than happy to accept external contributions to the project in the form of [feedback](https://groups.google.com/forum/?fromgroups=#!forum/polymer-dev), [bug reports](../../issues), and pull requests.
## Contributor License Agreement
Before we can accept patches, there's a quick web form you need to fill out.
- If you're contributing as and individual (e.g. you own the intellectual property), fill out [this form](http://code.google.com/legal/individual-cla-v1.0.html).
- If you're contributing under a company, fill out [this form](http://code.google.com/legal/corporate-cla-v1.0.html) instead.
This CLA asserts that contributions are owned by you and that we can license all work under our [license](LICENSE).
Other projects require a similar agreement: jQuery, Firefox, Apache, Node, and many more.
[More about CLAs](https://www.google.com/search?q=Contributor%20License%20Agreement)
## Initial setup
Here's an easy guide that should get you up and running:
1. Fork the project on github and pull down your copy.
> replace the {{ username }} with your username and {{ repository }} with the repository name
git clone git@github.com:{{ username }}/{{ repository }}.git --recursive
Note the `--recursive`. This is necessary for submodules to initialize properly. If you don't do a recursive clone, you'll have to init them manually:
git submodule init
git submodule update
2. Development happens on the `master` branch. Get yourself on it!
git checkout master
That's it for the one time setup. Now you're ready to make a change.
## Submitting a pull request
We iterate fast! To avoid potential merge conflicts, it's a good idea to pull from the main project before making a change and submitting a pull request. The easiest way to do this is setup a remote called `upstream` and do a pull before working on a change:
git remote add upstream git://github.com/Polymer/{{ repository }}.git
Then before making a change, do a pull from the upstream `master` branch:
git pull upstream master
To make life easier, add a "pull upstream" alias in your `.gitconfig`:
[alias]
pu = !"git fetch origin -v; git fetch upstream -v; git merge upstream/master"
That will pull in changes from your forked repo, the main (upstream) repo, and merge the two. Then it's just a matter of running `git pu` before a change and pushing to your repo:
git checkout master
git pu
# make change
git commit -a -m 'Awesome things.'
git push
Lastly, don't forget to submit the pull request.
// Copyright (c) 2012 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Additional IP Rights Grant (Patents)
"This implementation" means the copyrightable works distributed by
Google as part of the Polymer project.
Google hereby grants to You a perpetual, worldwide, non-exclusive,
no-charge, royalty-free, irrevocable (except as stated in this section)
patent license to make, have made, use, offer to sell, sell, import,
transfer and otherwise run, modify and propagate the contents of this
implementation of Polymer, where such license applies only to those
patent claims, both currently owned or controlled by Google and acquired
in the future, licensable by Google that are necessarily infringed by
this implementation of Polymer. This grant does not include claims
that would be infringed only as a consequence of further modification of
this implementation. If you or your agent or exclusive licensee
institute or order or agree to the institution of patent litigation
against any entity (including a cross-claim or counterclaim in a
lawsuit) alleging that this implementation of Polymer or any code
incorporated within this implementation of Polymer constitutes
direct or contributory patent infringement, or inducement of patent
infringement, then any patent rights granted to you under this License
for this implementation of Polymer shall terminate as of the date
such litigation is filed.
# Polymer TodoMVC Example
> Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform on modern browsers.
> _[Polymer - www.polymer-project.org](http://www.polymer-project.org/)_
## Learning Polymer
The [Polymer website](http://www.polymer-project.org) is a great resource for getting started.
Here are some links you may find helpful:
* [Getting Started](http://www.polymer-project.org/getting-started.html)
* [FAQ](http://www.polymer-project.org/faq.html)
* [Browser Compatibility](http://www.polymer-project.org/compatibility.html)
Get help from Polymer devs and users:
* Find us on IRC on __#polymer__ at freenode.
* Join the high-traffic [polymer-dev](https://groups.google.com/forum/?fromgroups=#!forum/polymer-dev) Google group or the announcement-only [polymer-announce](https://groups.google.com/forum/?fromgroups=#!forum/polymer-announce) Google group.
## Implementation
The Polymer implementation of TodoMVC has a few key differences with other implementations:
* Since [Web Components](https://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html) allow you to create new types of DOM elements, the DOM tree is very different from other implementations.
* The template, styling, and behavior are fully encapsulated in each custom element. Instead of having an overall stylesheet (`base.css` or `app.css`), each element that needs styling has its own stylesheet.
* Non-visual elements such as the router and the model are also implemented as custom elements and appear in the DOM. Implementing them as custom elements instead of plain objects allows you to take advantage of Polymer data binding and event handling throughout the app.
## Compatibility
Polymer and its polyfills are intended to work in the latest version of [evergreen browsers](http://tomdale.net/2013/05/evergreen-browsers/). IE9 is not supported. Please refer to [Browser Compatibility](http://www.polymer-project.org/compatibility.html) for more details.
## Running this sample
1. Install [node.js](nodejs.org) (required for `bower` client-side package management)
1. Install bower: `npm install -g bower`
1. From the `todomvc\` folder, run `bower update`
1. Start a web server in the `todomvc\` folder. Hint: if you have python installed, you can just run:
`python -m SimpleHTTPServer`
1. Browse to the server root
/* base.css overrides */
html,
body {
margin: 0;
padding: 0;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('../components/todomvc-common/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;
}
body > header {
padding-top: 22px;
margin-bottom: -5px;
}
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;
}
#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;
}
.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);
}
/**body*/.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
/**body*/.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
/**body*/.learn-bar > .learn {
left: 8px;
}
/**body*/.learn-bar #todoapp {
width: 550px;
margin: 130px auto 40px auto;
}
}
{
"name": "todomvc-template",
"version": "0.0.0",
"dependencies": {
"todomvc-common": "~0.1.4",
"director": "*",
"polymer": "*"
}
}
/.idea/
node_modules
npm-debug.log
.DS_Store
/test/browser/browserified-bundle.js
language: node_js
node_js:
- 0.6
- 0.8
- "0.10"
notifications:
email:
- travis@nodejitsu.com
irc: "irc.freenode.org#nodejitsu"
Copyright (c) 2011 Nodejitsu Inc.
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.
\ No newline at end of file
<img src="https://github.com/flatiron/director/raw/master/img/director.png" />
# Synopsis
Director is a router. Routing is the process of determining what code to run when a URL is requested.
# Motivation
A routing library that works in both the browser and node.js environments with as few differences as possible. Simplifies the development of Single Page Apps and Node.js applications. Dependency free (doesn't require jQuery or Express, etc).
# Status
[![Build Status](https://secure.travis-ci.org/flatiron/director.png?branch=master)](http://travis-ci.org/flatiron/director)
# Features
* [Client-Side Routing](#client-side)
* [Server-Side HTTP Routing](#http-routing)
* [Server-Side CLI Routing](#cli-routing)
# Usage
* [API Documentation](#api-documentation)
* [Frequently Asked Questions](#faq)
<a name="client-side"></a>
## Client-side Routing
It simply watches the hash of the URL to determine what to do, for example:
```
http://foo.com/#/bar
```
Client-side routing (aka hash-routing) allows you to specify some information about the state of the application using the URL. So that when the user visits a specific URL, the application can be transformed accordingly.
<img src="https://github.com/flatiron/director/raw/master/img/hashRoute.png" />
Here is a simple example:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A Gentle Introduction</title>
<script src="https://raw.github.com/flatiron/director/master/build/director.min.js"></script>
<script>
var author = function () { console.log("author"); },
books = function () { console.log("books"); },
viewBook = function(bookId) { console.log("viewBook: bookId is populated: " + bookId); };
var routes = {
'/author': author,
'/books': [books, function() { console.log("An inline route handler."); }],
'/books/view/:bookId': viewBook
};
var router = Router(routes);
router.init();
</script>
</head>
<body>
<ul>
<li><a href="#/author">#/author</a></li>
<li><a href="#/books">#/books</a></li>
<li><a href="#/books/view/1">#/books/view/1</a></li>
</ul>
</body>
</html>
```
Director works great with your favorite DOM library, such as jQuery.
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A Gentle Introduction 2</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script src="https://raw.github.com/flatiron/director/master/build/director.min.js"></script>
<script>
$('document').ready(function(){
//
// create some functions to be executed when
// the correct route is issued by the user.
//
var showAuthorInfo = function () { console.log("showAuthorInfo"); },
listBooks = function () { console.log("listBooks"); },
allroutes = function() {
var route = window.location.hash.slice(2),
sections = $('section'),
section;
if ((section = sections.filter('[data-route=' + route + ']')).length) {
sections.hide(250);
section.show(250);
}
};
//
// define the routing table.
//
var routes = {
'/author': showAuthorInfo,
'/books': listBooks
};
//
// instantiate the router.
//
var router = Router(routes);
//
// a global configuration setting.
//
router.configure({
on: allroutes
});
router.init();
});
</script>
</head>
<body>
<section data-route="author">Author Name</section>
<section data-route="books">Book1, Book2, Book3</section>
<ul>
<li><a href="#/author">#/author</a></li>
<li><a href="#/books">#/books</a></li>
</ul>
</body>
</html>
```
You can find a browser-specific build of `director` [here][1] which has all of the server code stripped away.
<a name="http-routing"></a>
## Server-Side HTTP Routing
Director handles routing for HTTP requests similar to `journey` or `express`:
```js
//
// require the native http module, as well as director.
//
var http = require('http'),
director = require('director');
//
// create some logic to be routed to.
//
function helloWorld() {
this.res.writeHead(200, { 'Content-Type': 'text/plain' })
this.res.end('hello world');
}
//
// define a routing table.
//
var router = new director.http.Router({
'/hello': {
get: helloWorld
}
});
//
// setup a server and when there is a request, dispatch the
// route that was requested in the request object.
//
var server = http.createServer(function (req, res) {
router.dispatch(req, res, function (err) {
if (err) {
res.writeHead(404);
res.end();
}
});
});
//
// You can also do ad-hoc routing, similar to `journey` or `express`.
// This can be done with a string or a regexp.
//
router.get('/bonjour', helloWorld);
router.get(/hola/, helloWorld);
//
// set the server to listen on port `8080`.
//
server.listen(8080);
```
### See Also:
- Auto-generated Node.js API Clients for routers using [Director-Reflector](http://github.com/flatiron/director-reflector)
- RESTful Resource routing using [restful](http://github.com/flatiron/restful)
- HTML / Plain Text views of routers using [Director-Explorer](http://github.com/flatiron/director-explorer)
<a name="cli-routing"></a>
## CLI Routing
Director supports Command Line Interface routing. Routes for cli options are based on command line input (i.e. `process.argv`) instead of a URL.
``` js
var director = require('director');
var router = new director.cli.Router();
router.on('create', function () {
console.log('create something');
});
router.on(/destroy/, function () {
console.log('destroy something');
});
// You will need to dispatch the cli arguments yourself
router.dispatch('on', process.argv.slice(2).join(' '));
```
Using the cli router, you can dispatch commands by passing them as a string. For example, if this example is in a file called `foo.js`:
``` bash
$ node foo.js create
create something
$ node foo.js destroy
destroy something
```
<a name="api-documentation"></a>
# API Documentation
* [Constructor](#constructor)
* [Routing Table](#routing-table)
* [Adhoc Routing](#adhoc-routing)
* [Scoped Routing](#scoped-routing)
* [Routing Events](#routing-events)
* [Configuration](#configuration)
* [URL Matching](#url-matching)
* [URL Params](#url-params)
* [Route Recursion](#route-recursion)
* [Async Routing](#async-routing)
* [Resources](#resources)
* [History API](#history-api)
* [Instance Methods](#instance-methods)
* [Attach Properties to `this`](#attach-to-this)
* [HTTP Streaming and Body Parsing](#http-streaming-body-parsing)
<a name="constructor"></a>
## Constructor
``` js
var router = Router(routes);
```
<a name="routing-table"></a>
## Routing Table
An object literal that contains nested route definitions. A potentially nested set of key/value pairs. The keys in the object literal represent each potential part of the URL. The values in the object literal contain references to the functions that should be associated with them. *bark* and *meow* are two functions that you have defined in your code.
``` js
//
// Assign routes to an object literal.
//
var routes = {
//
// a route which assigns the function `bark`.
//
'/dog': bark,
//
// a route which assigns the functions `meow` and `scratch`.
//
'/cat': [meow, scratch]
};
//
// Instantiate the router.
//
var router = Router(routes);
```
<a name="adhoc-routing"></a>
## Adhoc Routing
When developing large client-side or server-side applications it is not always possible to define routes in one location. Usually individual decoupled components register their own routes with the application router. We refer to this as _Adhoc Routing._ Lets take a look at the API `director` exposes for adhoc routing:
**Client-side Routing**
``` js
var router = new Router().init();
router.on('/some/resource', function () {
//
// Do something on `/#/some/resource`
//
});
```
**HTTP Routing**
``` js
var router = new director.http.Router();
router.get(/\/some\/resource/, function () {
//
// Do something on an GET to `/some/resource`
//
});
```
<a name="scoped-routing"></a>
## Scoped Routing
In large web appliations, both [Client-side](#client-side) and [Server-side](#http-routing), routes are often scoped within a few individual resources. Director exposes a simple way to do this for [Adhoc Routing](#adhoc-routing) scenarios:
``` js
var router = new director.http.Router();
//
// Create routes inside the `/users` scope.
//
router.path(/\/users\/(\w+)/, function () {
//
// The `this` context of the function passed to `.path()`
// is the Router itself.
//
this.post(function (id) {
//
// Create the user with the specified `id`.
//
});
this.get(function (id) {
//
// Retreive the user with the specified `id`.
//
});
this.get(/\/friends/, function (id) {
//
// Get the friends for the user with the specified `id`.
//
});
});
```
<a name="routing-events"></a>
## Routing Events
In `director`, a "routing event" is a named property in the [Routing Table](#routing-table) which can be assigned to a function or an Array of functions to be called when a route is matched in a call to `router.dispatch()`.
* **on:** A function or Array of functions to execute when the route is matched.
* **before:** A function or Array of functions to execute before calling the `on` method(s).
**Client-side only**
* **after:** A function or Array of functions to execute when leaving a particular route.
* **once:** A function or Array of functions to execute only once for a particular route.
<a name="configuration"></a>
## Configuration
Given the flexible nature of `director` there are several options available for both the [Client-side](#client-side) and [Server-side](#http-routing). These options can be set using the `.configure()` method:
``` js
var router = new director.Router(routes).configure(options);
```
The `options` are:
* **recurse:** Controls [route recursion](#route-recursion). Use `forward`, `backward`, or `false`. Default is `false` Client-side, and `backward` Server-side.
* **strict:** If set to `false`, then trailing slashes (or other delimiters) are allowed in routes. Default is `true`.
* **async:** Controls [async routing](#async-routing). Use `true` or `false`. Default is `false`.
* **delimiter:** Character separator between route fragments. Default is `/`.
* **notfound:** A function to call if no route is found on a call to `router.dispatch()`.
* **on:** A function (or list of functions) to call on every call to `router.dispatch()` when a route is found.
* **before:** A function (or list of functions) to call before every call to `router.dispatch()` when a route is found.
**Client-side only**
* **resource:** An object to which string-based routes will be bound. This can be especially useful for late-binding to route functions (such as async client-side requires).
* **after:** A function (or list of functions) to call when a given route is no longer the active route.
* **html5history:** If set to `true` and client supports `pushState()`, then uses HTML5 History API instead of hash fragments. See [History API](#history-api) for more information.
* **run_handler_in_init:** If `html5history` is enabled, the route handler by default is executed upon `Router.init()` since with real URIs the router can not know if it should call a route handler or not. Setting this to `false` disables the route handler initial execution.
<a name="url-matching"></a>
## URL Matching
``` js
var router = Router({
//
// given the route '/dog/yella'.
//
'/dog': {
'/:color': {
//
// this function will return the value 'yella'.
//
on: function (color) { console.log(color) }
}
}
});
```
Routes can sometimes become very complex, `simple/:tokens` don't always suffice. Director supports regular expressions inside the route names. The values captured from the regular expressions are passed to your listener function.
``` js
var router = Router({
//
// given the route '/hello/world'.
//
'/hello': {
'/(\\w+)': {
//
// this function will return the value 'world'.
//
on: function (who) { console.log(who) }
}
}
});
```
``` js
var router = Router({
//
// given the route '/hello/world/johny/appleseed'.
//
'/hello': {
'/world/?([^\/]*)\/([^\/]*)/?': function (a, b) {
console.log(a, b);
}
}
});
```
<a name="url-params"></a>
## URL Parameters
When you are using the same route fragments it is more descriptive to define these fragments by name and then use them in your [Routing Table](#routing-table) or [Adhoc Routes](#adhoc-routing). Consider a simple example where a `userId` is used repeatedly.
``` js
//
// Create a router. This could also be director.cli.Router() or
// director.http.Router().
//
var router = new director.Router();
//
// A route could be defined using the `userId` explicitly.
//
router.on(/([\w-_]+)/, function (userId) { });
//
// Define a shorthand for this fragment called `userId`.
//
router.param('userId', /([\\w\\-]+)/);
//
// Now multiple routes can be defined with the same
// regular expression.
//
router.on('/anything/:userId', function (userId) { });
router.on('/something-else/:userId', function (userId) { });
```
<a name="route-recursion"></a>
## Route Recursion
Can be assigned the value of `forward` or `backward`. The recurse option will determine the order in which to fire the listeners that are associated with your routes. If this option is NOT specified or set to null, then only the listeners associated with an exact match will be fired.
### No recursion, with the URL /dog/angry
``` js
var routes = {
'/dog': {
'/angry': {
//
// Only this method will be fired.
//
on: growl
},
on: bark
}
};
var router = Router(routes);
```
### Recursion set to `backward`, with the URL /dog/angry
``` js
var routes = {
'/dog': {
'/angry': {
//
// This method will be fired first.
//
on: growl
},
//
// This method will be fired second.
//
on: bark
}
};
var router = Router(routes).configure({ recurse: 'backward' });
```
### Recursion set to `forward`, with the URL /dog/angry
``` js
var routes = {
'/dog': {
'/angry': {
//
// This method will be fired second.
//
on: growl
},
//
// This method will be fired first.
//
on: bark
}
};
var router = Router(routes).configure({ recurse: 'forward' });
```
### Breaking out of recursion, with the URL /dog/angry
``` js
var routes = {
'/dog': {
'/angry': {
//
// This method will be fired first.
//
on: function() { return false; }
},
//
// This method will not be fired.
//
on: bark
}
};
//
// This feature works in reverse with recursion set to true.
//
var router = Router(routes).configure({ recurse: 'backward' });
```
<a name="async-routing"></a>
## Async Routing
Before diving into how Director exposes async routing, you should understand [Route Recursion](#route-recursion). At it's core route recursion is about evaluating a series of functions gathered when traversing the [Routing Table](#routing-table).
Normally this series of functions is evaluated synchronously. In async routing, these functions are evaluated asynchronously. Async routing can be extremely useful both on the client-side and the server-side:
* **Client-side:** To ensure an animation or other async operations (such as HTTP requests for authentication) have completed before continuing evaluation of a route.
* **Server-side:** To ensure arbitrary async operations (such as performing authentication) have completed before continuing the evaluation of a route.
The method signatures for route functions in synchronous and asynchronous evaluation are different: async route functions take an additional `next()` callback.
### Synchronous route functions
``` js
var router = new director.Router();
router.on('/:foo/:bar/:bazz', function (foo, bar, bazz) {
//
// Do something asynchronous with `foo`, `bar`, and `bazz`.
//
});
```
### Asynchronous route functions
``` js
var router = new director.http.Router().configure({ async: true });
router.on('/:foo/:bar/:bazz', function (foo, bar, bazz, next) {
//
// Go do something async, and determine that routing should stop
//
next(false);
});
```
<a name="resources"></a>
## Resources
**Available on the Client-side only.** An object literal containing functions. If a host object is specified, your route definitions can provide string literals that represent the function names inside the host object. A host object can provide the means for better encapsulation and design.
``` js
var router = Router({
'/hello': {
'/usa': 'americas',
'/china': 'asia'
}
}).configure({ resource: container }).init();
var container = {
americas: function() { return true; },
china: function() { return true; }
};
```
<a name="history-api"></a>
## History API
**Available on the Client-side only.** Director supports using HTML5 History API instead of hash fragments for navigation. To use the API, pass `{html5history: true}` to `configure()`. Use of the API is enabled only if the client supports `pushState()`.
Using the API gives you cleaner URIs but they come with a cost. Unlike with hash fragments your route URIs must exist. When the client enters a page, say http://foo.com/bar/baz, the web server must respond with something meaningful. Usually this means that your web server checks the URI points to something that, in a sense, exists, and then serves the client the JavaScript application.
If you're after a single-page application you can not use plain old `<a href="/bar/baz">` tags for navigation anymore. When such link is clicked, web browsers try to ask for the resource from server which is not of course desired for a single-page application. Instead you need to use e.g. click handlers and call the `setRoute()` method yourself.
<a name="attach-to-this"></a>
## Attach Properties To `this`
Generally, the `this` object bound to route handlers, will contain the request in `this.req` and the response in `this.res`. One may attach additional properties to `this` with the `router.attach` method:
```js
var director = require('director');
var router = new director.http.Router().configure(options);
//
// Attach properties to `this`
//
router.attach(function () {
this.data = [1,2,3];
});
//
// Access properties attached to `this` in your routes!
//
router.get('/hello', function () {
this.res.writeHead(200, { 'content-type': 'text/plain' });
//
// Response will be `[1,2,3]`!
//
this.res.end(this.data);
});
```
This API may be used to attach convenience methods to the `this` context of route handlers.
<a name="http-streaming-body-parsing">
## HTTP Streaming and Body Parsing
When you are performing HTTP routing there are two common scenarios:
* Buffer the request body and parse it according to the `Content-Type` header (usually `application/json` or `application/x-www-form-urlencoded`).
* Stream the request body by manually calling `.pipe` or listening to the `data` and `end` events.
By default `director.http.Router()` will attempt to parse either the `.chunks` or `.body` properties set on the request parameter passed to `router.dispatch(request, response, callback)`. The router instance will also wait for the `end` event before firing any routes.
**Default Behavior**
``` js
var director = require('director');
var router = new director.http.Router();
router.get('/', function () {
//
// This will not work, because all of the data
// events and the end event have already fired.
//
this.req.on('data', function (chunk) {
console.log(chunk)
});
});
```
In [flatiron][2], `director` is used in conjunction with [union][3] which uses a `BufferedStream` proxy to the raw `http.Request` instance. [union][3] will set the `req.chunks` property for you and director will automatically parse the body. If you wish to perform this buffering yourself directly with `director` you can use a simple request handler in your http server:
``` js
var http = require('http'),
director = require('director');
var router = new director.http.Router();
var server = http.createServer(function (req, res) {
req.chunks = [];
req.on('data', function (chunk) {
req.chunks.push(chunk.toString());
});
router.dispatch(req, res, function (err) {
if (err) {
res.writeHead(404);
res.end();
}
console.log('Served ' + req.url);
});
});
router.post('/', function () {
this.res.writeHead(200, { 'Content-Type': 'application/json' })
this.res.end(JSON.stringify(this.req.body));
});
```
**Streaming Support**
If you wish to get access to the request stream before the `end` event is fired, you can pass the `{ stream: true }` options to the route.
``` js
var director = require('director');
var router = new director.http.Router();
router.get('/', { stream: true }, function () {
//
// This will work because the route handler is invoked
// immediately without waiting for the `end` event.
//
this.req.on('data', function (chunk) {
console.log(chunk);
});
});
```
<a name="instance-methods"></a>
## Instance methods
### configure(options)
* `options` {Object}: Options to configure this instance with.
Configures the Router instance with the specified `options`. See [Configuration](#configuration) for more documentation.
### param(token, matcher)
* token {string}: Named parameter token to set to the specified `matcher`
* matcher {string|Regexp}: Matcher for the specified `token`.
Adds a route fragment for the given string `token` to the specified regex `matcher` to this Router instance. See [URL Parameters](#url-params) for more documentation.
### on(method, path, route)
* `method` {string}: Method to insert within the Routing Table (e.g. `on`, `get`, etc.).
* `path` {string}: Path within the Routing Table to set the `route` to.
* `route` {function|Array}: Route handler to invoke for the `method` and `path`.
Adds the `route` handler for the specified `method` and `path` within the [Routing Table](#routing-table).
### path(path, routesFn)
* `path` {string|Regexp}: Scope within the Routing Table to invoke the `routesFn` within.
* `routesFn` {function}: Adhoc Routing function with calls to `this.on()`, `this.get()` etc.
Invokes the `routesFn` within the scope of the specified `path` for this Router instance.
### dispatch(method, path[, callback])
* method {string}: Method to invoke handlers for within the Routing Table
* path {string}: Path within the Routing Table to match
* callback {function}: Invoked once all route handlers have been called.
Dispatches the route handlers matched within the [Routing Table](#routing-table) for this instance for the specified `method` and `path`.
### mount(routes, path)
* routes {object}: Partial routing table to insert into this instance.
* path {string|Regexp}: Path within the Routing Table to insert the `routes` into.
Inserts the partial [Routing Table](#routing-table), `routes`, into the Routing Table for this Router instance at the specified `path`.
## Instance methods (Client-side only)
### init([redirect])
* `redirect` {String}: This value will be used if '/#/' is not found in the URL. (e.g., init('/') will resolve to '/#/', init('foo') will resolve to '/#foo').
Initialize the router, start listening for changes to the URL.
### getRoute([index])
* `index` {Number}: The hash value is divided by forward slashes, each section then has an index, if this is provided, only that section of the route will be returned.
Returns the entire route or just a section of it.
### setRoute(route)
* `route` {String}: Supply a route value, such as `home/stats`.
Set the current route.
### setRoute(start, length)
* `start` {Number} - The position at which to start removing items.
* `length` {Number} - The number of items to remove from the route.
Remove a segment from the current route.
### setRoute(index, value)
* `index` {Number} - The hash value is divided by forward slashes, each section then has an index.
* `value` {String} - The new value to assign the the position indicated by the first parameter.
Set a segment of the current route.
<a name="faq"></a>
# Frequently Asked Questions
## What About SEO?
Is using a Client-side router a problem for SEO? Yes. If advertising is a requirement, you are probably building a "Web Page" and not a "Web Application". Director on the client is meant for script-heavy Web Applications.
# Licence
(The MIT License)
Copyright (c) 2010 Nodejitsu Inc. <http://www.twitter.com/nodejitsu>
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.
[0]: http://github.com/flatiron/director
[1]: https://github.com/flatiron/director/blob/master/build/director.min.js
[2]: http://github.com/flatiron/flatiron
[3]: http://github.com/flatiron/union
#!/usr/bin/env node
var Codesurgeon = require('codesurgeon').Codesurgeon;
var surgeon = new Codesurgeon;
var path = require('path');
var root = path.join(__dirname, '..');
var lib = path.join(root, 'lib', 'director');
//
// Distill and package the browser version.
//
surgeon
//
.configure({
package: root + '/package.json',
owner: 'Nodejitsu, Inc (Using Codesurgeon).',
noVersion: true
})
.read(
path.join(lib, 'browser.js')
)
//
// we want everything so far. specify extract with no
// parameters to get everything into the output buffer.
//
.extract()
//
// clear the input so far, but don't clear the output.
//
.clear('inputs')
//
// read the `router.js` file
//
.read(
path.join(lib, 'router.js')
)
//
// the current input buffer contains stuff that we dont
// want in the browser build, so let's cherry pick from
// the buffer.
//
.extract(
'_every',
'_flatten',
'_asyncEverySeries',
'paramifyString',
'regifyString',
'terminator',
'Router.prototype.configure',
'Router.prototype.param',
'Router.prototype.on',
'Router.prototype.dispatch',
'Router.prototype.invoke',
'Router.prototype.traverse',
'Router.prototype.insert',
'Router.prototype.insertEx',
'Router.prototype.extend',
'Router.prototype.runlist',
'Router.prototype.mount'
)
//
// wrap everything that is in the current buffer with a
// closure so that we dont get any collisions with other
// libraries
//
.wrap()
//
// write the debuggable version of the file. This file will
// get renamed to include the version from the package.json
//
.write(root + '/build/director.js')
//
// now lets make a minified version for production use.
//
.uglify()
.write(root + '/build/director.min.js')
;
{
"name": "director",
"version": "1.2.0",
"repository": {
"type": "git",
"url": "git://github.com/flatiron/director.git"
}
}
\ No newline at end of file
//
// Generated on Sun Dec 16 2012 22:47:05 GMT-0500 (EST) by Nodejitsu, Inc (Using Codesurgeon).
// Version 1.1.9
//
(function (exports) {
/*
* browser.js: Browser specific functionality for director.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
if (!Array.prototype.filter) {
Array.prototype.filter = function(filter, that) {
var other = [], v;
for (var i = 0, n = this.length; i < n; i++) {
if (i in this && filter.call(that, v = this[i], i, this)) {
other.push(v);
}
}
return other;
};
}
if (!Array.isArray){
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
}
var dloc = document.location;
function dlocHashEmpty() {
// Non-IE browsers return '' when the address bar shows '#'; Director's logic
// assumes both mean empty.
return dloc.hash === '' || dloc.hash === '#';
}
var listener = {
mode: 'modern',
hash: dloc.hash,
history: false,
check: function () {
var h = dloc.hash;
if (h != this.hash) {
this.hash = h;
this.onHashChanged();
}
},
fire: function () {
if (this.mode === 'modern') {
this.history === true ? window.onpopstate() : window.onhashchange();
}
else {
this.onHashChanged();
}
},
init: function (fn, history) {
var self = this;
this.history = history;
if (!Router.listeners) {
Router.listeners = [];
}
function onchange(onChangeEvent) {
for (var i = 0, l = Router.listeners.length; i < l; i++) {
Router.listeners[i](onChangeEvent);
}
}
//note IE8 is being counted as 'modern' because it has the hashchange event
if ('onhashchange' in window && (document.documentMode === undefined
|| document.documentMode > 7)) {
// At least for now HTML5 history is available for 'modern' browsers only
if (this.history === true) {
// There is an old bug in Chrome that causes onpopstate to fire even
// upon initial page load. Since the handler is run manually in init(),
// this would cause Chrome to run it twise. Currently the only
// workaround seems to be to set the handler after the initial page load
// http://code.google.com/p/chromium/issues/detail?id=63040
setTimeout(function() {
window.onpopstate = onchange;
}, 500);
}
else {
window.onhashchange = onchange;
}
this.mode = 'modern';
}
else {
//
// IE support, based on a concept by Erik Arvidson ...
//
var frame = document.createElement('iframe');
frame.id = 'state-frame';
frame.style.display = 'none';
document.body.appendChild(frame);
this.writeFrame('');
if ('onpropertychange' in document && 'attachEvent' in document) {
document.attachEvent('onpropertychange', function () {
if (event.propertyName === 'location') {
self.check();
}
});
}
window.setInterval(function () { self.check(); }, 50);
this.onHashChanged = onchange;
this.mode = 'legacy';
}
Router.listeners.push(fn);
return this.mode;
},
destroy: function (fn) {
if (!Router || !Router.listeners) {
return;
}
var listeners = Router.listeners;
for (var i = listeners.length - 1; i >= 0; i--) {
if (listeners[i] === fn) {
listeners.splice(i, 1);
}
}
},
setHash: function (s) {
// Mozilla always adds an entry to the history
if (this.mode === 'legacy') {
this.writeFrame(s);
}
if (this.history === true) {
window.history.pushState({}, document.title, s);
// Fire an onpopstate event manually since pushing does not obviously
// trigger the pop event.
this.fire();
} else {
dloc.hash = (s[0] === '/') ? s : '/' + s;
}
return this;
},
writeFrame: function (s) {
// IE support...
var f = document.getElementById('state-frame');
var d = f.contentDocument || f.contentWindow.document;
d.open();
d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
d.close();
},
syncHash: function () {
// IE support...
var s = this._hash;
if (s != dloc.hash) {
dloc.hash = s;
}
return this;
},
onHashChanged: function () {}
};
var Router = exports.Router = function (routes) {
if (!(this instanceof Router)) return new Router(routes);
this.params = {};
this.routes = {};
this.methods = ['on', 'once', 'after', 'before'];
this.scope = [];
this._methods = {};
this._insert = this.insert;
this.insert = this.insertEx;
this.historySupport = (window.history != null ? window.history.pushState : null) != null
this.configure();
this.mount(routes || {});
};
Router.prototype.init = function (r) {
var self = this;
this.handler = function(onChangeEvent) {
var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, '');
self.dispatch('on', url);
};
listener.init(this.handler, this.history);
if (this.history === false) {
if (dlocHashEmpty() && r) {
dloc.hash = r;
} else if (!dlocHashEmpty()) {
self.dispatch('on', dloc.hash.replace(/^#/, ''));
}
}
else {
var routeTo = dlocHashEmpty() && r ? r : !dlocHashEmpty() ? dloc.hash.replace(/^#/, '') : null;
if (routeTo) {
window.history.replaceState({}, document.title, routeTo);
}
// Router has been initialized, but due to the chrome bug it will not
// yet actually route HTML5 history state changes. Thus, decide if should route.
if (routeTo || this.run_in_init === true) {
this.handler();
}
}
return this;
};
Router.prototype.explode = function () {
var v = this.history === true ? this.getPath() : dloc.hash;
if (v.charAt(1) === '/') { v=v.slice(1) }
return v.slice(1, v.length).split("/");
};
Router.prototype.setRoute = function (i, v, val) {
var url = this.explode();
if (typeof i === 'number' && typeof v === 'string') {
url[i] = v;
}
else if (typeof val === 'string') {
url.splice(i, v, s);
}
else {
url = [i];
}
listener.setHash(url.join('/'));
return url;
};
//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router.prototype.insertEx = function(method, path, route, parent) {
if (method === "once") {
method = "on";
route = function(route) {
var once = false;
return function() {
if (once) return;
once = true;
return route.apply(this, arguments);
};
}(route);
}
return this._insert(method, path, route, parent);
};
Router.prototype.getRoute = function (v) {
var ret = v;
if (typeof v === "number") {
ret = this.explode()[v];
}
else if (typeof v === "string"){
var h = this.explode();
ret = h.indexOf(v);
}
else {
ret = this.explode();
}
return ret;
};
Router.prototype.destroy = function () {
listener.destroy(this.handler);
return this;
};
Router.prototype.getPath = function () {
var path = window.location.pathname;
if (path.substr(0, 1) !== '/') {
path = '/' + path;
}
return path;
};
function _every(arr, iterator) {
for (var i = 0; i < arr.length; i += 1) {
if (iterator(arr[i], i, arr) === false) {
return;
}
}
}
function _flatten(arr) {
var flat = [];
for (var i = 0, n = arr.length; i < n; i++) {
flat = flat.concat(arr[i]);
}
return flat;
}
function _asyncEverySeries(arr, iterator, callback) {
if (!arr.length) {
return callback();
}
var completed = 0;
(function iterate() {
iterator(arr[completed], function(err) {
if (err || err === false) {
callback(err);
callback = function() {};
} else {
completed += 1;
if (completed === arr.length) {
callback();
} else {
iterate();
}
}
});
})();
}
function paramifyString(str, params, mod) {
mod = str;
for (var param in params) {
if (params.hasOwnProperty(param)) {
mod = params[param](str);
if (mod !== str) {
break;
}
}
}
return mod === str ? "([._a-zA-Z0-9-]+)" : mod;
}
function regifyString(str, params) {
var matches, last = 0, out = "";
while (matches = str.substr(last).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/)) {
last = matches.index + matches[0].length;
matches[0] = matches[0].replace(/^\*/, "([_.()!\\ %@&a-zA-Z0-9-]+)");
out += str.substr(0, matches.index) + matches[0];
}
str = out += str.substr(last);
var captures = str.match(/:([^\/]+)/ig), length;
if (captures) {
length = captures.length;
for (var i = 0; i < length; i++) {
str = str.replace(captures[i], paramifyString(captures[i], params));
}
}
return str;
}
function terminator(routes, delimiter, start, stop) {
var last = 0, left = 0, right = 0, start = (start || "(").toString(), stop = (stop || ")").toString(), i;
for (i = 0; i < routes.length; i++) {
var chunk = routes[i];
if (chunk.indexOf(start, last) > chunk.indexOf(stop, last) || ~chunk.indexOf(start, last) && !~chunk.indexOf(stop, last) || !~chunk.indexOf(start, last) && ~chunk.indexOf(stop, last)) {
left = chunk.indexOf(start, last);
right = chunk.indexOf(stop, last);
if (~left && !~right || !~left && ~right) {
var tmp = routes.slice(0, (i || 1) + 1).join(delimiter);
routes = [ tmp ].concat(routes.slice((i || 1) + 1));
}
last = (right > left ? right : left) + 1;
i = 0;
} else {
last = 0;
}
}
return routes;
}
Router.prototype.configure = function(options) {
options = options || {};
for (var i = 0; i < this.methods.length; i++) {
this._methods[this.methods[i]] = true;
}
this.recurse = options.recurse || this.recurse || false;
this.async = options.async || false;
this.delimiter = options.delimiter || "/";
this.strict = typeof options.strict === "undefined" ? true : options.strict;
this.notfound = options.notfound;
this.resource = options.resource;
this.history = options.html5history && this.historySupport || false;
this.run_in_init = this.history === true && options.run_handler_in_init !== false;
this.every = {
after: options.after || null,
before: options.before || null,
on: options.on || null
};
return this;
};
Router.prototype.param = function(token, matcher) {
if (token[0] !== ":") {
token = ":" + token;
}
var compiled = new RegExp(token, "g");
this.params[token] = function(str) {
return str.replace(compiled, matcher.source || matcher);
};
};
Router.prototype.on = Router.prototype.route = function(method, path, route) {
var self = this;
if (!route && typeof path == "function") {
route = path;
path = method;
method = "on";
}
if (Array.isArray(path)) {
return path.forEach(function(p) {
self.on(method, p, route);
});
}
if (path.source) {
path = path.source.replace(/\\\//ig, "/");
}
if (Array.isArray(method)) {
return method.forEach(function(m) {
self.on(m.toLowerCase(), path, route);
});
}
path = path.split(new RegExp(this.delimiter));
path = terminator(path, this.delimiter);
this.insert(method, this.scope.concat(path), route);
};
Router.prototype.dispatch = function(method, path, callback) {
var self = this, fns = this.traverse(method, path, this.routes, ""), invoked = this._invoked, after;
this._invoked = true;
if (!fns || fns.length === 0) {
this.last = [];
if (typeof this.notfound === "function") {
this.invoke([ this.notfound ], {
method: method,
path: path
}, callback);
}
return false;
}
if (this.recurse === "forward") {
fns = fns.reverse();
}
function updateAndInvoke() {
self.last = fns.after;
self.invoke(self.runlist(fns), self, callback);
}
after = this.every && this.every.after ? [ this.every.after ].concat(this.last) : [ this.last ];
if (after && after.length > 0 && invoked) {
if (this.async) {
this.invoke(after, this, updateAndInvoke);
} else {
this.invoke(after, this);
updateAndInvoke();
}
return true;
}
updateAndInvoke();
return true;
};
Router.prototype.invoke = function(fns, thisArg, callback) {
var self = this;
if (this.async) {
_asyncEverySeries(fns, function apply(fn, next) {
if (Array.isArray(fn)) {
return _asyncEverySeries(fn, apply, next);
} else if (typeof fn == "function") {
fn.apply(thisArg, fns.captures.concat(next));
}
}, function() {
if (callback) {
callback.apply(thisArg, arguments);
}
});
} else {
_every(fns, function apply(fn) {
if (Array.isArray(fn)) {
return _every(fn, apply);
} else if (typeof fn === "function") {
return fn.apply(thisArg, fns.captures || []);
} else if (typeof fn === "string" && self.resource) {
self.resource[fn].apply(thisArg, fns.captures || []);
}
});
}
};
Router.prototype.traverse = function(method, path, routes, regexp, filter) {
var fns = [], current, exact, match, next, that;
function filterRoutes(routes) {
if (!filter) {
return routes;
}
function deepCopy(source) {
var result = [];
for (var i = 0; i < source.length; i++) {
result[i] = Array.isArray(source[i]) ? deepCopy(source[i]) : source[i];
}
return result;
}
function applyFilter(fns) {
for (var i = fns.length - 1; i >= 0; i--) {
if (Array.isArray(fns[i])) {
applyFilter(fns[i]);
if (fns[i].length === 0) {
fns.splice(i, 1);
}
} else {
if (!filter(fns[i])) {
fns.splice(i, 1);
}
}
}
}
var newRoutes = deepCopy(routes);
newRoutes.matched = routes.matched;
newRoutes.captures = routes.captures;
newRoutes.after = routes.after.filter(filter);
applyFilter(newRoutes);
return newRoutes;
}
if (path === this.delimiter && routes[method]) {
next = [ [ routes.before, routes[method] ].filter(Boolean) ];
next.after = [ routes.after ].filter(Boolean);
next.matched = true;
next.captures = [];
return filterRoutes(next);
}
for (var r in routes) {
if (routes.hasOwnProperty(r) && (!this._methods[r] || this._methods[r] && typeof routes[r] === "object" && !Array.isArray(routes[r]))) {
current = exact = regexp + this.delimiter + r;
if (!this.strict) {
exact += "[" + this.delimiter + "]?";
}
match = path.match(new RegExp("^" + exact));
if (!match) {
continue;
}
if (match[0] && match[0] == path && routes[r][method]) {
next = [ [ routes[r].before, routes[r][method] ].filter(Boolean) ];
next.after = [ routes[r].after ].filter(Boolean);
next.matched = true;
next.captures = match.slice(1);
if (this.recurse && routes === this.routes) {
next.push([ routes.before, routes.on ].filter(Boolean));
next.after = next.after.concat([ routes.after ].filter(Boolean));
}
return filterRoutes(next);
}
next = this.traverse(method, path, routes[r], current);
if (next.matched) {
if (next.length > 0) {
fns = fns.concat(next);
}
if (this.recurse) {
fns.push([ routes[r].before, routes[r].on ].filter(Boolean));
next.after = next.after.concat([ routes[r].after ].filter(Boolean));
if (routes === this.routes) {
fns.push([ routes["before"], routes["on"] ].filter(Boolean));
next.after = next.after.concat([ routes["after"] ].filter(Boolean));
}
}
fns.matched = true;
fns.captures = next.captures;
fns.after = next.after;
return filterRoutes(fns);
}
}
}
return false;
};
Router.prototype.insert = function(method, path, route, parent) {
var methodType, parentType, isArray, nested, part;
path = path.filter(function(p) {
return p && p.length > 0;
});
parent = parent || this.routes;
part = path.shift();
if (/\:|\*/.test(part) && !/\\d|\\w/.test(part)) {
part = regifyString(part, this.params);
}
if (path.length > 0) {
parent[part] = parent[part] || {};
return this.insert(method, path, route, parent[part]);
}
if (!part && !path.length && parent === this.routes) {
methodType = typeof parent[method];
switch (methodType) {
case "function":
parent[method] = [ parent[method], route ];
return;
case "object":
parent[method].push(route);
return;
case "undefined":
parent[method] = route;
return;
}
return;
}
parentType = typeof parent[part];
isArray = Array.isArray(parent[part]);
if (parent[part] && !isArray && parentType == "object") {
methodType = typeof parent[part][method];
switch (methodType) {
case "function":
parent[part][method] = [ parent[part][method], route ];
return;
case "object":
parent[part][method].push(route);
return;
case "undefined":
parent[part][method] = route;
return;
}
} else if (parentType == "undefined") {
nested = {};
nested[method] = route;
parent[part] = nested;
return;
}
throw new Error("Invalid route context: " + parentType);
};
Router.prototype.extend = function(methods) {
var self = this, len = methods.length, i;
function extend(method) {
self._methods[method] = true;
self[method] = function() {
var extra = arguments.length === 1 ? [ method, "" ] : [ method ];
self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments)));
};
}
for (i = 0; i < len; i++) {
extend(methods[i]);
}
};
Router.prototype.runlist = function(fns) {
var runlist = this.every && this.every.before ? [ this.every.before ].concat(_flatten(fns)) : _flatten(fns);
if (this.every && this.every.on) {
runlist.push(this.every.on);
}
runlist.captures = fns.captures;
runlist.source = fns.source;
return runlist;
};
Router.prototype.mount = function(routes, path) {
if (!routes || typeof routes !== "object" || Array.isArray(routes)) {
return;
}
var self = this;
path = path || [];
if (!Array.isArray(path)) {
path = path.split(self.delimiter);
}
function insertOrMount(route, local) {
var rename = route, parts = route.split(self.delimiter), routeType = typeof routes[route], isRoute = parts[0] === "" || !self._methods[parts[0]], event = isRoute ? "on" : rename;
if (isRoute) {
rename = rename.slice((rename.match(new RegExp(self.delimiter)) || [ "" ])[0].length);
parts.shift();
}
if (isRoute && routeType === "object" && !Array.isArray(routes[route])) {
local = local.concat(parts);
self.mount(routes[route], local);
return;
}
if (isRoute) {
local = local.concat(rename.split(self.delimiter));
local = terminator(local, self.delimiter);
}
self.insert(event, local, routes[route]);
}
for (var route in routes) {
if (routes.hasOwnProperty(route)) {
insertOrMount(route, path.slice(0));
}
}
};
}(typeof exports === "object" ? exports : window));
\ No newline at end of file
//
// Generated on Sun Dec 16 2012 22:47:05 GMT-0500 (EST) by Nodejitsu, Inc (Using Codesurgeon).
// Version 1.1.9
//
(function(a){function k(a,b,c,d){var e=0,f=0,g=0,c=(c||"(").toString(),d=(d||")").toString(),h;for(h=0;h<a.length;h++){var i=a[h];if(i.indexOf(c,e)>i.indexOf(d,e)||~i.indexOf(c,e)&&!~i.indexOf(d,e)||!~i.indexOf(c,e)&&~i.indexOf(d,e)){f=i.indexOf(c,e),g=i.indexOf(d,e);if(~f&&!~g||!~f&&~g){var j=a.slice(0,(h||1)+1).join(b);a=[j].concat(a.slice((h||1)+1))}e=(g>f?g:f)+1,h=0}else e=0}return a}function j(a,b){var c,d=0,e="";while(c=a.substr(d).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/))d=c.index+c[0].length,c[0]=c[0].replace(/^\*/,"([_.()!\\ %@&a-zA-Z0-9-]+)"),e+=a.substr(0,c.index)+c[0];a=e+=a.substr(d);var f=a.match(/:([^\/]+)/ig),g;if(f){g=f.length;for(var h=0;h<g;h++)a=a.replace(f[h],i(f[h],b))}return a}function i(a,b,c){c=a;for(var d in b)if(b.hasOwnProperty(d)){c=b[d](a);if(c!==a)break}return c===a?"([._a-zA-Z0-9-]+)":c}function h(a,b,c){if(!a.length)return c();var d=0;(function e(){b(a[d],function(b){b||b===!1?(c(b),c=function(){}):(d+=1,d===a.length?c():e())})})()}function g(a){var b=[];for(var c=0,d=a.length;c<d;c++)b=b.concat(a[c]);return b}function f(a,b){for(var c=0;c<a.length;c+=1)if(b(a[c],c,a)===!1)return}function c(){return b.hash===""||b.hash==="#"}Array.prototype.filter||(Array.prototype.filter=function(a,b){var c=[],d;for(var e=0,f=this.length;e<f;e++)e in this&&a.call(b,d=this[e],e,this)&&c.push(d);return c}),Array.isArray||(Array.isArray=function(a){return Object.prototype.toString.call(a)==="[object Array]"});var b=document.location,d={mode:"modern",hash:b.hash,history:!1,check:function(){var a=b.hash;a!=this.hash&&(this.hash=a,this.onHashChanged())},fire:function(){this.mode==="modern"?this.history===!0?window.onpopstate():window.onhashchange():this.onHashChanged()},init:function(a,b){function d(a){for(var b=0,c=e.listeners.length;b<c;b++)e.listeners[b](a)}var c=this;this.history=b,e.listeners||(e.listeners=[]);if("onhashchange"in window&&(document.documentMode===undefined||document.documentMode>7))this.history===!0?setTimeout(function(){window.onpopstate=d},500):window.onhashchange=d,this.mode="modern";else{var f=document.createElement("iframe");f.id="state-frame",f.style.display="none",document.body.appendChild(f),this.writeFrame(""),"onpropertychange"in document&&"attachEvent"in document&&document.attachEvent("onpropertychange",function(){event.propertyName==="location"&&c.check()}),window.setInterval(function(){c.check()},50),this.onHashChanged=d,this.mode="legacy"}e.listeners.push(a);return this.mode},destroy:function(a){if(!!e&&!!e.listeners){var b=e.listeners;for(var c=b.length-1;c>=0;c--)b[c]===a&&b.splice(c,1)}},setHash:function(a){this.mode==="legacy"&&this.writeFrame(a),this.history===!0?(window.history.pushState({},document.title,a),this.fire()):b.hash=a[0]==="/"?a:"/"+a;return this},writeFrame:function(a){var b=document.getElementById("state-frame"),c=b.contentDocument||b.contentWindow.document;c.open(),c.write("<script>_hash = '"+a+"'; onload = parent.listener.syncHash;<script>"),c.close()},syncHash:function(){var a=this._hash;a!=b.hash&&(b.hash=a);return this},onHashChanged:function(){}},e=a.Router=function(a){if(this instanceof e)this.params={},this.routes={},this.methods=["on","once","after","before"],this.scope=[],this._methods={},this._insert=this.insert,this.insert=this.insertEx,this.historySupport=(window.history!=null?window.history.pushState:null)!=null,this.configure(),this.mount(a||{});else return new e(a)};e.prototype.init=function(a){var e=this;this.handler=function(a){var b=a&&a.newURL||window.location.hash,c=e.history===!0?e.getPath():b.replace(/.*#/,"");e.dispatch("on",c)},d.init(this.handler,this.history);if(this.history===!1)c()&&a?b.hash=a:c()||e.dispatch("on",b.hash.replace(/^#/,""));else{var f=c()&&a?a:c()?null:b.hash.replace(/^#/,"");f&&window.history.replaceState({},document.title,f),(f||this.run_in_init===!0)&&this.handler()}return this},e.prototype.explode=function(){var a=this.history===!0?this.getPath():b.hash;a.charAt(1)==="/"&&(a=a.slice(1));return a.slice(1,a.length).split("/")},e.prototype.setRoute=function(a,b,c){var e=this.explode();typeof a=="number"&&typeof b=="string"?e[a]=b:typeof c=="string"?e.splice(a,b,s):e=[a],d.setHash(e.join("/"));return e},e.prototype.insertEx=function(a,b,c,d){a==="once"&&(a="on",c=function(a){var b=!1;return function(){if(!b){b=!0;return a.apply(this,arguments)}}}(c));return this._insert(a,b,c,d)},e.prototype.getRoute=function(a){var b=a;if(typeof a=="number")b=this.explode()[a];else if(typeof a=="string"){var c=this.explode();b=c.indexOf(a)}else b=this.explode();return b},e.prototype.destroy=function(){d.destroy(this.handler);return this},e.prototype.getPath=function(){var a=window.location.pathname;a.substr(0,1)!=="/"&&(a="/"+a);return a},e.prototype.configure=function(a){a=a||{};for(var b=0;b<this.methods.length;b++)this._methods[this.methods[b]]=!0;this.recurse=a.recurse||this.recurse||!1,this.async=a.async||!1,this.delimiter=a.delimiter||"/",this.strict=typeof a.strict=="undefined"?!0:a.strict,this.notfound=a.notfound,this.resource=a.resource,this.history=a.html5history&&this.historySupport||!1,this.run_in_init=this.history===!0&&a.run_handler_in_init!==!1,this.every={after:a.after||null,before:a.before||null,on:a.on||null};return this},e.prototype.param=function(a,b){a[0]!==":"&&(a=":"+a);var c=new RegExp(a,"g");this.params[a]=function(a){return a.replace(c,b.source||b)}},e.prototype.on=e.prototype.route=function(a,b,c){var d=this;!c&&typeof b=="function"&&(c=b,b=a,a="on");if(Array.isArray(b))return b.forEach(function(b){d.on(a,b,c)});b.source&&(b=b.source.replace(/\\\//ig,"/"));if(Array.isArray(a))return a.forEach(function(a){d.on(a.toLowerCase(),b,c)});b=b.split(new RegExp(this.delimiter)),b=k(b,this.delimiter),this.insert(a,this.scope.concat(b),c)},e.prototype.dispatch=function(a,b,c){function h(){d.last=e.after,d.invoke(d.runlist(e),d,c)}var d=this,e=this.traverse(a,b,this.routes,""),f=this._invoked,g;this._invoked=!0;if(!e||e.length===0){this.last=[],typeof this.notfound=="function"&&this.invoke([this.notfound],{method:a,path:b},c);return!1}this.recurse==="forward"&&(e=e.reverse()),g=this.every&&this.every.after?[this.every.after].concat(this.last):[this.last];if(g&&g.length>0&&f){this.async?this.invoke(g,this,h):(this.invoke(g,this),h());return!0}h();return!0},e.prototype.invoke=function(a,b,c){var d=this;this.async?h(a,function e(c,d){if(Array.isArray(c))return h(c,e,d);typeof c=="function"&&c.apply(b,a.captures.concat(d))},function(){c&&c.apply(b,arguments)}):f(a,function g(c){if(Array.isArray(c))return f(c,g);if(typeof c=="function")return c.apply(b,a.captures||[]);typeof c=="string"&&d.resource&&d.resource[c].apply(b,a.captures||[])})},e.prototype.traverse=function(a,b,c,d,e){function l(a){function c(a){for(var b=a.length-1;b>=0;b--)Array.isArray(a[b])?(c(a[b]),a[b].length===0&&a.splice(b,1)):e(a[b])||a.splice(b,1)}function b(a){var c=[];for(var d=0;d<a.length;d++)c[d]=Array.isArray(a[d])?b(a[d]):a[d];return c}if(!e)return a;var d=b(a);d.matched=a.matched,d.captures=a.captures,d.after=a.after.filter(e),c(d);return d}var f=[],g,h,i,j,k;if(b===this.delimiter&&c[a]){j=[[c.before,c[a]].filter(Boolean)],j.after=[c.after].filter(Boolean),j.matched=!0,j.captures=[];return l(j)}for(var m in c)if(c.hasOwnProperty(m)&&(!this._methods[m]||this._methods[m]&&typeof c[m]=="object"&&!Array.isArray(c[m]))){g=h=d+this.delimiter+m,this.strict||(h+="["+this.delimiter+"]?"),i=b.match(new RegExp("^"+h));if(!i)continue;if(i[0]&&i[0]==b&&c[m][a]){j=[[c[m].before,c[m][a]].filter(Boolean)],j.after=[c[m].after].filter(Boolean),j.matched=!0,j.captures=i.slice(1),this.recurse&&c===this.routes&&(j.push([c.before,c.on].filter(Boolean)),j.after=j.after.concat([c.after].filter(Boolean)));return l(j)}j=this.traverse(a,b,c[m],g);if(j.matched){j.length>0&&(f=f.concat(j)),this.recurse&&(f.push([c[m].before,c[m].on].filter(Boolean)),j.after=j.after.concat([c[m].after].filter(Boolean)),c===this.routes&&(f.push([c.before,c.on].filter(Boolean)),j.after=j.after.concat([c.after].filter(Boolean)))),f.matched=!0,f.captures=j.captures,f.after=j.after;return l(f)}}return!1},e.prototype.insert=function(a,b,c,d){var e,f,g,h,i;b=b.filter(function(a){return a&&a.length>0}),d=d||this.routes,i=b.shift(),/\:|\*/.test(i)&&!/\\d|\\w/.test(i)&&(i=j(i,this.params));if(b.length>0){d[i]=d[i]||{};return this.insert(a,b,c,d[i])}{if(!!i||!!b.length||d!==this.routes){f=typeof d[i],g=Array.isArray(d[i]);if(d[i]&&!g&&f=="object"){e=typeof d[i][a];switch(e){case"function":d[i][a]=[d[i][a],c];return;case"object":d[i][a].push(c);return;case"undefined":d[i][a]=c;return}}else if(f=="undefined"){h={},h[a]=c,d[i]=h;return}throw new Error("Invalid route context: "+f)}e=typeof d[a];switch(e){case"function":d[a]=[d[a],c];return;case"object":d[a].push(c);return;case"undefined":d[a]=c;return}}},e.prototype.extend=function(a){function e(a){b._methods[a]=!0,b[a]=function(){var c=arguments.length===1?[a,""]:[a];b.on.apply(b,c.concat(Array.prototype.slice.call(arguments)))}}var b=this,c=a.length,d;for(d=0;d<c;d++)e(a[d])},e.prototype.runlist=function(a){var b=this.every&&this.every.before?[this.every.before].concat(g(a)):g(a);this.every&&this.every.on&&b.push(this.every.on),b.captures=a.captures,b.source=a.source;return b},e.prototype.mount=function(a,b){function d(b,d){var e=b,f=b.split(c.delimiter),g=typeof a[b],h=f[0]===""||!c._methods[f[0]],i=h?"on":e;h&&(e=e.slice((e.match(new RegExp(c.delimiter))||[""])[0].length),f.shift());h&&g==="object"&&!Array.isArray(a[b])?(d=d.concat(f),c.mount(a[b],d)):(h&&(d=d.concat(e.split(c.delimiter)),d=k(d,c.delimiter)),c.insert(i,d,a[b]))}if(!!a&&typeof a=="object"&&!Array.isArray(a)){var c=this;b=b||[],Array.isArray(b)||(b=b.split(c.delimiter));for(var e in a)a.hasOwnProperty(e)&&d(e,b.slice(0))}}})(typeof exports=="object"?exports:window)
\ No newline at end of file
var http = require('http'),
director = require('../lib/director');
var router = new director.http.Router();
var server = http.createServer(function (req, res) {
req.chunks = [];
req.on('data', function (chunk) {
req.chunks.push(chunk.toString());
});
router.dispatch(req, res, function (err) {
if (err) {
res.writeHead(404);
res.end();
}
console.log('Served ' + req.url);
});
});
router.get(/foo/, function () {
this.res.writeHead(200, { 'Content-Type': 'text/plain' });
this.res.end('hello world\n');
});
router.post(/foo/, function () {
this.res.writeHead(200, { 'Content-Type': 'application/json' });
this.res.end(JSON.stringify(this.req.body));
});
server.listen(8080);
console.log('vanilla http server with director running on 8080');
exports.Router = require('./director/router').Router;
exports.http = require('./director/http');
exports.cli = require('./director/cli');
/*
* browser.js: Browser specific functionality for director.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
if (!Array.prototype.filter) {
Array.prototype.filter = function(filter, that) {
var other = [], v;
for (var i = 0, n = this.length; i < n; i++) {
if (i in this && filter.call(that, v = this[i], i, this)) {
other.push(v);
}
}
return other;
};
}
if (!Array.isArray){
Array.isArray = function(obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
}
var dloc = document.location;
function dlocHashEmpty() {
// Non-IE browsers return '' when the address bar shows '#'; Director's logic
// assumes both mean empty.
return dloc.hash === '' || dloc.hash === '#';
}
var listener = {
mode: 'modern',
hash: dloc.hash,
history: false,
check: function () {
var h = dloc.hash;
if (h != this.hash) {
this.hash = h;
this.onHashChanged();
}
},
fire: function () {
if (this.mode === 'modern') {
this.history === true ? window.onpopstate() : window.onhashchange();
}
else {
this.onHashChanged();
}
},
init: function (fn, history) {
var self = this;
this.history = history;
if (!Router.listeners) {
Router.listeners = [];
}
function onchange(onChangeEvent) {
for (var i = 0, l = Router.listeners.length; i < l; i++) {
Router.listeners[i](onChangeEvent);
}
}
//note IE8 is being counted as 'modern' because it has the hashchange event
if ('onhashchange' in window && (document.documentMode === undefined
|| document.documentMode > 7)) {
// At least for now HTML5 history is available for 'modern' browsers only
if (this.history === true) {
// There is an old bug in Chrome that causes onpopstate to fire even
// upon initial page load. Since the handler is run manually in init(),
// this would cause Chrome to run it twise. Currently the only
// workaround seems to be to set the handler after the initial page load
// http://code.google.com/p/chromium/issues/detail?id=63040
setTimeout(function() {
window.onpopstate = onchange;
}, 500);
}
else {
window.onhashchange = onchange;
}
this.mode = 'modern';
}
else {
//
// IE support, based on a concept by Erik Arvidson ...
//
var frame = document.createElement('iframe');
frame.id = 'state-frame';
frame.style.display = 'none';
document.body.appendChild(frame);
this.writeFrame('');
if ('onpropertychange' in document && 'attachEvent' in document) {
document.attachEvent('onpropertychange', function () {
if (event.propertyName === 'location') {
self.check();
}
});
}
window.setInterval(function () { self.check(); }, 50);
this.onHashChanged = onchange;
this.mode = 'legacy';
}
Router.listeners.push(fn);
return this.mode;
},
destroy: function (fn) {
if (!Router || !Router.listeners) {
return;
}
var listeners = Router.listeners;
for (var i = listeners.length - 1; i >= 0; i--) {
if (listeners[i] === fn) {
listeners.splice(i, 1);
}
}
},
setHash: function (s) {
// Mozilla always adds an entry to the history
if (this.mode === 'legacy') {
this.writeFrame(s);
}
if (this.history === true) {
window.history.pushState({}, document.title, s);
// Fire an onpopstate event manually since pushing does not obviously
// trigger the pop event.
this.fire();
} else {
dloc.hash = (s[0] === '/') ? s : '/' + s;
}
return this;
},
writeFrame: function (s) {
// IE support...
var f = document.getElementById('state-frame');
var d = f.contentDocument || f.contentWindow.document;
d.open();
d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>");
d.close();
},
syncHash: function () {
// IE support...
var s = this._hash;
if (s != dloc.hash) {
dloc.hash = s;
}
return this;
},
onHashChanged: function () {}
};
var Router = exports.Router = function (routes) {
if (!(this instanceof Router)) return new Router(routes);
this.params = {};
this.routes = {};
this.methods = ['on', 'once', 'after', 'before'];
this.scope = [];
this._methods = {};
this._insert = this.insert;
this.insert = this.insertEx;
this.historySupport = (window.history != null ? window.history.pushState : null) != null
this.configure();
this.mount(routes || {});
};
Router.prototype.init = function (r) {
var self = this;
this.handler = function(onChangeEvent) {
var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash;
var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, '');
self.dispatch('on', url);
};
listener.init(this.handler, this.history);
if (this.history === false) {
if (dlocHashEmpty() && r) {
dloc.hash = r;
} else if (!dlocHashEmpty()) {
self.dispatch('on', dloc.hash.replace(/^#/, ''));
}
}
else {
var routeTo = dlocHashEmpty() && r ? r : !dlocHashEmpty() ? dloc.hash.replace(/^#/, '') : null;
if (routeTo) {
window.history.replaceState({}, document.title, routeTo);
}
// Router has been initialized, but due to the chrome bug it will not
// yet actually route HTML5 history state changes. Thus, decide if should route.
if (routeTo || this.run_in_init === true) {
this.handler();
}
}
return this;
};
Router.prototype.explode = function () {
var v = this.history === true ? this.getPath() : dloc.hash;
if (v.charAt(1) === '/') { v=v.slice(1) }
return v.slice(1, v.length).split("/");
};
Router.prototype.setRoute = function (i, v, val) {
var url = this.explode();
if (typeof i === 'number' && typeof v === 'string') {
url[i] = v;
}
else if (typeof val === 'string') {
url.splice(i, v, s);
}
else {
url = [i];
}
listener.setHash(url.join('/'));
return url;
};
//
// ### function insertEx(method, path, route, parent)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// insert a callback that will only occur once per the matched route.
//
Router.prototype.insertEx = function(method, path, route, parent) {
if (method === "once") {
method = "on";
route = function(route) {
var once = false;
return function() {
if (once) return;
once = true;
return route.apply(this, arguments);
};
}(route);
}
return this._insert(method, path, route, parent);
};
Router.prototype.getRoute = function (v) {
var ret = v;
if (typeof v === "number") {
ret = this.explode()[v];
}
else if (typeof v === "string"){
var h = this.explode();
ret = h.indexOf(v);
}
else {
ret = this.explode();
}
return ret;
};
Router.prototype.destroy = function () {
listener.destroy(this.handler);
return this;
};
Router.prototype.getPath = function () {
var path = window.location.pathname;
if (path.substr(0, 1) !== '/') {
path = '/' + path;
}
return path;
};
var util = require('util'),
director = require('../director');
var Router = exports.Router = function (routes) {
director.Router.call(this, routes);
this.recurse = false;
};
//
// Inherit from `director.Router`.
//
util.inherits(Router, director.Router);
//
// ### function configure (options)
// #### @options {Object} **Optional** Options to configure this instance with
// Configures this instance with the specified `options`.
//
Router.prototype.configure = function (options) {
options = options || {};
director.Router.prototype.configure.call(this, options);
//
// Delimiter must always be `\s` in CLI routing.
// e.g. `jitsu users create`
//
this.delimiter = '\\s';
return this;
};
//
// ### function dispatch (method, path)
// #### @method {string} Method to dispatch
// #### @path {string} Path to dispatch
// Finds a set of functions on the traversal towards
// `method` and `path` in the core routing table then
// invokes them based on settings in this instance.
//
Router.prototype.dispatch = function (method, path, tty, callback) {
//
// Prepend a single space onto the path so that the traversal
// algorithm will recognize it. This is because we always assume
// that the `path` begins with `this.delimiter`.
//
path = ' ' + path;
var fns = this.traverse(method, path, this.routes, '');
if (!fns || fns.length === 0) {
if (typeof this.notfound === 'function') {
this.notfound.call({ tty: tty, cmd: path }, callback);
}
else if (callback) {
callback(new Error('Could not find path: ' + path));
}
return false;
}
if (this.recurse === 'forward') {
fns = fns.reverse();
}
this.invoke(this.runlist(fns), { tty: tty, cmd: path }, callback);
return true;
};
var events = require('events'),
qs = require('querystring'),
util = require('util'),
director = require('../../director'),
responses = require('./responses');
//
// ### Expose all HTTP methods and responses
//
exports.methods = require('./methods');
Object.keys(responses).forEach(function (name) {
exports[name] = responses[name];
});
//
// ### function Router (routes)
// #### @routes {Object} **Optional** Routing table for this instance.
// Constuctor function for the HTTP Router object responsible for building
// and dispatching from a given routing table.
//
var Router = exports.Router = function (routes) {
//
// ### Extend the `Router` prototype with all of the RFC methods.
//
this.params = {};
this.routes = {};
this.methods = ['on', 'after', 'before'];
this.scope = [];
this._methods = {};
this.recurse = 'forward';
this._attach = [];
this.extend(exports.methods.concat(['before', 'after']));
this.configure();
this.mount(routes || {});
};
//
// Inherit from `director.Router`.
//
util.inherits(Router, director.Router);
//
// ### function configure (options)
// #### @options {Object} **Optional** Options to configure this instance with
// Configures this instance with the specified `options`.
//
Router.prototype.configure = function (options) {
options = options || {};
// useful when using connect's bodyParser
this.stream = options.stream || false;
return director.Router.prototype.configure.call(this, options);
};
//
// ### function on (method, path, route)
// #### @method {string} **Optional** Method to use
// #### @path {string} Path to set this route on.
// #### @route {Array|function} Handler for the specified method and path.
// Adds a new `route` to this instance for the specified `method`
// and `path`.
//
Router.prototype.on = function (method, path) {
var args = Array.prototype.slice.call(arguments, 2),
route = args.pop(),
options = args.pop(),
accept;
if (options) {
if (options.stream) {
route.stream = true;
}
if (options.accept) {
this._hasAccepts = true;
accept = options.accept;
route.accept = (Array.isArray(accept) ? accept : [accept]).map(function (a) {
return typeof a === 'string' ? RegExp(a) : a;
});
}
}
if (typeof path !== 'string' && !path.source) {
path = '';
}
director.Router.prototype.on.call(this, method, path, route);
};
//
// ### function attach (func)
// ### @func {function} Function to execute on `this` before applying to router function
// Ask the router to attach objects or manipulate `this` object on which the
// function passed to the http router will get applied
Router.prototype.attach = function (func) {
this._attach.push(func);
};
//
// ### function dispatch (method, path)
// #### @req {http.ServerRequest} Incoming request to dispatch.
// #### @res {http.ServerResponse} Outgoing response to dispatch.
// #### @callback {function} **Optional** Continuation to respond to for async scenarios.
// Finds a set of functions on the traversal towards
// `method` and `path` in the core routing table then
// invokes them based on settings in this instance.
//
Router.prototype.dispatch = function (req, res, callback) {
//
// Dispatch `HEAD` requests to `GET`
//
var method = req.method === 'HEAD' ? 'get' : req.method.toLowerCase(),
thisArg = { req: req, res: res },
self = this,
contentType,
runlist,
stream,
error,
fns,
url;
//
// Trap bad URLs from `decodeUri`
//
try { url = decodeURI(req.url.split('?', 1)[0]) }
catch (ex) { url = null }
if (url && this._hasAccepts) {
contentType = req.headers['content-type'];
fns = this.traverse(method, url, this.routes, '', function (route) {
return !route.accept || route.accept.some(function (a) {
return a.test(contentType);
});
});
}
else if (url) {
fns = this.traverse(method, url, this.routes, '');
}
if (this._attach) {
for (var i in this._attach) {
this._attach[i].call(thisArg);
}
}
if (!fns || fns.length === 0) {
error = new exports.NotFound('Could not find path: ' + req.url);
if (typeof this.notfound === 'function') {
this.notfound.call(thisArg, callback);
}
else if (callback) {
callback.call(thisArg, error, req, res);
}
return false;
}
if (this.recurse === 'forward') {
fns = fns.reverse();
}
runlist = this.runlist(fns);
stream = this.stream || runlist.some(function (fn) { return fn.stream === true; });
function parseAndInvoke() {
error = self.parse(req);
if (error) {
if (callback) {
callback.call(thisArg, error, req, res);
}
return false;
}
self.invoke(runlist, thisArg, callback);
}
if (!stream) {
//
// If there is no streaming required on any of the functions on the
// way to `path`, then attempt to parse the fully buffered request stream
// once it has emitted the `end` event.
//
if (req.readable) {
//
// If the `http.ServerRequest` is still readable, then await
// the end event and then continue
//
req.once('end', parseAndInvoke);
// Streams2 requires us to start the stream if we're not explicitly
// reading from it.
req.resume();
}
else {
//
// Otherwise, just parse the body now.
//
parseAndInvoke();
}
}
else {
this.invoke(runlist, thisArg, callback);
}
return true;
};
//
// ### @parsers {Object}
// Lookup table of parsers to use when attempting to
// parse incoming responses.
//
Router.prototype.parsers = {
'application/x-www-form-urlencoded': qs.parse,
'application/json': JSON.parse
};
//
// ### function parse (req)
// #### @req {http.ServerResponse|BufferedStream} Incoming HTTP request to parse
// Attempts to parse `req.body` using the value found at `req.headers['content-type']`.
//
Router.prototype.parse = function (req) {
function mime(req) {
var str = req.headers['content-type'] || '';
return str.split(';')[0];
}
var parser = this.parsers[mime(req)],
body;
if (parser) {
req.body = req.body || '';
if (req.chunks) {
req.chunks.forEach(function (chunk) {
req.body += chunk;
});
}
try {
req.body = req.body && req.body.length
? parser(req.body)
: {};
}
catch (err) {
return new exports.BadRequest('Malformed data');
}
}
};
/*!
* Express - router - methods
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
* MIT Licensed
*
* Adapted for SS
* (C) 2011 Nodejitsu Inc. <info@nodejitsu.com>
*
*/
/**
* Hypertext Transfer Protocol -- HTTP/1.1
* http://www.ietf.org/rfc/rfc2616.txt
*/
var RFC2616 = ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'];
/**
* HTTP Extensions for Distributed Authoring -- WEBDAV
* http://www.ietf.org/rfc/rfc2518.txt
*/
var RFC2518 = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
/**
* Versioning Extensions to WebDAV
* http://www.ietf.org/rfc/rfc3253.txt
*/
var RFC3253 = ['VERSION-CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE-CONTROL', 'MKACTIVITY'];
/**
* Ordered Collections Protocol (WebDAV)
* http://www.ietf.org/rfc/rfc3648.txt
*/
var RFC3648 = ['ORDERPATCH'];
/**
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol
* http://www.ietf.org/rfc/rfc3744.txt
*/
var RFC3744 = ['ACL'];
/**
* Web Distributed Authoring and Versioning (WebDAV) SEARCH
* http://www.ietf.org/rfc/rfc5323.txt
*/
var RFC5323 = ['SEARCH'];
/**
* PATCH Method for HTTP
* http://www.ietf.org/rfc/rfc5789.txt
*/
var RFC5789 = ['PATCH'];
/**
* Expose the methods.
*/
module.exports = [].concat(
RFC2616,
RFC2518,
RFC3253,
RFC3648,
RFC3744,
RFC5323,
RFC5789
).map(function (method) {
return method.toLowerCase();
});
\ No newline at end of file
//
// HTTP Error objectst
//
var util = require('util');
exports.NotModified = function () {
this.status = 304;
this.options = {
removeContentHeaders: true
};
};
util.inherits(exports.NotModified, Error);
exports.BadRequest = function (msg) {
msg = msg || 'Bad request';
this.status = 400;
this.headers = {};
this.message = msg;
this.body = { error: msg };
};
util.inherits(exports.BadRequest, Error);
exports.NotAuthorized = function (msg) {
msg = msg || 'Not Authorized';
this.status = 401;
this.headers = {};
this.message = msg;
this.body = { error: msg };
};
util.inherits(exports.NotAuthorized, Error);
exports.Forbidden = function (msg) {
msg = msg || 'Not Authorized';
this.status = 403;
this.headers = {};
this.message = msg;
this.body = { error: msg };
};
util.inherits(exports.Forbidden, Error);
exports.NotFound = function (msg) {
msg = msg || 'Not Found';
this.status = 404;
this.headers = {};
this.message = msg;
this.body = { error: msg };
};
util.inherits(exports.NotFound, Error);
exports.MethodNotAllowed = function (allowed) {
var msg = 'method not allowed.';
this.status = 405;
this.headers = { allow: allowed };
this.message = msg;
this.body = { error: msg };
};
util.inherits(exports.MethodNotAllowed, Error);
exports.NotAcceptable = function (accept) {
var msg = 'cannot generate "' + accept + '" response';
this.status = 406;
this.headers = {};
this.message = msg;
this.body = {
error: msg,
only: 'application/json'
};
};
util.inherits(exports.NotAcceptable, Error);
exports.NotImplemented = function (msg) {
msg = msg || 'Not Implemented';
this.status = 501;
this.headers = {};
this.message = msg;
this.body = { error: msg };
};
util.inherits(exports.NotImplemented, Error);
/*
* router.js: Base functionality for the router.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
//
// Helper function to turn flatten an array.
//
function _flatten (arr) {
var flat = [];
for (var i = 0, n = arr.length; i < n; i++) {
flat = flat.concat(arr[i]);
}
return flat;
}
//
// Helper function for wrapping Array.every
// in the browser.
//
function _every (arr, iterator) {
for (var i = 0; i < arr.length; i += 1) {
if (iterator(arr[i], i, arr) === false) {
return;
}
}
}
//
// Helper function for performing an asynchronous every
// in series in the browser and the server.
//
function _asyncEverySeries (arr, iterator, callback) {
if (!arr.length) {
return callback();
}
var completed = 0;
(function iterate() {
iterator(arr[completed], function (err) {
if (err || err === false) {
callback(err);
callback = function () {};
}
else {
completed += 1;
if (completed === arr.length) {
callback();
}
else {
iterate();
}
}
});
})();
}
//
// Helper function for expanding "named" matches
// (e.g. `:dog`, etc.) against the given set
// of params:
//
// {
// ':dog': function (str) {
// return str.replace(/:dog/, 'TARGET');
// }
// ...
// }
//
function paramifyString(str, params, mod) {
mod = str;
for (var param in params) {
if (params.hasOwnProperty(param)) {
mod = params[param](str);
if (mod !== str) { break; }
}
}
return mod === str
? '([._a-zA-Z0-9-]+)'
: mod;
}
//
// Helper function for expanding wildcards (*) and
// "named" matches (:whatever)
//
function regifyString(str, params) {
var matches,
last = 0,
out = '';
while (matches = str.substr(last).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/)) {
last = matches.index + matches[0].length;
matches[0] = matches[0].replace(/^\*/, '([_\.\(\)!\\ %@&a-zA-Z0-9-]+)');
out += str.substr(0, matches.index) + matches[0];
}
str = out += str.substr(last);
var captures = str.match(/:([^\/]+)/ig),
length;
if (captures) {
length = captures.length;
for (var i = 0; i < length; i++) {
str = str.replace(captures[i], paramifyString(captures[i], params));
}
}
return str;
}
//
// ### Fix unterminated RegExp groups in routes.
//
function terminator(routes, delimiter, start, stop) {
var last = 0,
left = 0,
right = 0,
start = (start || '(').toString(),
stop = (stop || ')').toString(),
i;
for (i = 0; i < routes.length; i++) {
var chunk = routes[i];
if ((chunk.indexOf(start, last) > chunk.indexOf(stop, last)) ||
(~chunk.indexOf(start, last) && !~chunk.indexOf(stop, last)) ||
(!~chunk.indexOf(start, last) && ~chunk.indexOf(stop, last))) {
left = chunk.indexOf(start, last);
right = chunk.indexOf(stop, last);
if ((~left && !~right) || (!~left && ~right)) {
var tmp = routes.slice(0, (i || 1) + 1).join(delimiter);
routes = [tmp].concat(routes.slice((i || 1) + 1));
}
last = (right > left ? right : left) + 1;
i = 0;
}
else {
last = 0;
}
}
return routes;
}
//
// ### function Router (routes)
// #### @routes {Object} **Optional** Routing table for this instance.
// Constuctor function for the Router object responsible for building
// and dispatching from a given routing table.
//
var Router = exports.Router = function (routes) {
this.params = {};
this.routes = {};
this.methods = ['on', 'after', 'before'];
this.scope = [];
this._methods = {};
this.configure();
this.mount(routes || {});
};
//
// ### function configure (options)
// #### @options {Object} **Optional** Options to configure this instance with
// Configures this instance with the specified `options`.
//
Router.prototype.configure = function (options) {
options = options || {};
for (var i = 0; i < this.methods.length; i++) {
this._methods[this.methods[i]] = true;
}
this.recurse = options.recurse || this.recurse || false;
this.async = options.async || false;
this.delimiter = options.delimiter || '\/';
this.strict = typeof options.strict === 'undefined' ? true : options.strict;
this.notfound = options.notfound;
this.resource = options.resource;
// Client only, but browser.js does not include a super implementation
this.history = (options.html5history && this.historySupport) || false;
this.run_in_init = (this.history === true && options.run_handler_in_init !== false);
//
// TODO: Global once
//
this.every = {
after: options.after || null,
before: options.before || null,
on: options.on || null
};
return this;
};
//
// ### function param (token, regex)
// #### @token {string} Token which to replace (e.g. `:dog`, 'cat')
// #### @matcher {string|RegExp} Target to replace the token with.
// Setups up a `params` function which replaces any instance of `token`,
// inside of a given `str` with `matcher`. This is very useful if you
// have a common regular expression throughout your code base which
// you wish to be more DRY.
//
Router.prototype.param = function (token, matcher) {
if (token[0] !== ':') {
token = ':' + token;
}
var compiled = new RegExp(token, 'g');
this.params[token] = function (str) {
return str.replace(compiled, matcher.source || matcher);
};
};
//
// ### function on (method, path, route)
// #### @method {string} **Optional** Method to use
// #### @path {Array|string} Path to set this route on.
// #### @route {Array|function} Handler for the specified method and path.
// Adds a new `route` to this instance for the specified `method`
// and `path`.
//
Router.prototype.on = Router.prototype.route = function (method, path, route) {
var self = this;
if (!route && typeof path == 'function') {
//
// If only two arguments are supplied then assume this
// `route` was meant to be a generic `on`.
//
route = path;
path = method;
method = 'on';
}
if (Array.isArray(path)) {
return path.forEach(function(p) {
self.on(method, p, route);
});
}
if (path.source) {
path = path.source.replace(/\\\//ig, '/');
}
if (Array.isArray(method)) {
return method.forEach(function (m) {
self.on(m.toLowerCase(), path, route);
});
}
//
// ### Split the route up by the delimiter.
//
path = path.split(new RegExp(this.delimiter));
//
// ### Fix unterminated groups. Fixes #59
//
path = terminator(path, this.delimiter);
this.insert(method, this.scope.concat(path), route);
};
//
// ### function path (path, routesFn)
// #### @path {string|RegExp} Nested scope in which to path
// #### @routesFn {function} Function to evaluate in the new scope
// Evalutes the `routesFn` in the given path scope.
//
Router.prototype.path = function (path, routesFn) {
var self = this,
length = this.scope.length;
if (path.source) {
path = path.source.replace(/\\\//ig, '/');
}
//
// ### Split the route up by the delimiter.
//
path = path.split(new RegExp(this.delimiter));
//
// ### Fix unterminated groups.
//
path = terminator(path, this.delimiter);
this.scope = this.scope.concat(path);
routesFn.call(this, this);
this.scope.splice(length, path.length);
};
//
// ### function dispatch (method, path[, callback])
// #### @method {string} Method to dispatch
// #### @path {string} Path to dispatch
// #### @callback {function} **Optional** Continuation to respond to for async scenarios.
// Finds a set of functions on the traversal towards
// `method` and `path` in the core routing table then
// invokes them based on settings in this instance.
//
Router.prototype.dispatch = function (method, path, callback) {
var self = this,
fns = this.traverse(method, path, this.routes, ''),
invoked = this._invoked,
after;
this._invoked = true;
if (!fns || fns.length === 0) {
this.last = [];
if (typeof this.notfound === 'function') {
this.invoke([this.notfound], { method: method, path: path }, callback);
}
return false;
}
if (this.recurse === 'forward') {
fns = fns.reverse();
}
function updateAndInvoke() {
self.last = fns.after;
self.invoke(self.runlist(fns), self, callback);
}
//
// Builds the list of functions to invoke from this call
// to dispatch conforming to the following order:
//
// 1. Global after (if any)
// 2. After functions from the last call to dispatch
// 3. Global before (if any)
// 4. Global on (if any)
// 5. Matched functions from routing table (`['before', 'on'], ['before', 'on`], ...]`)
//
after = this.every && this.every.after
? [this.every.after].concat(this.last)
: [this.last];
if (after && after.length > 0 && invoked) {
if (this.async) {
this.invoke(after, this, updateAndInvoke);
}
else {
this.invoke(after, this);
updateAndInvoke();
}
return true;
}
updateAndInvoke();
return true;
};
//
// ### function runlist (fns)
// #### @fns {Array} List of functions to include in the runlist
// Builds the list of functions to invoke from this call
// to dispatch conforming to the following order:
//
// 1. Global before (if any)
// 2. Global on (if any)
// 3. Matched functions from routing table (`['before', 'on'], ['before', 'on`], ...]`)
//
Router.prototype.runlist = function (fns) {
var runlist = this.every && this.every.before
? [this.every.before].concat(_flatten(fns))
: _flatten(fns);
if (this.every && this.every.on) {
runlist.push(this.every.on);
}
runlist.captures = fns.captures;
runlist.source = fns.source;
return runlist;
};
//
// ### function invoke (fns, thisArg)
// #### @fns {Array} Set of functions to invoke in order.
// #### @thisArg {Object} `thisArg` for each function.
// #### @callback {function} **Optional** Continuation to pass control to for async `fns`.
// Invokes the `fns` synchronously or asynchronously depending on the
// value of `this.async`. Each function must **not** return (or respond)
// with false, or evaluation will short circuit.
//
Router.prototype.invoke = function (fns, thisArg, callback) {
var self = this;
if (this.async) {
_asyncEverySeries(fns, function apply(fn, next) {
if (Array.isArray(fn)) {
return _asyncEverySeries(fn, apply, next);
}
else if (typeof fn == 'function') {
fn.apply(thisArg, fns.captures.concat(next));
}
}, function () {
//
// Ignore the response here. Let the routed take care
// of themselves and eagerly return true.
//
if (callback) {
callback.apply(thisArg, arguments);
}
});
}
else {
_every(fns, function apply(fn) {
if (Array.isArray(fn)) {
return _every(fn, apply);
}
else if (typeof fn === 'function') {
return fn.apply(thisArg, fns.captures || []);
}
else if (typeof fn === 'string' && self.resource) {
self.resource[fn].apply(thisArg, fns.captures || []);
}
});
}
};
//
// ### function traverse (method, path, routes, regexp)
// #### @method {string} Method to find in the `routes` table.
// #### @path {string} Path to find in the `routes` table.
// #### @routes {Object} Partial routing table to match against
// #### @regexp {string} Partial regexp representing the path to `routes`.
// #### @filter {function} Filter function for filtering routes (expensive).
// Core routing logic for `director.Router`: traverses the
// specified `path` within `this.routes` looking for `method`
// returning any `fns` that are found.
//
Router.prototype.traverse = function (method, path, routes, regexp, filter) {
var fns = [],
current,
exact,
match,
next,
that;
function filterRoutes(routes) {
if (!filter) {
return routes;
}
function deepCopy(source) {
var result = [];
for (var i = 0; i < source.length; i++) {
result[i] = Array.isArray(source[i]) ? deepCopy(source[i]) : source[i];
}
return result;
}
function applyFilter(fns) {
for (var i = fns.length - 1; i >= 0; i--) {
if (Array.isArray(fns[i])) {
applyFilter(fns[i]);
if (fns[i].length === 0) {
fns.splice(i, 1);
}
}
else {
if (!filter(fns[i])) {
fns.splice(i, 1);
}
}
}
}
var newRoutes = deepCopy(routes);
newRoutes.matched = routes.matched;
newRoutes.captures = routes.captures;
newRoutes.after = routes.after.filter(filter);
applyFilter(newRoutes);
return newRoutes;
}
//
// Base Case #1:
// If we are dispatching from the root
// then only check if the method exists.
//
if (path === this.delimiter && routes[method]) {
next = [[routes.before, routes[method]].filter(Boolean)];
next.after = [routes.after].filter(Boolean);
next.matched = true;
next.captures = [];
return filterRoutes(next);
}
for (var r in routes) {
//
// We dont have an exact match, lets explore the tree
// in a depth-first, recursive, in-order manner where
// order is defined as:
//
// ['before', 'on', '<method>', 'after']
//
// Remember to ignore keys (i.e. values of `r`) which
// are actual methods (e.g. `on`, `before`, etc), but
// which are not actual nested route (i.e. JSON literals).
//
if (routes.hasOwnProperty(r) && (!this._methods[r] ||
this._methods[r] && typeof routes[r] === 'object' && !Array.isArray(routes[r]))) {
//
// Attempt to make an exact match for the current route
// which is built from the `regexp` that has been built
// through recursive iteration.
//
current = exact = regexp + this.delimiter + r;
if (!this.strict) {
exact += '[' + this.delimiter + ']?';
}
match = path.match(new RegExp('^' + exact));
if (!match) {
//
// If there isn't a `match` then continue. Here, the
// `match` is a partial match. e.g.
//
// '/foo/bar/buzz'.match(/^\/foo/) // ['/foo']
// '/no-match/route'.match(/^\/foo/) // null
//
continue;
}
if (match[0] && match[0] == path && routes[r][method]) {
//
// ### Base case 2:
// If we had a `match` and the capture is the path itself,
// then we have completed our recursion.
//
next = [[routes[r].before, routes[r][method]].filter(Boolean)];
next.after = [routes[r].after].filter(Boolean);
next.matched = true;
next.captures = match.slice(1);
if (this.recurse && routes === this.routes) {
next.push([routes.before, routes.on].filter(Boolean));
next.after = next.after.concat([routes.after].filter(Boolean));
}
return filterRoutes(next);
}
//
// ### Recursive case:
// If we had a match, but it is not yet an exact match then
// attempt to continue matching against the next portion of the
// routing table.
//
next = this.traverse(method, path, routes[r], current);
//
// `next.matched` will be true if the depth-first search of the routing
// table from this position was successful.
//
if (next.matched) {
//
// Build the in-place tree structure representing the function
// in the correct order.
//
if (next.length > 0) {
fns = fns.concat(next);
}
if (this.recurse) {
fns.push([routes[r].before, routes[r].on].filter(Boolean));
next.after = next.after.concat([routes[r].after].filter(Boolean));
if (routes === this.routes) {
fns.push([routes['before'], routes['on']].filter(Boolean));
next.after = next.after.concat([routes['after']].filter(Boolean));
}
}
fns.matched = true;
fns.captures = next.captures;
fns.after = next.after;
//
// ### Base case 2:
// Continue passing the partial tree structure back up the stack.
// The caller for `dispatch()` will decide what to do with the functions.
//
return filterRoutes(fns);
}
}
}
return false;
};
//
// ### function insert (method, path, route, context)
// #### @method {string} Method to insert the specific `route`.
// #### @path {Array} Parsed path to insert the `route` at.
// #### @route {Array|function} Route handlers to insert.
// #### @parent {Object} **Optional** Parent "routes" to insert into.
// Inserts the `route` for the `method` into the routing table for
// this instance at the specified `path` within the `context` provided.
// If no context is provided then `this.routes` will be used.
//
Router.prototype.insert = function (method, path, route, parent) {
var methodType,
parentType,
isArray,
nested,
part;
path = path.filter(function (p) {
return p && p.length > 0;
});
parent = parent || this.routes;
part = path.shift();
if (/\:|\*/.test(part) && !/\\d|\\w/.test(part)) {
part = regifyString(part, this.params);
}
if (path.length > 0) {
//
// If this is not the last part left in the `path`
// (e.g. `['cities', 'new-york']`) then recurse into that
// child
//
parent[part] = parent[part] || {};
return this.insert(method, path, route, parent[part]);
}
//
// If there is no part and the path has been exhausted
// and the parent is the root of the routing table,
// then we are inserting into the root and should
// only dive one level deep in the Routing Table.
//
if (!part && !path.length && parent === this.routes) {
methodType = typeof parent[method];
switch (methodType) {
case 'function':
parent[method] = [parent[method], route];
return;
case 'object':
parent[method].push(route);
return;
case 'undefined':
parent[method] = route;
return;
}
return;
}
//
// Otherwise, we are at the end of our insertion so we should
// insert the `route` based on the `method` after getting the
// `parent` of the last `part`.
//
parentType = typeof parent[part];
isArray = Array.isArray(parent[part]);
if (parent[part] && !isArray && parentType == 'object') {
methodType = typeof parent[part][method];
switch (methodType) {
case 'function':
parent[part][method] = [parent[part][method], route];
return;
case 'object':
parent[part][method].push(route);
return;
case 'undefined':
parent[part][method] = route;
return;
}
}
else if (parentType == 'undefined') {
nested = {};
nested[method] = route;
parent[part] = nested;
return;
}
throw new Error('Invalid route context: ' + parentType);
};
//
// ### function extend (methods)
// #### @methods {Array} List of method names to extend this instance with
// Extends this instance with simple helper methods to `this.on`
// for each of the specified `methods`
//
Router.prototype.extend = function(methods) {
var self = this,
len = methods.length,
i;
function extend(method) {
self._methods[method] = true;
self[method] = function () {
var extra = arguments.length === 1
? [method, '']
: [method];
self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments)));
};
}
for (i = 0; i < len; i++) {
extend(methods[i]);
}
};
//
// ### function mount (routes, context)
// #### @routes {Object} Routes to mount onto this instance
// Mounts the sanitized `routes` onto the root context for this instance.
//
// e.g.
//
// new Router().mount({ '/foo': { '/bar': function foobar() {} } })
//
// yields
//
// { 'foo': 'bar': function foobar() {} } }
//
Router.prototype.mount = function(routes, path) {
if (!routes || typeof routes !== "object" || Array.isArray(routes)) {
return;
}
var self = this;
path = path || [];
if (!Array.isArray(path)) {
path = path.split(self.delimiter);
}
function insertOrMount(route, local) {
var rename = route,
parts = route.split(self.delimiter),
routeType = typeof routes[route],
isRoute = parts[0] === "" || !self._methods[parts[0]],
event = isRoute ? "on" : rename;
if (isRoute) {
rename = rename.slice((rename.match(new RegExp(self.delimiter)) || [''])[0].length);
parts.shift();
}
if (isRoute && routeType === 'object' && !Array.isArray(routes[route])) {
local = local.concat(parts);
self.mount(routes[route], local);
return;
}
if (isRoute) {
local = local.concat(rename.split(self.delimiter));
local = terminator(local, self.delimiter);
}
self.insert(event, local, routes[route]);
}
for (var route in routes) {
if (routes.hasOwnProperty(route)) {
insertOrMount(route, path.slice(0));
}
}
};
{
"name": "director",
"description": "A client Side/Server Side Router",
"author": "Nodejitsu Inc. <info@nodejitsu.com>",
"version": "1.2.0",
"maintainers": [
"hij1nx <paolo@nodejitsu.com>",
"indexzero <charlie@nodejitsu.com>"
],
"repository": {
"type": "git",
"url": "http://github.com/flatiron/director.git"
},
"keywords": [
"URL",
"router",
"http",
"cli",
"flatiron",
"client side",
"ender"
],
"devDependencies": {
"codesurgeon": "https://github.com/hij1nx/codesurgeon/tarball/master",
"colors": "0.5.x",
"api-easy": "0.3.x",
"uglify-js": "1.0.6",
"request": "2.9.x",
"qunitjs": "1.9.x",
"vows": "0.6.x"
},
"ender": "./build/ender.js",
"browserify": "./build/director",
"main": "./lib/director",
"engines": {
"node": ">= 0.4.0"
},
"scripts": {
"test": "vows test/server/*/*-test.js --spec"
}
}
var http = require('http'),
fs = require('fs'),
path = require('path'),
director = require('../../../lib/director'),
index;
fs.readFile(path.join(__dirname, '..', 'html5-routes-harness.html'), function (err, data) {
if (err) {
throw err;
}
index = data;
});
var CONTENT_TYPES = {
'.js' : 'text/javascript',
'.css' : 'text/css'
};
// Dummy file server
function fileServer(folder, file) {
var root = path.resolve(__dirname, '..');
if (folder === 'build' || folder === 'node_modules') {
root = path.resolve(root, '..', '..');
}
var filepath = path.resolve(root, folder, file);
var res = this.res;
(fs.exists || path.exists)(filepath, function (exists) {
if (exists) {
fs.readFile(filepath, function (err, data) {
if (err) {
res.writeHead(404);
res.end();
}
res.writeHead(200, {'Content-Type': CONTENT_TYPES[path.extname(filepath)]});
res.end(data);
});
} else {
res.writeHead(404);
res.end();
}
});
}
var router = new director.http.Router({
'/files': {
'/:folder': {
'/(.+)': {
get: fileServer
},
get: fileServer
}
}
});
var server = http.createServer(function (req, res) {
router.dispatch(req, res, function (err) {
if (err && req.url != '/favicon.ico') {
// By default just reply with the index page
this.res.writeHead(200, {'Content-Type': 'text/html'});
this.res.end(index);
}
});
});
server.listen(8080);
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Director Browserify Tests</title>
<link rel="stylesheet" href="../../node_modules/qunitjs/qunit/qunit.css">
</head>
<body>
<p>To run these tests first generate a Browserify bundle for director by running the command <kbd>browserify -r ../director -o test/browser/browserified-bundle.js</kbd> in the repo's root directory.</p>
<div id="qunit">
<div id="qunit-fixture"></div>
<script src="browserified-bundle.js"></script>
<script>
var HTML5TEST = false;
var RouterAlias = require('/director').Router;
</script>
<script src="../../node_modules/qunitjs/qunit/qunit.js"></script>
<script src="helpers/api.js"></script>
<script src="routes-test.js"></script>
</body>
</html>
module("Director.js", {
setup: function() {
window.location.hash = "";
shared = {};
// Init needed keys earlier because of in HTML5 mode the route handler
// is executed upon Router.init() and due to that setting shared.fired
// in the param test of createTest is too late
if (HTML5TEST) {
shared.fired = [];
shared.fired_count = 0;
}
},
teardown: function() {
window.location.hash = "";
shared = {};
}
});
var shared;
function createTest(name, config, use, test, initialRoute) {
// We rename to `RouterAlias` for the browserify tests, since we want to be
// sure that no code is depending on `window.Router` being available.
var Router = window.Router || window.RouterAlias;
if (typeof use === 'function') {
test = use;
use = undefined;
}
if (HTML5TEST) {
if (use === undefined) {
use = {};
}
if (use.run_handler_in_init === undefined) {
use.run_handler_in_init = false;
}
use.html5history = true;
}
// Because of the use of setTimeout when defining onpopstate
var innerTimeout = HTML5TEST === true ? 500 : 0;
asyncTest(name, function() {
setTimeout(function() {
var router = new Router(config),
context;
if (use !== undefined) {
router.configure(use);
}
router.init(initialRoute);
setTimeout(function() {
test.call(context = {
router: router,
navigate: function(url, callback) {
if (HTML5TEST) {
router.setRoute(url);
} else {
window.location.hash = url;
}
setTimeout(function() {
callback.call(context);
}, 14);
},
finish: function() {
router.destroy();
start();
}
})
}, innerTimeout);
}, 14);
});
};
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Director HTML5 Tests</title>
<link rel="stylesheet" href="/files/node_modules/qunitjs/qunit/qunit.css">
</head>
<body>
<p>Note: in order to execute HTML5 mode test this file needs to be served with provided nodejs backend. Start the server from <kbd>director/test/browser/backend</kbd> and go to <kbd>http://localhost:8080/</kbd> to launch the tests.</p>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script>
var HTML5TEST = true;
</script>
<script src="/files/node_modules/qunitjs/qunit/qunit.js"></script>
<script src="/files/build/director.js"></script>
<script src="/files/helpers/api.js"></script>
<script src="/files/html5-routes-test.js"></script>
</body>
</html>
var browser_history_support = (window.history != null ? window.history.pushState : null) != null;
createTest('Nested route with the many children as a tokens, callbacks should yield historic params', {
'/a': {
'/:id': {
'/:id': function(a, b) {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''), a, b);
} else {
shared.fired.push(location.pathname, a, b);
}
}
}
}
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['/a/b/c', 'b', 'c']);
this.finish();
});
});
createTest('Nested route with the first child as a token, callback should yield a param', {
'/foo': {
'/:id': {
on: function(id) {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''), id);
} else {
shared.fired.push(location.pathname, id);
}
}
}
}
}, function() {
this.navigate('/foo/a', function() {
this.navigate('/foo/b/c', function() {
deepEqual(shared.fired, ['/foo/a', 'a']);
this.finish();
});
});
});
createTest('Nested route with the first child as a regexp, callback should yield a param', {
'/foo': {
'/(\\w+)': {
on: function(value) {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''), value);
} else {
shared.fired.push(location.pathname, value);
}
}
}
}
}, function() {
this.navigate('/foo/a', function() {
this.navigate('/foo/b/c', function() {
deepEqual(shared.fired, ['/foo/a', 'a']);
this.finish();
});
});
});
createTest('Nested route with the several regular expressions, callback should yield a param', {
'/a': {
'/(\\w+)': {
'/(\\w+)': function(a, b) {
shared.fired.push(a, b);
}
}
}
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['b', 'c']);
this.finish();
});
});
createTest('Single nested route with on member containing function value', {
'/a': {
'/b': {
on: function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}
}
}
}, function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['/a/b']);
this.finish();
});
});
createTest('Single non-nested route with on member containing function value', {
'/a/b': {
on: function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}
}
}, function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['/a/b']);
this.finish();
});
});
createTest('Single nested route with on member containing array of function values', {
'/a': {
'/b': {
on: [function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
},
function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}]
}
}
}, function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['/a/b', '/a/b']);
this.finish();
});
});
createTest('method should only fire once on the route.', {
'/a': {
'/b': {
once: function() {
shared.fired_count++;
}
}
}
}, function() {
this.navigate('/a/b', function() {
this.navigate('/a/b', function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired_count, 1);
this.finish();
});
});
});
});
createTest('method should only fire once on the route, multiple nesting.', {
'/a': {
on: function() { shared.fired_count++; },
once: function() { shared.fired_count++; }
},
'/b': {
on: function() { shared.fired_count++; },
once: function() { shared.fired_count++; }
}
}, function() {
this.navigate('/a', function() {
this.navigate('/b', function() {
this.navigate('/a', function() {
this.navigate('/b', function() {
deepEqual(shared.fired_count, 6);
this.finish();
});
});
});
});
});
createTest('overlapping routes with tokens.', {
'/a/:b/c' : function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
},
'/a/:b/c/:d' : function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}
}, function() {
this.navigate('/a/b/c', function() {
this.navigate('/a/b/c/d', function() {
deepEqual(shared.fired, ['/a/b/c', '/a/b/c/d']);
this.finish();
});
});
});
// // //
// // // Recursion features
// // // ----------------------------------------------------------
createTest('Nested routes with no recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['c']);
this.finish();
});
});
createTest('Nested routes with backward recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'backward'
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['c', 'b', 'a']);
this.finish();
});
});
createTest('Breaking out of nested routes with backward recursion', {
'/a': {
'/:b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
return false;
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'backward'
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['c', 'b']);
this.finish();
});
});
createTest('Nested routes with forward recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'forward'
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['a', 'b', 'c']);
this.finish();
});
});
createTest('Nested routes with forward recursion, single route with an after event.', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
},
after: function() {
shared.fired.push('c-after');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'forward'
}, function() {
this.navigate('/a/b/c', function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['a', 'b', 'c', 'c-after', 'a', 'b']);
this.finish();
});
});
});
createTest('Breaking out of nested routes with forward recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
return false;
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'forward'
}, function() {
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['a', 'b']);
this.finish();
});
});
//
// ABOVE IS WORKING
//
// //
// // Special Events
// // ----------------------------------------------------------
createTest('All global event should fire after every route', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
'/c': {
on: function a() {
shared.fired.push('a');
}
}
},
'/d': {
'/:e': {
on: function a() {
shared.fired.push('a');
}
}
}
}, {
after: function() {
shared.fired.push('b');
}
}, function() {
this.navigate('/a', function() {
this.navigate('/b/c', function() {
this.navigate('/d/e', function() {
deepEqual(shared.fired, ['a', 'b', 'a', 'b', 'a']);
this.finish();
});
});
});
});
createTest('Not found.', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
on: function a() {
shared.fired.push('b');
}
}
}, {
notfound: function() {
shared.fired.push('notfound');
}
}, function() {
this.navigate('/c', function() {
this.navigate('/d', function() {
deepEqual(shared.fired, ['notfound', 'notfound']);
this.finish();
});
});
});
createTest('On all.', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
on: function a() {
shared.fired.push('b');
}
}
}, {
on: function() {
shared.fired.push('c');
}
}, function() {
this.navigate('/a', function() {
this.navigate('/b', function() {
deepEqual(shared.fired, ['a', 'c', 'b', 'c']);
this.finish();
});
});
});
createTest('After all.', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
on: function a() {
shared.fired.push('b');
}
}
}, {
after: function() {
shared.fired.push('c');
}
}, function() {
this.navigate('/a', function() {
this.navigate('/b', function() {
deepEqual(shared.fired, ['a', 'c', 'b']);
this.finish();
});
});
});
createTest('resource object.', {
'/a': {
'/b/:c': {
on: 'f1'
},
on: 'f2'
},
'/d': {
on: ['f1', 'f2']
}
},
{
resource: {
f1: function (name){
shared.fired.push("f1-" + name);
},
f2: function (name){
shared.fired.push("f2");
}
}
}, function() {
this.navigate('/a/b/c', function() {
this.navigate('/d', function() {
deepEqual(shared.fired, ['f1-c', 'f1-undefined', 'f2']);
this.finish();
});
});
});
createTest('argument matching should be case agnostic', {
'/fooBar/:name': {
on: function(name) {
shared.fired.push("fooBar-" + name);
}
}
}, function() {
this.navigate('/fooBar/tesTing', function() {
deepEqual(shared.fired, ['fooBar-tesTing']);
this.finish();
});
});
createTest('sanity test', {
'/is/:this/:sane': {
on: function(a, b) {
shared.fired.push('yes ' + a + ' is ' + b);
}
},
'/': {
on: function() {
shared.fired.push('is there sanity?');
}
}
}, function() {
this.navigate('/is/there/sanity', function() {
deepEqual(shared.fired, ['yes there is sanity']);
this.finish();
});
});
createTest('`/` route should be navigable from the routing table', {
'/': {
on: function root() {
shared.fired.push('/');
}
},
'/:username': {
on: function afunc(username) {
shared.fired.push('/' + username);
}
}
}, function() {
this.navigate('/', function root() {
deepEqual(shared.fired, ['/']);
this.finish();
});
});
createTest('`/` route should not override a `/:token` route', {
'/': {
on: function root() {
shared.fired.push('/');
}
},
'/:username': {
on: function afunc(username) {
shared.fired.push('/' + username);
}
}
}, function() {
this.navigate('/a', function afunc() {
deepEqual(shared.fired, ['/a']);
this.finish();
});
});
createTest('should accept the root as a token.', {
'/:a': {
on: function root() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}
}
}, function() {
this.navigate('/a', function root() {
deepEqual(shared.fired, ['/a']);
this.finish();
});
});
createTest('routes should allow wildcards.', {
'/:a/b*d': {
on: function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}
}
}, function() {
this.navigate('/a/bcd', function root() {
deepEqual(shared.fired, ['/a/bcd']);
this.finish();
});
});
createTest('functions should have |this| context of the router instance.', {
'/': {
on: function root() {
shared.fired.push(!!this.routes);
}
}
}, function() {
this.navigate('/', function root() {
deepEqual(shared.fired, [true]);
this.finish();
});
});
createTest('setRoute with a single parameter should change location correctly', {
'/bonk': {
on: function() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(window.location.pathname);
}
}
}
}, function() {
var self = this;
this.router.setRoute('/bonk');
setTimeout(function() {
deepEqual(shared.fired, ['/bonk']);
self.finish();
}, 14)
});
createTest('route should accept _ and . within parameters', {
'/:a': {
on: function root() {
if (!browser_history_support) {
shared.fired.push(location.hash.replace(/^#/, ''));
} else {
shared.fired.push(location.pathname);
}
}
}
}, function() {
this.navigate('/a_complex_route.co.uk', function root() {
deepEqual(shared.fired, ['/a_complex_route.co.uk']);
this.finish();
});
});
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Director Tests</title>
<link rel="stylesheet" href="../../node_modules/qunitjs/qunit/qunit.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script>
var HTML5TEST = false;
</script>
<script src="../../node_modules/qunitjs/qunit/qunit.js"></script>
<script src="../../build/director.js"></script>
<script src="helpers/api.js"></script>
<script src="routes-test.js"></script>
</body>
</html>
createTest('Nested route with the many children as a tokens, callbacks should yield historic params', {
'/a': {
'/:id': {
'/:id': function(a, b) {
shared.fired.push(location.hash, a, b);
}
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['#/a/b/c', 'b', 'c']);
this.finish();
});
});
createTest('Nested route with the first child as a token, callback should yield a param', {
'/foo': {
'/:id': {
on: function(id) {
shared.fired.push(location.hash, id);
}
}
}
}, function() {
shared.fired = [];
this.navigate('/foo/a', function() {
this.navigate('/foo/b/c', function() {
deepEqual(shared.fired, ['#/foo/a', 'a']);
this.finish();
});
});
});
createTest('Nested route with the first child as a regexp, callback should yield a param', {
'/foo': {
'/(\\w+)': {
on: function(value) {
shared.fired.push(location.hash, value);
}
}
}
}, function() {
shared.fired = [];
this.navigate('/foo/a', function() {
this.navigate('/foo/b/c', function() {
deepEqual(shared.fired, ['#/foo/a', 'a']);
this.finish();
});
});
});
createTest('Nested route with the several regular expressions, callback should yield a param', {
'/a': {
'/(\\w+)': {
'/(\\w+)': function(a, b) {
shared.fired.push(a, b);
}
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['b', 'c']);
this.finish();
});
});
createTest('Single nested route with on member containing function value', {
'/a': {
'/b': {
on: function() {
shared.fired.push(location.hash);
}
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['#/a/b']);
this.finish();
});
});
createTest('Single non-nested route with on member containing function value', {
'/a/b': {
on: function() {
shared.fired.push(location.hash);
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['#/a/b']);
this.finish();
});
});
createTest('Single nested route with on member containing array of function values', {
'/a': {
'/b': {
on: [function() { shared.fired.push(location.hash); },
function() { shared.fired.push(location.hash); }]
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['#/a/b', '#/a/b']);
this.finish();
});
});
createTest('method should only fire once on the route.', {
'/a': {
'/b': {
once: function() {
shared.fired++;
}
}
}
}, function() {
shared.fired = 0;
this.navigate('/a/b', function() {
this.navigate('/a/b', function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired, 1);
this.finish();
});
});
});
});
createTest('method should only fire once on the route, multiple nesting.', {
'/a': {
on: function() { shared.fired++; },
once: function() { shared.fired++; }
},
'/b': {
on: function() { shared.fired++; },
once: function() { shared.fired++; }
}
}, function() {
shared.fired = 0;
this.navigate('/a', function() {
this.navigate('/b', function() {
this.navigate('/a', function() {
this.navigate('/b', function() {
deepEqual(shared.fired, 6);
this.finish();
});
});
});
});
});
createTest('overlapping routes with tokens.', {
'/a/:b/c' : function() {
shared.fired.push(location.hash);
},
'/a/:b/c/:d' : function() {
shared.fired.push(location.hash);
}
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
this.navigate('/a/b/c/d', function() {
deepEqual(shared.fired, ['#/a/b/c', '#/a/b/c/d']);
this.finish();
});
});
});
// // //
// // // Recursion features
// // // ----------------------------------------------------------
createTest('Nested routes with no recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['c']);
this.finish();
});
});
createTest('Nested routes with backward recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'backward'
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['c', 'b', 'a']);
this.finish();
});
});
createTest('Breaking out of nested routes with backward recursion', {
'/a': {
'/:b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
return false;
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'backward'
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['c', 'b']);
this.finish();
});
});
createTest('Nested routes with forward recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'forward'
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['a', 'b', 'c']);
this.finish();
});
});
createTest('Nested routes with forward recursion, single route with an after event.', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
},
after: function() {
shared.fired.push('c-after');
}
},
on: function b() {
shared.fired.push('b');
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'forward'
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
this.navigate('/a/b', function() {
deepEqual(shared.fired, ['a', 'b', 'c', 'c-after', 'a', 'b']);
this.finish();
});
});
});
createTest('Breaking out of nested routes with forward recursion', {
'/a': {
'/b': {
'/c': {
on: function c() {
shared.fired.push('c');
}
},
on: function b() {
shared.fired.push('b');
return false;
}
},
on: function a() {
shared.fired.push('a');
}
}
}, {
recurse: 'forward'
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
deepEqual(shared.fired, ['a', 'b']);
this.finish();
});
});
//
// ABOVE IS WORKING
//
// //
// // Special Events
// // ----------------------------------------------------------
createTest('All global event should fire after every route', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
'/c': {
on: function a() {
shared.fired.push('a');
}
}
},
'/d': {
'/:e': {
on: function a() {
shared.fired.push('a');
}
}
}
}, {
after: function() {
shared.fired.push('b');
}
}, function() {
shared.fired = [];
this.navigate('/a', function() {
this.navigate('/b/c', function() {
this.navigate('/d/e', function() {
deepEqual(shared.fired, ['a', 'b', 'a', 'b', 'a']);
this.finish();
});
});
});
});
createTest('Not found.', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
on: function a() {
shared.fired.push('b');
}
}
}, {
notfound: function() {
shared.fired.push('notfound');
}
}, function() {
shared.fired = [];
this.navigate('/c', function() {
this.navigate('/d', function() {
deepEqual(shared.fired, ['notfound', 'notfound']);
this.finish();
});
});
});
createTest('On all.', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
on: function a() {
shared.fired.push('b');
}
}
}, {
on: function() {
shared.fired.push('c');
}
}, function() {
shared.fired = [];
this.navigate('/a', function() {
this.navigate('/b', function() {
deepEqual(shared.fired, ['a', 'c', 'b', 'c']);
this.finish();
});
});
});
createTest('After all.', {
'/a': {
on: function a() {
shared.fired.push('a');
}
},
'/b': {
on: function a() {
shared.fired.push('b');
}
}
}, {
after: function() {
shared.fired.push('c');
}
}, function() {
shared.fired = [];
this.navigate('/a', function() {
this.navigate('/b', function() {
deepEqual(shared.fired, ['a', 'c', 'b']);
this.finish();
});
});
});
createTest('resource object.', {
'/a': {
'/b/:c': {
on: 'f1'
},
on: 'f2'
},
'/d': {
on: ['f1', 'f2']
}
},
{
resource: {
f1: function (name){
shared.fired.push("f1-" + name);
},
f2: function (name){
shared.fired.push("f2");
}
}
}, function() {
shared.fired = [];
this.navigate('/a/b/c', function() {
this.navigate('/d', function() {
deepEqual(shared.fired, ['f1-c', 'f1-undefined', 'f2']);
this.finish();
});
});
});
createTest('argument matching should be case agnostic', {
'/fooBar/:name': {
on: function(name) {
shared.fired.push("fooBar-" + name);
}
}
}, function() {
shared.fired = [];
this.navigate('/fooBar/tesTing', function() {
deepEqual(shared.fired, ['fooBar-tesTing']);
this.finish();
});
});
createTest('sanity test', {
'/is/:this/:sane': {
on: function(a, b) {
shared.fired.push('yes ' + a + ' is ' + b);
}
},
'/': {
on: function() {
shared.fired.push('is there sanity?');
}
}
}, function() {
shared.fired = [];
this.navigate('/is/there/sanity', function() {
deepEqual(shared.fired, ['yes there is sanity']);
this.finish();
});
});
createTest('`/` route should be navigable from the routing table', {
'/': {
on: function root() {
shared.fired.push('/');
}
},
'/:username': {
on: function afunc(username) {
shared.fired.push('/' + username);
}
}
}, function() {
shared.fired = [];
this.navigate('/', function root() {
deepEqual(shared.fired, ['/']);
this.finish();
});
});
createTest('`/` route should not override a `/:token` route', {
'/': {
on: function root() {
shared.fired.push('/');
}
},
'/:username': {
on: function afunc(username) {
shared.fired.push('/' + username);
}
}
}, function() {
shared.fired = [];
this.navigate('/a', function afunc() {
deepEqual(shared.fired, ['/a']);
this.finish();
});
});
createTest('should accept the root as a token.', {
'/:a': {
on: function root() {
shared.fired.push(location.hash);
}
}
}, function() {
shared.fired = [];
this.navigate('/a', function root() {
deepEqual(shared.fired, ['#/a']);
this.finish();
});
});
createTest('routes should allow wildcards.', {
'/:a/b*d': {
on: function() {
shared.fired.push(location.hash);
}
}
}, function() {
shared.fired = [];
this.navigate('/a/bcd', function root() {
deepEqual(shared.fired, ['#/a/bcd']);
this.finish();
});
});
createTest('functions should have |this| context of the router instance.', {
'/': {
on: function root() {
shared.fired.push(!!this.routes);
}
}
}, function() {
shared.fired = [];
this.navigate('/', function root() {
deepEqual(shared.fired, [true]);
this.finish();
});
});
createTest('setRoute with a single parameter should change location correctly', {
'/bonk': {
on: function() {
shared.fired.push(window.location.hash);
}
}
}, function() {
var self = this;
shared.fired = [];
this.router.setRoute('/bonk');
setTimeout(function() {
deepEqual(shared.fired, ['#/bonk']);
self.finish();
}, 14)
});
createTest('route should accept _ and . within parameters', {
'/:a': {
on: function root() {
shared.fired.push(location.hash);
}
}
}, function() {
shared.fired = [];
this.navigate('/a_complex_route.co.uk', function root() {
deepEqual(shared.fired, ['#/a_complex_route.co.uk']);
this.finish();
});
});
createTest('initializing with a default route should only result in one route handling', {
'/': {
on: function root() {
if (!shared.init){
shared.init = 0;
}
shared.init++;
}
},
'/test': {
on: function root() {
if (!shared.test){
shared.test = 0;
}
shared.test++;
}
}
}, function() {
this.navigate('/test', function root() {
equal(shared.init, 1);
equal(shared.test, 1);
this.finish();
});
},
null,
'/');
createTest('changing the hash twice should call each route once', {
'/hash1': {
on: function root() {
shared.fired.push('hash1');
}
},
'/hash2': {
on: function root() {
shared.fired.push('hash2');
}
}
}, function() {
shared.fired = [];
this.navigate('/hash1', function(){});
this.navigate('/hash2', function(){
deepEqual(shared.fired, ['hash1', 'hash2']);
this.finish();
});
}
);
/*
* dispatch-test.js: Tests for the core dispatch method.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/cli/dispatch').addBatch({
"An instance of director.cli.Router": {
topic: function () {
var router = new director.cli.Router(),
that = this;
that.matched = {};
that.matched['users'] = [];
that.matched['apps'] = []
router.on('users create', function () {
that.matched['users'].push('on users create');
});
router.on(/apps (\w+\s\w+)/, function () {
assert.equal(arguments.length, 1);
that.matched['apps'].push('on apps (\\w+\\s\\w+)');
});
return router;
},
"should have the correct routing table": function (router) {
assert.isObject(router.routes.users);
assert.isObject(router.routes.users.create);
},
"the dispatch() method": {
"users create": function (router) {
assert.isTrue(router.dispatch('on', 'users create'));
assert.equal(this.matched.users[0], 'on users create');
},
"apps foo bar": function (router) {
assert.isTrue(router.dispatch('on', 'apps foo bar'));
assert.equal(this.matched['apps'][0], 'on apps (\\w+\\s\\w+)');
},
"not here": function (router) {
assert.isFalse(router.dispatch('on', 'not here'));
},
"still not here": function (router) {
assert.isFalse(router.dispatch('on', 'still not here'));
}
}
}
}).export(module);
/*
* mount-test.js: Tests for the core mount method.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/cli/path').addBatch({
"An instance of director.cli.Router with routes": {
topic: new director.cli.Router({
'apps': function () {
console.log('apps');
},
' users': function () {
console.log('users');
}
}),
"should create the correct nested routing table": function (router) {
assert.isObject(router.routes.apps);
assert.isFunction(router.routes.apps.on);
assert.isObject(router.routes.users);
assert.isFunction(router.routes.users.on);
}
}
}).export(module);
/*
* dispatch-test.js: Tests for the core dispatch method.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/cli/path').addBatch({
"An instance of director.cli.Router": {
topic: new director.cli.Router(),
"the path() method": {
"should create the correct nested routing table": function (router) {
function noop () {}
router.path(/apps/, function () {
router.path(/foo/, function () {
router.on(/bar/, noop);
});
router.on(/list/, noop);
});
router.on(/users/, noop);
assert.isObject(router.routes.apps);
assert.isFunction(router.routes.apps.list.on);
assert.isObject(router.routes.apps.foo);
assert.isFunction(router.routes.apps.foo.bar.on);
assert.isFunction(router.routes.users.on);
}
}
}
}).export(module);
/*
* dispatch-test.js: Tests for the core dispatch method.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/core/dispatch').addBatch({
"An instance of director.Router": {
topic: function () {
var that = this;
that.matched = {};
that.matched['/'] = [];
that.matched['foo'] = [];
that.matched['f*'] = []
var router = new director.Router({
'/': {
before: function () { that.matched['/'].push('before /') },
on: function () { that.matched['/'].push('on /') },
after: function () { that.matched['/'].push('after /') }
},
'/foo': {
before: function () { that.matched.foo.push('before foo') },
on: function () { that.matched.foo.push('on foo') },
after: function () { that.matched.foo.push('after foo') },
'/bar': {
before: function () { that.matched.foo.push('before foo bar') },
on: function () { that.matched.foo.push('foo bar') },
after: function () { that.matched.foo.push('after foo bar') },
'/buzz': function () { that.matched.foo.push('foo bar buzz') }
}
},
'/f*': {
'/barbie': function () { that.matched['f*'].push('f* barbie') }
}
});
router.configure({
recurse: 'backward'
});
return router;
},
"should have the correct routing table": function (router) {
assert.isObject(router.routes.foo);
assert.isObject(router.routes.foo.bar);
assert.isObject(router.routes.foo.bar.buzz);
assert.isFunction(router.routes.foo.bar.buzz.on);
},
"the dispatch() method": {
"/": function (router) {
assert.isTrue(router.dispatch('on', '/'));
assert.isTrue(router.dispatch('on', '/'));
assert.equal(this.matched['/'][0], 'before /');
assert.equal(this.matched['/'][1], 'on /');
assert.equal(this.matched['/'][2], 'after /');
},
"/foo/bar/buzz": function (router) {
assert.isTrue(router.dispatch('on', '/foo/bar/buzz'));
assert.equal(this.matched.foo[0], 'foo bar buzz');
assert.equal(this.matched.foo[1], 'before foo bar');
assert.equal(this.matched.foo[2], 'foo bar');
assert.equal(this.matched.foo[3], 'before foo');
assert.equal(this.matched.foo[4], 'on foo');
},
"/foo/barbie": function (router) {
assert.isTrue(router.dispatch('on', '/foo/barbie'));
assert.equal(this.matched['f*'][0], 'f* barbie');
},
"/foo/barbie/": function (router) {
assert.isFalse(router.dispatch('on', '/foo/barbie/'));
},
"/foo/BAD": function (router) {
assert.isFalse(router.dispatch('on', '/foo/BAD'));
},
"/bar/bar": function (router) {
assert.isFalse(router.dispatch('on', '/bar/bar'));
},
"with the strict option disabled": {
topic: function (router) {
return router.configure({
recurse: 'backward',
strict: false
});
},
"should have the proper configuration set": function (router) {
assert.isFalse(router.strict);
},
"/foo/barbie/": function (router) {
assert.isTrue(router.dispatch('on', '/foo/barbie/'));
assert.equal(this.matched['f*'][0], 'f* barbie');
},
"/foo/bar/buzz": function (router) {
assert.isTrue(router.dispatch('on', '/foo/bar/buzz'));
assert.equal(this.matched.foo[0], 'foo bar buzz');
assert.equal(this.matched.foo[1], 'before foo bar');
assert.equal(this.matched.foo[2], 'foo bar');
assert.equal(this.matched.foo[3], 'before foo');
assert.equal(this.matched.foo[4], 'on foo');
},
}
}
}
}).export(module);
/*
* insert-test.js: Tests for inserting routes into a normalized routing table.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/core/insert').addBatch({
"An instance of director.Router": {
topic: new director.Router(),
"the insert() method": {
"'on', ['foo', 'bar']": function (router) {
function route () { }
router.insert('on', ['foo', 'bar'], route);
assert.strictEqual(router.routes.foo.bar.on, route);
},
"'on', ['foo', 'bar'] again": function (router) {
function route () { }
router.insert('on', ['foo', 'bar'], route);
assert.isArray(router.routes.foo.bar.on);
assert.strictEqual(router.routes.foo.bar.on.length, 2);
},
"'on', ['foo', 'bar'] a third time": function (router) {
function route () { }
router.insert('on', ['foo', 'bar'], route);
assert.isArray(router.routes.foo.bar.on);
assert.strictEqual(router.routes.foo.bar.on.length, 3);
},
"'get', ['fizz', 'buzz']": function (router) {
function route () { }
router.insert('get', ['fizz', 'buzz'], route);
assert.strictEqual(router.routes.fizz.buzz.get, route);
}
}
}
}).export(module);
\ No newline at end of file
/*
* mount-test.js: Tests for mounting and normalizing routes into a Router instance.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
function assertRoute (fn, path, route) {
if (path.length === 1) {
return assert.strictEqual(route[path.shift()], fn);
}
route = route[path.shift()];
assert.isObject(route);
assertRoute(fn, path, route);
}
vows.describe('director/core/mount').addBatch({
"An instance of director.Router": {
"with no preconfigured params": {
topic: new director.Router(),
"the mount() method": {
"should sanitize the routes correctly": function (router) {
function foobar () { }
function foostar () { }
function foobazzbuzz () { }
function foodog () { }
function root () {}
var fnArray = [foobar, foostar];
router.mount({
'/': {
before: root,
on: root,
after: root,
'/nesting': {
on: foobar,
'/deep': foostar
}
},
'/foo': {
'/bar': foobar,
'/*': foostar,
'/jitsu/then': {
before: foobar
}
},
'/foo/bazz': {
'/buzz': foobazzbuzz
},
'/foo/jitsu': {
'/then': fnArray
},
'/foo/jitsu/then/now': foostar,
'/foo/:dog': foodog
});
assertRoute(root, ['on'], router.routes);
assertRoute(root, ['after'], router.routes);
assertRoute(root, ['before'], router.routes);
assertRoute(foobar, ['nesting', 'on'], router.routes);
assertRoute(foostar, ['nesting', 'deep', 'on'], router.routes);
assertRoute(foobar, [ 'foo', 'bar', 'on'], router.routes);
assertRoute(foostar, ['foo', '([_.()!\\ %@&a-zA-Z0-9-]+)', 'on'], router.routes);
assertRoute(fnArray, ['foo', 'jitsu', 'then', 'on'], router.routes);
assertRoute(foobar, ['foo', 'jitsu', 'then', 'before'], router.routes);
assertRoute(foobazzbuzz, ['foo', 'bazz', 'buzz', 'on'], router.routes);
assertRoute(foostar, ['foo', 'jitsu', 'then', 'now', 'on'], router.routes);
assertRoute(foodog, ['foo', '([._a-zA-Z0-9-]+)', 'on'], router.routes);
},
"should accept string path": function(router) {
function dogs () { }
router.mount({
'/dogs': {
on: dogs
}
},
'/api');
assertRoute(dogs, ['api', 'dogs', 'on'], router.routes);
}
}
},
"with preconfigured params": {
topic: function () {
var router = new director.Router();
router.param('city', '([\\w\\-]+)');
router.param(':country', /([A-Z][A-Za-z]+)/);
router.param(':zip', /([\d]{5})/);
return router;
},
"should sanitize the routes correctly": function (router) {
function usaCityZip () { }
function countryCityZip () { }
router.mount({
'/usa/:city/:zip': usaCityZip,
'/world': {
'/:country': {
'/:city/:zip': countryCityZip
}
}
});
assertRoute(usaCityZip, ['usa', '([\\w\\-]+)', '([\\d]{5})', 'on'], router.routes);
assertRoute(countryCityZip, ['world', '([A-Z][A-Za-z]+)', '([\\w\\-]+)', '([\\d]{5})', 'on'], router.routes);
}
}
}
}).export(module);
/*
* on-test.js: Tests for the on/route method.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/core/insert').addBatch({
"An instance of director.Router": {
topic: new director.Router(),
"the on() method": {
"['foo', 'bar']": function (router) {
function noop () { }
router.on(['foo', 'bar'], noop);
assert.strictEqual(router.routes.foo.on, noop);
assert.strictEqual(router.routes.bar.on, noop);
},
"'baz'": function (router) {
function noop () { }
router.on('baz', noop);
assert.strictEqual(router.routes.baz.on, noop);
},
"'after', 'baz'": function (router) {
function noop () { }
router.on('after', 'boo', noop);
assert.strictEqual(router.routes.boo.after, noop);
}
}
}
}).export(module);
\ No newline at end of file
/*
* path-test.js: Tests for the core `.path()` method.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/core/path').addBatch({
"An instance of director.Router": {
topic: function () {
var that = this;
that.matched = {};
that.matched['foo'] = [];
that.matched['newyork'] = [];
var router = new director.Router({
'/foo': function () { that.matched['foo'].push('foo'); }
});
return router;
},
"the path() method": {
"should create the correct nested routing table": function (router) {
var that = this;
router.path('/regions', function () {
this.on('/:state', function(country) {
that.matched['newyork'].push('new york');
});
});
assert.isFunction(router.routes.foo.on);
assert.isObject(router.routes.regions);
assert.isFunction(router.routes.regions['([._a-zA-Z0-9-]+)'].on);
},
"should dispatch the function correctly": function (router) {
router.dispatch('on', '/regions/newyork')
router.dispatch('on', '/foo');
assert.equal(this.matched['foo'].length, 1);
assert.equal(this.matched['newyork'].length, 1);
assert.equal(this.matched['foo'][0], 'foo');
assert.equal(this.matched['newyork'][0], 'new york');
}
}
}
}).export(module);
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
var callback = function() {
return true;
};
var testRoute = function(route, callback) {
var router = new director.Router();
router.on(route, callback);
return function(value) {
return router.dispatch('on', value);
};
};
vows.describe('director/core/regifyString').addBatch({
'When using "/home(.*)"': {
topic: function() {
return testRoute('/home(.*)', callback);
},
'Should match "/homepage"': function(result) {
assert.isTrue(result('/homepage'));
},
'Should match "/home/page"': function(result) {
assert.isTrue(result('/home/page'));
},
'Should not match "/foo-bar"': function(result) {
assert.isFalse(result('/foo-bar'));
}
},
'When using "/home.*"': {
topic: function() {
return testRoute('/home.*', callback);
},
'Should match "/homepage"': function(result) {
assert.isTrue(result('/homepage'));
},
'Should match "/home/page"': function(result) {
assert.isTrue(result('/home/page'));
},
'Should not match "/foo-bar"': function(result) {
assert.isFalse(result('/foo-bar'));
}
},
'When using "/home(page[0-9])*"': {
topic: function() {
return testRoute('/home(page[0-9])*', callback);
},
'Should match "/home"': function(result) {
assert.isTrue(result('/home'));
},
'Should match "/homepage0", "/homepage1", etc.': function(result) {
for (i = 0; i < 10; i++) {
assert.isTrue(result('/homepage' + i));
}
},
'Should not match "/home_page"': function(result) {
assert.isFalse(result('/home_page'));
},
'Should not match "/home/page"': function(result) {
assert.isFalse(result('/home/page'));
}
},
'When using "/home*"': {
topic: function() {
return testRoute('/home*', callback);
},
'Should match "/homepage"': function(result) {
assert.isTrue(result('/homepage'));
},
'Should match "/home_page"': function(result) {
assert.isTrue(result('/home_page'));
},
'Should match "/home-page"': function(result) {
assert.isTrue(result('/home-page'));
},
'Should not match "/home/page"': function(result) {
assert.isFalse(result('/home/page'));
}
}
}).export(module);
/*
* index.js: Test helpers for director.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var http = require('http');
exports.createServer = function (router) {
return http.createServer(function (req, res) {
router.dispatch(req, res, function (err) {
if (err) {
res.writeHead(404);
res.end();
}
});
});
};
exports.handlers = {
respondWithId: function (id) {
this.res.writeHead(200, { 'Content-Type': 'text/plain' })
this.res.end('hello from (' + id + ')');
},
respondWithData: function () {
this.res.writeHead(200, { 'Content-Type': 'application/json' })
this.res.end(JSON.stringify(this.data));
},
respondWithOk: function () {
return function () {
this.res.writeHead(200);
this.res.end('ok');
};
},
streamBody: function () {
var body = '',
res = this.res;
this.req.on('data', function (chunk) {
body += chunk;
});
this.req.on('end', function () {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(body);
});
}
};
exports.macros = require('./macros');
/*
* macros.js: Test macros for director tests.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
request = require('request');
exports.assertGet = function(port, uri, expected) {
var context = {
topic: function () {
request({ uri: 'http://localhost:' + port + '/' + uri }, this.callback);
}
};
context['should respond with `' + expected + '`'] = function (err, res, body) {
assert.isNull(err);
assert.equal(res.statusCode, 200);
assert.equal(body, expected);
};
return context;
};
exports.assert404 = function (port, uri) {
return {
topic: function () {
request({ uri: 'http://localhost:' + port + '/' + uri }, this.callback);
},
"should respond with 404": function (err, res, body) {
assert.isNull(err);
assert.equal(res.statusCode, 404);
}
};
};
exports.assertPost = function(port, uri, expected) {
return {
topic: function () {
request({
method: 'POST',
uri: 'http://localhost:' + port + '/' + uri,
body: JSON.stringify(expected)
}, this.callback);
},
"should respond with the POST body": function (err, res, body) {
assert.isNull(err);
assert.equal(res.statusCode, 200);
assert.deepEqual(JSON.parse(body), expected);
}
};
};
/*
* accept-test.js: Tests for `content-type`-based routing
*
* (C) 2012, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
apiEasy = require('api-easy'),
director = require('../../../lib/director'),
helpers = require('../helpers'),
macros = helpers.macros,
handlers = helpers.handlers;
var PORT = 9067;
apiEasy.describe('director/http/accept')
.addBatch({
"An instance of `director.http.Router`": {
"with routes set up": {
topic: function () {
var router = new director.http.Router();
router.get('/json', { accept: 'application/json' }, handlers.respondWithOk());
router.get('/txt', { accept: 'text/plain' }, handlers.respondWithOk());
router.get('/both', { accept: ['text/plain', 'application/json'] }, handlers.respondWithOk());
router.get('/regex', { accept: /.+\/x\-.+/ }, handlers.respondWithOk());
router.get('/weird', { accept: 'application/json' }, function () {
this.res.writeHead(400);
this.res.end();
});
router.get('/weird', handlers.respondWithOk());
helpers.createServer(router).listen(PORT, this.callback);
},
"should be created": function (err) {
assert(!err);
}
}
}
})
.use('localhost', PORT)
.discuss('with `content-type: application/json`')
.setHeader('content-type', 'application/json')
.get('/json')
.expect(200)
.get('/txt')
.expect(404)
.get('/both')
.expect(200)
.get('/regex')
.expect(404)
.get('/weird')
.expect(400)
.undiscuss()
.next()
.discuss('with `content-type: text/plain`')
.setHeader('content-type', 'text/plain')
.get('/json')
.expect(404)
.get('/txt')
.expect(200)
.get('/both')
.expect(200)
.get('/regex')
.expect(404)
.get('/weird')
.expect(200)
.undiscuss()
.next()
.discuss('with `content-type: application/x-tar-gz`')
.setHeader('content-type', 'application/x-tar-gz`')
.get('/json')
.get('/json')
.expect(404)
.get('/txt')
.expect(404)
.get('/both')
.expect(404)
.get('/regex')
.expect(200)
.get('/weird')
.expect(200)
.undiscuss()
.export(module);
/*
* attach-test.js: Tests 'router.attach' functionality.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
http = require('http'),
vows = require('vows'),
request = require('request'),
director = require('../../../lib/director'),
helpers = require('../helpers'),
handlers = helpers.handlers,
macros = helpers.macros;
function assertData(uri) {
return macros.assertGet(
9091,
uri,
JSON.stringify([1,2,3])
);
}
vows.describe('director/http/attach').addBatch({
"An instance of director.http.Router": {
"instantiated with a Routing table": {
topic: new director.http.Router({
'/hello': {
get: handlers.respondWithData
}
}),
"should have the correct routes defined": function (router) {
assert.isObject(router.routes.hello);
assert.isFunction(router.routes.hello.get);
},
"when passed to an http.Server instance": {
topic: function (router) {
router.attach(function () {
this.data = [1,2,3];
});
helpers.createServer(router)
.listen(9091, this.callback);
},
"a request to hello": assertData('hello'),
}
}
}
}).export(module);
/*
* before-test.js: Tests for running before methods on HTTP server(s).
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
http = require('http'),
vows = require('vows'),
request = require('request'),
director = require('../../../lib/director'),
helpers = require('../helpers'),
handlers = helpers.handlers,
macros = helpers.macros;
vows.describe('director/http/before').addBatch({
"An instance of director.http.Router": {
"with ad-hoc routes including .before()": {
topic: function () {
var router = new director.http.Router();
router.before('/hello', function () { });
router.after('/hello', function () { });
router.get('/hello', handlers.respondWithId);
return router;
},
"should have the correct routes defined": function (router) {
assert.isObject(router.routes.hello);
assert.isFunction(router.routes.hello.before);
assert.isFunction(router.routes.hello.after);
assert.isFunction(router.routes.hello.get);
}
}
}
}).export(module);
\ No newline at end of file
/*
* http-test.js: Tests for basic HTTP server(s).
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
http = require('http'),
vows = require('vows'),
request = require('request'),
director = require('../../../lib/director'),
helpers = require('../helpers'),
handlers = helpers.handlers,
macros = helpers.macros;
function assertBark(uri) {
return macros.assertGet(
9090,
uri,
'hello from (bark)'
);
}
vows.describe('director/http').addBatch({
"An instance of director.http.Router": {
"instantiated with a Routing table": {
topic: new director.http.Router({
'/hello': {
get: handlers.respondWithId
}
}),
"should have the correct routes defined": function (router) {
assert.isObject(router.routes.hello);
assert.isFunction(router.routes.hello.get);
},
"when passed to an http.Server instance": {
topic: function (router) {
router.get(/foo\/bar\/(\w+)/, handlers.respondWithId);
router.get(/foo\/update\/(\w+)/, handlers.respondWithId);
router.path(/bar\/bazz\//, function () {
this.get(/(\w+)/, handlers.respondWithId);
});
router.get(/\/foo\/wild\/(.*)/, handlers.respondWithId);
router.get(/(\/v2)?\/somepath/, handlers.respondWithId);
helpers.createServer(router)
.listen(9090, this.callback);
},
"a request to foo/bar/bark": assertBark('foo/bar/bark'),
"a request to foo/update/bark": assertBark('foo/update/bark'),
"a request to bar/bazz/bark": assertBark('bar/bazz/bark'),
"a request to foo/bar/bark?test=test": assertBark('foo/bar/bark?test=test'),
"a request to foo/wild/bark": assertBark('foo/wild/bark'),
"a request to foo/%RT": macros.assert404(9090, 'foo/%RT'),
"a request to /v2/somepath": macros.assertGet(
9090,
'/v2/somepath',
'hello from (/v2)'
)
}
}
}
}).export(module);
\ No newline at end of file
/*
* methods-test.js: Tests for HTTP methods.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/http/methods').addBatch({
"When using director": {
"an instance of director.http.Router should have all relevant RFC methods": function () {
var router = new director.http.Router();
director.http.methods.forEach(function (method) {
assert.isFunction(router[method.toLowerCase()]);
});
},
"the path() method": {
topic: new director.http.Router(),
"/resource": {
"should insert nested routes correct": function (router) {
function getResource() {}
function modifyResource() {}
router.path(/\/resource/, function () {
this.get(getResource);
this.put(/\/update/, modifyResource);
this.post(/create/, modifyResource);
});
assert.equal(router.routes.resource.get, getResource);
assert.equal(router.routes.resource.update.put, modifyResource);
assert.equal(router.routes.resource.create.post, modifyResource);
}
}
}
}
}).export(module);
\ No newline at end of file
/*
* responses-test.js: Tests for HTTP responses.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
vows = require('vows'),
director = require('../../../lib/director');
vows.describe('director/http/responses').addBatch({
"When using director.http": {
"it should have the relevant responses defined": function () {
Object.keys(require('../../../lib/director/http/responses')).forEach(function (name) {
assert.isFunction(director.http[name]);
});
}
}
}).export(module);
\ No newline at end of file
/*
* stream-test.js: Tests for streaming HTTP in director.
*
* (C) 2011, Nodejitsu Inc.
* MIT LICENSE
*
*/
var assert = require('assert'),
http = require('http'),
vows = require('vows'),
request = require('request'),
director = require('../../../lib/director'),
helpers = require('../helpers'),
macros = helpers.macros,
handlers = helpers.handlers
vows.describe('director/http/stream').addBatch({
"An instance of director.http.Router": {
"with streaming routes": {
topic: function () {
var router = new director.http.Router();
router.post(/foo\/bar/, { stream: true }, handlers.streamBody);
router.path('/a-path', function () {
this.post({ stream: true }, handlers.streamBody);
});
return router;
},
"when passed to an http.Server instance": {
topic: function (router) {
helpers.createServer(router)
.listen(9092, this.callback);
},
"a POST request to /foo/bar": macros.assertPost(9092, 'foo/bar', {
foo: 'foo',
bar: 'bar'
}),
"a POST request to /a-path": macros.assertPost(9092, 'a-path', {
foo: 'foo',
bar: 'bar'
})
}
}
}
}).export(module);
\ No newline at end of file
// Copyright (c) 2012 The Polymer Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform on modern browsers.
For Docs, License, Tests, and pre-packed downloads, see:
http://www.polymer-project.org/
Many thanks to our contributors:
https://github.com/Polymer/platform/contributors
{
"name": "polymer",
"description": "Leverage the future of the web platform today.",
"homepage": "http://www.polymer-project.org/",
"keywords": [
"util",
"client",
"browser"
],
"author": {
"name": "Polymer Authors",
"email": "polymer-dev@googlegroups.com"
},
"version": "0.0.0",
"main": [
"polymer.min.js"
],
"gitHead": "d40e679c70baa0693fe59433fe928af913338544",
"readme": "Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform on modern browsers.\n\nFor Docs, License, Tests, and pre-packed downloads, see:\nhttp://www.polymer-project.org/\n\nMany thanks to our contributors:\nhttps://github.com/Polymer/platform/contributors\n",
"readmeFilename": "README.md",
"_id": "polymer@0.0.1",
"commit": "d40e679c70baa0693fe59433fe928af913338544",
"repository": {
"type": "git",
"url": "git://github.com/components/polymer.git"
}
}
\ No newline at end of file
BUILD LOG
---------
Build Time: 2013-06-17T12:21:13
NODEJS INFORMATION
==================
nodejs: v0.10.4
chai: 1.6.1
grunt: 0.4.1
grunt-audit: 0.0.0
grunt-contrib-uglify: 0.2.2
grunt-contrib-yuidoc: 0.4.0
grunt-karma-0.9.1: 0.4.3
karma: 0.9.2
karma-chrome-launcher: 0.0.1
karma-coffee-preprocessor: 0.0.1
karma-crbot-reporter: 0.0.3
karma-firefox-launcher: 0.0.2
karma-jasmine: 0.0.1
karma-mocha: 0.0.1
karma-phantomjs-launcher: 0.0.2
karma-requirejs: 0.0.1
karma-script-launcher: 0.0.1
mocha: 1.10.0
REPO REVISIONS
==============
polymer: 24689e70b7ebb122909d4ed17b53a2b2a455a2e5
platform: 569a80ae565a0ecbe01d3a79128021fdf1caaff7
ShadowDOM: 7a50b20542a559a0ffccb9e3a9330246a1f9fb5f
HTMLImports: 7960a892cd4461333809605c3806ba4da699b1f0
CustomElements: 76bfa07e8bfd5ed75c0c14682a66c40600e6baa0
PointerEvents: 9bab6b80e74fcbbdb4145286aae264ae54175419
PointerGestures: d6328b1e65daf111720f76b70f6d469a91a96335
mdv: aaa18dc0069764ec9f25654c949e4a1071f551f2
BUILD HASHES
============
polymer.min.js: 434cd0b3e8d81699760c75043ab6f7c5676db006
polymer.native.min.js: 4f966c0f59a909dd3ac28068f1164016551ec179
polymer.sandbox.min.js: 7444aba9341f62aeb0d89538ee033c77c16cd0e0
{
"name": "platform",
"repo": "components/platform",
"description": "Leverage the future of the web platform today.",
"homepage": "http://www.polymer-project.org/",
"keywords": [
"util",
"client",
"browser"
],
"author": "Polymer Authors <polymer-dev@googlegroups.com>",
"version": "0.0.1",
"main": "polymer.min.js",
"scripts": [
"polymer.min.js"
]
}
{
"name": "components/platform",
"description": "Leverage the future of the web platform today.",
"keywords": ["util", "client", "browser"],
"type": "component",
"homepage": "http://www.polymer-project.org/",
"support": {
"issues": "https://github.com/Polymer/platform/issues",
"source": "https://github.com/Polymer/platform"
},
"authors": [
{
"name": "Polymer Authors",
"email": "polymer-dev@googlegroups.com"
}
],
"extra": {
"component": {
"name": "polymer",
"scripts": [
"polymer.min.js"
],
"files": [
"src/**",
"platform/**",
"polymer.min.js",
"polymer.min.js.map",
"polymer.native.min.js",
"polymer.native.min.js.map"
],
"shim": {
"exports": "Platform"
}
}
}
}
{
"name": "components-polymer",
"description": "Leverage the future of the web platform today.",
"homepage": "http://www.polymer-project.org/",
"keywords": [
"util",
"client",
"browser"
],
"author": "Polymer Authors <polymer-dev@googlegroups.com>",
"repository": {
"type": "git",
"url": "git://github.com/Polymer/platform.git"
},
"main": "polymer.min.js",
"version": "0.0.1",
"devDependencies": {
"mocha": "*",
"chai": "*",
"grunt": "*",
"grunt-contrib-concat": "*",
"grunt-contrib-uglify": "*",
"grunt-contrib-yuidoc": "~0.4.0",
"grunt-karma-0.9.1": "~0.4.3",
"karma-mocha": "*",
"karma-script-launcher": "*",
"karma-crbot-reporter": "*"
}
}
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
var thisFile = 'polymer.js';
var scopeName = 'Polymer';
var modules = [
'src/platform.min.js',
'src/lang.js',
'src/oop.js',
'src/register.js',
'src/base.js',
'src/trackObservers.js',
'src/bindProperties.js',
'src/bindMDV.js',
'src/attrs.js',
'src/marshal.js',
'src/events.js',
'src/observeProperties.js',
'src/styling.js',
'src/shimStyling.js',
'src/path.js',
'src/job.js',
'src/boot.js'
];
// export
window[scopeName] = {
entryPointName: thisFile,
modules: modules
};
// bootstrap
var script = document.querySelector('script[src*="' + thisFile + '"]');
var src = script.attributes.src.value;
var basePath = src.slice(0, src.indexOf(thisFile));
console.log(src);
modules.forEach(function(m) {
document.write('<script src="' + basePath + m + '"></script>');
});
})();
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.
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// imports
var bindPattern = Polymer.bindPattern;
// constants
var published$ = '__published';
var attributes$ = 'attributes';
var attrProps$ = 'publish';
//var attrProps$ = 'attributeDefaults';
function publishAttributes(element, prototype) {
publishAttributesAttributes(element, prototype);
publishInstanceAttributes(element, prototype);
}
function publishAttributesAttributes(inElement, inPrototype) {
var published = {};
// merge attribute names from 'attributes' attribute
var attributes = inElement.getAttribute(attributes$);
if (attributes) {
// attributes='a b c' or attributes='a,b,c'
var names = attributes.split(attributes.indexOf(',') >= 0 ? ',' : ' ');
// record each name for publishing
names.forEach(function(p) {
p = p.trim();
if (p) {
published[p] = null;
}
});
}
// our suffix prototype chain (inPrototype is 'own')
var inherited = inElement.options.prototype;
// install 'attributes' as properties on the prototype, but don't
// override
Object.keys(published).forEach(function(p) {
if (!(p in inPrototype) && !(p in inherited)) {
inPrototype[p] = published[p];
}
});
// acquire properties published imperatively
var imperative = inPrototype[attrProps$];
if (imperative) {
// install imperative properties, overriding defaults
Object.keys(imperative).forEach(function(p) {
inPrototype[p] = imperative[p];
});
// combine declaratively and imperatively published properties
published = Platform.mixin(published, imperative);
}
// combine with inherited published properties
inPrototype[published$] = Platform.mixin(
{},
inherited[published$],
published
);
}
function publishInstanceAttributes(element, prototype) {
// our suffix prototype chain (prototype is 'own')
var inherited = element.options.prototype;
var attributes = element.attributes;
var a$ = prototype.instanceAttributes =
Object.create(inherited.instanceAttributes || null);
for (var i=0, l=attributes.length, a; (i<l) && (a=attributes[i]); i++) {
if (!publishInstanceAttributes.blackList[a.name]) {
if (a.name.slice(0, 3) !== 'on-') {
a$[a.name] = a.value;
}
}
}
}
publishInstanceAttributes.blackList = {name: 1, 'extends': 1, constructor: 1};
publishInstanceAttributes.blackList[attributes$] = 1;
function installInstanceAttributes() {
var a$ = this.instanceAttributes;
Object.keys(a$).forEach(function(name) {
this.setAttribute(name, a$[name]);
}, this);
}
function takeAttributes() {
// for each attribute
forEach(this.attributes, function(a) {
// try to match this attribute to a property (attributes are
// all lower-case, so this is case-insensitive search)
var name = propertyForAttribute.call(this, a.name);
if (name) {
// filter out 'mustached' values, these are to be
// replaced with bound-data and are not yet values
// themselves
if (a.value.search(bindPattern) >= 0) {
return;
}
// get original value
var defaultValue = this[name];
// deserialize Boolean or Number values from attribute
var value = deserializeValue(a.value, defaultValue);
// only act if the value has changed
if (value !== defaultValue) {
// install new value (has side-effects)
this[name] = value;
}
}
}, this);
}
// return the published property matching name, or undefined
function propertyForAttribute(name) {
// matchable properties must be published
var properties = Object.keys(this[published$]);
// search for a matchable property
return properties[properties.map(lowerCase).indexOf(name.toLowerCase())];
}
var lowerCase = String.prototype.toLowerCase.call.bind(
String.prototype.toLowerCase);
var typeHandlers = {
'string': function(value) {
return value;
},
'date': function(value) {
return new Date(Date.parse(value) || Date.now());
},
'boolean': function(value) {
if (value === '') {
return true;
}
return value === 'false' ? false : !!value;
},
'number': function(value) {
var floatVal = parseFloat(value);
return (String(floatVal) === value) ? floatVal : value;
},
'object': function(value, defaultValue) {
if (!defaultValue) {
return value;
}
try {
// If the string is an object, we can parse is with the JSON library.
// include convenience replace for single-quotes. If the author omits
// quotes altogether, parse will fail.
return JSON.parse(value.replace(/'/g, '"'));
} catch(e) {
// The object isn't valid JSON, return the raw value
return value;
}
}
};
function deserializeValue(value, defaultValue) {
// attempt to infer type from default value
var inferredType = typeof defaultValue;
if (defaultValue instanceof Date) {
inferredType = 'date';
}
return typeHandlers[inferredType](value, defaultValue);
}
// exports
Polymer.takeAttributes = takeAttributes;
Polymer.publishAttributes = publishAttributes;
Polymer.propertyForAttribute = propertyForAttribute;
Polymer.installInstanceAttributes = installInstanceAttributes;
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/**
* @module Polymer
*/
/**
* Base methods for Polymer elements.
* @class Base
*/
(function(scope) {
// imports
var log = window.logFlags || {};
var base = {
/**
* When called in a method, invoke the method's super.
* @method super
* @param {Array} Array of arguments.
*/
super: Polymer.$super,
/**
* True if this object is a Polymer element.
* @property isPolymerElement
* @type boolean
*/
isPolymerElement: true,
/**
* MDV bind.
* @method bind
*/
bind: function() {
Polymer.bind.apply(this, arguments);
},
/**
* MDV unbind.
* @method unbind
*/
unbind: function() {
Polymer.unbind.apply(this, arguments);
},
/**
* MDV unbindAll.
* @method unbindAll
*/
unbindAll: function() {
Polymer.unbindAll.apply(this, arguments);
},
/**
* Ensures MDV bindings persist.
*
* Typically, it's not necessary to call this method. Polymer
* automatically manages bindings when elements are inserted
* and removed from the document.
*
* However, if an element is created and not inserted into the document,
* cancelUnbindAll should be called to ensure bindings remain active.
* Otherwise bindings will be removed so that the element
* may be garbage collected, freeing the memory it uses. Please note that
* if cancelUnbindAll is called and the element is not inserted
* into the document, then unbindAll or asyncUnbindAll must be called
* to dispose of the element.
*
* @method cancelUnbindAll
* @param {Boolean} [preventCascade] If true, cancelUnbindAll will not
* cascade to shadowRoot children. In the case described above,
* and in general in application code, this should not be set to true.
*/
cancelUnbindAll: function(preventCascade) {
Polymer.cancelUnbindAll.apply(this, arguments);
},
/**
* Schedules MDV bindings to be removed asynchronously.
*
* Typically, it's not necessary to call this method. Polymer
* automatically manages bindings when elements are inserted
* and removed from the document.
*
* However, if an element is created and not inserted into the document,
* cancelUnbindAll should be called to ensure bindings remain active.
* Otherwise bindings will be removed so that the element
* may be garbage collected, freeing the memory it uses. Please note that
* if cancelUnbindAll is called and the element is not inserted
* into the document, then unbindAll or asyncUnbindAll must be called
* to dispose of the element.
*
* @method asyncUnbindAll
*/
asyncUnbindAll: function() {
Polymer.asyncUnbindAll.apply(this, arguments);
},
/**
* Schedules an async job with timeout and returns a handle.
* @method job
* @param {Polymer.Job} [job] A job handle if re-registering.
* @param {Function} callback Function to invoke.
* @param {number} [wait] Timeout in milliseconds.
* @return {Polymer.Job} A job handle which can be used to
* re-register, cancel or complete a job.
*/
job: function() {
return Polymer.job.apply(this, arguments);
},
/**
* Invokes a function asynchronously. The context of the callback
* function is bound to 'this' automatically.
* @method asyncMethod
* @param {Function} method
* @param {Object|Array} args
* @param {number} timeout
*/
asyncMethod: function(inMethod, inArgs, inTimeout) {
// when polyfilling Object.observe, ensure changes
// propagate before executing the async method
Platform.flush();
var args = (inArgs && inArgs.length) ? inArgs : [inArgs];
var fn = function() {
(this[inMethod] || inMethod).apply(this, args);
}.bind(this);
return inTimeout ? window.setTimeout(fn, inTimeout) :
requestAnimationFrame(fn);
},
/**
* Invoke a method.
* @method dispatch
* @param {string} methodName A method name.
* @param {Array} arguments
*/
dispatch: function(inMethodName, inArguments) {
if (this[inMethodName]) {
this[inMethodName].apply(this, inArguments);
}
},
/**
* Fire an event.
* @method fire
* @param {string} type An event name.
* @param detail
* @param {Node} toNode Target node.
*/
fire: function(inType, inDetail, inToNode) {
var node = inToNode || this;
log.events && console.log('[%s]: sending [%s]', node.localName, inType);
node.dispatchEvent(
new CustomEvent(inType, {bubbles: true, detail: inDetail}));
return inDetail;
},
/**
* Fire an event asynchronously.
* @method asyncFire
* @param {string} type An event name.
* @param detail
* @param {Node} toNode Target node.
*/
asyncFire: function(/*inType, inDetail*/) {
this.asyncMethod("fire", arguments);
},
/**
* Remove class from old, add class to anew, if they exist
* @param classFollows
* @param anew A node.
* @param old A node
* @param className
*/
classFollows: function(anew, old, className) {
if (old) {
old.classList.remove(className);
}
if (anew) {
anew.classList.add(className);
}
}
};
// TODO(sjmiles): backward-compat for deprecated syntax
base.send = base.fire;
base.asend = base.asyncFire;
// exports
scope.base = base;
})(window.Polymer);
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// imports
var log = window.logFlags || {};
// use the ExperssionSyntax
var expressionSyntax = new ExpressionSyntax;
// bind tracking
var bindings = new SideTable();
function registerBinding(element, name, path) {
var b$ = bindings.get(element);
if (!b$) {
bindings.set(element, b$ = {});
}
b$[name.toLowerCase()] = path;
}
function unregisterBinding(element, name) {
var b$ = bindings.get(element);
if (b$) {
delete b$[name.toLowerCase()];
}
}
function overrideBinding(ctor) {
var proto = ctor.prototype;
var originalBind = proto.bind;
var originalUnbind = proto.unbind;
proto.bind = function(name, model, path) {
originalBind.apply(this, arguments);
// note: must do this last because mdv may unbind before binding
registerBinding(this, name, path);
}
proto.unbind = function(name) {
originalUnbind.apply(this, arguments);
unregisterBinding(this, name);
}
};
[Node, Element, Text, HTMLInputElement].forEach(overrideBinding);
var emptyBindings = {};
function getBindings(element) {
return element && bindings.get(element) || emptyBindings;
}
function getBinding(element, name) {
return getBindings(element)[name.toLowerCase()];
}
// model bindings
function bind(name, model, path) {
var property = Polymer.propertyForAttribute.call(this, name);
if (property) {
registerBinding(this, property, path);
Polymer.registerObserver(this, 'binding', property,
Polymer.bindProperties(this, property, model, path)
);
} else {
HTMLElement.prototype.bind.apply(this, arguments);
}
}
function unbind(name) {
if (!Polymer.unregisterObserver(this, 'binding', name)) {
HTMLElement.prototype.unbind.apply(this, arguments);
}
}
function unbindAll() {
if (!isElementUnbound(this)) {
Polymer.unregisterObserversOfType(this, 'property');
HTMLElement.prototype.unbindAll.apply(this, arguments);
// unbind shadowRoot, whee
unbindNodeTree(this.webkitShadowRoot, true);
markElementUnbound(this);
}
}
function unbindNodeTree(node, olderShadows) {
forNodeTree(node, olderShadows, function(n) {
if (n.unbindAll) {
n.unbindAll();
}
});
}
function forNodeTree(node, olderShadows, callback) {
if (!node) {
return;
}
callback(node);
if (olderShadows && node.olderShadowRoot) {
forNodeTree(node.olderShadowRoot, olderShadows, callback);
}
for (var child = node.firstChild; child; child = child.nextSibling) {
forNodeTree(child, olderShadows, callback);
}
}
// binding state tracking
var unboundTable = new SideTable();
function markElementUnbound(element) {
unboundTable.set(element, true);
}
function isElementUnbound(element) {
return unboundTable.get(element);
}
// asynchronous binding management
var unbindAllJobTable = new SideTable();
function asyncUnbindAll() {
if (!isElementUnbound(this)) {
log.bind && console.log('asyncUnbindAll', this.localName);
unbindAllJobTable.set(this, this.job(unbindAllJobTable.get(this),
this.unbindAll));
}
}
function cancelUnbindAll(preventCascade) {
if (isElementUnbound(this)) {
log.bind && console.warn(this.localName,
'is unbound, cannot cancel unbindAll');
return;
}
log.bind && console.log('cancelUnbindAll', this.localName);
var unbindJob = unbindAllJobTable.get(this);
if (unbindJob) {
unbindJob.stop();
unbindAllJobTable.set(this, null);
}
// cancel unbinding our shadow tree iff we're not in the process of
// cascading our tree (as we do, for example, when the element is inserted).
if (!preventCascade) {
forNodeTree(this.webkitShadowRoot, true, function(n) {
if (n.cancelUnbindAll) {
n.cancelUnbindAll();
}
});
}
}
// bind arbitrary html to a model
function parseAndBindHTML(html, model) {
var template = document.createElement('template');
template.innerHTML = html;
return template.createInstance(model, expressionSyntax);
}
var mustachePattern = /\{\{([^{}]*)}}/;
// exports
Polymer.bind = bind;
Polymer.unbind = unbind;
Polymer.unbindAll = unbindAll;
Polymer.getBinding = getBinding;
Polymer.asyncUnbindAll = asyncUnbindAll;
Polymer.cancelUnbindAll = cancelUnbindAll;
Polymer.isElementUnbound = isElementUnbound;
Polymer.unbindNodeTree = unbindNodeTree;
Polymer.parseAndBindHTML = parseAndBindHTML;
Polymer.bindPattern = mustachePattern;
Polymer.expressionSyntax = expressionSyntax;
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
var log = window.logFlags || {};
// bind a property in A to a path in B by converting A[property] to a
// getter/setter pair that accesses B[...path...]
function bindProperties(inA, inProperty, inB, inPath) {
log.bind && console.log("[%s]: bindProperties: [%s] to [%s].[%s]",
inB.localName || 'object', inPath, inA.localName, inProperty);
// capture A's value if B's value is null or undefined,
// otherwise use B's value
var v = PathObserver.getValueAtPath(inB, inPath);
if (v === null || v === undefined) {
PathObserver.setValueAtPath(inB, inPath, inA[inProperty]);
}
return PathObserver.defineProperty(inA, inProperty, {object: inB, path: inPath});
}
// exports
Polymer.bindProperties = bindProperties;
})();
\ No newline at end of file
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(scope) {
// FOUC prevention tactic
var style = document.createElement('style');
style.textContent = 'body {opacity: 0;}';
var head = document.querySelector('head');
head.insertBefore(style, head.firstChild);
window.addEventListener('WebComponentsReady', function() {
document.body.style.webkitTransition = 'opacity 0.3s';
document.body.style.opacity = 1;
});
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
if (!window.Polymer) {
window.Polymer = {};
}
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// imports
var log = window.logFlags || {};
//
// automagic event mapping
//
var prefix = "on-";
var parseHostEvents = function(inAttributes, inPrototype) {
// inDefinition.eventDelegates = Platform.mixin(inDefinition.eventDelegates,
// parseEvents(inAttributes));
inPrototype.eventDelegates = parseEvents(inAttributes);
};
var parseEvents = function(inAttributes) {
var events = {};
if (inAttributes) {
for (var i=0, a; a=inAttributes[i]; i++) {
if (a.name.slice(0, prefix.length) == prefix) {
events[a.name.slice(prefix.length)] = a.value;
}
}
}
return events;
};
var accumulateEvents = function(inNode, inEvents) {
var events = inEvents || {};
accumulateNodeEvents(inNode, events);
accumulateChildEvents(inNode, events);
accumulateTemplatedEvents(inNode, events);
return events;
};
var accumulateNodeEvents = function(inNode, inEvents) {
var a$ = inNode.attributes;
if (a$) {
for (var i=0, a; (a=a$[i]); i++) {
if (a.name.slice(0, prefix.length) === prefix) {
accumulateEvent(a.name.slice(prefix.length), inEvents);
}
}
}
};
var event_translations = {
webkitanimationstart: 'webkitAnimationStart',
webkitanimationend: 'webkitAnimationEnd',
webkittransitionend: 'webkitTransitionEnd',
domfocusout: 'DOMFocusOut',
domfocusin: 'DOMFocusIn'
};
var accumulateEvent = function(inName, inEvents) {
var n = event_translations[inName] || inName;
inEvents[n] = 1;
};
var accumulateChildEvents = function(inNode, inEvents) {
var cn$ = inNode.childNodes;
for (var i=0, n; (n=cn$[i]); i++) {
// TODO(sjmiles): unify calling convention (.call or not .call)
accumulateEvents(n, inEvents);
//if (n.$protected) {
// accumulateHostEvents.call(n.$protected, inEvents);
//}
}
};
var accumulateTemplatedEvents = function(inNode, inEvents) {
if (inNode.localName == 'template') {
var content = getTemplateContent(inNode);
if (content) {
accumulateChildEvents(content, inEvents);
}
}
}
// TODO(sorvell): Currently in MDV, there is no way to get a template's
// effective content. A template can have a ref property
// that points to the template from which this one has been cloned.
// Remove this when the MDV api is improved
// (https://github.com/polymer-project/mdv/issues/15).
var getTemplateContent = function(inTemplate) {
return inTemplate.ref ? inTemplate.ref.content : inTemplate.content;
}
var accumulateHostEvents = function(inEvents) {
var events = inEvents || {};
// specifically search the __proto__ (as opposed to getPrototypeOf)
// __proto__ is simulated on platforms which don't support it naturally
// TODO(sjmiles): we walk the prototype tree to operate on the union of
// eventDelegates maps; it might be better to merge maps when extending
var p = this.__proto__;
while (p && p !== HTMLElement.prototype) {
if (p.hasOwnProperty('eventDelegates')) {
for (var n in p.eventDelegates) {
accumulateEvent(n, events);
}
}
p = p.__proto__;
}
return events;
};
function bindAccumulatedEvents(inNode, inEvents, inListener) {
var fn = inListener.bind(this);
for (var n in inEvents) {
log.events && console.log('[%s] bindAccumulatedEvents: addEventListener("%s", listen)', inNode.localName || 'root', n);
inNode.addEventListener(n, fn);
}
};
// host events should be listened for on a host element
function bindAccumulatedHostEvents(inEvents) {
bindAccumulatedEvents.call(this, this, inEvents, listenHost);
}
// local events should be listened for on a shadowRoot
function bindAccumulatedLocalEvents(inNode, inEvents) {
bindAccumulatedEvents.call(this, inNode, inEvents, listenLocal);
}
// experimental delegating declarative event handler
// TODO(sjmiles):
// we wanted to simply look for nearest ancestor
// with a 'controller' property to be WLOG
// but we need to honor ShadowDOM, so we had to
// customize this search
var findController = function(inNode) {
// find the shadow root that contains inNode
var n = inNode;
while (n.parentNode && n.localName !== 'shadow-root') {
n = n.parentNode;
}
return n.host;
};
var dispatch = function(inNode, inHandlerName, inArguments) {
if (inNode) {
log.events && console.group('[%s] dispatch [%s]', inNode.localName, inHandlerName);
inNode.dispatch(inHandlerName, inArguments);
log.events && console.groupEnd();
}
};
function listenLocal(inEvent) {
if (inEvent.cancelBubble) {
return;
}
inEvent.on = prefix + inEvent.type;
log.events && console.group("[%s]: listenLocal [%s]", this.localName,
inEvent.on);
if (!inEvent.path || window.ShadowDOMPolyfill) {
listenLocalNoEventPath(inEvent);
} else {
var c = null;
Array.prototype.some.call(inEvent.path, function(t) {
if (t === this) {
return true;
}
c = c === this ? c : findController(t);
if (c) {
if (handleEvent.call(c, t, inEvent)) {
return true;
}
}
}, this);
}
log.events && console.groupEnd();
}
// TODO(sorvell): remove when ShadowDOM polyfill supports event path.
// Note that findController will not return the expected
// controller when when the event target is a distributed node.
// This because we cannot traverse from a composed node to a node
// in shadowRoot.
// This will be addressed via an event path api
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
function listenLocalNoEventPath(inEvent) {
log.events && console.log('event.path() not supported for', inEvent.type);
var t = inEvent.target, c = null;
while (t && t != this) {
c = c === this ? c : findController(t);
if (c) {
if (handleEvent.call(c, t, inEvent)) {
return;
}
}
t = t.parentNode;
}
}
function listenHost(inEvent) {
if (inEvent.cancelBubble) {
return;
}
log.events && console.group("[%s]: listenHost [%s]", this.localName, inEvent.type);
handleHostEvent.call(this, this, inEvent);
log.events && console.groupEnd();
}
var eventHandledTable = new SideTable('handledList');
function getHandledListForEvent(inEvent) {
var handledList = eventHandledTable.get(inEvent);
if (!handledList) {
handledList = [];
eventHandledTable.set(inEvent, handledList);
}
return handledList;
}
function handleEvent(inNode, inEvent) {
if (inNode.attributes) {
var handledList = getHandledListForEvent(inEvent);
if (handledList.indexOf(inNode) < 0) {
handledList.push(inNode);
var h = inNode.getAttribute(inEvent.on);
if (h) {
log.events && console.log('[%s] found handler name [%s]', this.localName, h);
dispatch(this, h, [inEvent, inEvent.detail, inNode]);
}
}
}
return inEvent.cancelBubble;
};
function handleHostEvent(inNode, inEvent) {
var h = findHostHandler.call(inNode, inEvent.type);
if (h) {
log.events && console.log('[%s] found host handler name [%s]', inNode.localName, h);
dispatch(inNode, h, [inEvent, inEvent.detail, inNode]);
}
return inEvent.cancelBubble;
};
// find the method name (handler) in eventDelegates mapped to inEventName
var findHostHandler = function(inEventName) {
// TODO(sjmiles): walking the tree again is inefficient; combine with code
// in accumulateHostEvents into something more sane
var p = this;
while (p) {
if (p.hasOwnProperty('eventDelegates')) {
var h = p.eventDelegates[inEventName]
|| p.eventDelegates[inEventName.toLowerCase()];
if (h) {
return h;
}
}
p = p.__proto__;
}
};
// exports
Polymer.parseHostEvents = parseHostEvents;
Polymer.accumulateEvents = accumulateEvents;
Polymer.accumulateHostEvents = accumulateHostEvents;
Polymer.bindAccumulatedHostEvents = bindAccumulatedHostEvents;
Polymer.bindAccumulatedLocalEvents = bindAccumulatedLocalEvents;
})();
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// usage
// invoke cb.call(this) in 100ms, unless the job is re-registered,
// which resets the timer
//
// this.myJob = this.job(this.myJob, cb, 100)
//
// returns a job handle which can be used to re-register a job
var Job = function(inContext) {
this.context = inContext;
};
Job.prototype = {
go: function(inCallback, inWait) {
this.callback = inCallback;
this.handle = setTimeout(function() {
this.handle = null;
inCallback.call(this.context);
}.bind(this), inWait);
},
stop: function() {
if (this.handle) {
clearTimeout(this.handle);
this.handle = null;
}
},
complete: function() {
if (this.handle) {
this.stop();
this.callback.call(this.context);
}
}
};
function job(inJob, inCallback, inWait) {
var job = inJob || new Job(this);
job.stop();
job.go(inCallback, inWait);
return job;
}
Polymer.job = job;
})();
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
// exports
window.forEach = forEach;
})();
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
//
// reference marshalling
//
// locate nodes with id and store references to them in this.$ hash
Polymer.marshalNodeReferences = function(inRoot) {
// establish $ instance variable
var $ = this.$ = this.$ || {};
// populate $ from nodes with ID from the LOCAL tree
if (inRoot) {
var nodes = inRoot.querySelectorAll("[id]");
forEach(nodes, function(n) {
$[n.id] = n;
});
}
};
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
//
// automagic property observation side effects
// this implementation uses MDV polyfill
//
// imports
var log = window.logFlags || {};
var OBSERVE_SUFFIX = 'Changed';
function observeProperties() {
for (var p in this) {
observeProperty.call(this, p);
}
}
function observeProperty(inName) {
if (isObservable.call(this, inName)) {
log.observe && console.log('[' + this.localName + '] watching [' + inName + ']');
var observer = new PathObserver(this, inName, function(inNew, inOld) {
log.data && console.log('[%s#%s] watch: [%s] now [%s] was [%s]', this.localName, this.node.id || '', inName, this[inName], inOld);
propertyChanged.call(this, inName, inOld);
}.bind(this));
Polymer.registerObserver(this, 'property', inName, observer);
}
}
function isObservable(inName) {
return (inName[0] != '_')
&& !(inName in Object.prototype)
&& Boolean(this[inName + OBSERVE_SUFFIX]);
}
function propertyChanged(inName, inOldValue) {
//log.data && console.log('[%s#%s] propertyChanged: [%s] now [%s] was [%s]', this.node.localName, this.node.id || '', inName, this[inName], inOldValue);
var fn = inName + OBSERVE_SUFFIX;
if (this[fn]) {
this[fn](inOldValue);
}
}
// exports
Polymer.observeProperties = observeProperties;
})();
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(scope) {
// super
// TODO(sjmiles):
// $super must be installed on an instance or prototype chain
// as `super`, and invoked via `this`, e.g.
// `this.super();`
// will not work if function objects are not unique, for example,
// when using mixins.
// The memoization strategy assumes each function exists on only one
// prototype chain i.e. we use the function object for memoizing)
// perhaps we can bookkeep on the prototype itself instead
function $super(inArgs) {
// since we are thunking a method call, performance is important here:
// memoize all lookups, once memoized the fast path calls no other
// functions
//
// find the caller (cannot be `strict` because of 'caller')
var caller = $super.caller;
// memoization for 'name of method'
var nom = caller.nom;
if (!nom) {
nom = nameInThis.call(this, caller);
}
if (!nom) {
console.warn('called super() on a method not installed declaratively (has no .nom property)');
}
// super prototype is either cached or we have to find it
// by searching __proto__ (at the 'top')
if (!('_super' in caller)) {
memoizeSuper(caller, nom, Object.getPrototypeOf(this));
}
// memoized next implementation prototype
var _super = caller._super;
if (!_super) {
// if _super is falsey, there is no super implementation
//console.warn('called $super(' + nom + ') where there is no super implementation');
} else {
// our super function
var fn = _super[nom];
// memoize information so 'fn' can call 'super'
if (!('_super' in fn)) {
memoizeSuper(fn, nom, _super);
}
// invoke the inherited method
// if 'fn' is not function valued, this will throw
return fn.apply(this, inArgs || []);
}
};
function nextSuper(inProto, inName, inCaller) {
// look for an inherited prototype that implements inName
var proto = inProto;
while (proto &&
(!proto.hasOwnProperty(inName) || proto[inName] == inCaller)) {
proto = Object.getPrototypeOf(proto);
}
return proto;
};
function memoizeSuper(inMethod, inName, inProto) {
// find and cache next prototype containing inName
// we need the prototype so we can another lookup
// from here
inMethod._super = nextSuper(inProto, inName, inMethod);
if (inMethod._super) {
// _super is a prototype, the actual method is _super[inName]
// tag super method with it's name for further lookups
inMethod._super[inName]._nom = inName;
}
};
function nameInThis(inValue) {
console.group('nameInThis');
var p = this;
while (p && p !== HTMLElement.prototype) {
var n$ = Object.getOwnPropertyNames(p);
for (var i=0, l=n$.length, n; i<l && (n=n$[i]); i++) {
console.log(n);
var d = Object.getOwnPropertyDescriptor(p, n);
if (d.value == inValue) {
return n;
}
}
p = Object.getPrototypeOf(p);
}
console.groupEnd('nameInThis');
}
// exports
// `super` is a reserved word
scope.$super = $super;
})(Polymer);
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
function addResolvePath(inPrototype, inElement) {
var root = calcElementPath(inElement);
inPrototype.resolvePath = function(inPath) {
return root + inPath;
}
}
function urlToPath(inUrl) {
if (inUrl) {
var parts = inUrl.split('/');
parts.pop();
parts.push('');
return parts.join('/');
} else {
return '';
}
}
function calcElementPath(inElement) {
return urlToPath(HTMLImports.getDocumentUrl(inElement.ownerDocument));
}
// exports
Polymer.addResolvePath = addResolvePath;
})();
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
(function(global) {
'use strict';
function Scope() {}
var bindPattern = /([\w\.\$]*)[\s]+as[\s]+([\w]*)/;
var repeatPattern = /([\w]*)[\s]+in[\s]+([\w\.\$]*)/;
function createBindRepeatBinding(model, path, name, node) {
var scopeName, scopePath;
var match = path.match(repeatPattern);
if (match) {
scopeName = match[1];
scopePath = match[2];
} else {
match = path.match(bindPattern);
if (match) {
scopeName = match[2];
scopePath = match[1];
} else {
return;
}
}
var binding = new CompoundBinding(function(values) {
return values['value'];
});
binding.bind('value', model, scopePath);
templateScopeTable.set(node, { model: model, scope: scopeName });
return binding;
}
function createStringIfTruthyBinding(model, className, path) {
var binding = new CompoundBinding(function(values) {
return values['value'] ? className : '';
});
binding.bind('value', model, path);
return binding;
}
var templateScopeTable = new SideTable;
HTMLTemplateElement.syntax['Polymer'] = {
getBinding: function(model, path, name, node) {
if (node.nodeType === Node.ELEMENT_NODE &&
(name === 'bind' || name === 'repeat') &&
node.tagName === 'TEMPLATE') {
return createBindRepeatBinding(model, path, name, node);
}
// {{ string: path }}
var match = path.match(/([\w]+):[\W]*([\w\.\$]*)/);
if (match)
return createStringIfTruthyBinding(model, match[1], match[2]);
},
getInstanceModel: function(template, model) {
var scopeInfo = templateScopeTable.get(template);
if (!scopeInfo)
return model;
var scope;
if (scopeInfo.model) { // instanceof Scope) {
scope = Object.create(scopeInfo.model);
} else {
scope = new Scope();
}
scope[scopeInfo.scope] = model;
return scope;
}
};
})(this);
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function(scope) {
// imports
var log = window.logFlags || {};
// api
function register(inElement, inPrototype) {
// in the main document, parser runs script in <element> tags in the wrong
// context, filter that out here
if (inElement == window) {
return;
}
// catch common mistake of omitting 'this' in call to register
if (!inElement || !(inElement instanceof HTMLElement)) {
throw "First argument to Polymer.register must be an HTMLElement";
}
// TODO(sjmiles): it's not obvious at this point whether inElement
// will chain to another polymer element, so we just copy base boilerplate
// anyway
// this can result in multiple copies of boilerplate methods on a custom
// element chain, which is inefficient and has ramifications for 'super'
// also, we don't yet support intermediate prototypes in calls to
// HTMLElementElement.prototype.register, so we have to use mixin
var prototype = Platform.mixin({}, scope.base, inPrototype);
// capture defining element
prototype.elementElement = inElement;
// TODO(sorvell): install a helper method this.resolvePath to aid in
// setting resource paths. e.g.
// this.$.image.src = this.resolvePath('images/foo.png')
// Potentially remove when spec bug is addressed.
// https://www.w3.org/Bugs/Public/show_bug.cgi?id=21407
scope.addResolvePath(prototype, inElement);
// install instance method that closes over 'inElement'
prototype.installTemplate = function() {
this.super();
staticInstallTemplate.call(this, inElement);
};
// hint the supercall mechanism
// TODO(sjmiles): make prototype extension api that does this
prototype.installTemplate.nom = 'installTemplate';
// install callbacks
prototype.readyCallback = readyCallback;
prototype.insertedCallback = insertedCallback;
prototype.removedCallback = removedCallback;
prototype.attributeChangedCallback = attributeChangedCallback;
// hint super call engine by tagging methods with names
hintSuper(prototype);
// parse declared on-* delegates into imperative form
scope.parseHostEvents(inElement.attributes, prototype);
// parse attribute-attributes
scope.publishAttributes(inElement, prototype);
// install external stylesheets as if they are inline
scope.installSheets(inElement);
scope.shimStyling(inElement);
// invoke element.register
inElement.register({prototype: prototype});
// logging
logFlags.comps &&
console.log("Polymer: element registered" + inElement.options.name);
};
function readyCallback() {
// invoke 'installTemplate' closure
this.installTemplate();
// invoke boilerplate 'instanceReady'
instanceReady.call(this);
};
function staticInstallTemplate(inElement) {
var template = inElement.querySelector('template');
if (template) {
// make a shadow root
var root = this.webkitCreateShadowRoot();
// TODO(sjmiles): must be settable ex post facto
root.applyAuthorStyles = this.applyAuthorStyles;
// TODO(sjmiles): override createShadowRoot to do this automatically
CustomElements.watchShadow(this);
// TODO(sorvell): host not set per spec; we set it for convenience
// so we can traverse from root to host.
root.host = this;
// parse and apply MDV bindings
// do this before being inserted to avoid {{}} in attribute values
// e.g. to prevent <img src="images/{{icon}}"> from generating a 404.
root.appendChild(template.createInstance(this, Polymer.expressionSyntax));
rootCreated.call(this, root);
return root;
}
};
function rootCreated(inRoot) {
// to resolve this node synchronously we must process CustomElements
// in the subtree immediately
CustomElements.takeRecords();
// parse and apply MDV bindings
// locate nodes with id and store references to them in this.$ hash
scope.marshalNodeReferences.call(this, inRoot);
// add local events of interest...
var rootEvents = scope.accumulateEvents(inRoot);
scope.bindAccumulatedLocalEvents.call(this, inRoot, rootEvents);
// set up gestures
PointerGestures.register(inRoot);
PointerEventsPolyfill.setTouchAction(inRoot,
this.getAttribute('touch-action'));
};
function instanceReady(inElement) {
// install property observation side effects
// do this first so we can observe changes during initialization
scope.observeProperties.call(this);
// install boilerplate attributes
scope.installInstanceAttributes.call(this);
// process input attributes
scope.takeAttributes.call(this);
// add host-events...
var hostEvents = scope.accumulateHostEvents.call(this);
scope.bindAccumulatedHostEvents.call(this, hostEvents);
// asynchronously unbindAll... will be cancelled if inserted
this.asyncUnbindAll();
// invoke user 'ready'
if (this.ready) {
this.ready();
}
};
function insertedCallback() {
this.cancelUnbindAll(true);
// invoke user 'inserted'
if (this.inserted) {
this.inserted();
}
}
function removedCallback() {
this.asyncUnbindAll();
// invoke user 'removed'
if (this.removed) {
this.removed();
}
}
function attributeChangedCallback() {
if (this.attributeChanged) {
this.attributeChanged.apply(this, arguments);
}
}
function hintSuper(prototype) {
Object.getOwnPropertyNames(prototype).forEach(function(n) {
var d = Object.getOwnPropertyDescriptor(prototype, n);
if (typeof d.value == 'function') {
d.value.nom = n;
}
});
}
// user utility
function findDistributedTarget(inTarget, inNodes) {
// find first ancestor of target (including itself) that
// is in inNodes, if any
var n = inTarget;
while (n && n != this) {
var i = Array.prototype.indexOf.call(inNodes, n);
if (i >= 0) {
return i;
}
n = n.parentNode;
}
}
// exports
scope.register = register;
scope.findDistributedTarget = findDistributedTarget;
scope.instanceReady = instanceReady;
})(Polymer);
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
/*
This is a limited shim for shadowDOM css styling.
https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/shadow/index.html#styles
The intention here is to support only the styling features which can be
relatively simply implemented. The goal is to allow users to avoid the
most obvious pitfalls and do so without compromising performance significantly.
For shadowDOM styling that's not covered here, a set of best practices
can be provided that should allow users to accomplish more complex styling.
The following is a list of specific shadowDOM styling features and a brief
discussion of the approach used to shim.
Shimmed features:
* @host: ShadowDOM allows styling of the shadowRoot's host element using the
@host rule. To shim this feature, the @host styles are reformatted and
prefixed with a given scope name and promoted to a document level stylesheet.
For example, given a scope name of .foo, a rule like this:
@host {
* {
background: red;
}
}
becomes:
.foo {
background: red;
}
* encapsultion: Styles defined within shadowDOM, apply only to
dom inside the shadowDOM. Polymer uses one of two techniques to imlement
this feature.
By default, rules are prefixed with the host element tag name
as a descendant selector. This ensures styling does not leak out of the 'top'
of the element's shadowDOM. For example,
div {
font-weight: bold;
}
becomes:
x-foo div {
font-weight: bold;
}
becomes:
Alternatively, if Polymer.strictPolyfillStyling is set to true then
selectors are scoped by adding an attribute selector suffix to each
simple selector that contains the host element tag name. Each element
in the element's shadowDOM template is also given the scope attribute.
Thus, these rules match only elements that have the scope attribute.
For example, given a scope name of x-foo, a rule like this:
div {
font-weight: bold;
}
becomes:
div[x-foo] {
font-weight: bold;
}
Note that elements that are dynamically added to a scope must have the scope
selector added to them manually.
* ::pseudo: These rules are converted to rules that take advantage of the
pseudo attribute. For example, a shadowRoot like this inside an x-foo
<div pseudo="x-special">Special</div>
with a rule like this:
x-foo::x-special { ... }
becomes:
x-foo [pseudo=x-special] { ... }
Unaddressed shadowDOM styling features:
* upper/lower bound encapsulation: Styles which are defined outside a
shadowRoot should not cross the shadowDOM boundary and should not apply
inside a shadowRoot.
This styling behavior is not emulated. Some possible ways to do this that
were rejected due to complexity and/or performance concerns include: (1) reset
every possible property for every possible selector for a given scope name;
(2) re-implement css in javascript.
As an alternative, users should make sure to use selectors
specific to the scope in which they are working.
* ::distributed: This behavior is not emulated. It's often not necessary
to style the contents of a specific insertion point and instead, descendants
of the host element can be styled selectively. Users can also create an
extra node around an insertion point and style that node's contents
via descendent selectors. For example, with a shadowRoot like this:
<style>
content::-webkit-distributed(div) {
background: red;
}
</style>
<content></content>
could become:
<style>
/ *@polyfill .content-container div * /
content::-webkit-distributed(div) {
background: red;
}
</style>
<div class="content-container">
<content></content>
</div>
Note the use of @polyfill in the comment above a shadowDOM specific style
declaration. This is a directive to the styling shim to use the selector
in comments in lieu of the next selector when running under polyfill.
*/
(function(scope) {
var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
var concat = Array.prototype.concat.call.bind(Array.prototype.concat);
var slice = Array.prototype.slice.call.bind(Array.prototype.slice);
var stylizer = {
hostRuleRe: /@host[^{]*{(([^}]*?{[^{]*?}[\s\S]*?)+)}/gim,
selectorRe: /([^{]*)({[\s\S]*?})/gim,
hostElementRe: /(.*)((?:\*)|(?:\:scope))(.*)/,
hostFixableRe: /^[.\[:]/,
cssCommentRe: /\/\*[^*]*\*+([^/*][^*]*\*+)*\//gim,
cssPolyfillCommentRe: /\/\*\s*@polyfill ([^*]*\*+([^/*][^*]*\*+)*\/)([^{]*?){/gim,
cssPseudoRe: /::(x-[^\s{,(]*)/gim,
selectorReSuffix: '([>\\s~+\[.,{:][\\s\\S]*)?$',
hostRe: /@host/gim,
cache: {},
shimStyling: function(element) {
if (window.ShadowDOMPolyfill && element) {
// use caching to make working with styles nodes easier and to facilitate
// lookup of extendee
var name = element.options.name;
stylizer.cacheDefinition(element);
stylizer.shimPolyfillDirectives(element.styles, name);
// find styles and apply shimming...
if (Polymer.strictPolyfillStyling) {
stylizer.applyScopeToContent(element.templateContent, name);
}
stylizer.applyShimming(stylizer.stylesForElement(element), name);
}
},
// Shim styles to be placed inside a shadowRoot.
// 1. shim @host rules and inherited @host rules
// 2. shim scoping: apply .scoped when available or pseudo-scoping when not
// (e.g. a selector 'div' becomes 'x-foo div')
shimShadowDOMStyling: function(styles, name) {
if (window.ShadowDOMPolyfill) {
stylizer.shimPolyfillDirectives(styles, name);
stylizer.applyShimming(styles, name);
}
},
applyShimming: function(styles, name) {
var cssText = this.shimAtHost(styles, name);
cssText += this.shimScoping(styles, name);
this.addCssToDocument(cssText);
},
cacheDefinition: function(element) {
var name = element.options.name;
var template = element.querySelector('template');
var content = template && templateContent(template);
var styles = content && content.querySelectorAll('style');
element.styles = styles ? slice(styles) : [];
element.templateContent = content;
stylizer.cache[name] = element;
},
applyScopeToContent: function(root, name) {
if (root) {
forEach(root.querySelectorAll('*'), function(node) {
node.setAttribute(name, '');
});
forEach(root.querySelectorAll('template'), function(template) {
this.applyScopeToContent(templateContent(template), name);
}, this);
}
},
stylesForElement: function(element) {
var styles = element.styles;
var shadow = element.templateContent &&
element.templateContent.querySelector('shadow');
if (shadow || (element.templateContent === null)) {
var extendee = this.findExtendee(element.options.name);
if (extendee) {
var extendeeStyles = this.stylesForElement(extendee);
styles = concat(slice(extendeeStyles), slice(styles));
}
}
return styles;
},
findExtendee: function(name) {
var element = this.cache[name];
return element && this.cache[element.options.extends];
},
/*
* Process styles to convert native ShadowDOM rules that will trip
* up the css parser; we rely on decorating the stylesheet with comments.
*
* For example, we convert this rule:
*
* (comment start) @polyfill @host g-menu-item (comment end)
* shadow::-webkit-distributed(g-menu-item) {
*
* to this:
*
* scopeName g-menu-item {
*
**/
shimPolyfillDirectives: function(styles, name) {
if (window.ShadowDOMPolyfill) {
if (styles) {
forEach(styles, function(s) {
s.textContent = this.convertPolyfillDirectives(s.textContent, name);
}, this);
}
}
},
// form: @host { .foo { declarations } }
// becomes: scopeName.foo { declarations }
shimAtHost: function(styles, name) {
if (styles) {
return this.convertAtHostStyles(styles, name);
}
},
/* Ensure styles are scoped. Pseudo-scoping takes a rule like:
*
* .foo {... }
*
* and converts this to
*
* scopeName .foo { ... }
*/
shimScoping: function(styles, name) {
if (styles) {
return this.convertScopedStyles(styles, name);
}
},
convertPolyfillDirectives: function(cssText, name) {
var r = '', l = 0, matches, selector;
while (matches=this.cssPolyfillCommentRe.exec(cssText)) {
r += cssText.substring(l, matches.index);
// remove end comment delimiter (*/)
selector = matches[1].slice(0, -2).replace(this.hostRe, name);
r += this.scopeSelector(selector, name) + '{';
l = this.cssPolyfillCommentRe.lastIndex;
}
r += cssText.substring(l, cssText.length);
return r;
},
// consider styles that do not include component name in the selector to be
// unscoped and in need of promotion;
// for convenience, also consider keyframe rules this way.
findAtHostRules: function(cssRules, matcher) {
return Array.prototype.filter.call(cssRules,
this.isHostRule.bind(this, matcher));
},
isHostRule: function(matcher, cssRule) {
return (cssRule.selectorText && cssRule.selectorText.match(matcher)) ||
(cssRule.cssRules && this.findAtHostRules(cssRule.cssRules, matcher).length) ||
(cssRule.type == CSSRule.WEBKIT_KEYFRAMES_RULE);
},
convertAtHostStyles: function(styles, name) {
var cssText = this.stylesToCssText(styles);
var r = '', l=0, matches;
while (matches=this.hostRuleRe.exec(cssText)) {
r += cssText.substring(l, matches.index);
r += this.scopeHostCss(matches[1], name);
l = this.hostRuleRe.lastIndex;
}
r += cssText.substring(l, cssText.length);
var selectorRe = new RegExp('^' + name + this.selectorReSuffix, 'm');
var cssText = this.rulesToCss(this.findAtHostRules(this.cssToRules(r),
selectorRe));
return cssText;
},
scopeHostCss: function(cssText, name) {
var r = '', matches;
while (matches = this.selectorRe.exec(cssText)) {
r += this.scopeHostSelector(matches[1], name) +' ' + matches[2] + '\n\t';
}
return r;
},
// supports scopig by name and [is=name] syntax
scopeHostSelector: function(selector, name) {
var r = [], parts = selector.split(','), is = '[is=' + name + ']';
parts.forEach(function(p) {
p = p.trim();
// selector: *|:scope -> name
if (p.match(this.hostElementRe)) {
p = p.replace(this.hostElementRe, name + '$1$3, ' + is + '$1$3');
// selector: .foo -> name.foo, [bar] -> name[bar]
} else if (p.match(this.hostFixableRe)) {
p = name + p + ', ' + is + p;
}
r.push(p);
}, this);
return r.join(', ');
},
convertScopedStyles: function(styles, name) {
forEach(styles, function(s) {
if (s.parentNode) {
s.parentNode.removeChild(s);
}
});
var cssText = this.stylesToCssText(styles).replace(this.hostRuleRe, '');
cssText = this.convertPseudos(cssText);
var rules = this.cssToRules(cssText);
cssText = this.scopeRules(rules, name);
return cssText;
},
convertPseudos: function(cssText) {
return cssText.replace(this.cssPseudoRe, ' [pseudo=$1]');
},
// change a selector like 'div' to 'name div'
scopeRules: function(cssRules, name) {
var cssText = '';
forEach(cssRules, function(rule) {
if (rule.selectorText && (rule.style && rule.style.cssText)) {
cssText += this.scopeSelector(rule.selectorText, name,
Polymer.strictPolyfillStyling) + ' {\n\t';
cssText += this.propertiesFromRule(rule) + '\n}\n\n';
} else if (rule.media) {
cssText += '@media ' + rule.media.mediaText + ' {\n';
cssText += this.scopeRules(rule.cssRules, name);
cssText += '\n}\n\n';
} else if (rule.cssText) {
cssText += rule.cssText + '\n\n';
}
}, this);
return cssText;
},
propertiesFromRule: function(rule) {
var properties = rule.style.cssText;
// TODO(sorvell): Chrome cssom incorrectly removes quotes from the content
// property. (https://code.google.com/p/chromium/issues/detail?id=247231)
if (rule.style.content && !rule.style.content.match(/['"]+/)) {
properties = 'content: \'' + rule.style.content + '\';\n' +
rule.style.cssText.replace(/content:[^;]*;/g, '');
}
return properties;
},
selectorNeedsScoping: function(selector, name) {
var matchScope = '(' + name + '|\\[is=' + name + '\\])';
var selectorRe = new RegExp('^' + matchScope + this.selectorReSuffix, 'm');
return !selector.match(selectorRe);
},
scopeSelector: function(selector, name, strict) {
var r = [], parts = selector.split(',');
parts.forEach(function(p) {
p = p.trim();
if (this.selectorNeedsScoping(p, name)) {
p = strict ? this.applyStrictSelectorScope(p, name) :
this.applySimpleSelectorScope(p, name);
}
r.push(p);
}, this);
return r.join(', ');
},
// scope via name and [is=name]
applySimpleSelectorScope: function(selector, name) {
return name + ' ' + selector + ', ' + '[is=' + name + '] ' + selector;
},
// return a selector with [name] suffix on each simple selector
// e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name]
applyStrictSelectorScope: function(selector, name) {
var splits = [' ', '>', '+', '~'],
scoped = selector,
attrName = '[' + name + ']';
splits.forEach(function(sep) {
var parts = scoped.split(sep);
scoped = parts.map(function(p) {
var t = p.trim();
if (t && (splits.indexOf(t) < 0) && (t.indexOf(attrName) < 0)) {
p = t.replace(/([^:]*)(:*)(.*)/, '$1' + attrName + '$2$3')
}
return p;
}).join(sep);
});
return scoped;
},
stylesToCssText: function(styles, preserveComments) {
var cssText = '';
forEach(styles, function(s) {
cssText += s.textContent + '\n\n';
});
// strip comments for easier processing
if (!preserveComments) {
cssText = this.stripCssComments(cssText);
}
return cssText;
},
stripCssComments: function(cssText) {
return cssText.replace(this.cssCommentRe, '');
},
cssToRules: function(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
document.head.appendChild(style);
var rules = style.sheet.cssRules;
style.parentNode.removeChild(style);
return rules;
},
rulesToCss: function(cssRules) {
for (var i=0, css=[]; i < cssRules.length; i++) {
css.push(cssRules[i].cssText);
}
return css.join('\n\n');
},
addCssToDocument: function(cssText) {
if (cssText) {
this.getSheet().appendChild(document.createTextNode(cssText));
}
},
// support for creating @host rules
getSheet: function() {
if (!this.sheet) {
this.sheet = document.createElement("style");
this.sheet.setAttribute('polymer-polyfill', '');
}
return this.sheet;
},
addSheetToDocument: function() {
this.addCssToDocument('style { display: none !important; }\n');
var head = document.querySelector('head');
head.insertBefore(this.getSheet(), head.childNodes[0]);
}
};
// add polyfill stylesheet to document
if (window.ShadowDOMPolyfill) {
stylizer.addSheetToDocument();
}
// exports
Polymer.shimStyling = stylizer.shimStyling;
Polymer.shimShadowDOMStyling = stylizer.shimShadowDOMStyling;
Polymer.shimPolyfillDirectives = stylizer.shimPolyfillDirectives.bind(stylizer);
Polymer.strictPolyfillStyling = false;
})(window);
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// imports
var log = window.logFlags || {};
var doc = wrap(document);
/**
* Install external stylesheets loaded in <element> elements into the
* element's template.
* @param elementElement The <element> element to style.
*/
function installSheets(elementElement) {
installLocalSheets(elementElement);
installGlobalStyles(elementElement);
}
/**
* Takes external stylesheets loaded in an <element> element and moves
* their content into a <style> element inside the <element>'s template.
* The sheet is then removed from the <element>. This is done only so
* that if the element is loaded in the main document, the sheet does
* not become active.
* Note, ignores sheets with the attribute 'polymer-scope'.
* @param elementElement The <element> element to style.
*/
function installLocalSheets(elementElement) {
var sheets = findInElement(elementElement, SHEET_SELECTOR, function(s) {
return !s.hasAttribute(SCOPE_ATTR);
});
var content = elementTemplateContent(elementElement);
if (content) {
// in case we're in document, remove from element
var cssText = '';
sheets.forEach(function(sheet) {
sheet.parentNode.removeChild(sheet);
cssText += cssTextFromSheet(sheet) + '\n';
});
if (cssText) {
content.insertBefore(createStyleElement(cssText), content.firstChild);
}
}
}
/**
* Promotes external stylesheets and <style> elements with the attribute
* polymer-scope='global' into global scope.
* This is particularly useful for defining @keyframe rules which
* currently do not function in scoped or shadow style elements.
* (See wkb.ug/72462)
* @param elementElement The <element> element to style.
*/
// TODO(sorvell): remove when wkb.ug/72462 is addressed.
function installGlobalStyles(elementElement) {
applyStyleToScope(styleFromElement(elementElement, STYLE_GLOBAL_SCOPE),
doc.head);
}
/**
* Installs external stylesheets and <style> elements with the attribute
* polymer-scope='controller' into the scope of element. This is intended
* to be a called during custom element construction. Note, this incurs a per
* instance cost and should be used sparingly.
* The need for this type of styling should go away when the shadowDOM spec
* addresses these issues:
*
* https://www.w3.org/Bugs/Public/show_bug.cgi?id=21391
* https://www.w3.org/Bugs/Public/show_bug.cgi?id=21390
* https://www.w3.org/Bugs/Public/show_bug.cgi?id=21389
*
* @param element The custom element instance into whose controller (parent)
* scope styles will be installed.
* @param elementElement The <element> containing controller styles.
*/
// TODO(sorvell): remove when spec issues are addressed
function installControllerStyles(element, elementElement) {
if (!elementElement.controllerStyle) {
elementElement.controllerStyle = styleFromElement(elementElement,
STYLE_CONTROLLER_SCOPE);
}
var styleElement = elementElement.controllerStyle;
var scope = findStyleController(element);
// apply controller styles only if they are not yet applied
if (scope && !scopeHasElementStyle(scope, element,
STYLE_CONTROLLER_SCOPE)) {
Polymer.shimPolyfillDirectives([styleElement], element.localName);
applyStyleToScope(styleElement, scope);
}
}
function scopeHasElementStyle(scope, element, descriptor) {
return scope.querySelector('style[' + STYLE_SCOPE_ATTRIBUTE + '=' +
element.localName + '-' + descriptor + ']');
}
function cssTextFromElement(elementElement, descriptor) {
var cssText = '';
// handle stylesheets
var selector = '[' + SCOPE_ATTR + '=' + descriptor + ']';
var matcher = function(s) {
return matchesSelector(s, selector);
};
var sheets = findInElement(elementElement, SHEET_SELECTOR, matcher);
sheets.forEach(function(sheet) {
// in case we're in document, remove from element
sheet.parentNode.removeChild(sheet);
cssText += cssTextFromSheet(sheet) + '\n\n';
});
// handle style elements
var styles = findInElement(elementElement, STYLE_SELECTOR, matcher);
styles.forEach(function(style) {
// in case we're in document, remove from element
style.parentNode.removeChild(style);
cssText += style.textContent + '\n\n';
});
return cssText;
}
function styleFromElement(elementElement, descriptor) {
var cssText = cssTextFromElement(elementElement, descriptor);
if (cssText) {
var style = createStyleElement(cssText);
style.setAttribute(STYLE_SCOPE_ATTRIBUTE, elementElement.options.name +
'-' + descriptor);
return style;
}
}
function findInElement(elementElement, selector, matcher) {
var nodes = arrayFromNodeList(elementElement
.querySelectorAll(selector));
var content = elementTemplateContent(elementElement);
if (content) {
var templateNodes = arrayFromNodeList(content
.querySelectorAll(selector));
nodes = nodes.concat(templateNodes);
}
return nodes.filter(matcher);
}
function findStyleController(node) {
// find the shadow root that contains inNode
var n = node;
while (n.parentNode) {
n = n.parentNode;
}
return n == doc ? doc.head : n;
};
function createStyleElement(cssText) {
var style = document.createElement('style');
style.textContent = cssText;
return style;
}
function cssTextFromSheet(sheet) {
return (sheet && sheet.__resource) || '';
}
function applyStyleToScope(style, scope) {
if (style) {
var clone = style.cloneNode(true);
// TODO(sorvell): necessary for IE
// see https://connect.microsoft.com/IE/feedback/details/790212/
// cloning-a-style-element-and-adding-to-document-produces
// -unexpected-result#details
clone.textContent = style.textContent;
scope.appendChild(clone);
}
}
var eltProto = HTMLElement.prototype;
var matches = eltProto.matches || eltProto.matchesSelector ||
eltProto.webkitMatchesSelector || eltProto.mozMatchesSelector;
function matchesSelector(node, inSelector) {
if (matches) {
return matches.call(node, inSelector);
}
}
function elementTemplateContent(elementElement) {
var template = elementElement.querySelector('template');
return template && templateContent(template);
}
var STYLE_SELECTOR = 'style';
var SHEET_SELECTOR = '[rel=stylesheet]';
var STYLE_SCOPE_ATTRIBUTE = 'element';
var STYLE_GLOBAL_SCOPE = 'global';
var STYLE_CONTROLLER_SCOPE = 'controller';
var SCOPE_ATTR = 'polymer-scope';
function arrayFromNodeList(nodeList) {
return Array.prototype.slice.call(nodeList || [], 0);
}
// exports
Polymer.installSheets = installSheets;
Polymer.installControllerStyles = installControllerStyles;
})();
\ No newline at end of file
/*
* Copyright 2013 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
(function() {
// observer tracking
var trackingTable = new SideTable();
function registerObserver(element, type, name, observer) {
var o$ = getObserversOfType(element, type, true);
o$[name.toLowerCase()] = observer;
}
function unregisterObserver(element, type, name) {
var $o = getObserversOfType(element, type), lcName = name.toLowerCase();
if ($o && $o[lcName]) {
//console.log('remove observer: %s, %s', type, name);
$o[lcName].close();
$o[lcName] = null;
return true;
}
}
function unregisterObserversOfType(element, type) {
var $o = getObserversOfType(element, type);
if ($o) {
Object.keys($o).forEach(function(key) {
unregisterObserver(element, type, key);
});
}
}
function getObserversOfType(element, type, force) {
var b$ = trackingTable.get(element);
if (force) {
if (!b$) {
trackingTable.set(element, b$ = {});
}
if (!b$[type]) {
b$[type] = {};
}
}
return b$ && b$[type];
}
// exports
Polymer.registerObserver = registerObserver;
Polymer.unregisterObserver = unregisterObserver;
Polymer.unregisterObserversOfType = unregisterObserversOfType;
})();
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #eaeaea url('bg.png');
color: #4d4d4d;
width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
margin: 130px 0 40px 0;
border: 1px solid #ccc;
position: relative;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.15);
}
#todoapp:before {
content: '';
border-left: 1px solid #f5d6d6;
border-right: 1px solid #f5d6d6;
width: 2px;
position: absolute;
top: 0;
left: 40px;
height: 100%;
}
#todoapp input::-webkit-input-placeholder {
font-style: italic;
}
#todoapp input:-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp h1 {
position: absolute;
top: -120px;
width: 100%;
font-size: 70px;
font-weight: bold;
text-align: center;
color: #b3b3b3;
color: rgba(255, 255, 255, 0.3);
text-shadow: -1px -1px rgba(0, 0, 0, 0.2);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
-ms-text-rendering: optimizeLegibility;
-o-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
border: none; /* Mobile Safari */
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#todo-list li.editing .edit {
display: block;
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
#todo-list li.editing .view {
display: none;
}
#todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
#todo-list li .toggle:after {
content: '✔';
line-height: 43px; /* 40 + a couple of pixels visual adjustment */
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
#todo-list li .toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
#todo-list li label {
word-break: break-word;
padding: 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
-moz-transition: color 0.4s;
-ms-transition: color 0.4s;
-o-transition: color 0.4s;
transition: color 0.4s;
}
#todo-list li.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
#todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
transition: all 0.2s;
}
#todo-list li .destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-moz-transform: scale(1.3);
-ms-transform: scale(1.3);
-o-transform: scale(1.3);
transform: scale(1.3);
}
#todo-list li .destroy:after {
content: '✖';
}
#todo-list li:hover .destroy {
display: block;
}
#todo-list li .edit {
display: none;
}
#todo-list li.editing:last-child {
margin-bottom: -1px;
}
#footer {
color: #777;
padding: 0 15px;
position: absolute;
right: 0;
bottom: -31px;
left: 0;
height: 20px;
z-index: 1;
text-align: center;
}
#footer:before {
content: '';
position: absolute;
right: 0;
bottom: 31px;
left: 0;
height: 50px;
z-index: -1;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
0 6px 0 -3px rgba(255, 255, 255, 0.8),
0 7px 1px -3px rgba(0, 0, 0, 0.3),
0 43px 0 -6px rgba(255, 255, 255, 0.8),
0 44px 2px -6px rgba(0, 0, 0, 0.2);
}
#todo-count {
float: left;
text-align: left;
}
#filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
#filters li {
display: inline;
}
#filters li a {
color: #83756f;
margin: 2px;
text-decoration: none;
}
#filters li a.selected {
font-weight: bold;
}
#clear-completed {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
font-size: 11px;
padding: 0 10px;
border-radius: 3px;
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2);
}
#clear-completed:hover {
background: rgba(0, 0, 0, 0.15);
box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3);
}
#info {
margin: 65px auto 0;
color: #a6a6a6;
font-size: 12px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7);
text-align: center;
}
#info a {
color: inherit;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox and Opera
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all,
#todo-list li .toggle {
background: none;
}
#todo-list li .toggle {
height: 40px;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
.hidden{
display:none;
}
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);
}
/**body*/.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
/**body*/.learn-bar {
width: auto;
margin: 0 0 0 300px;
}
/**body*/.learn-bar > .learn {
left: 8px;
}
/**body*/.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) {
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);
})();
{
"name": "todomvc-common",
"version": "0.1.7",
"gitHead": "42348a8146fe0be847b93cd98664813fbae62be9",
"_id": "todomvc-common@0.1.7",
"readme": "ERROR: No README.md file found!",
"description": "ERROR: No README.md file found!",
"repository": {
"type": "git",
"url": "git://github.com/tastejs/todomvc-common.git"
}
}
\ No newline at end of file
/* base.css overrides */
@host {
.editing {
border-bottom: none;
padding: 0;
}
}
button {
margin: 0;
padding: 0;
border: 0;
background-color: transparent;
background-image: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
.edit {
width: 506px;
padding: 13px 17px 12px 17px;
margin: 0 0 0 43px;
}
.toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
.toggle:after {
content: '✔';
line-height: 43px; /* 40 + a couple of pixels visual adjustment */
font-size: 20px;
color: #d9d9d9;
text-shadow: 0 -1px 0 #bfbfbf;
}
.toggle:checked:after {
color: #85ada7;
text-shadow: 0 1px 0 #669991;
bottom: 1px;
position: relative;
}
label {
word-break: break-word;
padding: 15px;
margin-left: 45px;
display: block;
line-height: 1.2;
-webkit-transition: color 0.4s;
-moz-transition: color 0.4s;
-ms-transition: color 0.4s;
-o-transition: color 0.4s;
transition: color 0.4s;
}
.completed label {
color: #a9a9a9;
text-decoration: line-through;
}
.destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 22px;
color: #a88a8a;
-webkit-transition: all 0.2s;
-moz-transition: all 0.2s;
-ms-transition: all 0.2s;
-o-transition: all 0.2s;
transition: all 0.2s;
}
.destroy:hover {
text-shadow: 0 0 1px #000,
0 0 10px rgba(199, 107, 107, 0.8);
-webkit-transform: scale(1.3);
-moz-transform: scale(1.3);
-ms-transform: scale(1.3);
-o-transform: scale(1.3);
transform: scale(1.3);
}
.destroy:after {
content: '✖';
}
.view:hover .destroy {
display: block;
}
/*
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 {
background: none;
}
.toggle {
height: 40px;
}
}
<element name="td-item" extends="li" attributes="item editing" on-blur="commitAction">
<link rel="stylesheet" href="td-item.css">
<template>
<div class="view {{completed: item.completed; editing: editing}}" hidden?="{{editing}}" on-dblclick="editAction">
<input type="checkbox" class="toggle" checked="{{item.completed}}" on-click="itemChangeAction">
<label>{{item.title}}</label>
<button class="destroy" on-click="destroyAction"></button>
</div>
<input id="edit" class="edit" value="{{title}}" hidden?="{{!editing}}" on-keyup="keyAction">
</template>
<script>
var ENTER_KEY = 13;
var ESC_KEY = 27;
Polymer.register(this, {
editing: false,
keyAction: function(e) {
switch (e.keyCode) {
case ESC_KEY:
this.cancelAction();
break;
case ENTER_KEY:
this.commitAction();
break;
}
},
editAction: function() {
this.editing = true;
this.title = this.item.title;
// schedule focus for the end of microtask, when the input will be visible
Platform.flush();
this.asyncMethod(function() {
this.$.edit.focus();
});
},
commitAction: function() {
if (this.editing) {
this.editing = false;
this.item.title = this.title.trim();
if (this.item.title === '') {
this.destroyAction();
}
}
},
cancelAction: function() {
this.editing = false;
},
itemChangeAction: function() {
this.fire('td-item-changed');
},
destroyAction: function() {
this.fire('td-destroy-item', this.item);
}
});
</script>
</element>
<element name="td-model" attributes="filter items">
<script>
Polymer.register(this, {
filtered: null,
completedCount: 0,
activeCount: 0,
allCompleted: false,
ready: function() {
this.asyncMethod(function() {
this.items = this.items || [];
});
},
filterChanged: function() {
this.filterItems();
},
itemsChanged: function() {
this.completedCount =
this.items.filter(this.filters.completed).length;
this.activeCount = this.items.length - this.completedCount;
this.allCompleted = this.completedCount && !this.activeCount;
this.filterItems();
this.fire('td-model-changed');
},
filterItems: function() {
var fn = this.filters[this.filter];
this.filtered = fn ? this.items.filter(fn) : this.items;
},
newItem: function(title) {
title = String(title).trim();
if (title) {
var item = {
title: title,
completed: false
};
this.items.push(item);
this.itemsChanged();
}
},
destroyItem: function(item) {
var i = this.items.indexOf(item);
if (i >= 0) {
this.items.splice(i, 1);
}
this.itemsChanged();
},
clearItems: function(){
this.items = this.items.filter(this.filters.active);
},
setItemsCompleted: function(completed) {
this.items.forEach(function(item) {
item.completed = completed;
});
this.itemsChanged();
},
filters: {
active: function(item) {
return !item.completed;
},
completed: function(item) {
return item.completed;
}
}
});
</script>
</element>
/* base.css overrides */
@host {
* {
display: block;
position: relative;
}
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
color: inherit;
-webkit-appearance: none;
/*-moz-appearance: none;*/
-ms-appearance: none;
-o-appearance: none;
appearance: none;
}
input::-webkit-input-placeholder {
font-style: italic;
}
input:-moz-placeholder {
font-style: italic;
color: #a9a9a9;
}
#todoapp {
background: #fff;
background: rgba(255, 255, 255, 0.9);
/*margin: 130px 0 40px 0;*/
margin: 0 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%;
}
#header {
padding-top: 15px;
border-radius: inherit;
}
#header:before {
content: '';
position: absolute;
top: 0;
right: 0;
left: 0;
height: 15px;
z-index: 2;
border-bottom: 1px solid #6c615c;
background: #8d7d77;
background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8)));
background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670');
border-top-left-radius: 1px;
border-top-right-radius: 1px;
}
#new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
line-height: 1.4em;
border: 0;
outline: none;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-font-smoothing: antialiased;
-ms-font-smoothing: antialiased;
-o-font-smoothing: antialiased;
font-smoothing: antialiased;
}
#new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.02);
z-index: 2;
box-shadow: none;
}
#main {
position: relative;
z-index: 2;
border-top: 1px dotted #adadad;
}
label[for='toggle-all'] {
display: none;
}
#toggle-all {
position: absolute;
top: -42px;
left: -4px;
width: 40px;
text-align: center;
border: none; /* Mobile Safari */
}
#toggle-all:before {
content: '»';
font-size: 28px;
color: #d9d9d9;
padding: 0 25px 7px;
}
#toggle-all:checked:before {
color: #737373;
}
#todo-list {
margin: 0;
padding: 0;
list-style: none;
}
#todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px dotted #ccc;
}
#todo-list li:last-child {
border-bottom: none;
}
#todo-list li.editing {
border-bottom: none;
padding: 0;
}
#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.selected a {
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);
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
#toggle-all {
background: none;
}
#toggle-all {
top: -56px;
left: -15px;
width: 65px;
height: 41px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
-webkit-appearance: none;
appearance: none;
}
}
<link rel="import" href="../lib-elements/polymer-selector.html">
<link rel="import" href="../lib-elements/flatiron-director.html">
<link rel="import" href="../lib-elements/polymer-localstorage.html">
<link rel="import" href="td-item.html">
<link rel="import" href="td-model.html">
<element name="td-todos" attributes="route">
<link rel="stylesheet" href="td-todos.css">
<template>
<flatiron-director route="{{route}}"></flatiron-director>
<polymer-localstorage id="storage" name="todos-polymer" value="{{items}}"></polymer-localstorage>
<td-model id="model" items="{{items}}" filter="{{route}}" on-td-model-changed="modelChangedAction"></td-model>
<section id="todoapp">
<header id="header">
<input id="new-todo" placeholder="What needs to be done?" autofocus on-keyup="keyAction">
</header>
<section id="main" hidden?="{{$.model.items.length == 0}}">
<input id="toggle-all" type="checkbox" on-change="toggleAllCompletedAction" checked="{{$.model.allCompleted}}">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list" on-td-item-changed="itemChangedAction" on-td-destroy-item="destroyItemAction">
<template repeat="{{$.model.filtered}}">
<li is="td-item" item="{{}}"></li>
</template>
</ul>
</section>
<footer id="footer" hidden?="{{$.model.items.length == 0}}">
<span id="todo-count"><strong>{{$.model.activeCount}}</strong> {{$.model.activeCount == 1 ? 'item' : 'items'}} left</span>
<polymer-selector id="filters" selected="{{route || 'all'}}">
<li name="all">
<a href="../#/">All</a>
</li>
<li name="active">
<a href="../#/active">Active</a>
</li>
<li name="completed">
<a href="../#/completed">Completed</a>
</li>
</polymer-selector>
<button hidden?="{{$.model.completedCount == 0}}" id="clear-completed" on-click="clearCompletedAction">Clear completed ({{$.model.completedCount}})</button>
</footer>
</section>
</template>
<script>
var ENTER_KEY = 13;
var ESC_KEY = 27;
Polymer.register(this, {
keyAction: function(e, detail, sender) {
switch (e.keyCode) {
case ENTER_KEY:
this.$.model.newItem(sender.value);
// when polyfilling Object.observe, make sure we update immediately
Platform.flush();
case ESC_KEY:
sender.value = '';
break;
}
},
modelChangedAction: function() {
this.$.storage.save();
},
itemChangedAction: function() {
this.$.model.itemsChanged();
},
destroyItemAction: function(e, detail) {
this.$.model.destroyItem(detail);
},
toggleAllCompletedAction: function(e, detail, sender) {
this.$.model.setItemsCompleted(sender.checked);
},
clearCompletedAction: function() {
this.$.model.clearItems();
}
});
</script>
</element>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Polymer • TodoMVC</title>
<link rel="stylesheet" href="app/app.css">
</head>
<body>
<header>
<h1>todos</h1>
</header>
<td-todos></td-todos>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://www.polymer-project.org">The Polymer Authors</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<!-- Scripts here. Don't remove this ↓ -->
<script src="components/polymer/polymer.min.js"></script>
<link rel="import" href="elements/td-todos.html">
</body>
</html>
<script src="../components/director/build/director.min.js"></script>
<element name="flatiron-director" attributes="route">
<script>
var private_router;
Polymer.register(this, {
ready: function() {
this.router.on(/(\w*)/, function(route) {
this.route = route;
}.bind(this));
this.asyncMethod(function() {
var initialRoute = this.router.getRoute(0);
this.route = initialRoute || '';
});
},
get router() {
if (!private_router) {
private_router = new Router();
private_router.init();
}
return private_router;
},
routeChanged: function() {
this.fire('route', this.route);
}
});
</script>
</element>
<!--
Copyright 2013 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<!--
/**
* @module Polymer Elements
*/
-->
<element name="polymer-localstorage" attributes="name value isJson">
<template>
<style>
@host {
* {
display: none !important;
}
}
</style>
</template>
<script>
Polymer.register(this, {
isJson: true,
ready: function() {
this.load();
},
valueChanged: function() {
this.save();
},
load: function() {
var s = window.localStorage.getItem(this.name);
if (s && this.isJson) {
this.value = JSON.parse(s);
} else {
this.value = s;
}
},
save: function() {
window.localStorage.setItem(this.name,
this.isJson ? JSON.stringify(this.value) : this.value);
}
});
</script>
</element>
<!--
Copyright 2013 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<!--
/**
* @module Polymer Elements
*/
-->
<element name="polymer-selection" attributes="multi">
<template>
<style>
@host {
* {
display: none !important;
}
}
</style>
</template>
<script>
Polymer.register(this, {
multi: false,
ready: function() {
this.clear();
},
clear: function() {
this.selection = [];
},
getSelection: function() {
return this.multi ? this.selection : this.selection[0];
},
isSelected: function(inItem) {
return this.selection.indexOf(inItem) >= 0;
},
setItemSelected: function(inItem, inIsSelected) {
if (inItem) {
if (inIsSelected) {
this.selection.push(inItem);
} else {
var i = this.selection.indexOf(inItem);
if (i >= 0) {
this.selection.splice(i, 1);
}
}
// TODO(sjmiles): consider replacing with summary
// notifications (asynchronous job)
this.asend("select", {isSelected: inIsSelected, item: inItem});
}
},
select: function(inItem) {
if (this.multi) {
this.toggle(inItem);
} else if (this.getSelection() !== inItem) {
this.setItemSelected(this.getSelection(), false);
this.setItemSelected(inItem, true);
}
},
toggle: function(inItem) {
this.setItemSelected(inItem, !this.isSelected(inItem));
}
});
</script>
</element>
<!--
Copyright 2013 The Polymer Authors. All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
-->
<!--
/**
* @module Polymer Elements
*/
/**
* polymer-selector is used to display a list of elements that can be selected.
* The attribute "selected" indicates which element is being selected.
* Tapping on the element to change selection would fire "activate"
* event.
*
* Example:
*
* <polymer-selector selected="0" on-activate="activateHandler">
* <div>Item 1</div>
* <div>Item 2</div>
* <div>Item 3</div>
* </polymer-selector>
*
* polymer-selector is not styled. So one needs to use "selected" CSS class
* to style the selected element.
*
* <style>
* .item.selected {
* background: #eee;
* }
* </style>
* ...
* <polymer-selector>
* <div class="item">Item 1</div>
* <div class="item">Item 2</div>
* <div class="item">Item 3</div>
* </polymer-selector>
*
* @class polymer-selector
*/
/**
* Fired when an element is selected via tap.
*
* @event activate
* @param {Object} inDetail
* @param {Object} inDetail.item the selected element
*/
-->
<link rel="import" href="polymer-selection.html">
<element name="polymer-selector" on-tap="activateHandler" attributes="selected selectedClass selectedModel multi valueattr notap">
<template>
<polymer-selection id="selection" multi="{{multi}}" on-select="selectionSelect"></polymer-selection>
<content id="items" select="*"></content>
</template>
<script>
Polymer.register(this, {
/**
* Gets or sets the selected element. Default is to use the index
* of the currently selected element.
*
* If you want a specific attribute value of the selected element to be
* used instead of index, set "valueattr" to that attribute name.
*
* Example:
*
* <polymer-selector valueattr="label" selected="foo">
* <div label="foo"></div>
* <div label="bar"></div>
* <div label="zot"></div>
* </polymer-selector>
*
* @attribute selected
* @type string
* @default null
*/
selected: null,
/**
* If true, multiple selections are allowed.
*
* @attribute multi
* @type boolean
* @default false
*/
multi: false,
/**
* Specifies the attribute to be used for "selected" attribute.
*
* @attribute valueattr
* @type string
* @default 'name'
*/
valueattr: 'name',
/**
* Specifies the CSS class to be used to add to the selected element.
*
* @attribute selectedClass
* @type string
* @default 'selected'
*/
selectedClass: 'selected',
/**
* Returns the model associated with the selected element.
*
* @attribute selectedModel
* @type Object
* @default null
*/
selectedModel: null,
ready: function() {
this.setAttribute('touch-action', 'none');
},
get items() {
return this.$.items.getDistributedNodes();
},
get selection() {
return this.$.selection.getSelection();
},
selectedChanged: function() {
this.valueToSelection(this.selected);
},
valueToSelection: function(inValue) {
var item = this.items[this.valueToIndex(inValue)];
if (item) {
this.$.selection.select(item);
var t = item.templateInstance;
this.selectedModel = t ? t.model : undefined;
} else {
this.selectedModel = null;
}
},
valueToIndex: function(inValue) {
// find an item with value == inValue and return it's index
for (var i=0, items=this.items, c; (c=items[i]); i++) {
if (this.valueForNode(c) == inValue) {
return i;
}
}
// if no item found, the value itself is probably the index
return inValue;
},
valueForNode: function(inNode) {
return inNode[this.valueattr] || inNode.getAttribute(this.valueattr);
},
// events fired from <polymer-selection> object
selectionSelect: function(inEvent, inInfo) {
if (inInfo.item && this.selectedClass) {
inInfo.item.classList.toggle(this.selectedClass, inInfo.isSelected);
}
},
// event fired from host
activateHandler: function(inEvent) {
if (this.notap) {
return;
}
var items = this.items;
var i = Polymer.findDistributedTarget(inEvent.target, items);
if (i >= 0) {
var selected = this.valueForNode(items[i]) || i;
if (this.multi) {
this.valueToSelection(selected);
} else {
this.selected = selected;
}
this.asend('activate', {item: items[i]});
}
}
});
</script>
</element>
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