├── examples ├── codereview │ ├── static │ │ ├── style.css │ │ └── codereview.js │ ├── views │ │ ├── home │ │ │ └── index.html │ │ └── master.html │ ├── app.js │ └── controllers │ │ └── home.js ├── hello │ ├── static │ │ ├── my.js │ │ ├── favicon.ico │ │ ├── style.css │ │ └── jquery-1.4.2.min.js │ ├── views │ │ ├── home │ │ │ ├── index.html │ │ │ └── cookie.html │ │ ├── product │ │ │ └── index.html │ │ └── master.html │ ├── controllers │ │ ├── error.js │ │ ├── product.js │ │ └── home.js │ ├── tests │ │ ├── view.js │ │ └── controller.js │ └── app.js ├── tasks-ajax │ ├── views │ │ ├── task │ │ │ ├── details.html │ │ │ ├── index.html │ │ │ └── create.html │ │ └── master.html │ ├── app.js │ ├── static │ │ ├── style.css │ │ └── tasks.js │ ├── models │ │ └── task.js │ └── controllers │ │ └── task.js └── tasks │ ├── views │ ├── task │ │ ├── details.html │ │ ├── index.html │ │ └── create.html │ ├── home │ │ └── index.html │ └── master.html │ ├── README.md │ ├── app.js │ ├── controllers │ ├── home.js │ └── task.js │ └── models │ └── tasks.js ├── lib ├── josi │ ├── package.js │ ├── tasks │ │ ├── version.js │ │ ├── run.js │ │ ├── help.js │ │ ├── index.js │ │ ├── test.js │ │ └── create.js │ ├── routeresults.js │ ├── test.js │ ├── class.js │ ├── routing.js │ ├── templating.js │ ├── actionresults.js │ ├── server.js │ └── utilities.js ├── multipart-js │ ├── multipart.js │ ├── utils.js │ ├── README.md │ ├── write.js │ └── parse.js └── dirty │ └── dirty.js ├── tests ├── results.js ├── functionrouting.js ├── templating.js ├── utilities.js └── controllerrouting.js ├── README.md └── bin └── josi /examples/codereview/static/style.css: -------------------------------------------------------------------------------- 1 | @CHARSET "UTF-8"; -------------------------------------------------------------------------------- /examples/hello/static/my.js: -------------------------------------------------------------------------------- 1 | $('h1').hide().fadeIn(); 2 | -------------------------------------------------------------------------------- /examples/hello/views/home/index.html: -------------------------------------------------------------------------------- 1 |

Details: <%= details %>

2 | -------------------------------------------------------------------------------- /examples/tasks-ajax/views/task/details.html: -------------------------------------------------------------------------------- 1 |

2 | <%= description %> 3 |

4 | -------------------------------------------------------------------------------- /examples/codereview/views/home/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /examples/tasks/views/task/details.html: -------------------------------------------------------------------------------- 1 |

<%= task.title %>

2 |

<%= task.details %>

3 | -------------------------------------------------------------------------------- /examples/hello/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thatismatt/josi/HEAD/examples/hello/static/favicon.ico -------------------------------------------------------------------------------- /lib/josi/package.js: -------------------------------------------------------------------------------- 1 | this.name = 'josi'; 2 | this.version = '0.2.0'; 3 | this.author = 'Matt Lee - @thatismatt'; 4 | -------------------------------------------------------------------------------- /examples/hello/controllers/error.js: -------------------------------------------------------------------------------- 1 | this.index = function() { 2 | throw new Error("Ooops... that was an error!"); 3 | }; 4 | -------------------------------------------------------------------------------- /examples/hello/views/home/cookie.html: -------------------------------------------------------------------------------- 1 |

2 | The cookie a's value is: <%= a %> 3 |

4 |

5 | The cookie b's value is: <%= b %> 6 |

