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 @@
<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>
</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">
<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>
......
# 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
#!/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(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);
{
"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>
<!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>
/*
* 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);
This diff is collapsed.
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
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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