7 | -------------------------------------------------------------------------------- /examples/hello/static/style.css: -------------------------------------------------------------------------------- 1 | body { font-family: Georgia; } 2 | div.main { clear: left; } 3 | ul.nav { padding: 0; } 4 | .nav li { float: left; padding: 5px; list-style-type: none; } 5 | -------------------------------------------------------------------------------- /examples/hello/tests/view.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | this.name = 'View Tests'; 4 | 5 | this.tests = { 6 | 'test 1': function() { 7 | assert.ok(true); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /examples/tasks/views/home/index.html: -------------------------------------------------------------------------------- 1 |

Controller: <%= controller %>

2 |

Action: <%= action %>

3 |

<%= description %>

4 |

Tasks list

5 | 6 | -------------------------------------------------------------------------------- /examples/hello/tests/controller.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | this.name = 'Controller Tests'; 4 | 5 | this.tests = { 6 | 'test 1': function() { 7 | assert.ok(true); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /examples/tasks/README.md: -------------------------------------------------------------------------------- 1 | Tasks 2 | ===== 3 | 4 | This example app has a corresponding tutorial that steps through its creation. The tutorial can be found [here](http://thatismatt.github.com/josi/tutorial.html). 5 | -------------------------------------------------------------------------------- /examples/tasks/views/master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= title %> 4 | 5 | 6 |

<%= title %>

7 | <%= main %> 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/hello/app.js: -------------------------------------------------------------------------------- 1 | this.init = function() { 2 | this.router.addStatic('static'); 3 | this.router.add( 4 | /^\/(?:(\w+)\/?)?(?:(\w+)\/?)?(?:(\w+)\/?)?$/, 5 | { controller: 'home', action: 'index' } 6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/multipart-js/multipart.js: -------------------------------------------------------------------------------- 1 | 2 | exports.lib = 3 | { parse : require("./parse") 4 | , write : require("./write") 5 | }; 6 | 7 | exports.parser = exports.lib.parse.parser; 8 | exports.writer = exports.lib.write.writer; 9 | -------------------------------------------------------------------------------- /examples/tasks-ajax/app.js: -------------------------------------------------------------------------------- 1 | this.init = function() { 2 | this.router.add( 3 | /^\/(?:(\w+)\/?)?(?:(\w+)\/?)?(?:(\w+)\/?)?$/, 4 | { controller: 'task', action: 'index' } 5 | ); 6 | this.router.addStatic('static'); 7 | }; 8 | -------------------------------------------------------------------------------- /examples/tasks/views/task/index.html: -------------------------------------------------------------------------------- 1 | 6 |

Create a new task

7 | -------------------------------------------------------------------------------- /examples/tasks/app.js: -------------------------------------------------------------------------------- 1 | this.init = function() { 2 | this.router.add( 3 | // this route matches: /// 4 | /^\/(?:(\w+)\/?)?(?:(\w+)\/?)?(?:([0-9]+)\/?)?$/, 5 | { controller: 'home', action: 'index' } 6 | ); 7 | }; 8 | -------------------------------------------------------------------------------- /lib/josi/tasks/version.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | 3 | var package = require('josi/package'); 4 | 5 | this.task = { 6 | name: 'version', 7 | doc: 'print the version number', 8 | execute: function() { 9 | sys.puts(package.version); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /examples/hello/views/product/index.html: -------------------------------------------------------------------------------- 1 |
    2 | <% for (var id in products) { 3 | var p = products[id]; %> 4 |
  • 5 | 6 | <%= p.name %> 7 | 8 | @ <%= p.price %> 9 |
  • 10 | <% } %> 11 |
-------------------------------------------------------------------------------- /examples/tasks/controllers/home.js: -------------------------------------------------------------------------------- 1 | var view = require('josi/actionresults').view; 2 | 3 | this.index = function() { 4 | return view({ 5 | title: 'tasks - a josi app', 6 | controller: 'home', 7 | action: 'index', 8 | description: 'tasks is a josi app' 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /examples/codereview/app.js: -------------------------------------------------------------------------------- 1 | var templating = require('josi/templating'); 2 | 3 | this.init = function() { 4 | this.router.add( 5 | /^\/(?:(\w+)\/?)?(?:(\w+)\/?)?(?:([0-9]+)\/?)?$/, 6 | { controller: 'home', action: 'index' } 7 | ); 8 | this.router.addStatic('static'); 9 | }; 10 | 11 | this.templater = new templating.jQueryTemplatingEngine(); 12 | -------------------------------------------------------------------------------- /examples/tasks/views/task/create.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /lib/multipart-js/utils.js: -------------------------------------------------------------------------------- 1 | 2 | exports.emit = emit; 3 | exports.error = error; 4 | 5 | function error (emitter, message) { 6 | emitter.error = new Error(message); 7 | emit(emitter, "onError", emitter.error); 8 | if (emitter.error) throw emitter.error; 9 | } 10 | function emit (emitter, ev, data) { 11 | if (emitter[ev]) emitter[ev](data); 12 | else if (emitter[ev.toLowerCase()]) emitter[ev.toLowerCase()](data); 13 | } 14 | -------------------------------------------------------------------------------- /examples/tasks-ajax/views/task/index.html: -------------------------------------------------------------------------------- 1 |

There are <%= tasks.length %> tasks.

2 | 3 | <% for (var i in tasks) { %> 4 | <% var task = tasks[i]; %> 5 |

class="done"<% } %>> 6 | checked="checked"<% } %>> 7 | 8 | <%= task.title %> 9 | 10 |

11 | <% } %> 12 | -------------------------------------------------------------------------------- /examples/codereview/views/master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${title} 4 | 5 | 6 | 7 |

${title}

8 |
9 | ${main} 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/hello/controllers/product.js: -------------------------------------------------------------------------------- 1 | var view = require('josi/actionresults').view; 2 | 3 | var products = { 4 | '1': { name: 'Raspberry Jam', price: '$1.99' }, 5 | '2': { name: 'Marmalade', price: '$2.99' }, 6 | '3': { name: 'Lemon Curd', price: '$2.49' }, 7 | '4': { name: 'Chocolate Spread', price: '$1.99' } 8 | }; 9 | 10 | exports.index = function() { 11 | return view({ title: 'List of products', products: products }); 12 | }; 13 | 14 | exports.details = function() { 15 | var id = this.route[0]; 16 | return products[id]; 17 | }; 18 | -------------------------------------------------------------------------------- /lib/josi/tasks/run.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var Server = require('josi/server').Server; 3 | 4 | this.task = { 5 | name: 'run', 6 | doc: 'start the josi web server', 7 | opts: 'port - port number for the server to listen on', 8 | appOnly: true, 9 | execute: function(opts, args) { 10 | var port = parseInt(opts.port || args[0]) || 8080; 11 | var server = new Server(process.cwd()); 12 | server.listen(port); 13 | sys.puts('josi server started on http://localhost:' + port + '/.'); 14 | sys.puts('ctrl-c to stop.'); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /examples/tasks-ajax/static/style.css: -------------------------------------------------------------------------------- 1 | h1 { padding: 0; margin: 10px; } 2 | body { font-family: Georgia; } 3 | div.main { clear: left; margin: 10px; } 4 | ul.nav { padding: 0; margin: 0; } 5 | .nav li { float: left; padding: 5px; margin: 5px 5px 20px 5px; list-style-type: none; background-color: #ccc; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } 6 | label { display: block; float: left; width: 100px; } 7 | form { margin: 10px 0; } 8 | form div { margin: 5px 0; } 9 | 10 | .done { text-decoration: line-through; } 11 | -------------------------------------------------------------------------------- /examples/tasks/models/tasks.js: -------------------------------------------------------------------------------- 1 | var tasks = []; 2 | 3 | this.list = function() { 4 | return tasks; 5 | }; 6 | 7 | this.get = function(id) { 8 | return tasks[id]; 9 | }; 10 | 11 | this.save = function(task) { 12 | if (!task.id) { 13 | tasks.push(task); 14 | } else { 15 | tasks[task.id] = task; 16 | } 17 | }; 18 | 19 | // test data 20 | tasks.push( 21 | { title: 'Go for a jog', details: 'Run miles, and miles, and miles.' }, 22 | { title: 'Sleep', details: 'I love sleep.' }, 23 | { title: 'Read a book', details: 'Read a book by someone famous.' } 24 | ); 25 | -------------------------------------------------------------------------------- /examples/codereview/static/codereview.js: -------------------------------------------------------------------------------- 1 | $('form').submit(function() { 2 | var msg = $('#msg'); 3 | $.ajax({ 4 | url: '/home/send', 5 | type: 'POST', 6 | data: { msg: msg.val() }, 7 | error: function() { 8 | // todo: deal with error sending message 9 | } 10 | }); 11 | msg.val(''); 12 | return false; 13 | }); 14 | 15 | // long poll 16 | var poll = function() { 17 | $.get('/home/listen/', function(data) { 18 | $('body').append('

' + data + '

'); 19 | poll(); 20 | }); 21 | }; 22 | 23 | setTimeout(poll, 1); // chrome fix: end loading state 24 | 25 | -------------------------------------------------------------------------------- /examples/tasks-ajax/static/tasks.js: -------------------------------------------------------------------------------- 1 | $('.completed').change(function() { 2 | var $this = $(this); 3 | $.ajax({ 4 | url: '/task/' + ($this.attr('checked') ? 'done' : 'undone') + '/', 5 | data: { 6 | id: $this.parent().attr('id') 7 | }, 8 | type: 'POST', 9 | error: function() { 10 | // todo: deal with error when changing state of task 11 | }, 12 | success: function() { 13 | if ($this.attr('checked')) { 14 | $this.parent().addClass('done'); 15 | } else { 16 | $this.parent().removeClass('done'); 17 | } 18 | } 19 | }) 20 | }); 21 | -------------------------------------------------------------------------------- /lib/josi/routeresults.js: -------------------------------------------------------------------------------- 1 | require('josi/class'); 2 | 3 | this.RouteResult = RouteResult = Class.extend({ 4 | init: function(action, route) { 5 | this.action = action; 6 | this.route = route; 7 | } 8 | }); 9 | 10 | this.MissingRouteResult = MissingRouteResult = RouteResult.extend({ 11 | init: function(msg) { 12 | this.message = msg || 'No route was matched.'; 13 | } 14 | }); 15 | 16 | this.ErrorRouteResult = ErrorRouteResult = RouteResult.extend({ 17 | init: function(error) { 18 | this.error = error; 19 | } 20 | }); 21 | 22 | this.missingRoute = function(msg) { 23 | return new MissingRouteResult(msg); 24 | }; 25 | -------------------------------------------------------------------------------- /examples/tasks-ajax/views/master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= title %> 4 | 5 | 6 | 7 |

<%= title %>

8 | 12 |
13 | <%= main %> 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/tasks-ajax/views/task/create.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | 26 | -------------------------------------------------------------------------------- /lib/josi/tasks/help.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var package = require('josi/package'); 3 | 4 | this.task = { 5 | name: 'help', 6 | doc: 'print this message', 7 | execute: function(opts, args, tasks) { 8 | sys.puts('josi ' + package.version + ' by ' + package.author); 9 | sys.puts('Usage:\tjosi task [opts]\tRun the specified task.'); 10 | sys.puts(' \tjosi app_name \tCreate a josi app called app_name. Alias to "josi create app_name"'); 11 | sys.puts('Where task is one of the following: '); 12 | tasks.forEach(function(task) { 13 | sys.puts('\t' + task.name + (task.doc ? '\t\t' + task.doc : '')); 14 | if (task.opts) { 15 | sys.puts('\t\t' + task.opts); 16 | } 17 | }); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /examples/tasks-ajax/models/task.js: -------------------------------------------------------------------------------- 1 | var dirty = require('dirty/dirty'); 2 | var utilities = require('josi/utilities'); 3 | 4 | // filename is relative to cwd 5 | var dirtyStore = new dirty.Dirty('models/store.dirty'); 6 | 7 | this.all = function() { 8 | return dirtyStore 9 | .filter(function() { return true; }); 10 | }; 11 | 12 | this.get = function(id) { 13 | return dirtyStore.get(id); 14 | }; 15 | 16 | this.save = function(obj) { 17 | if (!obj.id) { 18 | obj.id = dirty.uuid(); 19 | } 20 | dirtyStore.set(obj.id, obj); 21 | }; 22 | 23 | this.update = function(id, obj) { 24 | var orig = this.get(id); 25 | // todo: check obj with that id exists 26 | var updated = utilities.merge(orig, obj); 27 | this.save(updated); 28 | }; 29 | -------------------------------------------------------------------------------- /lib/josi/tasks/index.js: -------------------------------------------------------------------------------- 1 | var help = require('josi/tasks/help'); 2 | var create = require('josi/tasks/create'); 3 | var run = require('josi/tasks/run'); 4 | var test = require('josi/tasks/test'); 5 | var version = require('josi/tasks/version'); 6 | 7 | var tasks = [ 8 | help.task, 9 | create.task, 10 | run.task, 11 | test.task, 12 | version.task 13 | ]; 14 | 15 | var getAllMatching = function(name) { 16 | var r = new RegExp('^' + name); 17 | return tasks 18 | .filter(function(t) { 19 | return r.test(t.name); 20 | }); 21 | }; 22 | 23 | var getFirstMatching = function(name) { 24 | return getAllMatching(name)[0]; 25 | }; 26 | 27 | this.tasks = tasks; 28 | this.getAllMatching = getAllMatching; 29 | this.getFirstMatching = getFirstMatching; 30 | -------------------------------------------------------------------------------- /examples/tasks/controllers/task.js: -------------------------------------------------------------------------------- 1 | var actionresults = require('josi/actionresults'); 2 | var view = actionresults.view; 3 | var redirect = actionresults.redirect; 4 | var tasks = require('../models/tasks'); 5 | 6 | this.index = function() { 7 | return view({ 8 | title: 'All Tasks', 9 | tasks: tasks.list() 10 | }); 11 | }; 12 | 13 | this.details = function() { 14 | var task = tasks.get(this.route[0]); 15 | return view({ 16 | title: 'Task Details', 17 | task: task 18 | }); 19 | }; 20 | 21 | this.create = function() { 22 | if (this.form.title) { 23 | tasks.save({ 24 | title: this.form.title, 25 | details: this.form.details || 'No details.' 26 | }); 27 | return redirect('/task/'); 28 | } else { 29 | return view({ title: 'Create Task' }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /examples/codereview/controllers/home.js: -------------------------------------------------------------------------------- 1 | var actionresults = require('josi/actionresults'); 2 | var view = actionresults.view; 3 | var async = actionresults.async; 4 | 5 | var clients = []; 6 | 7 | this.index = function() { 8 | return view({ 9 | title: 'Code Review' 10 | }); 11 | }; 12 | 13 | this.send = function() { 14 | var msg = this.params.msg; 15 | var tmp = clients.slice(); 16 | clients = []; 17 | tmp.forEach(function(c) { 18 | c.write(msg); 19 | c.end(); 20 | }); 21 | return { result: 'success' }; 22 | }; 23 | 24 | this.listen = function() { 25 | return async(function(req, res) { 26 | clients.push(res); 27 | res.socket.onend = function() { 28 | clients.splice(clients.indexOf(res), 1); 29 | }; 30 | }); 31 | }; 32 | 33 | this.clients = function() { 34 | return { clients: clients.length }; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/josi/tasks/test.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var utilities = require('josi/utilities'); 6 | var Runner = require('josi/test').Runner; 7 | 8 | this.task = { 9 | name: 'test', 10 | doc: 'run the app\'s tests', 11 | opts: 'suite - the suite to run', 12 | execute: function(opts) { 13 | if (!utilities.fileOrDirectoryExists('tests')) { 14 | sys.puts('This josi app contains no tests.'); 15 | return; 16 | } 17 | var tests = fs.readdirSync('tests') 18 | .map(function(t) { return utilities.stripExtension(t); }); 19 | if (opts.suite) { 20 | tests = tests.filter(function(t) { return t == opts.suite; }); 21 | } 22 | tests = tests.map(function(t) { return path.join(process.cwd(), 'tests', t); }); 23 | var runner = new Runner(tests); 24 | runner.run(); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /examples/hello/controllers/home.js: -------------------------------------------------------------------------------- 1 | var actionresults = require('josi/actionresults'); 2 | var redirect = actionresults.redirect; 3 | var view = actionresults.view; 4 | 5 | this.index = function() { 6 | return view({ 7 | title: 'A Title', 8 | details: 'Some details' 9 | }); 10 | }; 11 | 12 | this.another = function() { 13 | return view({ 14 | title: 'Another Title', 15 | details: 'A different action, using the same view.' 16 | }, 'index'); 17 | }; 18 | 19 | this.redir = function() { 20 | return redirect('/product/details/1'); 21 | }; 22 | 23 | this.cookie = function() { 24 | var aCookie = this.cookie.get('a') === 'red' ? 'blue' : 'red'; 25 | var bCookie = (parseInt(this.cookie.get('b'), 10) || 0) + 1; 26 | this.cookie.set('a', aCookie); 27 | this.cookie.set('b', bCookie); 28 | return view({ 29 | title: 'Cookies', 30 | a: aCookie, 31 | b: bCookie 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /examples/hello/views/master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 |

<%= title %>

9 | 16 |
17 | <%= main %> 18 |
19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/results.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var actionresults = require('josi/actionresults'); 3 | 4 | this.name = 'Results Tests'; 5 | 6 | var getHeader = function(headers, key) { 7 | for (var i in headers) { 8 | var header = headers[i]; 9 | if (header[0] === key) { 10 | return header[1]; 11 | } 12 | } 13 | }; 14 | 15 | this.tests = { 16 | 'Content Type of view is HTML': function() { 17 | var result = actionresults.view({}); 18 | assert.equal(getHeader(result.headers, 'Content-Type'), 'text/html'); 19 | }, 20 | 'Content Type of json is json': function() { 21 | var result = actionresults.json({}); 22 | assert.equal(getHeader(result.headers, 'Content-Type'), 'application/json'); 23 | }, 24 | 'Content Type of raw is plain text': function() { 25 | var result = actionresults.raw({}); 26 | assert.equal(getHeader(result.headers, 'Content-Type'), 'text/plain'); 27 | }, 28 | 'Content Type of error is plain text': function() { 29 | var result = actionresults.error({}); 30 | assert.equal(getHeader(result.headers, 'Content-Type'), 'text/plain'); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /examples/tasks-ajax/controllers/task.js: -------------------------------------------------------------------------------- 1 | var actionresults = require('josi/actionresults'); 2 | var view = actionresults.view; 3 | var redirect = actionresults.redirect; 4 | 5 | var tasks = require('../models/task') 6 | 7 | this.index = function() { 8 | return view({ title: 'Tasks', tasks: tasks.all() }); 9 | }; 10 | 11 | this.details = function() { 12 | var id = this.route[0]; 13 | return view(tasks.get(id)); 14 | }; 15 | 16 | this.create = function() { 17 | if (this.params.title) { 18 | tasks.save({ title: this.params.title, description: this.params.description || 'No description.' }); 19 | return redirect('/task/'); 20 | } else { 21 | return view({ title: 'New Task' }); 22 | } 23 | }; 24 | 25 | this.done = function() { 26 | if (this.params.id) { 27 | tasks.update(this.params.id, { completed: true }); 28 | return { success: true }; 29 | } else { 30 | return { success: false, error: 'Argument "id" missing.' }; 31 | } 32 | }; 33 | 34 | this.undone = function() { 35 | if (this.params.id) { 36 | tasks.update(this.params.id, { completed: false }); 37 | return { success: true }; 38 | } else { 39 | return { success: false, error: 'Argument "id" missing.' }; 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![josi](http://thatismatt.github.com/josi/josi.png "josi") 2 | 3 | *"not so sketchy"* 4 | 5 | josi is a web framework for [node.js](http://nodejs.org) that aims to be easy to pick up for developers familiar with MVC web frameworks (e.g. ASP.NET MVC, Rails, Django) as well as making the things that are exciting about node.js - its asynchronous, event driven nature - easy to achieve. 6 | 7 | [Tell me more...](http://thatismatt.github.com/josi/) 8 | 9 | License 10 | ------- 11 | 12 | (The MIT License) 13 | 14 | Copyright (c) 2010 Matthew Lee 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining 17 | a copy of this software and associated documentation files (the 18 | 'Software'), to deal in the Software without restriction, including 19 | without limitation the rights to use, copy, modify, merge, publish, 20 | distribute, sublicense, and/or sell copies of the Software, and to 21 | permit persons to whom the Software is furnished to do so, subject to 22 | the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be 25 | included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 28 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 29 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 30 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 31 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 32 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 33 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 34 | -------------------------------------------------------------------------------- /bin/josi: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var sys = require('sys'); 5 | var path = require('path'); 6 | 7 | try { 8 | var tasks = require('josi/tasks'); 9 | } catch(err) { 10 | // if that isn't on the path then assume the script being run is 11 | // in the source, so add the directory of the script to the path. 12 | if(err.message == "Cannot find module 'josi/tasks'") { 13 | require.paths.push( 14 | path.join( 15 | path.normalize(path.dirname(__filename) + '/..'), 'lib')); 16 | var tasks = require('josi/tasks'); 17 | } 18 | } 19 | 20 | var utilities = require('josi/utilities'); 21 | 22 | var processedArgs = utilities.processARGV(process.argv); 23 | 24 | var task = processedArgs.task; 25 | var opts = processedArgs.opts; 26 | var args = processedArgs.args; 27 | 28 | var matchingTasks = task ? tasks.getAllMatching(task) : []; 29 | if (matchingTasks.length == 1) { 30 | var matchedTask = matchingTasks[0]; 31 | if (!utilities.cwdContainsApp() && matchedTask.appOnly) { 32 | sys.puts('ERROR: The task "' + matchedTask.name + '" can only be executed in a directory containing a josi app.'); 33 | } else { 34 | matchedTask.execute(opts, args, tasks.tasks); 35 | } 36 | } else if (matchingTasks.length > 1) { 37 | sys.puts('"' + task + '" is not a josi command, did you mean one of these?'); 38 | matchingTasks.forEach(function(t) { 39 | sys.puts('\t' + t.name); 40 | }); 41 | } else { 42 | if (task) { 43 | opts.name = task; 44 | tasks.getFirstMatching('create').execute(opts, args, tasks.tasks); 45 | } else { 46 | tasks.getFirstMatching('help').execute(opts, args, tasks.tasks); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/multipart-js/README.md: -------------------------------------------------------------------------------- 1 | # multipart-js 2 | 3 | A JavaScript library for parsing and writing multipart messages. 4 | 5 | ## Current State 6 | 7 | Pre-pre-alpha. Almost nothing is here, and what is here is likely completely broken. 8 | 9 | ## Usage 10 | 11 | If you're familiar with [sax-js](http://github.com/isaacs/sax-js), then most of this should 12 | be pretty straightforward. Attach event handlers, call functions, close it when you're 13 | done. Please keep fingers and dangling clothing away from the state machine. 14 | 15 | var multipart = require("multipart"); 16 | 17 | // parsing 18 | var parser = multipart.parser(); 19 | 20 | // in all event handlers, "this" is the parser, and "this.part" is the 21 | // part that's currently being dealt with. 22 | parser.onpartbegin = function (part) { doSomething(part) }; 23 | parser.ondata = function (chunk) { doSomethingElse(chunk) }; 24 | parser.onend = function () { closeItUp() }; 25 | 26 | // now start feeding the message through it. 27 | // you can do this all in one go, if you like, or one byte at a time, 28 | // or anything in between. 29 | parser.boundary = "foo"; 30 | var chunk; 31 | while ( chunk = upstreamThing.getNextChunk() ) { 32 | parser.write(chunk); 33 | } 34 | parser.close(); 35 | 36 | 37 | // writing 38 | var writer = multipart.writer(); 39 | 40 | // attach event handlers for the things we care about. 41 | writer.ondata = function (chunk) { doSomething(chunk) }; 42 | writer.onend = function () { closeItUp() }; 43 | 44 | // now trigger the events to fire by feeding files through it. 45 | writer.boundary = "foo"; 46 | writer.partBegin({ "content-type" : "text/plain", filename : "foo.txt" }); 47 | var chunk; 48 | while ( chunk = getChunkOfFile() ) { 49 | writer.write(chunk); 50 | } 51 | writer.partEnd(); 52 | writer.close(); 53 | -------------------------------------------------------------------------------- /lib/josi/test.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var path = require('path'); 3 | 4 | require('josi/class'); 5 | 6 | var red = function(str) { return "\033[31m" + str + "\033[39m"; }; 7 | var green = function(str) { return "\033[32m" + str + "\033[39m"; }; 8 | var bold = function(str) { return "\033[1m" + str + "\033[22m"; }; 9 | 10 | var putsResults = function(passes, fails) { 11 | sys.puts('passes: ' + green(passes) + ' fails: ' + red(fails)); 12 | }; 13 | 14 | this.Runner = Class.extend({ 15 | init: function(suites) { 16 | this.suites = suites; 17 | }, 18 | run: function() { 19 | var totalPasses = 0; 20 | var totalFails = 0; 21 | var failDetails = []; 22 | 23 | this.suites.forEach(function(suiteFilename) { 24 | var suite = require(suiteFilename); 25 | sys.print('Suite: ' + suite.name + ' '); 26 | var passes = 0; 27 | var fails = 0; 28 | for (var testName in suite.tests) { 29 | var test = suite.tests[testName]; 30 | try { 31 | test(); 32 | passes++; 33 | } catch (e) { 34 | fails++; 35 | failDetails.push({ suite: suite.name, test: testName, error: e }); 36 | } 37 | sys.print('.'); 38 | } 39 | sys.puts(''); 40 | putsResults(passes, fails); 41 | 42 | totalPasses += passes; 43 | totalFails += fails; 44 | }); 45 | 46 | sys.puts('Totals'); 47 | putsResults(totalPasses, totalFails); 48 | 49 | if (totalFails) { 50 | failDetails.forEach(function(fd) { 51 | sys.puts(fd.suite + ' - ' + fd.test); 52 | var stack = fd.error.stack.split('\n'); 53 | var maxStackHeight = 3; 54 | if (stack.length > maxStackHeight) { 55 | stack = stack.splice(0, maxStackHeight); 56 | stack.push(' ...'); 57 | } 58 | stack = stack.join('\n'); 59 | sys.puts(stack); 60 | }); 61 | } 62 | } 63 | }); 64 | 65 | this.createMockFunction = function(name) { 66 | var callCount = 0; 67 | var mock = function() { callCount++; }; 68 | mock.isCalled = function(n) { return callCount != (n || 0); }; 69 | mock.getName = function() { return name; }; 70 | return mock; 71 | }; 72 | -------------------------------------------------------------------------------- /lib/josi/class.js: -------------------------------------------------------------------------------- 1 | // Simple JavaScript Inheritance 2 | // by John Resig - http://ejohn.org/ 3 | // http://ejohn.org/blog/simple-javascript-inheritance/ 4 | // Inspired by base2 and Prototype 5 | 6 | (function(){ 7 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 8 | // The base Class implementation (does nothing) 9 | this.Class = function(){}; 10 | 11 | // Create a new Class that inherits from this class 12 | Class.extend = function(prop) { 13 | var _super = this.prototype; 14 | 15 | // Instantiate a base class (but only create the instance, 16 | // don't run the init constructor) 17 | initializing = true; 18 | var prototype = new this(); 19 | initializing = false; 20 | 21 | // Copy the properties over onto the new prototype 22 | for (var name in prop) { 23 | // Check if we're overwriting an existing function 24 | prototype[name] = typeof prop[name] == "function" && 25 | typeof _super[name] == "function" && fnTest.test(prop[name]) ? 26 | (function(name, fn){ 27 | return function() { 28 | var tmp = this._super; 29 | 30 | // Add a new ._super() method that is the same method 31 | // but on the super-class 32 | this._super = _super[name]; 33 | 34 | // The method only need to be bound temporarily, so we 35 | // remove it when we're done executing 36 | var ret = fn.apply(this, arguments); 37 | this._super = tmp; 38 | 39 | return ret; 40 | }; 41 | })(name, prop[name]) : 42 | prop[name]; 43 | } 44 | 45 | // The dummy class constructor 46 | function Class() { 47 | // All construction is actually done in the init method 48 | if ( !initializing && this.init ) 49 | this.init.apply(this, arguments); 50 | } 51 | 52 | // Populate our constructed prototype object 53 | Class.prototype = prototype; 54 | 55 | // Enforce the constructor to be what we expect 56 | Class.constructor = Class; 57 | 58 | // And make this class extendable 59 | Class.extend = arguments.callee; 60 | 61 | return Class; 62 | }; 63 | })(); -------------------------------------------------------------------------------- /tests/functionrouting.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var test = require('josi/test'); 4 | var utilities = require('josi/utilities'); 5 | var FunctionRouter = require('josi/routing').FunctionRouter; 6 | 7 | this.name = 'Function Router Tests'; 8 | 9 | var createRouter = function() { 10 | var router = new FunctionRouter(); 11 | router.add(/^(a)(route)$/, test.createMockFunction('route a')); 12 | router.add(/^broute$/, test.createMockFunction('route b')); 13 | return router; 14 | }; 15 | 16 | this.tests = { 17 | 'Result is a RouteResult': function() { 18 | var router = createRouter(); 19 | var result = router.route('aroute'); 20 | assert.ok(result instanceof RouteResult); 21 | }, 22 | 'Route function is not evaluated': function() { 23 | var router = createRouter(); 24 | var result = router.route('aroute'); 25 | assert.ok(!result.action.isCalled()); 26 | }, 27 | 'Result\'s action is correct': function() { 28 | var router = createRouter(); 29 | var aResult = router.route('aroute'); 30 | assert.equal('route a', aResult.action.getName()); 31 | }, 32 | 'Result\'s action is correct with multiple routes': function() { 33 | var router = createRouter(); 34 | var aResult = router.route('aroute'); 35 | var bResult = router.route('broute'); 36 | assert.equal('route a', aResult.action.getName()); 37 | assert.equal('route b', bResult.action.getName()); 38 | }, 39 | 'Result\'s route data is correct': function() { 40 | var router = createRouter(); 41 | var result = router.route('aroute'); 42 | assert.ok(result.route.indexOf('a') != -1); 43 | assert.ok(result.route.indexOf('route') != -1); 44 | }, 45 | 'When no matching route result is a RouteResult': function() { 46 | var router = createRouter(); 47 | var result = router.route('notaroute'); 48 | assert.ok(result instanceof RouteResult); 49 | }, 50 | 'When no matching route result is a MissingRouteResult': function() { 51 | var router = createRouter(); 52 | var result = router.route('notaroute'); 53 | assert.ok(result instanceof MissingRouteResult); 54 | }, 55 | 'When no matching route result has no action': function() { 56 | var router = createRouter(); 57 | var result = router.route('notaroute'); 58 | assert.ok(!result.action); 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /tests/templating.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var templating = require('josi/templating'); 3 | 4 | this.name = 'Templating Tests'; 5 | 6 | var compileTemplate = function(str) { 7 | var templater = new templating.MicroTemplatingEngine(); 8 | return templater.compile(str); 9 | }; 10 | 11 | this.tests = { 12 | 'MicroTemplatingEngine simple replacement': function() { 13 | var template = compileTemplate('a <%= c %> b'); 14 | var rendered = template({ c: 'd' }); 15 | assert.equal(rendered, 'a d b'); 16 | }, 17 | 'MicroTemplatingEngine loop': function() { 18 | var template = compileTemplate('a <% for (var i in c) { %><%= i %> <%= c[i] %> <% } %>b'); 19 | var rendered = template({ c: ['d', 'e', 'f'] }); 20 | assert.equal(rendered, 'a 0 d 1 e 2 f b'); 21 | }, 22 | 'MicroTemplatingEngine if': function() { 23 | var template = compileTemplate('a <% if (showCOnce) { %><%= c %><% } %><% if (showCTwice) { %><%= c %><% } %> b'); 24 | var rendered = template({ showCOnce: true, showCTwice: false, c: 'd' }); 25 | assert.equal(rendered, 'a d b'); 26 | }, 27 | 'MicroTemplatingEngine if else': function() { 28 | var template = compileTemplate( 29 | 'a ' + 30 | '<% if (showCOnce) { %><%= c %><% } else { %>no<%= c %><% } %> ' + 31 | '<% if (showCTwice) { %><%= c %><% } else { %>no<%= c %><% } %> ' + 32 | 'b'); 33 | var rendered = template({ showCOnce: true, showCTwice: false, c: 'd' }); 34 | assert.equal(rendered, 'a d nod b'); 35 | }, 36 | 'MicroTemplatingEngine single quotes': function() { 37 | var template = compileTemplate("'<%= c %>'"); 38 | var rendered = template({ c: 'd' }); 39 | assert.equal(rendered, "'d'"); 40 | }, 41 | 'MicroTemplatingEngine double quotes': function() { 42 | var template = compileTemplate('"<%= c %>"'); 43 | var rendered = template({ c: 'd' }); 44 | assert.equal(rendered, '"d"'); 45 | }, 46 | 'MicroTemplatingEngine script tag': function() { 47 | var template = compileTemplate('<%= c %>'); 48 | var rendered = template({ c: 'd' }); 49 | assert.equal(rendered, 'd'); 50 | }, 51 | 'MicroTemplatingEngine inline code': function() { 52 | var template = compileTemplate('<% var doubleIt = function(it) { return it + it; }; %><%= doubleIt(c) %>'); 53 | var rendered = template({ c: 'd' }); 54 | assert.equal(rendered, 'dd'); 55 | }, 56 | 'MicroTemplatingEngine escape HTML': function() { 57 | var template = compileTemplate('a <%: d %> b <%: e %> c'); 58 | var rendered = template({ d: '
', e: 'this & that' }); 59 | assert.equal(rendered, 'a <div> b this & that c'); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /tests/utilities.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var utilities = require('josi/utilities'); 3 | 4 | this.name = 'Utilities Tests'; 5 | 6 | this.tests = { 7 | 'Extension is stripped': function() { 8 | var result = utilities.stripExtension('index.html'); 9 | assert.equal(result, 'index'); 10 | }, 11 | 'Extensionless filename is unchanged': function() { 12 | var result = utilities.stripExtension('index'); 13 | assert.equal(result, 'index'); 14 | }, 15 | 'Trailing dot is stripped': function() { 16 | var result = utilities.stripExtension('index.'); 17 | assert.equal(result, 'index'); 18 | }, 19 | 'Other dots in file ignored': function() { 20 | var result = utilities.stripExtension('index.2.html'); 21 | assert.equal(result, 'index.2'); 22 | }, 23 | 'First argument is task': function() { 24 | var processed = utilities.processARGV(['', '', 'atask']); 25 | assert.equal(processed.task, 'atask'); 26 | }, 27 | 'Dashes are ignored': function() { 28 | var processed = utilities.processARGV(['', '', 'atask', '-a=b']); 29 | assert.equal(processed.opts.a, 'b'); 30 | }, 31 | 'Double dashes are ignored': function() { 32 | var processed = utilities.processARGV(['', '', 'atask', '--a=b']); 33 | assert.equal(processed.opts.a, 'b'); 34 | }, 35 | 'Dashes not required': function() { 36 | var processed = utilities.processARGV(['', '', 'atask', 'a=b']); 37 | assert.equal(processed.opts.a, 'b'); 38 | }, 39 | 'If not key value pair then it is an arg': function() { 40 | var processed = utilities.processARGV(['', '', 'atask', 'arg']); 41 | assert.equal(processed.args[0], 'arg'); 42 | }, 43 | 'Opt keys are case insensitive': function() { 44 | var processed = utilities.processARGV(['', '', 'atask', 'CaMel=b', 'c=HumPs', 'third=alongerthing']); 45 | assert.equal(processed.opts.camel, 'b'); 46 | assert.equal(processed.opts.c, 'HumPs'); 47 | assert.equal(processed.opts.third, 'alongerthing'); 48 | }, 49 | 'Opt values are case sensitive': function() { 50 | var processed = utilities.processARGV(['', '', 'atask', 'CaMel=b']); 51 | assert.equal(processed.opts.camel, 'b'); 52 | }, 53 | 'Args are case sensitive': function() { 54 | var processed = utilities.processARGV(['', '', 'atask', 'HumPs']); 55 | assert.equal(processed.args[0], 'HumPs'); 56 | }, 57 | 'Multiple opts work': function() { 58 | var processed = utilities.processARGV(['', '', 'atask', 'a=b', 'c=100', 'third=alongerthing']); 59 | assert.equal(processed.opts.a, 'b'); 60 | assert.equal(processed.opts.c, '100'); 61 | assert.equal(processed.opts.third, 'alongerthing'); 62 | }, 63 | 'Multiple args work': function() { 64 | var processed = utilities.processARGV(['', '', 'atask', 'arg1', 'arg2', 'arg3']); 65 | assert.equal(processed.args[0], 'arg1'); 66 | assert.equal(processed.args[1], 'arg2'); 67 | assert.equal(processed.args[2], 'arg3'); 68 | }, 69 | 'Merge doesn\'t touch arguments': function() { 70 | var a = { a: 1 }; 71 | var b = { a: 2 }; 72 | var result = utilities.merge(a, b); 73 | assert.equal(a.a, 1); 74 | assert.equal(Object.keys(a).length, 1); 75 | assert.equal(b.a, 2); 76 | assert.equal(Object.keys(b).length, 1); 77 | }, 78 | 'Merge obeys ordering or arguments': function() { 79 | var a = { a: 1 }; 80 | var b = { a: 2 }; 81 | var result = utilities.merge(a, b); 82 | assert.equal(result.a, 2); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /tests/controllerrouting.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var test = require('josi/test'); 4 | var routeresults = require('josi/routeresults'); 5 | var ControllerRouter = require('josi/routing').ControllerRouter; 6 | 7 | this.name = 'Controller Router Tests'; 8 | 9 | var createRouter = function(setRouterDefaults) { 10 | var factory = { 11 | getController: function(controllerName) { 12 | if (controllerName == 'missing') { 13 | throw new Error('Missing Controller'); 14 | } else { 15 | return { 16 | index: test.createMockFunction(controllerName + ' controller, index action') 17 | }; 18 | } 19 | } 20 | }; 21 | var router = new ControllerRouter(factory); 22 | router.add( 23 | /^\/(?:(\w+)\/?)?(?:(\w+)\/?)?(?:(\w+)\/?)?$/, 24 | setRouterDefaults ? 25 | { controller: 'home', action: 'index' } : 26 | {} 27 | ); 28 | return router; 29 | }; 30 | 31 | this.tests = { 32 | 'Result is a RouteResult': function() { 33 | var router = createRouter(); 34 | var result = router.route('/'); 35 | assert.ok(result instanceof routeresults.RouteResult); 36 | }, 37 | 'If no controller matched and no default then missing route': function() { 38 | var router = createRouter(); 39 | var result = router.route('/'); 40 | assert.ok(result instanceof routeresults.MissingRouteResult); 41 | }, 42 | 'If no controller matched and no default then correct error message': function() { 43 | var router = createRouter(); 44 | var result = router.route('/'); 45 | assert.equal(result.message, 'No controller was matched in the url and there was no default controller specified.'); 46 | }, 47 | 'If no action matched and no default then missing route': function() { 48 | var router = createRouter(); 49 | var result = router.route('/'); 50 | assert.ok(result instanceof routeresults.MissingRouteResult); 51 | }, 52 | 'If no controller matched fallback to default': function() { 53 | var router = createRouter(true); 54 | var result = router.route('/'); 55 | assert.equal(result.action.getName(), 'home controller, index action'); 56 | }, 57 | 'If no action matched fallback to default': function() { 58 | var router = createRouter(true); 59 | var result = router.route('/product'); 60 | assert.equal(result.action.getName(), 'product controller, index action'); 61 | }, 62 | 'If factory doesn\'t return controller then missing route': function() { 63 | var router = createRouter(); 64 | var result = router.route('/missing'); 65 | assert.ok(result instanceof routeresults.MissingRouteResult); 66 | }, 67 | 'If factory doesn\'t return controller then correct error message': function() { 68 | var router = createRouter(); 69 | var result = router.route('/missing'); 70 | assert.equal(result.message, 'The "missing" controller is missing.'); 71 | }, 72 | 'If action missing from controller then missing route': function() { 73 | var router = createRouter(); 74 | var result = router.route('/product/missing'); 75 | assert.ok(result instanceof routeresults.MissingRouteResult); 76 | }, 77 | 'If action missing from controller then correct error message': function() { 78 | var router = createRouter(); 79 | var result = router.route('/product/missing'); 80 | assert.equal(result.message, 'The "product" controller doesn\'t have an action called "missing"'); 81 | }, 82 | 'Result\'s context has controller set': function() { 83 | var router = createRouter(true); 84 | var result = router.route('/'); 85 | assert.equal(result.route.controller, 'home'); 86 | }, 87 | 'Result\'s context has action set': function() { 88 | var router = createRouter(true); 89 | var result = router.route('/'); 90 | assert.equal(result.route.action, 'index'); 91 | }, 92 | }; 93 | -------------------------------------------------------------------------------- /lib/josi/routing.js: -------------------------------------------------------------------------------- 1 | require('josi/class'); 2 | var routeresults = require('josi/routeresults'); 3 | var actionresults = require('josi/actionresults'); 4 | var utilities = require('josi/utilities'); 5 | 6 | this.FunctionRouter = FunctionRouter = Class.extend({ 7 | init: function() { 8 | this.routes = []; 9 | }, 10 | add: function(pattern, action, remap) { 11 | this.routes.push({ pattern: pattern, action: action, remap: remap || false }); 12 | }, 13 | addStatic: function(dir) { 14 | this.routes.push({ 15 | pattern: new RegExp("/" + dir + "/([^?]*)(.*)?"), 16 | action: function() { 17 | return actionresults.content(dir + "/" + this.route[0]); 18 | }, 19 | remap: false 20 | }); 21 | }, 22 | route: function(url) { 23 | for (var i in this.routes) { 24 | var route = this.routes[i]; 25 | if (route.pattern instanceof RegExp) { 26 | var match = route.pattern.exec(url); 27 | if (match) { 28 | var context = match.slice(1); 29 | if (route.remap) { 30 | var mappingResult = route.action(context); 31 | if (mappingResult instanceof routeresults.RouteResult) { 32 | return mappingResult; 33 | } else { 34 | throw new Error('Remapped route from router.route must return an instance of RouteResult'); 35 | } 36 | } else { 37 | return new routeresults.RouteResult(route.action, context); 38 | } 39 | } 40 | } 41 | } 42 | return routeresults.missingRoute(); 43 | } 44 | }); 45 | 46 | this.ControllerRouter = ControllerRouter = FunctionRouter.extend({ 47 | init: function(factory) { 48 | this.factory = factory; 49 | this._super(); 50 | }, 51 | add: function(pattern, defaults) { 52 | var factory = this.factory; 53 | this._super( 54 | pattern, 55 | function(context) { 56 | var controller = context[0] || defaults.controller; 57 | var action = context[1] || defaults.action; 58 | if (!controller) { 59 | return routeresults.missingRoute('No controller was matched in the url and there was no default controller specified.'); 60 | } 61 | try { 62 | var controllerObj = factory.getController(controller); 63 | } catch (e) { 64 | if (e.message && e.message === 'Missing Controller') { 65 | return routeresults.missingRoute('The "' + controller + '" controller is missing.'); 66 | } else { 67 | return new routeresults.ErrorRouteResult(e); 68 | } 69 | } 70 | if (controllerObj[action]) { 71 | var result = controllerObj[action]; 72 | context = context.slice(2); 73 | context.controller = controller; 74 | context.action = action; 75 | return new routeresults.RouteResult(result, context); 76 | } else { 77 | return routeresults.missingRoute('The "' + controller + '" controller doesn\'t have an action called "' + action + '"'); 78 | } 79 | }, 80 | true // remap 81 | ); 82 | } 83 | }); 84 | 85 | this.ControllerFactory = ControllerFactory = Class.extend(); 86 | 87 | this.ModuleControllerFactory = ModuleControllerFactory = ControllerFactory.extend({ 88 | init: function(dir) { 89 | this.dir = dir; 90 | }, 91 | getController: function(controller) { 92 | var moduleDir = this.dir + '/controllers/' + controller; 93 | try { 94 | return require(moduleDir); 95 | } catch (e) { 96 | if (e.message && e.message === "Cannot find module '" + moduleDir + "'") { 97 | throw new Error('Missing Controller'); 98 | } else { 99 | throw e; 100 | } 101 | } 102 | } 103 | }); 104 | -------------------------------------------------------------------------------- /lib/multipart-js/write.js: -------------------------------------------------------------------------------- 1 | // This API should be a symmetrical mirror of the writer API in writer.js 2 | // Instead of having onpartbegin and onpartend events, call the partBegin 3 | // and partEnd methods, and write file contents. Then, the writer emits 4 | // "data" events with chunks of data suitable for sending over the wire. 5 | // That is, it converts the data objects into one big string. 6 | 7 | // var w = writer(); 8 | // w.boundary = "foo-bar-bazfj3980haf38h"; 9 | // w.type = "form-data"; 10 | // w.headers = {...}; 11 | // w.partBegin({...}); // send the headers, causes the initial "data" event to emit 12 | // w.write("..."); // encode the data, wrap it, etc., and emit more "data" events 13 | // w.partEnd(); // close off that part, emitting a "data" event with the --boundary 14 | // w.partBegin({...}); // another part... 15 | // w.partBegin({...}); // if the last one was multipart, then do a child, else error. 16 | // w.partEnd(); // close off that child part 17 | // w.partEnd(); // close off the parent 18 | // w.close(); // close off all open parts, and emit "end" event 19 | 20 | var 21 | utils = require("./utils"), 22 | error = utils.error, 23 | emit = utils.emit, 24 | EVENTS = exports.EVENTS = ["onData", "onEnd", "onError"]; 25 | 26 | var S = 0; 27 | exports.STATE = 28 | { STARTED : S++ //nothing received 29 | , PART_STARTED : S++ // a part header was written 30 | , WRITING : S++ // client is writing a part 31 | , PART_ENDED : S++ // a end part was written 32 | , CLOSED : S++ // close was called 33 | }; 34 | for (S in exports.STATE) exports.STATE[exports.STATE[S]] = S; 35 | S = exports.STATE; 36 | 37 | exports.writer = writer; 38 | exports.Writer = Writer; 39 | 40 | function NYI () { throw new Error("Not yet implemented") } 41 | 42 | // Returns a new writer. 43 | // Attaches event handlers to it, and they'll get notified 44 | // call myWriter.write(chunk) and then myWriter.close() when it's done. 45 | function writer () { return new Writer() } 46 | 47 | function end (writer) { 48 | // close the whole stack of open parts, and emit "end" 49 | throw new Error("TODO"); 50 | } 51 | function newPart (writer) { 52 | var p = 53 | { headers:{} 54 | , parent : writer.part 55 | }; 56 | parent.parts = parent.parts || []; 57 | parent.parts.push(p); 58 | } 59 | 60 | function Writer () { 61 | this.firstPartReceived = false; 62 | this.state = S.STARTED; 63 | } 64 | 65 | 66 | // Writes a chunk to the multipart stream 67 | // Sets error if not called after a partBegin 68 | Writer.prototype.write = write; 69 | function write (chunk) { 70 | var writer = this; 71 | //ye old state machine 72 | if (chunk === null) return; 73 | if (writer.state !== S.PART_STARTED) { 74 | error(writer, "Illegal state. Must call partBegin before writing."); 75 | return; 76 | } 77 | // TODO - encode the data if base64 content-transfer-encoding 78 | emit(writer, "onData", chunk); 79 | } 80 | 81 | Writer.prototype.close = NYI; 82 | 83 | // Starts a part or nested part. 84 | // Emits data events to listeners with headers for part. 85 | // If first part, will emit http headers plus headers of first part. 86 | // Sets error if part is added to a part of type other than multipart, 87 | // or if new part is started before the old one is written correctly. 88 | // 89 | // Params: object describing header for event 90 | // e.g. { "content-type" : "text/plain", filename : "foo.txt" } 91 | Writer.prototype.partBegin = partBegin; 92 | function partBegin (partDesc) { 93 | var writer = this; 94 | if (!writer.boundary || writer.boundary.length < 1) { 95 | error(writer, "Missing boundary. Set boundary property."); 96 | return; 97 | } 98 | if (writer.state !== S.STARTED && writer.state !== S.PART_ENDED) { 99 | error(writer, "Illegal state. Cannot begin new part right now."); 100 | return; 101 | } 102 | else if (this.currentPart && !isMulti(this.currentPart)) { 103 | error(writer, "Bad format. Cannot add part to non multipart parent."); 104 | return; 105 | } else { 106 | // TODO - check for invalid state before adding the new part 107 | //newPart(writer); 108 | partChunk = ""; 109 | if (!writer.firstPartReceived) { 110 | //write the http headers 111 | } 112 | // TODO - encode part headers based on partDesc 113 | emit(writer, "onData", "TODO: encoded chunk"); 114 | } 115 | } 116 | 117 | Writer.prototype.partEnd = NYI; 118 | 119 | -------------------------------------------------------------------------------- /lib/josi/templating.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | require('josi/class'); 3 | 4 | var TemplatingEngine = Class.extend({ 5 | init: function() { 6 | var cache = {}; 7 | var self = this; 8 | this.load = function(path, cb) { 9 | if (cache[path]) { 10 | cb(cache[path]); 11 | } else { 12 | fs.readFile(path, 'utf8', function(err, data) { 13 | if (err) { 14 | // todo: deal with error when loading view 15 | throw err; 16 | } 17 | cache[path] = self.compile(data); 18 | cb(cache[path]); 19 | }); 20 | } 21 | }; 22 | }, 23 | compile: function(str) { 24 | throw new Error('Not implemented error. To use create a subclass of TemplatingEngine that implements compile.'); 25 | }, 26 | render: function(data, callback, view, master) { 27 | var self = this; 28 | data = data || {}; 29 | this.load(view, function(template) { 30 | try { 31 | var rendered = template(data); 32 | } catch (e) { 33 | callback(e); 34 | } 35 | if (master) { 36 | data.main = rendered; 37 | self.load(master, function(masterTemplate) { 38 | var rendered = masterTemplate(data); 39 | callback(null, rendered); 40 | }); 41 | } else { 42 | callback(null, rendered); 43 | } 44 | }); 45 | } 46 | }); 47 | 48 | // JavaScript Micro-Templating 49 | // by John Resig - http://ejohn.org/ - MIT Licensed 50 | // http://ejohn.org/blog/javascript-micro-templating/ 51 | // single quote fix by Neil Donewar - #comment-321850 52 | this.MicroTemplatingEngine = TemplatingEngine.extend({ 53 | compile: function(str) { 54 | var unscopedTemplate = new Function("obj", "helpers", 55 | "var __ = { stack: [], helpers: helpers };" + 56 | 57 | // 'namespace' the view data if the user provided a namespace 58 | (this.namespace ? 'obj.' + this.namespace + ' = obj;' : '') + 59 | 60 | // Introduce the data as local variables using with(){} 61 | "with(obj){__.stack.push('" + 62 | 63 | // Convert the template into pure JavaScript 64 | str 65 | .replace(/[\r\t\n]/g, " ") 66 | .replace(/'(?=[^%]*%>)/g,"\t") 67 | .split("'").join("\\'") 68 | .split("\t").join("'") 69 | .replace(/<%=(.+?)%>/g, "',$1,'") 70 | .replace(/<%:(.+?)%>/g, "',__.helpers.escapeHTML($1),'") 71 | .split("<%").join("');") 72 | .split("%>").join("__.stack.push('") + 73 | "');}return __.stack.join('');" 74 | ); 75 | return function(obj) { 76 | return unscopedTemplate(obj, { escapeHTML: escapeHTML }); 77 | }; 78 | } 79 | }); 80 | 81 | var escapeHTML = function (str) { 82 | return str 83 | .replace(/&/g,'&') 84 | .replace(//g,'>'); 86 | }; 87 | 88 | // Adapted from jQuery Templating Plugin 89 | // Copyright 2010, John Resig 90 | // Dual licensed under the MIT or GPL Version 2 licenses. 91 | // http://github.com/jquery/jquery-tmpl/ 92 | this.jQueryTemplatingEngine = TemplatingEngine.extend({ 93 | compile: function(str) { 94 | var tmplcmd = { 95 | 'each': { 96 | _default: [null, "$i"], 97 | prefix: "for(var $2 in $1){", 98 | suffix: "}" 99 | }, 100 | 'if': { 101 | prefix: "if($1){", 102 | suffix: "}" 103 | }, 104 | 'else': { 105 | prefix: "}else{" 106 | }, 107 | '=': { 108 | _default: ["this"], 109 | prefix: "_.push(typeof $1==='function'?$1.call(this):$1);" 110 | } 111 | }; 112 | var fn = new Function("$data", 113 | "var _=[];_.data=$data;" + 114 | 115 | // Introduce the data as local variables using with(){} 116 | "with($data){_.push('" + 117 | 118 | // Convert the template into pure JavaScript 119 | str 120 | .replace(/[\r\t\n]/g, " ") 121 | .replace(/\${([^}]*)}/g, "{{= $1}}") 122 | .replace(/{{(\/?)(\w+|.)(?:\((.*?)\))?(?: (.*?))?}}/g, function(all, slash, type, fnargs, args) { 123 | var tmpl = tmplcmd[type]; 124 | 125 | if (!tmpl) { 126 | throw "Template command not found: " + type; 127 | } 128 | 129 | var def = tmpl._default; 130 | 131 | return "');" + tmpl[slash ? "suffix" : "prefix"] 132 | .split("$1").join(args || (def ? def[0] : '')) 133 | .split("$2").join(fnargs || (def ? def[1] : '')) + "_.push('"; 134 | }) 135 | + "');}return _.join('');"); 136 | return fn; 137 | } 138 | }); 139 | -------------------------------------------------------------------------------- /lib/josi/actionresults.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | require('josi/class'); 4 | var utilities = require('josi/utilities'); 5 | 6 | this.ActionResult = ActionResult = Class.extend({ 7 | init: function(body, headers, statusCode) { 8 | this.statusCode = statusCode || 200; 9 | this.headers = headers || [ ['Content-Type', 'text/html'] ]; 10 | this.body = body; 11 | }, 12 | execute: function(req, res) { 13 | res.writeHead(this.statusCode, this.headers); 14 | if (this.body) { 15 | res.write(this.body); 16 | } 17 | res.end(); 18 | } 19 | }); 20 | 21 | this.ViewResult = ViewResult = ActionResult.extend({ 22 | init: function(data, action, controller) { 23 | this._super(); 24 | this.data = data; 25 | this.context = { 26 | controller: controller, 27 | action: action 28 | }; 29 | }, 30 | execute: function(req, res, app) { 31 | var viewResult = this; 32 | var execute = this._super; 33 | app.templater.render( 34 | this.data, 35 | function(err, rendered) { 36 | if (err) { 37 | error(err).execute(req, res); 38 | return; 39 | } 40 | viewResult.body = rendered; 41 | execute.apply(viewResult, [req, res]); 42 | }, 43 | this.view, 44 | this.master 45 | ); 46 | } 47 | }); 48 | 49 | this.ContentResult = ContentResult = ActionResult.extend({ 50 | init: function(filename) { 51 | this._super(null, []); 52 | this.filename = filename; 53 | }, 54 | execute: function(req, res) { 55 | var self = this; 56 | var filename = this.filename; 57 | fs.stat(filename, function(err, stat) { 58 | if (err) { 59 | if (err.errno && err.errno === 2) { 60 | notFound('Static content not found.').execute(req, res); 61 | return; 62 | } 63 | } 64 | var etag = Number(stat.mtime); 65 | if (req.headers['if-none-match'] && 66 | req.headers['if-none-match'] == etag) { 67 | notModified().execute(req, res); 68 | return; 69 | } 70 | self.headers.push(['Content-Length', stat.size]); 71 | self.headers.push(['ETag', etag]); 72 | var first = true; 73 | var stream = fs.createReadStream(filename); 74 | stream 75 | .addListener('error', function(err) { 76 | if (first) { 77 | return error(err).execute(req, res); 78 | } 79 | stream.destroy(); 80 | req.end(); 81 | }) 82 | .addListener('data', function(data){ 83 | if (first) { 84 | first = false; 85 | var contentType = utilities.mime.lookup(utilities.extension(filename)); 86 | self.headers.push(['Transfer-Encoding', 'chunked']); 87 | self.headers.push(['Content-Type', contentType]); 88 | res.writeHead(200, self.headers); 89 | } 90 | res.write(data, 'binary'); 91 | }) 92 | .addListener('end', function() { 93 | res.end(); 94 | }); 95 | }); 96 | } 97 | }); 98 | 99 | this.AsyncResult = AsyncResult = ActionResult.extend({ 100 | init: function(func) { 101 | this._super(); 102 | this.func = func; 103 | }, 104 | execute: function(req, res) { 105 | res.writeHead(this.statusCode, this.headers); 106 | this.func(req, res); 107 | } 108 | }); 109 | 110 | this.async = function(func) { 111 | return new AsyncResult(func); 112 | }; 113 | 114 | this.view = function(data, action, controller) { 115 | return new ViewResult(data, action, controller); 116 | }; 117 | 118 | this.redirect = function(url) { 119 | return new ActionResult('Redirecting...', [ ['Content-Type', 'text/html'], ['Location', url] ], 301); 120 | }; 121 | 122 | this.notFound = notFound = function(msg) { 123 | return new ActionResult(msg || 'Not found.', [ ['Content-Type', 'text/html'] ], 404); 124 | }; 125 | 126 | this.notModified = notModified = function() { 127 | return new ActionResult(null, [], 304); 128 | }; 129 | 130 | this.error = error = function(err, httpStatusCode) { 131 | var msg = err instanceof Error ? err.message + '\r\n' + err.stack : err; 132 | return new ActionResult(msg, [ ['Content-Type', 'text/plain'] ], httpStatusCode || 500); 133 | }; 134 | 135 | this.raw = function(data) { 136 | return new ActionResult(data, [ ['Content-Type', 'text/plain'] ]); 137 | }; 138 | 139 | this.json = function(data) { 140 | return new ActionResult(JSON.stringify(data), [ ['Content-Type', 'application/json'] ]); 141 | }; 142 | 143 | this.content = function(filename) { 144 | return new ContentResult(filename); 145 | }; 146 | -------------------------------------------------------------------------------- /lib/dirty/dirty.js: -------------------------------------------------------------------------------- 1 | var 2 | dirty = exports, 3 | events = require('events'), 4 | fs = require('fs'); 5 | 6 | dirty.uuid = function() { 7 | var uuid = '', i; 8 | for (i = 0; i < 32; i++) { 9 | uuid += Math.floor(Math.random() * 16).toString(16); 10 | } 11 | return uuid; 12 | }; 13 | 14 | var Dirty = dirty.Dirty = function(filePath, options) { 15 | process.EventEmitter.call(this); 16 | 17 | options = options || {}; 18 | options.flushInterval = options.flushInterval || 10; 19 | options.flushLimit = options.flushLimit || 1000; 20 | 21 | var 22 | self = this, 23 | docs = {}, 24 | log = [], 25 | loading = false, 26 | flushing = false, 27 | readStream = fs.createReadStream(filePath), 28 | writeStream = fs.createWriteStream(filePath, {flags: 'a+'}), 29 | readBuffer = '', 30 | timer; 31 | 32 | readStream 33 | .addListener('error', function(err) { 34 | // No such file or directory 35 | if (err.errno == 2) { 36 | self.emit('load'); 37 | return; 38 | } 39 | self.emit('error', err); 40 | }) 41 | .addListener('data', function(chunk) { 42 | var 43 | offset, 44 | chunk, 45 | doc; 46 | 47 | readBuffer += chunk; 48 | 49 | while ((offset = readBuffer.indexOf("\n")) > -1) { 50 | chunk = readBuffer.substr(0, offset); 51 | readBuffer = readBuffer.substr(offset+1); 52 | 53 | try { 54 | doc = JSON.parse(chunk); 55 | } catch (e) { 56 | continue; 57 | } 58 | 59 | if (doc.deleted) { 60 | delete docs[doc.id]; 61 | } else { 62 | docs[doc.id] = doc.value; 63 | } 64 | } 65 | }) 66 | .addListener('end', function() { 67 | self.emit('load'); 68 | }); 69 | 70 | writeStream 71 | .addListener('drain', function() { 72 | if (!log.length) { 73 | self.emit('drain'); 74 | } 75 | }) 76 | .addListener('error', function(err) { 77 | self.emit('error', err); 78 | }); 79 | 80 | this.add = function(doc, cb) { 81 | var id = dirty.uuid(); 82 | this.set(id, doc, cb); 83 | return id; 84 | }; 85 | 86 | this.set = function(id, doc, cb) { 87 | if (doc !== undefined) { 88 | docs[id] = doc; 89 | } else { 90 | delete docs[id]; 91 | } 92 | 93 | log.push(!cb ? id : [id, cb]); 94 | 95 | if (loading === true) { 96 | // Do not flush while loading 97 | } else if (options.flushLimit && !flushing && log.length >= options.flushLimit) { 98 | this.flush(); 99 | if (timer) { 100 | clearTimeout(timer); 101 | } 102 | } else if (options.flushInterval && !flushing && !timer) { 103 | timer = setTimeout(function() { 104 | self.flush(); 105 | timer = null; 106 | }, options.flushInterval); 107 | } 108 | }; 109 | 110 | this.get = function(id) { 111 | return docs[id]; 112 | }; 113 | 114 | this.remove = function(id, cb) { 115 | this.set(id, undefined, cb); 116 | }; 117 | 118 | this.flush = function(flushCb) { 119 | flushing = true; 120 | 121 | var 122 | chunkCallbacks = [], 123 | chunk = '', 124 | id; 125 | 126 | while (true) { 127 | id = log.shift(); 128 | if (id === undefined) { 129 | break; 130 | } 131 | 132 | if (id instanceof Array) { 133 | chunkCallbacks.push(id[1]); 134 | id = id[0]; 135 | } 136 | 137 | if (id in docs) { 138 | chunk += JSON.stringify({id: id, value: docs[id]})+"\n"; 139 | } else { 140 | chunk += JSON.stringify({id: id, deleted: true})+"\n"; 141 | } 142 | 143 | if (chunk.length < 16 * 1024 && log.length) { 144 | continue; 145 | } 146 | 147 | writeStream.write(chunk, function(err) { 148 | chunkCallbacks.forEach(function(cb) { 149 | cb(err); 150 | }); 151 | callbacks = []; 152 | 153 | if (err) { 154 | if (flushCb) { 155 | flushCb(err); 156 | } 157 | self.emit('error', err); 158 | } 159 | 160 | flushing = false; 161 | 162 | if (log.length) { 163 | self.flush(); 164 | } else if (flushCb) { 165 | cb(null); 166 | } 167 | }); 168 | } 169 | }; 170 | 171 | this.filter = function(fn) { 172 | var ret = []; 173 | for (var key in docs) if (docs.hasOwnProperty(key)) { 174 | if (fn(docs[key])) ret.push(docs[key]); 175 | } 176 | return ret; 177 | } 178 | 179 | }; 180 | 181 | // from the old sys module 182 | var inherits = function (ctor, superCtor) { 183 | var tempCtor = function(){}; 184 | tempCtor.prototype = superCtor.prototype; 185 | ctor.super_ = superCtor; 186 | ctor.prototype = new tempCtor(); 187 | ctor.prototype.constructor = ctor; 188 | } 189 | 190 | inherits(Dirty, events.EventEmitter); 191 | -------------------------------------------------------------------------------- /lib/josi/tasks/create.js: -------------------------------------------------------------------------------- 1 | var sys = require('sys'); 2 | var fs = require('fs'); 3 | 4 | var utilities = require('josi/utilities'); 5 | 6 | this.task = { 7 | name: 'create', 8 | doc: 'create a new josi app or components of an existing josi app', 9 | execute: function(opts, args) { 10 | if (args.length < 2) { 11 | var appName = opts.name || args[0]; 12 | if (!appName) { 13 | sys.puts('ERROR: When creating a josi app you must specify its name.'); 14 | return; 15 | } 16 | if (utilities.fileOrDirectoryExists(appName)) { 17 | sys.puts('ERROR: Can\'t create a josi app named "' + appName + '",' + 18 | ' as a file or directory with that name already exists.'); 19 | return; 20 | } 21 | sys.puts('Creating app...'); 22 | createApp(appName); 23 | sys.puts('App created. Type "cd ' + appName + '; josi run" to start the web server.'); 24 | } else { 25 | if (!utilities.cwdContainsApp()) { 26 | sys.puts('ERROR: The creation of josi components can only be done within a josi app.'); 27 | return; 28 | } 29 | var component = args[0].toLowerCase(); 30 | switch (component) { 31 | case 'controller': 32 | sys.puts('Creating controller...'); 33 | createController('.', args[1]); 34 | createView('.', args[1], 'index'); 35 | sys.puts('Controller created.'); 36 | break; 37 | case 'action': 38 | if (args.length != 3) { 39 | sys.puts('ERROR: The name of the action to be created must be specified.'); 40 | return; 41 | } 42 | if (!utilities.fileOrDirectoryExists('controllers/' + args[1] + '.js')) { 43 | sys.puts('ERROR: The "' + args[1] + '" controller doesn\'t exist.'); 44 | return; 45 | } 46 | sys.puts('Creating action...'); 47 | createView('.', args[1], args[2]); 48 | addActionToController(args[1], args[2]); 49 | sys.puts('Action created.'); 50 | break; 51 | // todo: case 'test': 52 | default: 53 | sys.puts('ERROR: "' + args[1] + '" is not a valid josi component to create.'); 54 | return; 55 | } 56 | } 57 | } 58 | }; 59 | 60 | var createApp = function(appName) { 61 | fs.mkdirSync(appName, 0777); 62 | fs.writeFileSync(appName + '/app.js', 63 | [ 'this.init = function() {', 64 | ' this.router.add(', 65 | ' // this route matches: ///', 66 | ' /^\\/(?:(\\w+)\\/?)?(?:(\\w+)\\/?)?(?:([0-9]+)\\/?)?$/,', 67 | ' { controller: \'home\', action: \'index\' }', 68 | ' );', 69 | '};', '' 70 | ].join('\r\n') 71 | ); 72 | createController(appName, 'home'); 73 | fs.mkdirSync(appName + '/views', 0777); 74 | createViewMaster(appName); 75 | createView(appName, 'home', 'index'); 76 | }; 77 | 78 | var createController = function(appName, controllerName) { 79 | var controllersDir = appName + '/controllers'; 80 | if (!utilities.fileOrDirectoryExists(controllersDir)) { 81 | fs.mkdirSync(controllersDir, 0777); 82 | } 83 | var filename = appName + '/controllers/' + controllerName + '.js'; 84 | if (utilities.fileOrDirectoryExists(filename)) { 85 | throw new Error('Controller already exists'); 86 | } 87 | fs.writeFileSync(filename, 88 | [ 'var view = require(\'josi/actionresults\').view;', 89 | '', 90 | 'this.index = function() {', 91 | ' return view({', 92 | ' title: \'' + (appName == '.' ? 'A josi app' : appName + ' - a josi app') + '\',', 93 | ' controller: \'' + controllerName + '\',', 94 | ' action: \'index\',', 95 | ' description: \'' + (appName == '.' ? 'This' : appName) + ' is a josi app\'', 96 | ' });', 97 | '};', '' 98 | ].join('\r\n') 99 | ); 100 | }; 101 | 102 | var createView = function(appName, controllerName, viewName) { 103 | var viewsDir = appName + '/views'; 104 | if (!utilities.fileOrDirectoryExists(viewsDir)) { 105 | fs.mkdirSync(viewsDir, 0777); 106 | } 107 | viewsDir = viewsDir + '/' + controllerName; 108 | if (!utilities.fileOrDirectoryExists(viewsDir)) { 109 | fs.mkdirSync(viewsDir, 0777); 110 | } 111 | var filename = appName + '/views/' + controllerName + '/' + viewName + '.html'; 112 | if (utilities.fileOrDirectoryExists(filename)) { 113 | throw new Error('View already exists'); 114 | } 115 | fs.writeFileSync(filename, 116 | [ '

Controller: <%= controller %>

', 117 | '

Action: <%= action %>

', 118 | '

<%= description %>

', '' 119 | ].join('\r\n') 120 | ); 121 | }; 122 | 123 | var createViewMaster = function(appName) { 124 | fs.writeFileSync(appName + '/views/master.html', 125 | [ '', 126 | ' ', 127 | ' <%= title %>', 128 | ' ', 129 | ' ', 130 | '

<%= title %>

', 131 | ' <%= main %>', 132 | ' ', 133 | '', '' 134 | ].join('\r\n') 135 | ); 136 | }; 137 | 138 | var addActionToController = function(controllerName, actionName) { 139 | var writeStream = fs.createWriteStream('controllers/' + controllerName + '.js', { flags: 'a' }); 140 | writeStream.write( 141 | [ '', 142 | 'this.' + actionName + ' = function() {', 143 | ' return view({', 144 | ' title: \'A josi app\',', 145 | ' controller: \'' + controllerName + '\',', 146 | ' action: \'' + actionName + '\',', 147 | ' description: \'This is a josi app\'', 148 | ' });', 149 | '};', '' 150 | ].join('\r\n') 151 | ); 152 | writeStream.end(); 153 | }; 154 | -------------------------------------------------------------------------------- /lib/josi/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var url = require('url'); 3 | var querystring = require('querystring'); 4 | 5 | var actionresults = require('josi/actionresults'); 6 | var routing = require('josi/routing'); 7 | var templating = require('josi/templating'); 8 | var utilities = require('josi/utilities'); 9 | var multipart = require('multipart-js/multipart'); 10 | 11 | this.Server = function(dir) { 12 | var app = require(dir + '/app'); 13 | app.settings = utilities.merge( 14 | { 15 | maxContentLength: 100 * 1024 // 100KB 16 | }, 17 | app.settings 18 | ); 19 | if (!app.router) { 20 | var controllerFactory = new routing.ModuleControllerFactory(dir); 21 | app.router = new routing.ControllerRouter(controllerFactory); 22 | } 23 | if (!app.templater) { 24 | app.templater = new templating.MicroTemplatingEngine(); 25 | } 26 | app.init(); 27 | 28 | var processActionResult = function(actionResult, context) { 29 | if (actionResult instanceof actionresults.ActionResult) { 30 | if (actionResult instanceof actionresults.ViewResult) { 31 | actionResult.view = 'views/' + (actionResult.context.controller || context.route.controller) + 32 | '/' + (actionResult.context.action || context.route.action) + '.html'; 33 | actionResult.master = 'views/master.html'; 34 | } 35 | return actionResult; 36 | } else if (actionResult instanceof Object) { 37 | return actionresults.json(actionResult); 38 | } else if (typeof actionResult === 'string') { 39 | return actionresults.raw(actionResult); 40 | } else { 41 | // todo: better error message when action result not instance of ActionResult 42 | return actionresults.error('Bad action result.'); 43 | } 44 | }; 45 | 46 | var parseCookies = function(header) { 47 | var cookies = {}; 48 | if (header) { 49 | header 50 | .split(';') 51 | .forEach(function(cookie) { 52 | var pair = cookie.split('='); 53 | pair = pair.map(function(p) { return p.trim(); }); 54 | pair.length === 1 ? 55 | cookies[''] = pair[0] : 56 | cookies[pair[0]] = pair[1]; 57 | }); 58 | } 59 | return cookies; 60 | }; 61 | 62 | var server = http.createServer(function(req, res) { 63 | var parsedUrl = url.parse(req.url); 64 | var cookie = (function() { 65 | var reqCookies = parseCookies(req.headers['cookie']); 66 | var resCookies = {}; 67 | return { 68 | get: function(key) { 69 | return reqCookies[key]; 70 | }, 71 | // todo: enable cookie expiry, domain, path & secure 72 | set: function(key, value) { 73 | resCookies[key] = value; 74 | }, 75 | toHeaders: function() { 76 | var cookies = []; 77 | for (var key in resCookies) { 78 | cookies.push(key + '=' + resCookies[key] + '; path=/;'); 79 | } 80 | return cookies; 81 | } 82 | // todo: enable cookie removal 83 | }; 84 | })(); 85 | var routeResult = app.router.route(parsedUrl.pathname); 86 | var actionContext = { 87 | route: routeResult.route || {}, 88 | query: querystring.parse(parsedUrl.query), 89 | form: {}, 90 | files: {}, 91 | cookie: cookie 92 | }; 93 | var callback = function(err) { 94 | if (err) { 95 | actionresults.error(err, 413).execute(req, res); 96 | return; 97 | } 98 | actionContext.params = utilities.merge(actionContext.query, actionContext.form); 99 | if (actionContext.route.controller) { 100 | actionContext.params.controller = actionContext.route.controller; 101 | } 102 | if (actionContext.route.action) { 103 | actionContext.params.action = actionContext.route.action; 104 | } 105 | try { 106 | var actionResult; 107 | if (routeResult instanceof RouteResult) { 108 | if (routeResult instanceof MissingRouteResult) { 109 | actionResult = actionresults.notFound(routeResult.message || 'No route matched the url: ' + parsedUrl.pathname); 110 | } else if (routeResult instanceof ErrorRouteResult) { 111 | actionResult = actionresults.error(routeResult.error); 112 | } else { 113 | actionResult = routeResult.action.apply(actionContext); 114 | } 115 | } else { 116 | throw new Error('Result from router not an instance of RouteResult.'); 117 | } 118 | actionResult = processActionResult(actionResult, actionContext); 119 | var cookieHeaders = cookie.toHeaders(); 120 | if (cookieHeaders) { 121 | cookieHeaders.forEach(function(header) { 122 | actionResult.headers.push(['Set-Cookie', header]); 123 | }); 124 | } 125 | actionResult.execute(req, res, app); 126 | } catch (e) { 127 | actionresults.error(e).execute(req, res); 128 | } 129 | }; 130 | var contentType = req.headers['content-type'] || req.headers['Content-Type']; 131 | var contentLength = parseInt(req.headers['content-length'] || req.headers['Content-Length'], 10); 132 | if (contentLength > app.settings.maxContentLength) { 133 | callback(new Error('Max content length exceeded')); 134 | return; 135 | } 136 | if (contentType && /multipart\/form-data/.test(contentType)) { 137 | var currentPart; 138 | var parser = multipart.parser(); 139 | parser.headers = req.headers; 140 | parser.onPartBegin = function(part) { 141 | currentPart = { 142 | name: part.name, 143 | filename: part.filename, 144 | data: '' 145 | }; 146 | }; 147 | parser.onPartEnd = function(part) { 148 | if (part) { 149 | if (currentPart.filename) { 150 | actionContext.files[currentPart.name] = currentPart; 151 | } else { 152 | actionContext.form[currentPart.name] = currentPart.data; 153 | } 154 | } 155 | }; 156 | parser.onData = function(data) { 157 | // todo: write to tmp file, rather than buffering 158 | currentPart.data += data; 159 | }; 160 | req.addListener('data', function(chunk) { 161 | parser.write(chunk); 162 | }); 163 | req.addListener('end', function() { 164 | parser.close(); 165 | callback(); 166 | }); 167 | } else { 168 | var postedBody = ''; 169 | req.addListener('data', function(chunk) { 170 | postedBody += chunk; 171 | }); 172 | req.addListener('end', function() { 173 | actionContext.form = querystring.parse(postedBody); 174 | callback(); 175 | }); 176 | } 177 | req.addListener('error', function(error) { 178 | // todo: deal with request error 179 | }); 180 | }); 181 | 182 | this.listen = function(port) { 183 | server.listen(port); 184 | }; 185 | }; 186 | -------------------------------------------------------------------------------- /lib/josi/utilities.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | this.merge = function() { 5 | var result = {}; 6 | for (var a in arguments) { 7 | var arg = arguments[a]; 8 | for (var i in arg) { 9 | result[i] = arg[i]; 10 | } 11 | } 12 | return result; 13 | }; 14 | 15 | this.argumentsToArray = function(args) { 16 | var arr = []; 17 | for (var a in args) { 18 | arr.push(args[a]); 19 | } 20 | return arr; 21 | }; 22 | 23 | this.fileOrDirectoryExists = fileOrDirectoryExists = function(fileOrDirectory) { 24 | try { 25 | fs.statSync(fileOrDirectory); 26 | return true; 27 | } catch (e) { 28 | if (e.errno && e.errno == 2) { 29 | return false; 30 | } else { 31 | throw e; 32 | } 33 | } 34 | }; 35 | 36 | this.cwdContainsApp = function() { 37 | return fileOrDirectoryExists(path.join(process.cwd(), 'app.js')); 38 | }; 39 | 40 | this.processARGV = function(argv) { 41 | var task = (argv[2] && /^\w+$/.test(argv[2])) ? argv[2] : ''; 42 | var optsRE = /-{0,2}(\w+)=(\w+)/; 43 | var opts = {}; 44 | var args = []; 45 | argv 46 | .slice(3) 47 | .forEach(function(arg) { 48 | var match = optsRE.exec(arg); 49 | if (match) { 50 | opts[match[1].toLowerCase()] = match[2]; 51 | } else { 52 | args.push(arg); 53 | } 54 | }); 55 | return { 56 | task: task, 57 | opts: opts, 58 | args: args 59 | }; 60 | }; 61 | 62 | this.stripExtension = function(filename) { 63 | var index = filename.lastIndexOf('.'); 64 | return index < 0 ? filename : filename.substring(0, index); 65 | }; 66 | 67 | this.extension = function (filename) { 68 | var index = filename.lastIndexOf('.'); 69 | return index < 0 ? '' : filename.substring(index); 70 | }; 71 | 72 | this.mime = mime = { 73 | lookup: function(ext, fallback) { 74 | return mime.types[ext.toLowerCase()] || fallback || mime.defaultType; 75 | }, 76 | defaultType: 'application/octet-stream', 77 | types: { 78 | ".3gp" : "video/3gpp", 79 | ".a" : "application/octet-stream", 80 | ".ai" : "application/postscript", 81 | ".aif" : "audio/x-aiff", 82 | ".aiff" : "audio/x-aiff", 83 | ".asc" : "application/pgp-signature", 84 | ".asf" : "video/x-ms-asf", 85 | ".asm" : "text/x-asm", 86 | ".asx" : "video/x-ms-asf", 87 | ".atom" : "application/atom+xml", 88 | ".au" : "audio/basic", 89 | ".avi" : "video/x-msvideo", 90 | ".bat" : "application/x-msdownload", 91 | ".bin" : "application/octet-stream", 92 | ".bmp" : "image/bmp", 93 | ".bz2" : "application/x-bzip2", 94 | ".c" : "text/x-c", 95 | ".cab" : "application/vnd.ms-cab-compressed", 96 | ".cc" : "text/x-c", 97 | ".chm" : "application/vnd.ms-htmlhelp", 98 | ".class" : "application/octet-stream", 99 | ".com" : "application/x-msdownload", 100 | ".conf" : "text/plain", 101 | ".cpp" : "text/x-c", 102 | ".crt" : "application/x-x509-ca-cert", 103 | ".css" : "text/css", 104 | ".csv" : "text/csv", 105 | ".cxx" : "text/x-c", 106 | ".deb" : "application/x-debian-package", 107 | ".der" : "application/x-x509-ca-cert", 108 | ".diff" : "text/x-diff", 109 | ".djv" : "image/vnd.djvu", 110 | ".djvu" : "image/vnd.djvu", 111 | ".dll" : "application/x-msdownload", 112 | ".dmg" : "application/octet-stream", 113 | ".doc" : "application/msword", 114 | ".dot" : "application/msword", 115 | ".dtd" : "application/xml-dtd", 116 | ".dvi" : "application/x-dvi", 117 | ".ear" : "application/java-archive", 118 | ".eml" : "message/rfc822", 119 | ".eps" : "application/postscript", 120 | ".exe" : "application/x-msdownload", 121 | ".f" : "text/x-fortran", 122 | ".f77" : "text/x-fortran", 123 | ".f90" : "text/x-fortran", 124 | ".flv" : "video/x-flv", 125 | ".for" : "text/x-fortran", 126 | ".gem" : "application/octet-stream", 127 | ".gemspec": "text/x-script.ruby", 128 | ".gif" : "image/gif", 129 | ".gz" : "application/x-gzip", 130 | ".h" : "text/x-c", 131 | ".hh" : "text/x-c", 132 | ".htm" : "text/html", 133 | ".html" : "text/html", 134 | ".ico" : "image/x-icon", 135 | ".ics" : "text/calendar", 136 | ".ifb" : "text/calendar", 137 | ".iso" : "application/octet-stream", 138 | ".jar" : "application/java-archive", 139 | ".java" : "text/x-java-source", 140 | ".jnlp" : "application/x-java-jnlp-file", 141 | ".jpeg" : "image/jpeg", 142 | ".jpg" : "image/jpeg", 143 | ".js" : "application/javascript", 144 | ".json" : "application/json", 145 | ".log" : "text/plain", 146 | ".m3u" : "audio/x-mpegurl", 147 | ".m4v" : "video/mp4", 148 | ".man" : "text/troff", 149 | ".mathml" : "application/mathml+xml", 150 | ".mbox" : "application/mbox", 151 | ".mdoc" : "text/troff", 152 | ".me" : "text/troff", 153 | ".mid" : "audio/midi", 154 | ".midi" : "audio/midi", 155 | ".mime" : "message/rfc822", 156 | ".mml" : "application/mathml+xml", 157 | ".mng" : "video/x-mng", 158 | ".mov" : "video/quicktime", 159 | ".mp3" : "audio/mpeg", 160 | ".mp4" : "video/mp4", 161 | ".mp4v" : "video/mp4", 162 | ".mpeg" : "video/mpeg", 163 | ".mpg" : "video/mpeg", 164 | ".ms" : "text/troff", 165 | ".msi" : "application/x-msdownload", 166 | ".odp" : "application/vnd.oasis.opendocument.presentation", 167 | ".ods" : "application/vnd.oasis.opendocument.spreadsheet", 168 | ".odt" : "application/vnd.oasis.opendocument.text", 169 | ".ogg" : "application/ogg", 170 | ".p" : "text/x-pascal", 171 | ".pas" : "text/x-pascal", 172 | ".pbm" : "image/x-portable-bitmap", 173 | ".pdf" : "application/pdf", 174 | ".pem" : "application/x-x509-ca-cert", 175 | ".pgm" : "image/x-portable-graymap", 176 | ".pgp" : "application/pgp-encrypted", 177 | ".pkg" : "application/octet-stream", 178 | ".pl" : "text/x-script.perl", 179 | ".pm" : "text/x-script.perl-module", 180 | ".png" : "image/png", 181 | ".pnm" : "image/x-portable-anymap", 182 | ".ppm" : "image/x-portable-pixmap", 183 | ".pps" : "application/vnd.ms-powerpoint", 184 | ".ppt" : "application/vnd.ms-powerpoint", 185 | ".ps" : "application/postscript", 186 | ".psd" : "image/vnd.adobe.photoshop", 187 | ".py" : "text/x-script.python", 188 | ".qt" : "video/quicktime", 189 | ".ra" : "audio/x-pn-realaudio", 190 | ".rake" : "text/x-script.ruby", 191 | ".ram" : "audio/x-pn-realaudio", 192 | ".rar" : "application/x-rar-compressed", 193 | ".rb" : "text/x-script.ruby", 194 | ".rdf" : "application/rdf+xml", 195 | ".roff" : "text/troff", 196 | ".rpm" : "application/x-redhat-package-manager", 197 | ".rss" : "application/rss+xml", 198 | ".rtf" : "application/rtf", 199 | ".ru" : "text/x-script.ruby", 200 | ".s" : "text/x-asm", 201 | ".sgm" : "text/sgml", 202 | ".sgml" : "text/sgml", 203 | ".sh" : "application/x-sh", 204 | ".sig" : "application/pgp-signature", 205 | ".snd" : "audio/basic", 206 | ".so" : "application/octet-stream", 207 | ".svg" : "image/svg+xml", 208 | ".svgz" : "image/svg+xml", 209 | ".swf" : "application/x-shockwave-flash", 210 | ".t" : "text/troff", 211 | ".tar" : "application/x-tar", 212 | ".tbz" : "application/x-bzip-compressed-tar", 213 | ".tcl" : "application/x-tcl", 214 | ".tex" : "application/x-tex", 215 | ".texi" : "application/x-texinfo", 216 | ".texinfo" : "application/x-texinfo", 217 | ".text" : "text/plain", 218 | ".tif" : "image/tiff", 219 | ".tiff" : "image/tiff", 220 | ".torrent" : "application/x-bittorrent", 221 | ".tr" : "text/troff", 222 | ".txt" : "text/plain", 223 | ".vcf" : "text/x-vcard", 224 | ".vcs" : "text/x-vcalendar", 225 | ".vrml" : "model/vrml", 226 | ".war" : "application/java-archive", 227 | ".wav" : "audio/x-wav", 228 | ".wma" : "audio/x-ms-wma", 229 | ".wmv" : "video/x-ms-wmv", 230 | ".wmx" : "video/x-ms-wmx", 231 | ".wrl" : "model/vrml", 232 | ".wsdl" : "application/wsdl+xml", 233 | ".xbm" : "image/x-xbitmap", 234 | ".xhtml" : "application/xhtml+xml", 235 | ".xls" : "application/vnd.ms-excel", 236 | ".xml" : "application/xml", 237 | ".xpm" : "image/x-xpixmap", 238 | ".xsl" : "application/xml", 239 | ".xslt" : "application/xslt+xml", 240 | ".yaml" : "text/yaml", 241 | ".yml" : "text/yaml", 242 | ".zip" : "application/zip" 243 | } 244 | }; 245 | -------------------------------------------------------------------------------- /lib/multipart-js/parse.js: -------------------------------------------------------------------------------- 1 | 2 | var utils = require("./utils") 3 | , error = utils.error 4 | , emit = utils.emit 5 | , wrapExpression = /^[ \t]+/ 6 | , multipartExpression = new RegExp( 7 | "^multipart\/(" + 8 | "mixed|rfc822|message|digest|alternative|" + 9 | "related|report|signed|encrypted|form-data|" + 10 | "x-mixed-replace|byteranges)", "i") 11 | , boundaryExpression = /boundary=([^;]+)/i 12 | , CR = "\r" 13 | , LF = "\n" 14 | , CRLF = CR+LF 15 | , MAX_BUFFER_LENGTH = 16 * 1024 16 | 17 | // parser states. 18 | var S = 0 19 | exports.STATE = 20 | { NEW_PART : S++ 21 | , HEADER : S++ 22 | , BODY : S++ 23 | }; 24 | for (S in exports.STATE) exports.STATE[exports.STATE[S]] = S; 25 | S = exports.STATE; 26 | 27 | // events for discoverability 28 | exports.EVENTS = [ "onPartBegin", "onPartEnd", "onData", "onEnd", "onError" ]; 29 | 30 | exports.parser = function parser () { return new Parser() } 31 | exports.Parser = Parser; 32 | 33 | // the parser's "parts" object is a nested collection of the header objects 34 | // check the parser's "part" member to know what it's currently chewin on. 35 | // this.part.parent refers to that part's containing message (which may be 36 | // the parser itself) 37 | // child messages inherit their parent's headers 38 | function Parser () { 39 | this.buffer = ""; 40 | this.part = this; 41 | this.state = S.NEW_PART; 42 | // handy for debugging bad input 43 | this.position = this.column = this.line = 0; 44 | this.parent = this; 45 | this.type = this.headers = this.isMultiPart = this.boundary = null; 46 | } 47 | 48 | Parser.prototype.write = function (chunk) { 49 | // sys.debug("write: "+chunk); 50 | var parser = this 51 | , part = parser.part 52 | // write to the buffer, and then process the buffer. 53 | parser.buffer += chunk; 54 | 55 | while (parser.buffer) { 56 | switch (parser.state) { 57 | case S.NEW_PART: 58 | // part is a multipart message. 59 | // we're either going to start reading a new part, or we're going to 60 | // end the current part, depending on whether the boundary has -- at 61 | // the end. either way, we expect --boundary right away. 62 | if (!parser.part.isMultiPart) { 63 | multipartHeaders(parser.part); 64 | } 65 | if (!parser.part.isMultiPart) { 66 | error(parser, "Expected multipart message (did you set the headers?)"); 67 | } 68 | var boundary = parser.part.boundary 69 | , len = boundary.length 70 | , offset = parser.buffer.indexOf(boundary) 71 | if (offset === -1) { 72 | if (parser.buffer.length > len) { 73 | error(parser, "Malformed: boundary not found at start of message"); 74 | } 75 | // keep waiting for it. 76 | return; 77 | } 78 | if (offset > 0) { 79 | error(parser, "Malformed: data before the boundary"); 80 | return; 81 | } 82 | if (parser.buffer.length < (len + 2)) { 83 | // we'll need to see either -- or CRLF after the boundary. 84 | // get it on the next pass. 85 | return; 86 | } 87 | if (parser.buffer.substr(len, 2) === "--") { 88 | // this message is done. 89 | // chomp off the boundary and crlf and move up 90 | if (parser.part !== parser) { 91 | // wait to see the crlf, unless this is the top-level message. 92 | if (parser.buffer.length < (len + 4)) return; 93 | if (parser.buffer.substr(len+2, 2) !== CRLF) { 94 | error(parser, "Malformed: CRLF not found after boundary"); 95 | return; 96 | } 97 | } 98 | parser.buffer = parser.buffer.substr(len + 4); 99 | emit(parser, "onPartEnd", parser.part); 100 | parser.part = parser.part.parent; 101 | parser.state = S.NEW_PART; 102 | continue; 103 | } 104 | if (parser.part !== parser) { 105 | // wait to see the crlf, unless this is the top-level message. 106 | if (parser.buffer.length < (len + 2)) return; 107 | if (parser.buffer.substr(len, 2) !== CRLF) { 108 | error(parser, "Malformed: CRLF not found after boundary"); 109 | return; 110 | } 111 | } 112 | // walk past the crlf 113 | parser.buffer = parser.buffer.substr(len + 2); 114 | // mint a new child part, and start parsing headers. 115 | parser.part = startPart(parser.part); 116 | parser.state = S.HEADER; 117 | continue; 118 | case S.HEADER: 119 | // just grab everything to the double crlf. 120 | var headerEnd = parser.buffer.indexOf(CRLF+CRLF) 121 | if (headerEnd === -1) { 122 | if (parser.buffer.length > MAX_BUFFER_LENGTH) { 123 | error(parser, "Malformed: header unreasonably long."); 124 | } 125 | return; 126 | } 127 | var headerString = parser.buffer.substr(0, headerEnd) 128 | parseHeaderString(parser.part.headers, headerString, parser); 129 | // chomp off the header and the empty line. 130 | parser.buffer = parser.buffer.substr(headerEnd + 4); 131 | multipartHeaders(parser.part); 132 | 133 | // let the world know 134 | emit(parser, "onPartBegin", parser.part); 135 | 136 | if (parser.part.isMultiPart) { 137 | // it has a boundary and we're ready to grab parts out. 138 | parser.state = S.NEW_PART; 139 | } else { 140 | // it doesn't have a boundary, and is about to 141 | // start spitting out body bits. 142 | parser.state = S.BODY; 143 | } 144 | continue; 145 | case S.BODY: 146 | // look for parser.part.parent.boundary 147 | var boundary = parser.part.parent.boundary 148 | , offset = parser.buffer.indexOf(boundary) 149 | if (offset === -1) { 150 | // emit and wait for more data, but be careful, because 151 | // we might only have half of the boundary so far. 152 | // make sure to leave behind the boundary's length, so that we'll 153 | // definitely get it next time if it's on its way. 154 | var emittable = parser.buffer.length - boundary.length 155 | if (parser.buffer.substr(-1) === CR) emittable -= 1; 156 | if (parser.buffer.substr(-2) === CRLF) emittable -= 2; 157 | 158 | if (emittable > 0) { 159 | emit(parser, "onData", parser.buffer.substr(0, emittable)); 160 | parser.buffer = parser.buffer.substr(emittable); 161 | } 162 | // haven't seen the boundary, so wait for more bytes. 163 | return; 164 | } 165 | if (offset > 0) { 166 | var emittable = parser.buffer.substr(0, offset) 167 | if (emittable.substr(-2) === CRLF) { 168 | emittable = emittable.substr(0, emittable.length-2); 169 | } 170 | if (emittable) emit(parser, "onData", emittable); 171 | parser.buffer = parser.buffer.substr(offset); 172 | } 173 | 174 | // let em know we're done. 175 | emit(parser, "onPartEnd", parser.part); 176 | 177 | // now buffer starts with boundary. 178 | if (parser.buffer.substr(boundary.length, 2) === "--") { 179 | // message end. 180 | // parent ends, look for a new part in the grandparent. 181 | parser.part = parser.part.parent; 182 | emit(parser, "onPartEnd", parser.part); 183 | parser.part = parser.part.parent; 184 | parser.state = S.NEW_PART; 185 | parser.buffer = parser.buffer.substr(boundary.length + 4); 186 | } else { 187 | // another part coming for the parent message. 188 | parser.part = parser.part.parent; 189 | parser.state = S.NEW_PART; 190 | } 191 | continue; 192 | } 193 | } 194 | } 195 | 196 | Parser.prototype.close = function () { 197 | emit(this, "onEnd"); 198 | Parser.call(this); 199 | } 200 | 201 | function parseHeaderString (headers, string, parser) { 202 | var lines = string.split(CRLF) 203 | , field, value, line 204 | for (var i = 0, l = lines.length; i < l; i ++) { 205 | line = lines[i]; 206 | if (line.match(wrapExpression)) { 207 | if (!field) { 208 | error(parser, "Malformed. First header starts with whitespace."); 209 | } 210 | value += line.replace(wrapExpression, " "); 211 | continue; 212 | } else if (field) { 213 | // now that we know it's not wrapping, put it on the headers obj. 214 | affixHeader(headers, field, value); 215 | } 216 | line = line.split(":"); 217 | field = line.shift().toLowerCase(); 218 | if (!field) { 219 | error(parser, "Malformed: improper field name."); 220 | } 221 | value = line.join(":").replace(/^\s+/, ""); 222 | } 223 | // now affix the last field. 224 | affixHeader(headers, field, value); 225 | } 226 | 227 | function affixHeader (headers, field, value) { 228 | if (!headers.hasOwnProperty(field)) { 229 | headers[field] = value; 230 | } else if (Array.isArray(headers[field])) { 231 | headers[field].push(value); 232 | } else { 233 | headers[field] = [headers[field], value]; 234 | } 235 | } 236 | 237 | function startPart (parent) { 238 | return { headers : {} 239 | , parent : parent 240 | }; 241 | } 242 | 243 | // check the headers of a message. If it wants to be multipart, 244 | // then we'll be returning true, and setting some additional data, 245 | // like type and boundary. 246 | function multipartHeaders (message) { 247 | // if it has a boundary already, then it's most likely the parser object, 248 | // and the user has told us what they expect the boundary to be. 249 | // take their word for it. 250 | if (message.boundary) { 251 | if (message.boundary.substr(0, 2) !== "--") { 252 | message.boundary = "--" + message.boundary; 253 | } 254 | return message.isMultiPart = true; 255 | } 256 | 257 | var field 258 | , val 259 | , contentType 260 | , contentDisposition = "" 261 | for (var h in message.headers) if (message.headers.hasOwnProperty(h)) { 262 | val = message.headers[h]; 263 | field = h.toLowerCase(); 264 | if (field === "content-type") { 265 | contentType = val; 266 | } else if (field === "content-disposition") { 267 | contentDisposition = val; 268 | } 269 | } 270 | 271 | if (!Array.isArray(contentDisposition)) { 272 | contentDisposition = contentDisposition.split(","); 273 | } 274 | contentDisposition = contentDisposition[contentDisposition.length - 1]; 275 | 276 | // Name and filename can come along with either content-disposition 277 | // or content-type. Well-behaved agents use CD rather than CT, 278 | // but sadly not all agents are well-behaved. 279 | [contentDisposition, contentType].forEach(function (h) { 280 | if (!h) return; 281 | var cd = h.split(/; */) 282 | cd.shift(); 283 | for (var i = 0, l = cd.length; i < l; i ++) { 284 | var bit = cd[i].split("=") 285 | , name = bit.shift() 286 | , val = stripQuotes(bit.join("=")) 287 | if (name === "filename" || name === "name") { 288 | message[name] = val; 289 | } 290 | } 291 | }); 292 | 293 | if (!contentType) return false; 294 | 295 | if (!Array.isArray(contentType)) contentType = contentType.split(","); 296 | contentType = contentType[contentType.length-1]; 297 | 298 | // make sure it's actually multipart. 299 | var mpType = multipartExpression.exec(contentType) 300 | if (!mpType) return false; 301 | 302 | // make sure we have a boundary. 303 | var boundary = boundaryExpression.exec(contentType) 304 | if (!boundary) return false; 305 | 306 | message.type = mpType[1]; 307 | message.boundary = "--" + boundary[1]; 308 | message.isMultiPart = true; 309 | return true; 310 | } 311 | 312 | function stripslashes(str) { 313 | // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) 314 | // + improved by: Ates Goral (http://magnetiq.com) 315 | // + fixed by: Mick@el 316 | // + improved by: marrtins 317 | // + bugfixed by: Onno Marsman 318 | // + improved by: rezna 319 | // + input by: Rick Waldron 320 | // + reimplemented by: Brett Zamir (http://brett-zamir.me) 321 | // * example 1: stripslashes("Kevin\'s code"); 322 | // * returns 1: "Kevin's code" 323 | // * example 2: stripslashes("Kevin\\\'s code"); 324 | // * returns 2: "Kevin\'s code" 325 | return (str+"").replace(/\\(.?)/g, function (s, n1) { 326 | switch(n1) { 327 | case "\\": 328 | return "\\"; 329 | case "0": 330 | return "\0"; 331 | case "": 332 | return ""; 333 | default: 334 | return n1; 335 | } 336 | }); 337 | } 338 | function stripQuotes (str) { 339 | str = stripslashes(str); 340 | return str.substr(1, str.length - 2); 341 | } 342 | -------------------------------------------------------------------------------- /examples/hello/static/jquery-1.4.2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery JavaScript Library v1.4.2 3 | * http://jquery.com/ 4 | * 5 | * Copyright 2010, John Resig 6 | * Dual licensed under the MIT or GPL Version 2 licenses. 7 | * http://jquery.org/license 8 | * 9 | * Includes Sizzle.js 10 | * http://sizzlejs.com/ 11 | * Copyright 2010, The Dojo Foundation 12 | * Released under the MIT, BSD, and GPL Licenses. 13 | * 14 | * Date: Sat Feb 13 22:33:48 2010 -0500 15 | */ 16 | (function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, 21 | Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& 22 | (d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, 23 | a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== 24 | "find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, 25 | function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a"; 34 | var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, 35 | parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= 36 | false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= 37 | s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, 38 | applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; 39 | else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, 40 | a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== 41 | w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, 42 | cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= 47 | c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); 48 | a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, 49 | function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); 50 | k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), 51 | C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= 53 | e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& 54 | f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; 55 | if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", 63 | e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, 64 | "_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, 65 | d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 71 | e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); 72 | t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| 73 | g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, 80 | CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, 81 | g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, 82 | text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, 83 | setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= 84 | h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== 86 | "="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, 87 | h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& 90 | q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; 91 | if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); 92 | (function(){var g=s.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: 93 | function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= 96 | {},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== 97 | "string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", 98 | d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? 99 | a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== 100 | 1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= 102 | c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, 103 | wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, 104 | prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, 105 | this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); 106 | return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, 107 | ""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); 111 | return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", 112 | ""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= 113 | c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? 114 | c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= 115 | function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= 116 | Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, 117 | "border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= 118 | a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= 119 | a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== 120 | "string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, 121 | serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), 122 | function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, 123 | global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& 124 | e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? 125 | "&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== 126 | false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= 127 | false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", 128 | c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| 129 | d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); 130 | g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== 131 | 1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== 132 | "json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; 133 | if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== 139 | "number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| 140 | c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; 141 | this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= 142 | this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, 143 | e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
"; 149 | a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); 150 | c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, 151 | d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- 152 | f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": 153 | "pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in 154 | e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); 155 | --------------------------------------------------------------------------------