diff --git a/.gitignore b/.gitignore index 2af02d437..648d392c9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ conf/*.json *.sock *.gz *.sass-cache +.idea diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 9fbad1c50..000000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -calipso \ No newline at end of file diff --git a/.idea/calipso.iml b/.idea/calipso.iml deleted file mode 100644 index d833013fc..000000000 --- a/.idea/calipso.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 9178b389f..000000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/.idea/cssxfire.xml b/.idea/cssxfire.xml deleted file mode 100644 index f4acc388f..000000000 --- a/.idea/cssxfire.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/encodings.xml b/.idea/encodings.xml deleted file mode 100644 index e206d70d8..000000000 --- a/.idea/encodings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml deleted file mode 100644 index 73f21433a..000000000 --- a/.idea/jsLibraryMappings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/libraries/sass_stdlib.xml b/.idea/libraries/sass_stdlib.xml deleted file mode 100644 index 546bfd172..000000000 --- a/.idea/libraries/sass_stdlib.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 13fc00188..000000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - jar:file:\C:\Program Files (x86)\JetBrains\PhpStorm 5.0.1\lib\webide.jar!\resources\html5-schema\html5.rnc - - - - diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 23b3bf1df..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml deleted file mode 100644 index 922003b84..000000000 --- a/.idea/scopes/scope_settings.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index c80f2198b..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 08fc6da72..000000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,1267 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1356596335698 - 1356596335698 - - - 1356596877785 - 1356596877785 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/JasmineAdapter-1.1.2.js b/JasmineAdapter-1.1.2.js new file mode 100644 index 000000000..5cb46f7f4 --- /dev/null +++ b/JasmineAdapter-1.1.2.js @@ -0,0 +1,216 @@ +/** + * @fileoverview Jasmine JsTestDriver Adapter. + * @author misko@hevery.com (Misko Hevery) + * @author olmo.maldonado@gmail.com (Olmo Maldonado) + */ +(function () { + + + var Env = function (onTestDone, onComplete) { + jasmine.Env.call(this); + + this.specFilter = function (spec) { + if (!this.exclusive) { + return true; + } + var blocks = spec.queue.blocks, l = blocks.length; + for (var i = 0; i < l; i++) { + if (blocks[i].func.exclusive >= this.exclusive) { + return true; + } + } + return false; + }; + + this.reporter = new Reporter(onTestDone, onComplete); + }; + jasmine.util.inherit(Env, jasmine.Env); + +// Here we store: +// 0: everyone runs +// 1: run everything under ddescribe +// 2: run only iits (ignore ddescribe) + Env.prototype.exclusive = 0; + + + Env.prototype.execute = function () { + collectMode = false; + playback(); + jasmine.Env.prototype.execute.call(this); + }; + + + var Reporter = function (onTestDone, onComplete) { + this.onTestDone = onTestDone; + this.onComplete = onComplete; + this.reset(); + }; + jasmine.util.inherit(Reporter, jasmine.Reporter); + + + Reporter.formatStack = function (stack) { + var line, lines = (stack || '').split(/\r?\n/), l = lines.length, frames = []; + for (var i = 0; i < l; i++) { + line = lines[i]; + if (line.match(/\/jasmine[\.-]/)) { + continue; + } + frames.push(line.replace(/https?:\/\/\w+(:\d+)?\/test\//, '').replace(/^\s*/, ' ')); + } + return frames.join('\n'); + }; + + + Reporter.prototype.reset = function () { + this.specLog = jstestdriver.console.log_ = []; + }; + + + Reporter.prototype.log = function (str) { + this.specLog.push(str); + }; + + + Reporter.prototype.reportSpecStarting = function () { + this.reset(); + this.start = +new Date(); + }; + + + Reporter.prototype.reportSpecResults = function (spec) { + var elapsed = +new Date() - this.start, results = spec.results(); + + if (results.skipped) { + return; + } + + var item, state = 'passed', items = results.getItems(), l = items.length, messages = []; + for (var i = 0; i < l; i++) { + item = items[i]; + if (item.passed()) { + continue; + } + state = (item.message.indexOf('AssertionError:') != -1) ? 'error' : 'failed'; + messages.push({ + message:item + '', + name:item.trace.name, + stack:Reporter.formatStack(item.trace.stack) + }); + } + + this.onTestDone(new jstestdriver.TestResult( + spec.suite.getFullName(), + spec.description, + state, + jstestdriver.angular.toJson(messages), + this.specLog.join('\n'), + elapsed + )); + }; + + + Reporter.prototype.reportRunnerResults = function () { + this.onComplete(); + }; + + + var collectMode = true, intercepted = {}; + + describe = intercept('describe'); + beforeEach = intercept('beforeEach'); + afterEach = intercept('afterEach'); + + var JASMINE_TYPE = 'jasmine test case'; + TestCase('Jasmine Adapter Tests', null, JASMINE_TYPE); + + jstestdriver.pluginRegistrar.register({ + + name:'jasmine', + + getTestRunsConfigurationFor:function (testCaseInfos, expressions, testRunsConfiguration) { + for (var i = 0; i < testCaseInfos.length; i++) { + if (testCaseInfos[i].getType() == JASMINE_TYPE) { + testRunsConfiguration.push(new jstestdriver.TestRunConfiguration(testCaseInfos[i], [])); + } + } + return false; // allow other TestCases to be collected. + }, + + runTestConfiguration:function (config, onTestDone, onComplete) { + if (config.getTestCaseInfo().getType() != JASMINE_TYPE) { + return false; + } + (jasmine.currentEnv_ = new Env(onTestDone, onComplete)).execute(); + return true; + }, + + onTestsFinish:function () { + jasmine.currentEnv_ = null; + collectMode = true; + } + + }); + + function intercept(method) { + var bucket = intercepted[method] = [], method = window[method]; + return function (desc, fn) { + if (collectMode) { + bucket.push(function () { + method(desc, fn); + }); + } + else { + method(desc, fn); + } + }; + } + + function playback() { + for (var method in intercepted) { + var bucket = intercepted[method]; + for (var i = 0, l = bucket.length; i < l; i++) { + bucket[i](); + } + } + } + +})(); + +var ddescribe = function (name, fn) { + var env = jasmine.getEnv(); + if (!env.exclusive) { + env.exclusive = 1; + } // run ddescribe only + describe(name, function () { + var oldIt = it; + it = function (name, fn) { + fn.exclusive = 1; // run anything under ddescribe + env.it(name, fn); + }; + + try { + fn.call(this); + } finally { + it = oldIt; + } + ; + }); +}; + +var iit = function (name, fn) { + var env = jasmine.getEnv(); + env.exclusive = fn.exclusive = 2; // run only iits + env.it(name, fn); +}; + +// Patch Jasmine for proper stack traces +jasmine.Spec.prototype.fail = function (e) { + var result = new jasmine.ExpectationResult({ + passed:false, + message:e ? jasmine.util.formatException(e) : 'Exception' + }); + if (e) { + result.trace = e; + } + this.results_.addResult(result); +}; diff --git a/README.md b/README.md index 7454ae9ae..74d909609 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,12 @@ MongoDB, ensure that you have the right compilers installed (for OSX, XCode4 will work, for Ubuntu, the build-essential and libssl-dev packages) and then use NPM: +```sh npm install calipso -g calipso site /var/www/MySite cd /var/www/MySite calipso server +``` #### Using node v0.5.3 and later @@ -34,10 +36,11 @@ Since node v0.5.3 has removed require.paths, in order to require(‘lib/calipso’), you must include the following to your file: - var rootpath = process.cwd() + '/', - path = require('path'), - calipso = require(path.join(rootpath, 'lib/calipso')); - +```javascript +var rootpath = process.cwd() + '/', + path = require('path'), + calipso = require(path.join(rootpath, 'lib/calipso')); +``` That also goes for including anything that is based on the root path of the project directory. ### Development Steps @@ -57,9 +60,10 @@ your file: #### Commands That Run Anywhere +```sh calipso : Show this help file. calipso site : Create site in folder. - +``` #### Commands That Run In Site Folder @@ -67,6 +71,7 @@ The most important of these at the moment is ‘modules check’ (this will ensure that all modules have all of their dependencies installed via npm), and should be run on site install. +```sh calipso install : Re-run site install. calipso cluster --port=3000 : Run as cluster. @@ -82,6 +87,7 @@ npm), and should be run on site install. calipso themes list : List installed themes. calipso themes uninstall *theme : Remove theme (delete from disk) calipso themes download *url : Download (url: http://, gh: cliftonc/calipso-site-theme, repo: calipso-site). +``` ### Contributors @@ -105,4 +111,14 @@ npm), and should be run on site install. [Martin Moen]: https://github.com/botto [dale tan]: https://github.com/dtan [Nate Hunzaker]: https://github.com/nhunzaker - [Andreas Richter]: https://github.com/richtera \ No newline at end of file + [Andreas Richter]: https://github.com/richtera + +#MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWAR \ No newline at end of file diff --git a/app-cluster.js b/app-cluster.js index 142e083dc..90c65881f 100644 --- a/app-cluster.js +++ b/app-cluster.js @@ -2,79 +2,82 @@ * Master server process * Initialises a number of instances of the app, based on the number of CPU's it detects * is available. - * + * * First arg is the port * Second arg is the num worker threads; - * - * e.g. node server 3000 8 - * + * + * e.g. node server 3000 8 + * */ // Dependencies var rootpath = process.cwd() + '/', - cluster = require('cluster'), - path = require('path'), - logo = require(path.join(rootpath, 'logo')), - colors = require('colors'), - port = process.env.PORT || 3000 || process.argv.port, - restarts = 0, - totalWorkers = 0, - runningWorkers = 0; + cluster = require('cluster'), + path = require('path'), + logo = require(path.join(rootpath, 'logo')), + colors = require('colors'), + port = process.env.PORT || 3000 || process.argv.port, + restarts = 0, + totalWorkers = 0, + runningWorkers = 0; var argv = processArgs(); -if (module.parent) +if (module.parent) { module.exports.launchServer = launchServer; -else +} +else { launchServer(); +} /** * Launch server instance, initially master, then each worker instance is forked. * All instances share same config. */ function launchServer(inPort) { - if (inPort) + if (inPort) { port = inPort; + } // Check if we are the master process if (cluster.isMaster) { //require('./app').boot(function (app) { - // Load configuration - var Config = require(rootpath + "lib/core/Configuration"), - config = new Config(); + // Load configuration + var Config = require(rootpath + "lib/core/Configuration"), + config = new Config(); + + config.init(); + + // Print the logo + logo.print(); + + // Set the number of workers + totalWorkers = config.get('server:cluster:workers') || argv.c; - config.init(); + // Fork workers based on num cpus + console.log("Loading ".green + totalWorkers + " workers, please wait ...".green); + for (var i = 0; i < totalWorkers; i++) { + forkWorker(); + } - // Print the logo - logo.print(); + // Log worker death + // TODO : Auto restart with number of retries + cluster.on('death', function (worker) { - // Set the number of workers - totalWorkers = config.get('server:cluster:workers') || argv.c; + console.error('worker ' + worker.pid + ' died ...'); - // Fork workers based on num cpus - console.log("Loading ".green + totalWorkers + " workers, please wait ...".green); - for (var i = 0; i < totalWorkers; i++) { - forkWorker(); + // Manage restarting of workers + if (config.get('server:cluster:restartWorkers')) { + if (restarts > config.get('server:cluster:maximumRestarts')) { + console.error('Maximum number of restarts reached, not restarting this worker.'.red); + } else { + restarts++; + forkWorker(); + } } - - // Log worker death - // TODO : Auto restart with number of retries - cluster.on('death', function(worker) { - - console.error('worker ' + worker.pid + ' died ...'); - - // Manage restarting of workers - if(config.get('server:cluster:restartWorkers')) { - if(restarts > config.get('server:cluster:maximumRestarts')) { - console.error('Maximum number of restarts reached, not restarting this worker.'.red); - } else { - restarts++; - forkWorker(); - } - } - - }); + + }); //}); @@ -83,10 +86,10 @@ function launchServer(inPort) { // We are a child worker, so bootstrap the app. require(rootpath + 'app').boot(true, function (app) { - //logger.info("Worker [" + argv.m.cyan + "] with pid " + (process.pid + "").grey + " online."); + //logger.info("Worker [" + argv.m.cyan + "] with pid " + (process.pid + "").grey + " online."); app.listen(port); - process.send({ cmd: 'workerStarted', pid: process.pid, port: port }); + process.send({ cmd:'workerStarted', pid:process.pid, port:port }); }); @@ -99,18 +102,18 @@ function launchServer(inPort) { * hence the messaging back, also deal with messaging around job management from worker threads. */ function forkWorker() { - + var worker = cluster.fork(); - worker.on('message', function(msg) { + worker.on('message', function (msg) { if (msg.cmd) { - - if(msg.cmd == 'workerStarted') { + + if (msg.cmd == 'workerStarted') { runningWorkers++; - if(runningWorkers === parseInt(totalWorkers)) { + if (runningWorkers === parseInt(totalWorkers)) { console.log("Calipso configured for: ".green + (global.process.env.NODE_ENV || 'development') + " environment.".green); console.log("Calipso server running ".green + runningWorkers + " workers, listening on port: ".green + port); } @@ -128,9 +131,9 @@ function forkWorker() { */ function processArgs() { return require('optimist') - .usage('Launch Calipso in Clustered Mode\nUsage: $0') - .describe('c', 'Number of CPUs') - .alias('c', 'cpu') - .default('c', require('os').cpus().length) - .argv; + .usage('Launch Calipso in Clustered Mode\nUsage: $0') + .describe('c', 'Number of CPUs') + .alias('c', 'cpu') + .default('c', require('os').cpus().length) + .argv; } diff --git a/app.js b/app.js index a0e46ddf9..70a124789 100644 --- a/app.js +++ b/app.js @@ -8,13 +8,20 @@ * */ -var req = require('express/lib/request'); +var req = require('express/lib/request'), + utils = require('express/lib/utils'); var flashFormatters = req.flashFormatters = { - s:function (val) - { - return String(val); - } + s:function (val) { + return String(val); + } +}; + +function miniMarkdown(str) { + return String(str) + .replace(/(__|\*\*)(.*?)\1/g, '$2') + .replace(/(_|\*)(.*?)\1/g, '$2') + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); }; /** @@ -51,152 +58,118 @@ var flashFormatters = req.flashFormatters = { * @return {Array|Object|Number} * @api public */ - -function miniMarkdown(str) -{ - return String(str) - .replace(/(__|\*\*)(.*?)\1/g, '$2') - .replace(/(_|\*)(.*?)\1/g, '$2') - .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); -}; - -req.flash = function (type, msg) -{ - if (this.session === undefined) - { - throw Error('req.flash() requires sessions'); - } - var msgs = this.session.flash = this.session.flash || {}; - if (type && msg) - { - var i = 2 - , args = arguments - , formatters = this.app.flashFormatters || {}; - formatters.__proto__ = flashFormatters; - msg = miniMarkdown(msg); - msg = msg.replace(/%([a-zA-Z])/g, function (_, format) - { - var formatter = formatters[format]; - if (formatter) - { - return formatter(utils.escape(args[i++])); - } - }); - return (msgs[type] = msgs[type] || []).push(msg); - } - else if (type) - { - var arr = msgs[type]; - delete msgs[type]; - return arr || []; - } - else - { - this.session.flash = {}; - return msgs; - } +req.flash = function (type, msg) { + if (this.session === undefined) { + throw Error('req.flash() requires sessions'); + } + var msgs = this.session.flash = this.session.flash || {}; + if (type && msg) { + var i = 2, + args = arguments, + formatters = this.app.flashFormatters || {}; + formatters.__proto__ = flashFormatters; + msg = miniMarkdown(msg); + msg = msg.replace(/%([a-zA-Z])/g, function (_, format) { + var formatter = formatters[format]; + if (formatter) { + return formatter(utils.escape(args[i++])); + } + }); + return (msgs[type] = msgs[type] || []).push(msg); + } else if (type) { + var arr = msgs[type]; + delete msgs[type]; + return arr || []; + } else { + this.session.flash = {}; + return msgs; + } }; var sys; -try -{ - sys = require('util'); +try { + sys = require('util'); } -catch (e) -{ - sys = require('sys'); +catch (e) { + sys = require('sys'); } var rootpath = process.cwd() + '/', - path = require('path'), - fs = require('fs'), - express = require('express'), - nodepath = require('path'), - stylus = require('stylus'), - colors = require('colors'), - calipso = require(path.join(rootpath, 'lib/calipso')), - translate = require(path.join(rootpath, 'i18n/translate')), - logo = require(path.join(rootpath, 'logo')), - everyauth = require("everyauth"); + fs = require('fs'), + express = require('express'), + stylus = require('stylus'), + colors = require('colors'), + nodepath = require('path'), + calipso = require(nodepath.join(rootpath, 'lib/calipso')), + translate = require(nodepath.join(rootpath, 'i18n/translate')), + logo = require(nodepath.join(rootpath, 'logo')), + everyauth = require('everyauth'); // To enable everyauth debugging. //everyauth.debug = true; everyauth.everymodule - .findUserById(function (req, id, callback) - { - var User = calipso.db.model('User'); - User.findById(id, callback); - }); - -function calipsoFindOrCreateUser(user, sess, promise) -{ - var User = calipso.db.model('User'); - - function finishUser(user) - { - if (sess) - { - if (!sess._pending) - { - return promise.fulfill(user); - } - var req = sess._pending; - delete sess._pending; - return calipso.lib.user.createUserSession(req, null, user, function (err) - { - if (err) - { - calipso.error("Error saving session: " + err); - return promise.fail(err); - } - promise.fulfill(user); - }); - } - else - { - promise.fulfill(user); - } - } - - User.findOne({username:user.username}, function (err, u) - { - if (err) - { - return promise.fail(err); - } - if (u) - { - return finishUser(u); - } - u = new User({ - username:user.username, - fullname:user.name, - email:user.email, - hash:'external:auth' - }); - u.roles = ['Guest']; // Todo - need to make sure guest role can't be deleted? - - calipso.e.pre_emit('USER_CREATE', u); - - u.save(function (err) - { - if (err) - { - return promise.fail(err); - } - calipso.e.post_emit('USER_CREATE', u); - // If not already redirecting, then redirect - finishUser(u); - }); - }); - return promise; + .findUserById(function (req, id, callback) { + var User = calipso.db.model('User'); + User.findById(id, callback); + }); + +function calipsoFindOrCreateUser(user, sess, promise) { + var User = calipso.db.model('User'); + + function finishUser(user) { + if (sess) { + if (!sess._pending) { + return promise.fulfill(user); + } + var req = sess._pending; + delete sess._pending; + return calipso.lib.user.createUserSession(req, null, user, function (err) { + if (err) { + calipso.error("Error saving session: " + err); + return promise.fail(err); + } + promise.fulfill(user); + }); + } else { + promise.fulfill(user); + } + } + + User.findOne({username:user.username}, function (err, u) { + if (err) { + return promise.fail(err); + } + if (u) { + return finishUser(u); + } + u = new User({ + username:user.username, + fullname:user.name, + email:user.email, + hash:'external:auth' + }); + u.roles = ['Guest']; // Todo - need to make sure guest role can't be deleted? + + calipso.e.pre_emit('USER_CREATE', u); + + u.save(function (err) { + if (err) { + return promise.fail(err); + } + calipso.e.post_emit('USER_CREATE', u); + // If not already redirecting, then redirect + finishUser(u); + return null; + }); + }); + return promise; } // Local App Variables var path = rootpath, - theme = 'default', - port = process.env.PORT || 3000; + theme = 'default', + port = process.env.PORT || 3000; /** * Catch All exception handler @@ -210,275 +183,233 @@ var path = rootpath, * Any of these can be added into the by environment configuration files to * enable modification by env. */ -function bootApplication(cluster, next) -{ - - // Create our express instance, export for later reference - var app = express(); - app.path = function () - { - return path - }; - app.isCluster = cluster; - - // Load configuration - var Config = calipso.configuration; //require(path + "/lib/core/Config").Config; - app.config = new Config(); - app.config.init(function (err) - { - - if (err) - { - return console.error(err.message); - } - - // Default Theme - calipso.defaultTheme = app.config.get('themes:default'); - - app.use(express.bodyParser()); - // Pause requests if they were not parsed to allow PUT and POST with custom mime types - app.use(function (req, res, next) - { - if (!req._body) - { - req.pause(); - } - next(); - }); - app.use(express.methodOverride()); - app.use(express.cookieParser(app.config.get('session:secret'))); - app.use(express.responseTime()); - - // Create dummy session middleware - tag it so we can later replace - var temporarySession = app.config.get('installed') ? {} : express.session({ secret:"installing calipso is great fun" }); - temporarySession.tag = "session"; - app.use(temporarySession); - - // Create holders for theme dependent middleware - // These are here because they need to be in the connect stack before the calipso router - // THese helpers are re-used when theme switching. - app.mwHelpers = {}; - - calipso.auth = {password:app.config.get('server:authentication:password'), migrate2pbkdf2:app.config.get('server:authentication:migrate2pbkdf2') - }; - if (calipso.auth.password === undefined) - { - calipso.auth.password = true; - } - if (calipso.auth.migrate2pbkdf2 === undefined) - { - calipso.auth.migrate2pbkdf2 = false; - } - - var appId = app.config.get('server:authentication:facebookAppId'); - var appSecret = app.config.get('server:authentication:facebookAppSecret'); - if (appId && appSecret) - { - calipso.auth.facebook = true; - everyauth - .facebook - .myHostname(app.config.get('server:url')) - .getSession(function (req) - { - if (!req.session) - { - req.session = { _pending:req }; - } - else - { - req.session._pending = req; - } - return req.session; - }) - .appId(appId) - .appSecret(appSecret) - .findOrCreateUser(function (sess, accessToken, accessTokenExtra, fbUserMetadata) - { - var promise = this.Promise(); - - return calipsoFindOrCreateUser({username:'facebook:' + fbUserMetadata.username, - email:fbUserMetadata.username + '@facebook.com', name:fbUserMetadata.name}, sess, promise); - }) - .redirectPath('/'); - } - - var consumerKey = app.config.get('server:authentication:twitterConsumerKey'); - var consumerSecret = app.config.get('server:authentication:twitterConsumerSecret'); - if (consumerKey && consumerSecret) - { - calipso.auth.twitter = true; - everyauth - .twitter - .getSession(function (req) - { - if (!req.session) - { - req.session = { _pending:req }; - } - else - { - req.session._pending = req; - } - return req.session; - }) - .myHostname(app.config.get('server:url')) - .apiHost('https://api.twitter.com/1') - .consumerKey(consumerKey) - .consumerSecret(consumerSecret) - .findOrCreateUser(function (sess, accessToken, accessSecret, twitUser) - { - var promise = this.Promise(); - - return calipsoFindOrCreateUser({username:'twitter:' + twitUser.screen_name, - email:twitUser.screen_name + '@twitter.com', name:twitUser.name}, sess, promise); - }) - .redirectPath('/'); - } - - var clientId = app.config.get('server:authentication:googleClientId'); - var clientSecret = app.config.get('server:authentication:googleClientSecret'); - if (clientId && clientSecret) - { - calipso.auth.google = true; - everyauth - .google - .myHostname(app.config.get('server:url')) - .appId(clientId) - .appSecret(clientSecret) - .scope('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email') - .getSession(function (req) - { - if (!req.session) - { - req.session = { _pending:req }; - } - else - { - req.session._pending = req; - } - return req.session; - }) - .findOrCreateUser(function (sess, accessToken, extra, googleUser) - { - googleUser.refreshToken = extra.refresh_token; - googleUser.expiresIn = extra.expires_in; - - var promise = this.Promise(); - - return calipsoFindOrCreateUser({username:'google:' + googleUser.email, - email:googleUser.email, name:googleUser.name}, sess, promise); - }) - .redirectPath('/'); - } - - app.use(everyauth.middleware()); - - // Load placeholder, replaced later - if (app.config.get('libraries:stylus:enable')) - { - app.mwHelpers.stylusMiddleware = function (themePath) - { - var mw = stylus.middleware({ - src:themePath + '/stylus', - dest:themePath + '/public', - debug:false, - compile:function (str, path) - { // optional, but recommended - return stylus(str) - .set('filename', path) - .set('warn', app.config.get('libraries:stylus:warn')) - .set('compress', app.config.get('libraries:stylus:compress')); - } - }); - mw.tag = 'theme.stylus'; - return mw; - }; - app.use(app.mwHelpers.stylusMiddleware('')); - } - - // Static - app.mwHelpers.staticMiddleware = function (themePath) - { - var mw = express["static"](themePath + '/public', {maxAge:86400000}); - mw.tag = 'theme.static'; - return mw; - }; - // Load placeholder, replaced later - app.use(app.mwHelpers.staticMiddleware('')); - - // Core static paths - app.use(express["static"](path + '/media', {maxAge:86400000})); - app.use(express["static"](path + '/lib/client/js', {maxAge:86400000})); - - // Translation - after static, set to add mode if appropriate - app.use(translate.translate(app.config.get('i18n:language'), app.config.get('i18n:languages'), app.config.get('i18n:additive'))); - - // Core calipso router - calipso.init(app, function () - { - - // Add the calipso mw - app.use(calipso.routingFn()); - - // return our app refrerence - next(app); - - }) - - }); +function bootApplication(cluster, next) { + + // Create our express instance, export for later reference + var app = express(); + app.path = function () { + return path + }; + app.isCluster = cluster; + + // Load configuration + var Config = calipso.configuration; //require(path + "/lib/core/Config").Config; + app.config = new Config(); + app.config.init(function (err) { + + if (err) { + return console.error(err.message); + } + + // Default Theme + calipso.defaultTheme = app.config.get('themes:default'); + + app.use(express.bodyParser()); + // Pause requests if they were not parsed to allow PUT and POST with custom mime types + app.use(function (req, res, next) { + if (!req._body) { + req.pause(); + } + next(); + }); + app.use(express.methodOverride()); + app.use(express.cookieParser(app.config.get('session:secret'))); + app.use(express.responseTime()); + + // Create dummy session middleware - tag it so we can later replace + var temporarySession = app.config.get('installed') ? {} : express.session({ secret:"installing calipso is great fun" }); + temporarySession.tag = "session"; + app.use(temporarySession); + + // Create holders for theme dependent middleware + // These are here because they need to be in the connect stack before the calipso router + // THese helpers are re-used when theme switching. + app.mwHelpers = {}; + + calipso.auth = { + password:app.config.get('server:authentication:password'), + migrate2pbkdf2:app.config.get('server:authentication:migrate2pbkdf2') + }; + if (calipso.auth.password === undefined) { + calipso.auth.password = true; + } + if (calipso.auth.migrate2pbkdf2 === undefined) { + calipso.auth.migrate2pbkdf2 = false; + } + + var appId = app.config.get('server:authentication:facebookAppId'); + var appSecret = app.config.get('server:authentication:facebookAppSecret'); + if (appId && appSecret) { + calipso.auth.facebook = true; + everyauth + .facebook + .myHostname(app.config.get('server:url')) + .getSession(function (req) { + if (!req.session) { + req.session = { _pending:req }; + } else { + req.session._pending = req; + } + return req.session; + }) + .appId(appId) + .appSecret(appSecret) + .findOrCreateUser(function (sess, accessToken, accessTokenExtra, fbUserMetadata) { + var promise = this.Promise(); + + return calipsoFindOrCreateUser({username:'facebook:' + fbUserMetadata.username, + email:fbUserMetadata.username + '@facebook.com', name:fbUserMetadata.name}, sess, promise); + }) + .redirectPath('/'); + } + + var consumerKey = app.config.get('server:authentication:twitterConsumerKey'); + var consumerSecret = app.config.get('server:authentication:twitterConsumerSecret'); + if (consumerKey && consumerSecret) { + calipso.auth.twitter = true; + everyauth + .twitter + .getSession(function (req) { + if (!req.session) { + req.session = { _pending:req }; + } else { + req.session._pending = req; + } + return req.session; + }) + .myHostname(app.config.get('server:url')) + .apiHost('https://api.twitter.com/1') + .consumerKey(consumerKey) + .consumerSecret(consumerSecret) + .findOrCreateUser(function (sess, accessToken, accessSecret, twitUser) { + var promise = this.Promise(); + + return calipsoFindOrCreateUser({username:'twitter:' + twitUser.screen_name, + email:twitUser.screen_name + '@twitter.com', name:twitUser.name}, sess, promise); + }) + .redirectPath('/'); + } + + var clientId = app.config.get('server:authentication:googleClientId'); + var clientSecret = app.config.get('server:authentication:googleClientSecret'); + if (clientId && clientSecret) { + calipso.auth.google = true; + everyauth + .google + .myHostname(app.config.get('server:url')) + .appId(clientId) + .appSecret(clientSecret) + .scope('https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email') + .getSession(function (req) { + if (!req.session) { + req.session = { _pending:req }; + } else { + req.session._pending = req; + } + return req.session; + }) + .findOrCreateUser(function (sess, accessToken, extra, googleUser) { + googleUser.refreshToken = extra.refresh_token; + googleUser.expiresIn = extra.expires_in; + + var promise = this.Promise(); + + return calipsoFindOrCreateUser({username:'google:' + googleUser.email, + email:googleUser.email, name:googleUser.name}, sess, promise); + }) + .redirectPath('/'); + } + + app.use(everyauth.middleware()); + + // Load placeholder, replaced later + if (app.config.get('libraries:stylus:enable')) { + app.mwHelpers.stylusMiddleware = function (themePath) { + var mw = stylus.middleware({ + src:themePath + '/stylus', + dest:themePath + '/public', + debug:false, + compile:function (str, path) { // optional, but recommended + return stylus(str) + .set('filename', path) + .set('warn', app.config.get('libraries:stylus:warn')) + .set('compress', app.config.get('libraries:stylus:compress')); + } + }); + mw.tag = 'theme.stylus'; + return mw; + }; + app.use(app.mwHelpers.stylusMiddleware('')); + } + + // Static + app.mwHelpers.staticMiddleware = function (themePath) { + var mw = express["static"](themePath + '/public', {maxAge:86400000}); + mw.tag = 'theme.static'; + return mw; + }; + // Load placeholder, replaced later + app.use(app.mwHelpers.staticMiddleware('')); + + // Core static paths + app.use(express["static"](path + '/media', {maxAge:86400000})); + app.use(express["static"](path + '/lib/client/js', {maxAge:86400000})); + + // Translation - after static, set to add mode if appropriate + app.use(translate.translate(app.config.get('i18n:language'), app.config.get('i18n:languages'), app.config.get('i18n:additive'))); + + // Core calipso router + calipso.init(app, function () { + // Add the calipso mw + app.use(calipso.routingFn()); + + // return our app refrerence + next(app); + + }) + + }); } /** * Initial bootstrapping */ -exports.boot = function (cluster, next) -{ +exports.boot = function (cluster, next) { - // Bootstrap application - bootApplication(cluster, next); + // Bootstrap application + bootApplication(cluster, next); }; // allow normal node loading if appropriate // e.g. not called from app-cluster or bin/calipso -if (!module.parent) -{ - - logo.print(); - - exports.boot(false, function (app) - { - - if (app) - { - var out = app.listen(port, function () - { - console.log("Calipso version: ".green + app.about.version); - console.log("Calipso configured for: ".green + (global.process.env.NODE_ENV || 'development') + " environment.".green); - if (app.address) - { - console.log("Calipso server listening on port: ".green + app.address().port); - } - else - { - console.log("Calipso server listening on port: ".green + port); - } - }); - process.nextTick(function () - { - if (out && out.address && out.address().port !== port) - { - console.log("Calipso server listening on port: ".red + out.address().port); - } - }); - } - else - { - console.log("\r\nCalipso terminated ...\r\n".grey); - process.exit(); - } - - }); +if (!module.parent) { + + logo.print(); + + exports.boot(false, function (app) { + + if (app) { + var out = app.listen(port, function () { + console.log("Calipso version: ".green + app.about.version); + console.log("Calipso configured for: ".green + (global.process.env.NODE_ENV || 'development') + " environment.".green); + if (app.address) { + console.log("Calipso server listening on port: ".green + app.address().port); + } else { + console.log("Calipso server listening on port: ".green + port); + } + }); + process.nextTick(function () { + if (out && out.address && out.address().port !== port) { + console.log("Calipso server listening on port: ".red + out.address().port); + } + }); + } else { + console.log("\r\nCalipso terminated ...\r\n".grey); + process.exit(); + } + + }); } diff --git a/docs/coverage.html b/docs/coverage.html index af3696adf..ff5204ef4 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -1,334 +1,34431 @@ -Coverage

Coverage

63%
1966
1242
724

core/Configuration.js

96%
58
56
2
LineHitsSource
1/**
2 * Configuration library
3 */
4
51var rootpath = process.cwd() + '/',
6 path = require('path'),
7 step = require('step'),
8 _ = require('underscore'),
9 fs = require('fs');
10
11/**
12 * Config object, wrapper for nconf
13 * @type
14 * @options
15 */
16
171function Configuration(options) {
18
19 // Defaults
2011 this.type = options && options.type ? options.type : 'file';
2111 this.env = options && options.env ? options.env : (process.env.NODE_ENV || 'development');
2211 this.path = options && options.path ? options.path : path.join(rootpath, 'conf');
2311 this.defaultConfig = options && options.defaultConfig ? options.defaultConfig : path.join(rootpath, 'lib', 'conf', 'default.json');
2411 this.file = path.join(this.path, this.env + '.json');
25
26 // Track if changes have been made to config
2711 this.dirty = false;
28
29}
30
311Configuration.prototype.init = function(next) {
3210 if (typeof next !== 'function') next = function(err) {
330 if (err) console.error(err.message)
34 };
3510 var Provider = require('nconf').Provider;
3610 this.nconf = new Provider();
3710 this.load(next);
38}
39
40/**
41 * Check to see if configuration for this environment
42 * doesn't exist, if so, it loads the default from default.json.
43 */
441Configuration.prototype.check = function() {
45
4611 if (!(fs.existsSync || path.existsSync)(this.file)) {
478 try {
488 var defaultFile = fs.readFileSync(this.defaultConfig);
49 // Parse it to make sure there are no errors
507 defaultFile = JSON.stringify(JSON.parse(defaultFile), true);
517 fs.writeFileSync(this.file, defaultFile);
52 } catch (ex) {
531 return ex.message;
54 }
557 return;
56 } else {
573 return;
58 }
59
60}
61
62/**
63 * Load the configuration
64 */
651Configuration.prototype.load = function(next) {
66
67 // Check if config exists for this environment or default it
6811 var checkConfig = this.check();
6911 if (!checkConfig) {
70
71 // Initialise nconf
7210 try {
7310 this.nconf.use(this.type, this);
74 } catch (ex) {
750 return next(ex);
76 }
77
7810 this.nconf.load(next);
79
80 } else {
81
821 next(new Error("Unable to load configuration defined in " + this.env + ".json, there may be a problem with the default configuration in " + this.defaultConfig + ", reason: " + checkConfig));
83
84 }
85
86}
87
88/**
89 * Get config - wrapper
90 */
911Configuration.prototype.get = function(key) {
9294 return this.nconf.get(key);
93}
94
95/**
96 * Get config for module - wrapper
97 */
981Configuration.prototype.getModuleConfig = function(moduleName, key) {
992 var moduleKey = 'modules:' + moduleName + ':config' + (key ? ':' + key : '');
1002 return this.nconf.get(moduleKey);
101}
102
103
104/**
105 * Set config
106 */
1071Configuration.prototype.set = function(key, value) {
1084 this.dirty = true;
1094 this.nconf.set(key, value);
110}
111
112/**
113 * Set config for module - wrapper
114 */
1151Configuration.prototype.setModuleConfig = function(moduleName, key, value) {
1161 var moduleKey = 'modules:' + moduleName + ':config' + (key ? ':' + key : '');
1171 this.dirty = true;
1181 this.nconf.set(moduleKey, value);
119}
120
121/**
122 * Set default config for module - wrapper
123 */
1241Configuration.prototype.setDefaultModuleConfig = function(moduleName, config) {
125
1261 var moduleKey = 'modules:' + moduleName + ':config';
1271 this.dirty = true;
128
129 // Extract the defaults from the config
1301 var defaultConfig = _.reduce(_.keys(config), function(memo, key) {
1311 memo[key] = config[key].
132 default;
1331 return memo;
134 }, {})
135
1361 this.nconf.set(moduleKey, defaultConfig);
137
138}
139
140/**
141 * Save config
142 */
1431Configuration.prototype.save = function(next) {
1442 this.dirty = false;
1452 this.nconf.save(next);
146}
147
148/**
149 * Set & save config
150 */
151
1521Configuration.prototype.setSave = function(key, value, next) {
1531 this.set(key, value);
1541 this.dirty = false;
1551 this.save(next);
156}
157
158/**
159 * Export the config object
160 */
1611module.exports = Configuration;

/Users/andy/calipso/test/helpers/require.js

66%
6
4
2
LineHitsSource
1
2/**
3 * This helper allows us to include files that either have or haven't been marked up by jscoverage.
4 * All modules under test should be included via;
5 *
6 * library = require('./helpers/require')('core/Config.js');
7 *
8 * The path is always relative to the lib folder, and this approach only works for core Calipso libraries.
9 *
10 */
116if (process.env.CALIPSO_COV) {
126 var jsc = require('jscoverage'),
13 require = jsc.require(module); // rewrite require function
146 module.exports = function (library) {
1524 return require('../../lib-cov/' + library);
16 }
17} else {
180 module.exports = function (library) {
190 return require('../../lib/' + library);
20 }
21}

calipso.js

81%
92
75
17
LineHitsSource
1/*!
2 * Calipso Core Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This is the core Calipso middleware that controls the bootstrapping, and core routing functions, required for
8 * Calipso to function. This is loaded into an Express application via:
9 *
10 * app.use(calipso.calipsoRouter(next);
11 *
12 * Further detail is contained in comments for each function and object.
13 *
14 */
151var rootpath = process.cwd() + '/',
16 path = require('path'),
17 fs = require('fs'),
18 events = require('events');
19
20// Core object
211var calipso = module.exports = {
22
23 // Router and initialisation
24 routingFn: routingFn,
25 init: init,
26
27 // Configuration exposed
28 reloadConfig: reloadConfig,
29
30 // Core objects - themes, data, modules
31 theme: {},
32 data: {},
33 modules: {}
34
35};
36
37// Load libraries in the core folder
381loadCore(calipso);
39
401function loadCore(calipso) {
41
421 fs.readdirSync(__dirname + '/core').forEach(function(library) {
4319 var isLibrary = library.split(".").length > 0 && library.split(".")[1] === 'js',
44 libName = library.split(".")[0].toLowerCase();
4537 if (isLibrary) calipso[libName] = require(__dirname + '/core/' + library);
46 });
47
48}
491module.exports.loaded = true;
50
51/**
52 * Calipso initialisation
53 */
54
551function init(app, initCallback) {
56
571 calipso.app = app;
58
59 // Load the calipso package.json into app.about
601 calipso.module.loadAbout(app, rootpath, 'package.json');
61
62 // config is the actual instance of loaded config, configuration is the library.
631 calipso.config = app.config;
64
65 // Store the callback function for later
661 calipso.initCallback = function() {
671 initCallback();
68 };
69
70 // Configure the cache
711 calipso.cacheService = calipso.cache.Cache({
72 ttl: calipso.config.get('performance:cache:ttl')
73 });
74
75 // Create our calipso event emitter
761 calipso.e = new calipso.event.CalipsoEventEmitter({maxListeners: calipso.config.get('server:events:maxListeners')});
77
78 // Load configuration
791 initialiseCalipso();
80
81}
82
83/**
84 * Core router function.
85 *
86 * Returns a connect middleware function that manages the roucting
87 * of requests to modules.
88 *
89 * Expects Calipso to be initialised.
90 */
91
921function routingFn() {
93
94 // Return the function that manages the routing
95 // Ok being non-synchro
964 return function(req, res, next) {
97
98 // Default menus and blocks for each request
99 // More of these can be added in modules, these are jsut the defaults
1004 res.menu = {
101 admin: new calipso.menu('admin', 'weight', 'root', {
102 cls: 'admin'
103 }),
104 adminToolbar: new calipso.menu('adminToolbar', 'weight', 'root', {
105 cls: 'admin-toolbar toolbar'
106 }),
107 // TODO - Configurable!
108 userToolbar: new calipso.menu('userToolbar', 'weight', 'root', {
109 cls: 'user-toolbar toolbar'
110 }),
111 primary: new calipso.menu('primary', 'name', 'root', {
112 cls: 'primary'
113 }),
114 secondary: new calipso.menu('secondary', 'name', 'root', {
115 cls: 'secondary'
116 })
117 };
118
119
120 // Initialise our clientJS library linked to this request
1214 var Client = require('./client/Client');
1224 res.client = new Client();
123
124 // Initialise helpers - first pass
1254 calipso.helpers.getDynamicHelpers(req, res, calipso);
126
127 // Route the modules
1284 calipso.module.eventRouteModules(req, res, next);
129
130 };
131
132}
133
134/**
135 * Load the application configuration
136 * Configure the logging
137 * Configure the theme
138 * Load the modules
139 * Initialise the modules
140 *
141 * @argument config
142 *
143 */
144
1451function initialiseCalipso(reloadConfig) {
146
147 // Check if we need to reload the config from disk (e.g. from cluster mode)
1482 if (reloadConfig) {
1491 calipso.config.load();
150 }
151
152 // Clear Event listeners
1532 calipso.e.init();
154
155 // Configure the logging
1562 calipso.logging.configureLogging();
157
158 // Check / Connect Mongo
1592 calipso.storage.mongoConnect(calipso.config.get('database:uri'), false, function(err, connected) {
160
1612 if (err) {
1620 console.log("There was an error connecting to the database: " + err.message);
1630 process.exit();
164 }
165
166 // Load all the themes
1672 loadThemes(function() {
168
169 // Initialise the modules and theming engine
1702 configureTheme(function() {
171
172 // Load all the modules
1732 calipso.module.loadModules(function() {
174
175 // Initialise, callback via calipso.initCallback
1762 calipso.module.initModules();
177
178 });
179
180 });
181
182 });
183
184 });
185
186}
187
188/**
189 * Called both via a hook.io event as
190 * well as via the server that initiated it.
191 */
1921function reloadConfig(event, data, next) {
193
194 // Create a callback
1951 calipso.initCallback = function(err) {
196 // If called via event emitter rather than hook
1972 if (typeof next === "function") next(err);
198 };
1991 return initialiseCalipso(true);
200
201}
202
203/**
204 * Load the available themes into the calipso.themes object
205 */
206
2071function loadThemes(next) {
208
2092 var themeBasePath = calipso.config.get('server:themePath'),
210 themePath, legacyTheme, themes;
211
212 // Load the available themes
2132 calipso.availableThemes = calipso.availableThemes || {};
214
2152 calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, themeBasePath)).forEach(function(folder) {
216
2172 if (folder != "README" && folder[0] != '.') {
218
2192 themes = calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, themeBasePath, folder));
220
221 // First scan for legacy themes
2222 legacyTheme = false;
2232 themes.forEach(function(theme) {
2242 if (theme === "theme.json") {
2250 legacyTheme = true;
2260 console.log("Themes are now stored in sub-folders under the themes folder, please move: " + folder + " (e.g. to custom/" + folder + ").\r\n");
227 }
228 });
229
230 // Process
2312 if (!legacyTheme) {
2322 themes.forEach(function(theme) {
233
2342 if (theme != "README" && theme[0] != '.') {
2352 themePath = calipso.lib.path.join(rootpath, themeBasePath, folder, theme);
236 // Create the theme object
2372 calipso.availableThemes[theme] = {
238 name: theme,
239 path: themePath
240 };
241 // Load the about info from package.json
2422 calipso.module.loadAbout(calipso.availableThemes[theme], themePath, 'theme.json');
243 }
244 });
245 }
246 }
247 });
248
2492 next();
250
251}
252
253/**
254 * Configure a theme using the theme library.
255 */
256
2571function configureTheme(next, overrideTheme) {
258
2592 var defaultTheme = calipso.config.get("theme:default");
2602 var themeName = overrideTheme ? overrideTheme : calipso.config.get('theme:front');
2612 var themeConfig = calipso.availableThemes[themeName]; // Reference to theme.json
2622 if (themeConfig) {
263
264 // Themes is the library
2652 calipso.themes.Theme(themeConfig, function(err, loadedTheme) {
266
267 // Current theme is always in calipso.theme
2682 calipso.theme = loadedTheme;
269
2702 if (err) {
2710 calipso.error(err.message);
272 }
273
2742 if (!calipso.theme) {
275
2760 if (loadedTheme.name === defaultTheme) {
2770 calipso.error('There has been a failure loading the default theme, calipso cannot start until this is fixed, terminating.');
2780 process.exit();
2790 return;
280 } else {
2810 calipso.error('The `' + themeName + '` theme failed to load, attempting to use the default theme: `' + defaultTheme + '`');
2820 configureTheme(next, defaultTheme);
2830 return;
284 }
285
286 } else {
287
288 // Search for middleware that already has themeStatic tag
2892 var foundMiddleware = false,
290 mw;
2912 calipso.app.stack.forEach(function(middleware, key) {
292
2936 if (middleware.handle.tag === 'theme.stylus') {
2941 foundMiddleware = true;
2951 mw = calipso.app.mwHelpers.stylusMiddleware(themeConfig.path);
2961 calipso.app.stack[key].handle = mw;
297 }
298
2996 if (middleware.handle.tag === 'theme.static') {
3001 foundMiddleware = true;
3011 mw = calipso.app.mwHelpers.staticMiddleware(themeConfig.path);
3021 calipso.app.stack[key].handle = mw;
303 }
304
305 });
306
3072 next();
308
309 }
310
311 });
312
313 } else {
314
3150 if (themeName === defaultTheme) {
3160 console.error("Unable to locate the theme: " + themeName + ", terminating.");
3170 process.exit();
318 } else {
3190 calipso.error('The `' + themeName + '` theme is missing, trying the defaul theme: `' + defaultTheme + '`');
3200 configureTheme(next, defaultTheme);
321 }
322
323 }
324
325}

core/Blocks.js

70%
30
21
9
LineHitsSource
1/*!
2 * Calipso Core Library - Storage of Rendered Blocks
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This class controls the storage and retrieval of blocks rendered via the Router, e.g. specific pieces of output.
7 *
8 */
9
101var rootpath = process.cwd() + '/',
11 path = require('path'),
12 calipso = require(path.join('..', 'calipso'));
13
14/**
15 * Holder for rendered blocks (get / set)
16 * Idea is that this will give us an opportunity
17 * to cache expensive sections of a page.
18 */
19
201function RenderedBlocks(cache) {
21
22 // Store the content rendered by modules
231 this.content = {};
24
25 // Flags to indicate if it should be cached
261 this.contentCache = {};
27
28 // The cache itself
291 this.cache = cache;
30
31}
32
33/**
34 * Set block content
35 */
361RenderedBlocks.prototype.set = function(block, content, layout, params, next) {
37
381 var cacheKey = calipso.cacheService.getCacheKey(['block', block], params);
39
401 this.content[block] = this.content[block] || [];
411 this.content[block].push(content);
42
43 // If we are caching, then cache it.
441 if (this.contentCache[block]) {
450 calipso.silly("Cache set for " + cacheKey);
460 this.cache.set(cacheKey, {
47 content: content,
48 layout: layout
49 }, null, next);
50 } else {
511 next();
52 }
53
54};
55
56/**
57 * Get block content
58 */
591RenderedBlocks.prototype.get = function(key, next) {
60
61 // Check to see if the key is a regex, for 0.4 and 0.5 nodej
628 if (typeof key === 'object' || typeof key === "function") {
638 var item, items = [];
648 for (item in this.content) {
653 if (this.content.hasOwnProperty(item)) {
663 if (item.match(key)) {
671 items.push(this.content[item]);
68 }
69 }
70 }
718 next(null, items);
72 } else {
730 next(null, this.content[key] || []);
74 }
75
76};
77
78/**
79 * Get content from cache and load into block
80 */
811RenderedBlocks.prototype.getCache = function(key, block, next) {
82
830 calipso.silly("Cache hit for block " + key);
84
850 var self = this;
860 this.cache.get(key, function(err, cache) {
87
880 self.content[block] = self.content[block] || [];
890 self.content[block].push(cache.content);
900 next(err, cache.layout);
91
92 });
93
94};
95
961module.exports.RenderedBlocks = RenderedBlocks;

core/Cache.js

100%
7
7
0
LineHitsSource
1/*!
2 * Calipso Core Caching Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This is the core Calipso library that enables caching to be turned on
8 * that will cache both module and full page output.
9 *
10 * The idea is to have a pluggable cache storage module, copied liberally
11 * from concepts behind the express session module.
12 *
13 *
14 *
15 */
161var rootpath = process.cwd() + '/',
17 path = require('path'),
18 calipso = require(path.join('..', 'calipso')),
19 MemoryStore = require('./cacheAdapters/memory'),
20 Store = require('./cacheAdapters/store');
21
22// Exports
231exports.Cache = Cache;
241exports.Store = Store;
251exports.MemoryStore = MemoryStore;
26
27/**
28 * Very simple wrapper that
29 * Enables pluggability of cache store, defaulting to in Memory
30 *
31 * cache.set('cc','RAH!',500,function() {
32 * cache.get('cc',function(err,item) {
33 * console.log(item);
34 * });
35 * });
36 *
37 */
38
391function Cache(options) {
40
411 var options = options || {},
42 store = store || new MemoryStore(options);
43
441 return store;
45
46}

core/cacheAdapters/memory.js

30%
39
12
27
LineHitsSource
1
2/*!
3 * Calipso - cache - Memory Store
4 * Approach copied from Connect session store
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var Store = require('./store');
13
14/**
15 * Initialize a new `MemoryStore`.
16 *
17 * @api public
18 */
19
201var MemoryStore = module.exports = function MemoryStore(options) {
211 this.cache = {};
221 this.options = options || {};
23};
24
25/**
26 * Inherit from `Store.prototype`.
27 */
281MemoryStore.prototype.__proto__ = Store.prototype;
29
30/**
31 * Attempt to fetch cache by the given `key'.
32 *
33 * @param {String} key
34 * @param {Function} fn
35 * @api public
36 */
37
381MemoryStore.prototype.get = function(key, fn){
390 var self = this;
40
41 //process.nextTick(function(){
420 var cache = self.cache[key];
430 if (cache) {
440 fn(null, cache.item);
45 } else {
460 fn(new Error('Cache miss: ' + key));
47 }
48 //});
49};
50
51/**
52 * Check cache by the given `key'.
53 *
54 * @param {String} key
55 * @param {Function} fn
56 * @api public
57 */
58
591MemoryStore.prototype.check = function(key, fn){
60
610 var self = this;
620 var cache = self.cache[key];
63
640 if (cache) {
65 // Check to see if it has expired
660 if (!cache.expires || Date.now() < cache.expires) { // TODO
670 fn(null, true);
68 } else {
690 self.destroy(key, fn);
70 }
71 } else {
720 fn(null,false);
73 }
74
75};
76
77/**
78 * Add an item to the cache, referenced by key
79 * with expires
80 *
81 * @param {String} key
82 * @param {String} item
83 * @param {Number} expires (milliseconds)
84 * @param {Function} fn
85 * @api public
86 */
87
881MemoryStore.prototype.set = function(key, item, ttl, fn){
890 var self = this;
90 //process.nextTick(function(){
910 ttl = ttl || (self.options.ttl || 600);
920 var expires = Date.now() + ttl;
930 self.cache[key] = {item:item, expires:expires}
940 fn && fn();
95 //});
96};
97
98/**
99 * Destroy the session associated with the given `key`.
100 *
101 * @param {String} key
102 * @api public
103 */
104
1051MemoryStore.prototype.destroy = function(key, fn){
1060 var self = this;
107 //process.nextTick(function(){
1080 delete self.cache[key];
1090 fn && fn();
110 //});
111};
112
113/**
114 * Invoke the given callback `fn` with all active sessions.
115 *
116 * @param {Function} fn
117 * @api public
118 */
119
1201MemoryStore.prototype.all = function(fn){
1210 var arr = []
122 , keys = Object.keys(this.cache);
1230 for (var i = 0, len = keys.length; i < len; ++i) {
1240 arr.push(this.cache[keys[i]]);
125 }
1260 fn(null, arr);
127};
128
129/**
130 * Clear cache
131 *
132 * @param {Function} fn
133 * @api public
134 */
135
1361MemoryStore.prototype.clear = function(fn){
1370 this.cache = {};
1380 fn && fn();
139};
140
141/**
142 * Fetch number of cache items
143 *
144 * @param {Function} fn
145 * @api public
146 */
147
1481MemoryStore.prototype.length = function(fn){
1490 fn(null, Object.keys(this.cache).length);
150};

core/cacheAdapters/store.js

75%
16
12
4
LineHitsSource
1
2/*!
3 * Calipso - cache - Store
4 * Concepts taken from connect session
5 * MIT Licensed
6 */
7
8/**
9 * Initialize abstract `Store`.
10 *
11 * @api private
12 */
131var rootpath = process.cwd() + '/',
14 path = require('path'),
15 calipso = require(path.join('..', '..', 'calipso'));
16
17/**
18 * Store object - options:
19 * prefix - a prefix to attach to all cache keys, defaults to calipso.
20 */
211var Store = module.exports = function Store(options){
22
230 this.options = options || {};
24
25};
26
27/**
28* Generate a cache key - applies to all store types
29*/
301Store.prototype.getCacheKey = function(keys, params) {
31
321 var prefix = this.options.prefix || "calipso";
33
34 // Append the theme, allows for theme change
351 var cacheKey = prefix + "::" + calipso.theme.theme, paramCount = 0;
36
37 // Create the key from the keys
381 keys.forEach(function(value) {
392 cacheKey += "::" + value;
40 })
41
421 var qs = require("querystring");
43
441 if(params) {
451 cacheKey += "::";
461 calipso.lib._.each(params,function(param,key) {
470 if(param) {
480 cacheKey += (paramCount > 0 ? "::" : "") + (param ? (key + "=" + qs.escape(param)) : "");
490 paramCount += 1;
50 }
51 });
52 }
53
541 return cacheKey;
55}

core/Date.js

13%
149
20
129
LineHitsSource
1/**
2 * This is a generic date parsing and formatting library, to avoid any confusion
3 * about how dates are handled across both the back and front end (assuming jQuery UI will be)
4 * the default.
5 *
6 * These functions are extracted from the jQuery UI Datepicker (see below).
7 */
8
9/**
10 * jQuery UI Datepicker
11 *
12 *
13 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
14 * Dual licensed under the MIT or GPL Version 2 licenses.
15 * - License http://jquery.org/license
16 * - Original Source http://docs.jquery.com/UI/Datepicker
17 */
18
191function CalipsoDate() {
20
211 this.regional = []; // Available regional settings, indexed by language code
221 this.regional[''] = { // Default regional settings
23 closeText: 'Done',
24 // Display text for close link
25 prevText: 'Prev',
26 // Display text for previous month link
27 nextText: 'Next',
28 // Display text for next month link
29 currentText: 'Today',
30 // Display text for current month link
31 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
32 // Names of months for drop-down and formatting
33 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
34 // For formatting
35 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
36 // For formatting
37 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
38 // For formatting
39 dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
40 // Column headings for days starting at Sunday
41 weekHeader: 'Wk',
42 // Column header for week of the year
43 dateFormat: 'mm/dd/yy',
44 // See format options on parseDate
45 firstDay: 0,
46 // The first day of the week, Sun = 0, Mon = 1, ...
47 isRTL: false,
48 // True if right-to-left language, false if left-to-right
49 showMonthAfterYear: false,
50 // True if the year select precedes month, false for month then year
51 yearSuffix: '' // Additional text to append to the year in the month headers
52 };
53
541 this._defaults = this.regional[''];
55
56 // Standard date formats.
571 this.ATOM = 'yy-mm-dd'; // RFC 3339 (ISO 8601)
581 this.COOKIE = 'D, dd M yy';
591 this.ISO_8601 = 'yy-mm-dd';
601 this.RFC_822 = 'D, d M y';
611 this.RFC_850 = 'DD, dd-M-y';
621 this.RFC_1036 = 'D, d M y';
631 this.RFC_1123 = 'D, d M yy';
641 this.RFC_2822 = 'D, d M yy';
651 this.RSS = 'D, d M y'; // RFC 822
661 this.TICKS = '!';
671 this.TIMESTAMP = '@';
681 this.W3C = 'yy-mm-dd'; // ISO 8601
691 this._ticksTo1970 = (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000);
70
71}
72
73/* Parse a string value into a date object.
74 See formatDate below for the possible formats.
75
76 @param format string - the expected format of the date
77 @param value string - the date in the above format
78 @param settings Object - attributes include:
79 shortYearCutoff number - the cutoff year for determining the century (optional)
80 dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
81 dayNames string[7] - names of the days from Sunday (optional)
82 monthNamesShort string[12] - abbreviated names of the months (optional)
83 monthNames string[12] - names of the months (optional)
84 @return Date - the extracted date value or null if value is blank */
851CalipsoDate.prototype.parseDate = function(format, value, settings) {
860 if (format == null || value == null) throw 'Invalid arguments';
870 value = (typeof value == 'object' ? value.toString() : value + '');
880 if (value == '') return null;
890 var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff;
900 shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
910 var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
920 var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
930 var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
940 var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
950 var year = -1;
960 var month = -1;
970 var day = -1;
980 var doy = -1;
990 var literal = false;
100 // Check whether a format character is doubled
1010 var lookAhead = function(match) {
1020 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
1030 if (matches) iFormat++;
1040 return matches;
105 };
106 // Extract a number from the string value
1070 var getNumber = function(match) {
1080 var isDoubled = lookAhead(match);
1090 var size = (match == '@' ? 14 : (match == '!' ? 20 : (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2))));
1100 var digits = new RegExp('^\\d{1,' + size + '}');
1110 var num = value.substring(iValue).match(digits);
1120 if (!num) throw 'Missing number at position ' + iValue;
1130 iValue += num[0].length;
1140 return parseInt(num[0], 10);
115 };
116 // Extract a name from the string value and convert to an index
1170 var getName = function(match, shortNames, longNames) {
1180 var names = $.map(lookAhead(match) ? longNames : shortNames, function(v, k) {
1190 return [[k, v]];
120 }).sort(function(a, b) {
1210 return -(a[1].length - b[1].length);
122 });
1230 var index = -1;
1240 $.each(names, function(i, pair) {
1250 var name = pair[1];
1260 if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) {
1270 index = pair[0];
1280 iValue += name.length;
1290 return false;
130 }
131 });
1320 if (index != -1) return index + 1;
1330 else throw 'Unknown name at position ' + iValue;
134 };
135 // Confirm that a literal character matches the string value
1360 var checkLiteral = function() {
1370 if (value.charAt(iValue) != format.charAt(iFormat)) throw 'Unexpected literal at position ' + iValue;
1380 iValue++;
139 };
1400 var iValue = 0;
1410 for (var iFormat = 0; iFormat < format.length; iFormat++) {
1420 if (literal) if (format.charAt(iFormat) == "'" && !lookAhead("'")) literal = false;
1430 else checkLiteral();
1440 else switch (format.charAt(iFormat)) {
145 case 'd':
1460 day = getNumber('d');
1470 break;
148 case 'D':
1490 getName('D', dayNamesShort, dayNames);
1500 break;
151 case 'o':
1520 doy = getNumber('o');
1530 break;
154 case 'm':
1550 month = getNumber('m');
1560 break;
157 case 'M':
1580 month = getName('M', monthNamesShort, monthNames);
1590 break;
160 case 'y':
1610 year = getNumber('y');
1620 break;
163 case '@':
1640 var date = new Date(getNumber('@'));
1650 year = date.getFullYear();
1660 month = date.getMonth() + 1;
1670 day = date.getDate();
1680 break;
169 case '!':
1700 var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
1710 year = date.getFullYear();
1720 month = date.getMonth() + 1;
1730 day = date.getDate();
1740 break;
175 case "'":
1760 if (lookAhead("'")) checkLiteral();
1770 else literal = true;
1780 break;
179 default:
1800 checkLiteral();
181 }
182 }
1830 if (year == -1) year = new Date().getFullYear();
1840 else if (year < 100) year += new Date().getFullYear() - new Date().getFullYear() % 100 + (year <= shortYearCutoff ? 0 : -100);
1850 if (doy > -1) {
1860 month = 1;
1870 day = doy;
1880 do {
1890 var dim = this._getDaysInMonth(year, month - 1);
1900 if (day <= dim) break;
1910 month++;
1920 day -= dim;
193 } while (true);
194 }
1950 var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
1960 if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) throw 'Invalid date'; // E.g. 31/02/00
1970 return date;
198}
199
200/*
201 Format a date object into a string value.
202
203 The format can be combinations of the following
204
205 d - day of month (no leading zero)
206 dd - day of month (two digit)
207 o - day of year (no leading zeros)
208 oo - day of year (three digit)
209 D - day name short
210 DD - day name long
211 m - month of year (no leading zero)
212 mm - month of year (two digit)
213 M - month name short
214 MM - month name long
215 y - year (two digit)
216 yy - year (four digit)
217 @ - Unix timestamp (ms since 01/01/1970)
218 ! - Windows ticks (100ns since 01/01/0001)
219 '...' - literal text
220 '' - single quote
221
222 @param format string - the desired format of the date
223 @param date Date - the date value to format
224 @param settings Object - attributes include:
225 dayNamesShort string[7] - abbreviated names of the days from Sunday (optional)
226 dayNames string[7] - names of the days from Sunday (optional)
227 monthNamesShort string[12] - abbreviated names of the months (optional)
228 monthNames string[12] - names of the months (optional)
229 @return string - the date in the above format */
230
2311CalipsoDate.prototype.formatDate = function(format, date, settings) {
2320 if (!date) return '';
2330 var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort;
2340 var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames;
2350 var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort;
2360 var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames;
237 // Check whether a format character is doubled
2380 var lookAhead = function(match) {
2390 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match);
2400 if (matches) iFormat++;
2410 return matches;
242 };
243 // Format a number, with leading zero if necessary
2440 var formatNumber = function(match, value, len) {
2450 var num = '' + value;
2460 if (lookAhead(match)) while (num.length < len)
2470 num = '0' + num;
2480 return num;
249 };
250 // Format a name, short or long as requested
2510 var formatName = function(match, value, shortNames, longNames) {
2520 return (lookAhead(match) ? longNames[value] : shortNames[value]);
253 };
2540 var output = '';
2550 var literal = false;
2560 if (date) for (var iFormat = 0; iFormat < format.length; iFormat++) {
2570 if (literal) if (format.charAt(iFormat) == "'" && !lookAhead("'")) literal = false;
2580 else output += format.charAt(iFormat);
2590 else switch (format.charAt(iFormat)) {
260 case 'd':
2610 output += formatNumber('d', date.getDate(), 2);
2620 break;
263 case 'D':
2640 output += formatName('D', date.getDay(), dayNamesShort, dayNames);
2650 break;
266 case 'o':
2670 output += formatNumber('o', (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000, 3);
2680 break;
269 case 'm':
2700 output += formatNumber('m', date.getMonth() + 1, 2);
2710 break;
272 case 'M':
2730 output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
2740 break;
275 case 'y':
2760 output += (lookAhead('y') ? date.getFullYear() : (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100);
2770 break;
278 case '@':
2790 output += date.getTime();
2800 break;
281 case '!':
2820 output += date.getTime() * 10000 + this._ticksTo1970;
2830 break;
284 case "'":
2850 if (lookAhead("'")) output += "'";
2860 else literal = true;
2870 break;
288 default:
2890 output += format.charAt(iFormat);
290 }
291 }
2920 return output;
293}
294
295/**
296 * Export an instance of our date object
297 */
2981module.exports = new CalipsoDate();

core/Event.js

95%
132
126
6
LineHitsSource
1/*!
2 * Calipso Module Event Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library provides an event emitter for modules that is created on each request,
7 * to provide the ability for module dependencies to be managed, as well as enable modules
8 * to ensure that they run after all other modules have emitted certain events (e.g. menu rendering).
9 *
10 *
11 */
12
13/**
14 * Includes
15 */
161var rootpath = process.cwd() + '/',
17 path = require('path'),
18 util = require('util'),
19 events = require('events'),
20 calipso = require(path.join('..', 'calipso'));
21
221exports = module.exports = {
23 CalipsoEventEmitter: CalipsoEventEmitter,
24 RequestEventListener: RequestEventListener,
25 addModuleEventListener: addModuleEventListener,
26 // Module & Routing Event constants
27 ROUTE_START: 'route_s',
28 ROUTE_FINISH: 'route_f',
29 INIT_START: 'init_s',
30 INIT_FINISH: 'init_f'
31};
32
33
34/**
35 * Calipso event emitter, object that enables calipso to emit events.
36 * Events are always triggered at server scope, and cannot be used to
37 * Execute functions in request scope
38 */
39
401function CalipsoEventEmitter(options) {
41
425 var self = this;
43
44 // Initialise options
455 this.options = options || {};
46
47 // Create an emitter to drive events
485 this.emitter = new events.EventEmitter();
495 this.emitter.setMaxListeners(this.options.maxListeners || 100);
50
51 // Holder for events, enable debugging of module events
525 this.events = {};
53
54 // Clear all existing listeners
555 this.init = function() {
56
57 // Clear down the event emitters
587 for (var event in self.events) {
59
603 self.emitter.removeAllListeners("PRE_" + event);
613 self.emitter.removeAllListeners("POST_" + event);
62
633 if (self.events[event].custom) {
643 for (var key in self.events[event].custom) {
650 self.emitter.removeAllListeners(event + "_" + key);
66 }
67 }
68
69 }
70
71 // Add core events not created by modules
727 this.addEvent('FORM');
73
74 };
75
76 // Wrapper for event emitter, enable turn on / off
775 this.addEvent = function(event, options) {
78
7911 options = calipso.lib._.extend({
80 enabled: true
81 }, options);
82
8311 this.events[event] = options;
84 // Enable tracking of attached listeners for debugging purposes
8511 this.events[event].preListeners = {
86 '#': 0
87 };
8811 this.events[event].postListeners = {
89 '#': 0
90 };
9111 this.events[event].custom = {};
92 };
93
94 // Pre and post event prefixes
955 var pre_prefix = 'PRE_',
96 post_prefix = 'POST_';
97
98 // Register a pre listener
995 this.pre = function(event, listener, fn) {
100
1012 self.emitter.on(pre_prefix + event, fn);
1022 this.events[event].preListeners[listener] = this.events[event].preListeners[listener] || [];
1032 this.events[event].preListeners[listener].push({
104 name: fn.name
105 });
1062 this.events[event].preListeners['#'] += 1;
107 };
108
109 // Register a post listener
1105 this.post = function(event, listener, fn) {
1112 self.emitter.on(post_prefix + event, fn);
1122 this.events[event].postListeners[listener] = this.events[event].postListeners[listener] || [];
1132 this.events[event].postListeners[listener].push({
114 name: fn.name
115 });
1162 this.events[event].postListeners['#'] += 1;
117 };
118
119 // Register a custom event listener
1205 this.custom = function(event, key, listener, fn) {
121
1222 self.emitter.on(event + '_' + key, fn);
123
124 // Register under key
1252 this.events[event].custom[key] = this.events[event].custom[key] || {
126 customListeners: {
127 '#': 0
128 }
129 };
130
131 // Register
1322 this.events[event].custom[key].customListeners[listener] = this.events[event].custom[key].customListeners[listener] || [];
1332 this.events[event].custom[key].customListeners[listener].push({
134 name: fn.name
135 });
1362 this.events[event].custom[key].customListeners['#'] += 1;
137
138 };
139
140 // Emit a pre event
1415 this.pre_emit = function(event, data, next) {
142
1432 var cb;
144
145 // Create a callback to track completion of all events (only if next exists)
1462 if (typeof next === "function") {
1471 cb = createCallback(this.events[event].preListeners['#'], data, next);
148 } else {
1491 cb = function() {};
150 }
151
1522 if (this.events[event] && this.events[event].enabled) {
1532 self.emitter.emit(pre_prefix + event, pre_prefix + event, data, cb);
154 }
155
156 };
157
158 // Emit a post event
1595 this.post_emit = function(event, data, next) {
160
1612 var cb;
162
163 // Create a callback to track completion of all events (only if next exists)
1642 if (typeof next === "function") {
1651 cb = createCallback(this.events[event].postListeners['#'], data, next);
166 } else {
1671 cb = function() {};
168 }
169
1702 if (this.events[event] && this.events[event].enabled) {
1712 self.emitter.emit(post_prefix + event, post_prefix + event, data, cb);
172 }
173
174 };
175
176 // Emit a custom event
1775 this.custom_emit = function(event, key, data, next) {
178
1792 var cb;
180
1812 if (this.events[event] && this.events[event].custom[key] && this.events[event].enabled) {
182
183 // Create a callback to track completion of all events (only if next exists)
1842 if (typeof next === "function") {
1852 cb = createCallback(this.events[event].custom[key].customListeners['#'], data, next);
186 } else {
1870 cb = function() {};
188 }
189
1902 self.emitter.emit(event + '_' + key, event + '_' + key, data, cb);
191
192 } else {
1930 next(data);
194 }
195
196 };
197
198 // Create a curried callback function for use in the emit code
199
2005 function createCallback(total, data, callback) {
201
2024 var count = 0,
203 total = total,
204 outputStack = [];
205
2068 if (data) outputStack.push(data);
207
208 // No listeners, so callback immediately
2094 if (total === 0) {
2100 callback(data);
2110 return;
212 }
213
2144 return function(data) {
215
2164 count += 1;
217
2188 if (data) outputStack.push(data);
219
220 // Merge the outputs from the stack
2214 if (count === total) {
2224 callback(mergeArray(outputStack));
223 }
224
225 };
226
227 }
228
229}
230
231/**
232 * Module event emitter, object that enables modules to emit events.
233 * This contains both server and request scope event emitters, though clearly
234 * an instance of an object only emits one or the other depending on
235 * where it is instantiated.
236 */
237
2381function ModuleInitEventEmitter(moduleName, options) {
239
2409 events.EventEmitter.call(this);
241
2429 var self = this;
243
2449 self.options = options || {};
2459 this.moduleName = moduleName;
246
247 // Set the max listeners
2489 var maxListeners = self.options.maxListeners || 100;
2499 this.setMaxListeners(maxListeners);
250
2519 this.init_start = function(options) {
2528 self.emit(exports.INIT_START, self.moduleName, options);
253 };
254
2559 this.init_finish = function(options) {
2568 self.emit(exports.INIT_FINISH, self.moduleName, options);
257 };
258
259}
260
261
262/**
263 * Event listener linked to the module itself
264 * This is for server events (e.g. init, reload)
265 * No events here can sit within the request context as
266 * they will apply to all requests
267 */
268
2691function addModuleEventListener(module, options) {
270
2719 options = options || {};
272
2739 var moduleEventEmitter = module.event = new ModuleInitEventEmitter(module.name, options),
274 notifyDependencyFn = options.notifyDependencyFn || function() {};
275
276 // Link events
2779 moduleEventEmitter.once(exports.INIT_START, function(moduleName, options) {
278 // Do nothing
279 });
280
2819 moduleEventEmitter.once(exports.INIT_FINISH, function(moduleName, options) {
282 // Check for dependent modules, init them
2838 notifyDependencyFn(moduleName, options);
284 });
285
286}
287
288/**
289 * Module event emitter, object that enables modules to emit events.
290 * This contains both server and request scope event emitters, though clearly
291 * an instance of an object only emits one or the other depending on
292 * where it is instantiated.
293 */
2941function ModuleRequestEventEmitter(moduleName, options) {
295
29617 events.EventEmitter.call(this);
297
298 // Refresh the require
29917 var self = this;
30017 self.options = options || {};
30117 self.moduleName = moduleName;
302
303 // Set the max listeners
30417 var maxListeners = self.options.maxListeners || 100;
30517 this.setMaxListeners(maxListeners);
306
30717 this.route_start = function(options) {
3089 self.emit(exports.ROUTE_START, self.moduleName, options);
309 };
310
31117 this.route_finish = function(options) {
3129 self.emit(exports.ROUTE_FINISH, self.moduleName, options);
313 };
314
315}
316
317/**
318 * Event listener linked to the request object
319 * This is the object that will listen to each module event emitter
320 * and call other modules or perform other defined functions
321 */
322
3231function RequestEventListener(options) {
324
3255 options = options || {};
326
327 // Register a module, listen to its events
3285 var self = this,
329 notifyDependencyFn = options.notifyDependencyFn || function() {},
330 registerDependenciesFn = options.registerDependenciesFn || function() {};
331
332 // Local hash of module event emitters, used to track routing status
3335 this.modules = {};
334
335 // Register a module
3365 this.registerModule = function(req, res, moduleName, options) {
337
338 // Register event emitter
33917 var moduleEventEmitter = self.modules[moduleName] = new ModuleRequestEventEmitter(moduleName, options);
340
341 // Configure event listener
34217 self.modules[moduleName].routed = false; // Is it done
34317 self.modules[moduleName].check = {}; // Hash of dependent modules to check if initialised
344
345 // Curried function to notify dependent modules that we have finished
34617 var notifyDependencies = function(moduleName) {
3479 notifyDependencyFn(req, res, moduleName, self.modules);
348 };
349
35017 registerDependenciesFn(self, moduleName);
351
352 // Start
35317 moduleEventEmitter.once(exports.ROUTE_START, function(moduleName, options) {
3549 self.modules[moduleName].start = new Date();
355 });
356
357 // Finish
35817 moduleEventEmitter.once(exports.ROUTE_FINISH, function(moduleName, options) {
359
3609 self.modules[moduleName].finish = new Date();
3619 self.modules[moduleName].duration = self.modules[moduleName].finish - self.modules[moduleName].start;
3629 self.modules[moduleName].routed = true;
363
364 // Callback to Calipso to notify dependent objects of route
365 // calipso.notifyDependenciesOfRoute(req, res, moduleName, self.modules);
3669 notifyDependencies(moduleName);
367
368 });
369
370 };
371
372}
373
374/**
375 * Inherits
376 */
3771util.inherits(ModuleInitEventEmitter, events.EventEmitter);
3781util.inherits(ModuleRequestEventEmitter, events.EventEmitter);
379
380
381/**
382 * Helper functions TODO CONSOLIDATE!
383 */
384
3851function mergeArray(arr, first) {
3864 var output = {};
3874 arr.forEach(function(value, key) {
3888 if (first) {
3890 output = merge(value, output);
390 } else {
3918 output = merge(output, value);
392 }
393 });
3944 return output;
395}
396
3971function merge(a, b) {
3988 if (a && b) {
3998 for (var key in b) {
40016 a[key] = b[key];
401 }
402 }
4038 return a;
404}

core/Form.js

13%
302
40
262
LineHitsSource
1/*!a
2 * Calipso Form Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * Core form generation module.
8 *
9 * This is loaded by calipso as a plugin, so can be replaced by modules.
10 * Module must expose a single object as below, with a single
11 * function that is called by other modules to generate form markup.
12 *
13 * This is most likely a synchronous call, but has been written asynch just
14 * in case a module author wants to make an asynch version (for some reason!).
15 *
16 * TODO: validation, redisplay of submitted values
17 *
18 */
19
20
211var rootpath = process.cwd() + '/',
22 path = require('path'),
23 calipso = require(path.join('..', 'calipso')),
24 qs = require('qs'),
25 merge = require('connect').utils.merge;
26
27// Global variable (in this context) for translation function
281var t;
29
30/**
31 * The default calipso form object, with default configuration values.
32 * Constructor
33 */
341function Form() {
35
36 // TODO - tagStyle should also affect whether attributes can be minimised ('selected' vs. 'selected="selected"')
37
38 // tagStyle should be one of [html, xhtml, xml]
391 this.tagStyle = "html";
40
41 // adjust the way tags are closed based on the given tagStyle.
421 this.tagClose = this.tagStyle == "html" ? '>' : ' />';
43
44 // cheap way of ensuring unique radio ids
451 this.radioCount = 0;
46
47}
48
491var f = new Form();
50
511var me = Form.prototype;
52
53// instead of referring to the singleton (`f`), we could implement a function
54// that would give us the current instance, for a more sure `this`
55// but it would be a little bit slower, due to the function call
56//me.getInstance = function(){
57// return this;
58//};
59//me.getContext = function(){
60// return this;
61//};
62
63/* just an idea.
64function getAttributeString(el){
65 var validAttrs = ['type','name','id','class','value','disabled'];
66 var output = '';
67 validAttrs.forEach(function(i, attrName){
68 if(el[attrName]){
69 output += ' ' + attrName + '="' + el.attr[attrName] + '"';
70 }
71 });
72 return output;
73}
74*/
75
76// if complete for every country, this will be a lot of data and should
77// probably be broken out to a separate file.
781me.countries = [
79 "Afghanistan", "Albania", "Algeria", "Andorra", "Angola",
80 "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria",
81 "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus",
82 "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina",
83 "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi",
84 "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic",
85 "Chad", "Chile", "China (People's Republic of China)", "Colombia", "Comoros",
86 "Democratic Republic of the Congo", "Republic of the Congo",
87 "Costa Rica", "Côte d'Ivoire", "Croatia", "Cuba", "Cyprus",
88 "Czech Republic", "Denmark, the Kingdom of", "Djibouti", "Dominica",
89 "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador",
90 "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Fiji",
91 "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana",
92 "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana",
93 "Haiti", "Honduras", "Hungary", "Iceland", "India", "Indonesia", "Iran",
94 "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan",
95 "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea",
96 "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon",
97 "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
98 "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali",
99 "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico",
100 "Federated States of Micronesia", "Moldova", "Monaco", "Mongolia",
101 "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru",
102 "Nepal", "Netherlands, the Kingdom of", "New Zealand", "Nicaragua", "Niger",
103 "Nigeria", "Norway", "Oman", "Pakistan", "Palau", "Palestinian territories",
104 "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland",
105 "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis",
106 "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino",
107 "São Tomé and Príncipe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles",
108 "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
109 "Somalia", "South Africa", "South Sudan", "Spain", "Sri Lanka", "Sudan",
110 "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria",
111 "Taiwan (Republic of China)", "Tajikistan", "Tanzania", "Thailand", "Togo",
112 "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan",
113 "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom",
114 "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City",
115 "Venezuela", "Vietnam", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"
116];
117
1181me.states = {
119 "United States": {
120 AL:"Alabama",
121 AK:"Alaska",
122 AZ:"Arizona",
123 AR:"Arkansas",
124 CA:"California",
125 CO:"Colorado",
126 CT:"Connecticut",
127 DE:"Delaware",
128 DC:"District Of Columbia",
129 FL:"Florida",
130 GA:"Georgia",
131 HI:"Hawaii",
132 ID:"Idaho",
133 IL:"Illinois",
134 IN:"Indiana",
135 IA:"Iowa",
136 KS:"Kansas",
137 KY:"Kentucky",
138 LA:"Louisiana",
139 ME:"Maine",
140 MD:"Maryland",
141 MA:"Massachusetts",
142 MI:"Michigan",
143 MN:"Minnesota",
144 MS:"Mississippi",
145 MO:"Missouri",
146 MT:"Montana",
147 NE:"Nebraska",
148 NV:"Nevada",
149 NH:"New Hampshire",
150 NJ:"New Jersey",
151 NM:"New Mexico",
152 NY:"New York",
153 NC:"North Carolina",
154 ND:"North Dakota",
155 OH:"Ohio",
156 OK:"Oklahoma",
157 OR:"Oregon",
158 PA:"Pennsylvania",
159 RI:"Rhode Island",
160 SC:"South Carolina",
161 SD:"South Dakota",
162 TN:"Tennessee",
163 TX:"Texas",
164 UT:"Utah",
165 VT:"Vermont",
166 VA:"Virginia",
167 WA:"Washington",
168 WV:"West Virginia",
169 WI:"Wisconsin",
170 WY:"Wyoming"
171 }
172};
173
174
175/**
176 * Functions for each tag type, these are now exposed directly on the object
177 * so that they can be redefined within modules (e.g. in a module that provides
178 * a rich text editor), or a module can add new types specific to that module.
179 *
180 * Current field types available are:
181 *
182 * text : default text field (used if no function matches field type)
183 * textarea : default textarea, can set rows in form definition to control rows in a textarea field item.
184 * hidden : hidden field
185 * select : single select box, requires values to be set (as array or function)
186 * submit : submit button
187 * button : general button
188 * date : date input control (very rudimentary)
189 * time : time input controls
190 * datetime : combination of date and time controls
191 * crontime : crontime editor (6 small text boxes)
192 * password : password field
193 * checkbox : checkbox field
194 * radio : radio button
195 * file : file field
196 *
197**/
198
1991me.defaultTagRenderer = function(field, value, bare){
2000 var isCheckable = field.type == 'radio' || field.type == 'checkbox';
2010 var checked = field.checked || (isCheckable && value && (field.value == value || value===true));
202
203 //console.log('... field: ', field, value);
2040 var tagOutput = "";
205
2060 if(field.type == 'checkbox' && !field.readonly && !field.disabled){
207 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value
2080 tagOutput += '<input type="hidden" name="' + field.name + '" value="false" />';
209 }
210
2110 tagOutput += '<input type="' + field.type + '"'
212 + ' class="'+ field.type + (field.cls ? ' ' + field.cls : "") + (field.labelFirst ? ' labelFirst' : '') + '"'
213 + ' name="' + field.name + '"'
214 + (field.href ? ' onClick=\'window.location="' + field.href + '"\';' : '')
215 + ' id="' + (field.id ? field.id : field.name + (field.type=='radio' ? (++f.radioCount) : '')) + '"'
216 + (field.src ? ' src="' + field.src + '"' : '') // for input type=image .. which should be avoided anyway.
217 + (field.multiple ? ' multiple="' + field.multiple + '"' : '') // for input type=file
218 + (field.directory ? ' mozdirectory webkitdirectory directory' : '') //for input type=file
219 + ' value="' + calipso.utils.escapeHtmlQuotes(value || field.value || (isCheckable && 'on') || '') + '"'
220 + (field.readonly || field.disabled ? ' disabled' : '')
221 + (checked ? ' checked' : '')
222 + f.tagClose;
2230 if(field.readonly || field.disabled){
224 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value
2250 tagOutput += '<input type="hidden" name="' + field.name + '" value="' + (checked ? 'true' : 'false') + '" />';
226 }
227
2280 return bare ? tagOutput : me.decorateField(field, tagOutput);
229
230};
231
2321me.decorateField = function(field, tagHTML){
2330 calipso.silly('FORM: decorateField, field: ', field);
2340 var isCheckable = !field.labelFirst && (field.type == "checkbox" || field.type == "radio");
2350 var labelHTML = field.label ? (
236 '<label' + (isCheckable ? ' class="for-checkable"' : '')
237 + ' for="' + field.name + (field.type == 'radio' ? f.radioCount : '')
238 + '">' + t(field.label) + (isCheckable ? '' : ':') + '</label>'
239 ) : '';
240
2410 var wrapperId = (
242 field.name.replace(/\[/g, '_').replace(/\]/g, '')
243 + (field.type == 'radio' ? '-radio' + me.radioCount : '')
244 );
245
2460 return field.label && field.label.length > 0 ? (
247 '<div class="form-item field-type-' + field.type + '" id="' + wrapperId + '-wrapper">' +
248 '<div class="form-field">' +
249 // put checkboxes and radios ("checkables") before their labels, unless field.labelFirst is true
250 (isCheckable ? tagHTML + labelHTML : labelHTML + tagHTML) +
251 '</div>' +
252 (field.description ? '<span class="description ' + field.type + '-description">' + t(field.description) + '</span>' : '') +
253 '</div>'
254 ) : tagHTML;
255};
256
257// if there is no `canContain` or `cannotContain`, then the element is not a container.
258// the following psuedofunction should suffice:
259// x.canContain(y) =
260// ((x.canContain && y in x.canContain) || (x.cannotContain && !(y in x.cannotContain)))
261// && (!y.canBeContainedBy || x in y.canBeContainedBy)
2621me.elementTypes = {
263
264 'page': {
265 cannotContain: ['page'],
266 render: function(el){}
267 },
268
269 'section': {
270 cannotContain: ['page'],
271 isTab : false,
272 render: function(el, values, isTabs){
2730 return (
274 '<section' + (el.isTab || isTabs ? ' class="tab-content"':'') + ' id="' + el.id + '">' +
275 (el.label ? '<h3>' + t(el.label) + '</h3>' : '') +
276 (el.description ? '<p>' + el.description + '</p>' : '') +
277 '<div class="section-fields">' +
278 me.render_fields(el, values) +
279 '</div>' +
280 '</section>'
281 );
282 }
283 },
284
285 // todo: allow for pre-rendered markup for the description, or other renderers (such as markdown)
286 'fieldset': {
287 cannotContain: ['section', 'page'],
288 render: function(el, values){
2890 if(!el.label) el.label = el.legend;
2900 return (
291 '<fieldset class="' + (el.type != 'fieldset' ? el.type + '-fieldset' : 'fieldset') + '">' +
292 // <legend> is preferable, but legends are not fully stylable, so 'label' = <h4>
293 (el.label ? '<h4>' + t(el.label) + '</h4>' : '') +
294 (el.description ? '<p>' + el.description + '</p>' : '') +
295 '<div class="fieldset-fields">' +
296 me.render_fields(el, values) +
297 '</div>' +
298 '</fieldset>'
299 );
300 }
301 },
302
303 // special .. might also be used as a container (i.e., depending on what radio is active, elements 'under' it are active?)
304 // special - have to share ids .. are part of a set - TODO - allow for more than one radio group (already done?)
305 'radios': { // it's a container because radios must belong to a 'set' .. also, sometimes a form uses radios kindof like tabs....
306 canContain: ['option'],
307 render: function(field, values){
3080 return me.elementTypes.fieldset.render(field, values);
309 }
310 },
311
312 // special .. might also be used as a container (i.e., depending on whether a checkbox is checked, elements 'under' it are active?)
313 'checkboxes': {
314 canContain: ['option'],
315 render: function(field, values){
3160 return me.elementTypes.fieldset.render(field, values);
317 }
318 },
319
320 'select': { // it's a container because it contains options
321 canContain: ['options','optgroup'],
322 render: function(field, value){
323
3240 var tagOutput = '<select'
325 + ' class="select ' + (field.cls ? field.cls : "") + '"'
326 + ' name="' + field.name + '"'
327 + ' id="' + field.name + '"'
328 + (field.multiple ? ' multiple="multiple"' : '')
329 + '>';
330
3310 var options = typeof field.options === 'function' ? field.options() : field.options;
332
3330 if(field.optgroups){
3340 field.optgroups.forEach(function(optgroup){
3350 tagOutput += '<optgroup label="' + optgroup.label + '">';
3360 optgroup.options.forEach(function(option){
3370 tagOutput += me.elementTypes.option.render(option, value, 'select');
338 });
3390 tagOutput += '</optgroup>';
340 });
341 } else {
3420 options.forEach(function(option){
3430 tagOutput += me.elementTypes.option.render(option, value, 'select');
344 });
345 }
3460 tagOutput += '</select>';
347
3480 return me.decorateField(field, tagOutput);
349 }
350 },
351
352 'optgroup': {
353 canBeContainedBy: ['select'],
354 canContain: ['option']
355 },
356
357 'options': {
358 canBeContainedBy: ['select'],
359 canContain: ['option']
360 },
361
362 // an "option" can be an <option> or a radio or a checkbox.
363 'option': {
364 canBeContainedBy: ['radios','checkboxes','select','optgroup'],
365 // container determines render method.
366 render: function(option, value, containerType){
3670 if(containerType == 'select'){
3680 var displayText = option.label || option;
3690 var optionValue = option.value || option;
3700 return (
371 '<option'
372 + ' value="' + optionValue + '"'
373 + (value === optionValue ? ' selected' : '')
374 + (option.cls ? ' class="' + option.cls + '"' : '')
375 + '>'
376 + displayText
377 + '</option>'
378 );
379 } else {
3800 return me.defaultTagRenderer(option, value);
381 }
382 }
383 },
384
385 // type: 'radio' should become type: option, and be in a {type: radios}
386 'radio': {
387 render: me.defaultTagRenderer
388 },
389
390 // type: 'checkbox' should become type: option, and be in a {type: checkboxes}
391 'checkbox': {
392 render: function(field, value, bare) {
393
394 // Quickly flip values to true/false if on/off
3950 value = (value === "on" ? true : (value === "off" ? false : value));
396
397 // Now set the checked variable
3980 var checked = (value ? true : (field.value ? true : (field.checked ? true : false)));
399
4000 var tagOutput = "";
401
4020 if(!field.readonly && !field.disabled){
403 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value
4040 tagOutput += '<input type="hidden" name="' + field.name + '" value="off" />';
405 }
406
4070 tagOutput += '<input type="' + field.type + '"'
408 + ' class="'+ field.type + (field.cls ? ' ' + field.cls : "") + (field.labelFirst ? ' labelFirst' : '') + '"'
409 + ' name="' + field.name + '"'
410 + ' id="' + field.name + '"'
411 + (field.readonly || field.disabled ? ' disabled' : '')
412 + (checked ? ' checked' : '')
413 + f.tagClose;
414
4150 if(field.readonly || field.disabled){
416 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value
4170 tagOutput += '<input type="hidden" name="' + field.name + '" value="' + (checked ? "on" : "off") + '" />';
418 }
4190 return bare ? tagOutput : me.decorateField(field, tagOutput);
420 }
421 },
422
423 'text': {
424 render: me.defaultTagRenderer
425 },
426
427 'textarea': {
428 render: function(field, value){
4290 return me.decorateField(field, '<textarea'
430 + ' class="textarea ' + (field.cls ? field.cls : "") + '"'
431 + ' rows="' + (field.rows ? field.rows : "10") + '"'
432 + ' name="' + field.name + '"'
433 + ' id="' + field.name + '"'
434 + (field.required ? ' required' : '')
435 + '>'
436 + value
437 + '</textarea>');
438 }
439 },
440
441 'hidden': {
442 render: me.defaultTagRenderer
443 },
444
445 'password': { // can be special, if there is a 'verify'
446 render: me.defaultTagRenderer
447 },
448
449 // might allow file to take a url
450 'file': {
451 render: me.defaultTagRenderer
452 },
453
454 'buttons': {
455 canContain: ['submit','image','reset','button','link']
456 },
457
458 // buttons should only be able to be added to the 'action set'
459 // button can be [submit, reset, cancel (a link), button (generic), link (generic)]
460 // if form has pages, 'previous' and 'next' buttons should interpolate until the last page, 'submit'
461 'button': {
462 render: me.defaultTagRenderer
463 },
464
465 'submit': {
466 render: me.defaultTagRenderer
467 },
468
469 'image': {
470 render: me.defaultTagRenderer
471 },
472
473 'reset': {
474 render: me.defaultTagRenderer
475 },
476
477 // a link is not really a form control, but is provided here for convenience
478 // it also doesn't really make sense for it to have a value.
479 // a link should have an href and text, and optionally, cls ('class'), id
480 'link': {
481 render: function(field, value){
4820 var id = field.id || field.name;
4830 var text = field.text || field.value;
4840 return '<a href="' + field.href + '"'
485 + ' class="form-link' + (field.cls ? ' ' + field.cls : "") + '"'
486 + (id ? ' id="' + id + '"' : '')
487 + '>' + text + '</a>';
488 }
489 },
490
491 'date': {
492 render: function(field, value, bare){
493
4940 if(!value) {
4950 value = new Date();
496 }
497
498 // TODO - use user's Locale
4990 var monthNames = calipso.date.regional[''].monthNamesShort;
500
5010 var tagOutput = '<input type="text"'
502 + ' class="date date-day' + (field.cls ? ' date-day-'+field.cls : '') + '"'
503 + ' name="' + field.name + '[day]"'
504 + ' value="' + value.getDate() + '"'
505 + (field.required ? ' required' : '')
506 + f.tagClose;
507
5080 tagOutput += ' ';
509
5100 tagOutput += '<select class="date date-month' + (field.cls ? ' date-month-'+field.cls : '') + '"'
511 + (field.required ? ' required' : '')
512 + ' name="' + field.name + '[month]">';
5130 for(var monthNameCounter=0; monthNameCounter<12; monthNameCounter++) {
5140 tagOutput += (
515 '<option value="'+monthNameCounter+'"' + (value.getMonth() === monthNameCounter ? ' selected' : '') + '>'
516 + monthNames[monthNameCounter]
517 + '</option>'
518 );
519 }
5200 tagOutput += '</select>';
521
5220 tagOutput += ' ';
523
5240 tagOutput += '<input type="text"'
525 + ' class="date date-year' + (field.cls ? ' date-year-'+field.cls : '') + '"'
526 + ' name="' + field.name + '[year]"'
527 + ' value="' + value.getFullYear() + '"'
528 + (field.required ? ' required' : '')
529 + f.tagClose;
530
5310 return bare ? tagOutput : me.decorateField(field, tagOutput);
532 }
533 },
534
535 'time': {
536 render: function(field, value, bare) {
537
538 // TODO
5390 if(!value) {
5400 value = new Date(); // why 1900? why not 'now'?
541 }
542
5430 var tagOutput = '<input type="text" class="time time-hours' + (field.cls ? ' time-hours-'+field.cls : '') + '"'
544 + ' name="' + field.name + '[hours]"'
545 + ' value="' + value.getHours() + '"'
546 + (field.required ? ' required' : '')
547 + f.tagClose;
548
5490 tagOutput += ' ';
550
5510 tagOutput += '<input type="text" class="time time-minutes' + (field.cls ? ' time-minutes-'+field.cls : '') + '"'
552 + ' name="' + field.name + '[minutes]"'
553 + ' value="' + value.getMinutes() + '"'
554 + (field.required ? ' required' : '')
555 + f.tagClose;
556
5570 return bare ? tagOutput : me.decorateField(field, tagOutput);
558
559 }
560 },
561
562 'datetime': {
563 render: function(field, value) {
564 // Call both types
5650 return me.decorateField(field,
566 me.elementTypes.date.render({
567 name: field.name,
568 type: "date",
569 required: field.required
570 }, value, true) +
571 ' ' +
572 me.elementTypes.time.render({
573 name: field.name,
574 type: "time",
575 required: field.required
576 }, value, true)
577 );
578 }
579 },
580
581 'crontime': {
582 render: function(field, value) {
5830 var tagOutput = '';
5840 var cronTimeValues = value ? value.split(/\s/) : ['*','*','*','*','*','*'];
5850 for(var cronTimeInputCounter = 0; cronTimeInputCounter < 6; cronTimeInputCounter++) {
5860 tagOutput += (
587 '<input type="text" class="text crontime" value="' +
588 cronTimeValues[cronTimeInputCounter] +
589 '" name="job[cronTime' + cronTimeInputCounter + ']"' +
590 (field.required ? ' required' : '') +
591 f.tagClose
592 );
593 }
5940 return me.decorateField(field, tagOutput);
595 }
596 }
597
598};
599
600// any element types that reference other element types have to be declared afterward
601// so that the references exist.
6021me.elementTypes.richtext = {
603 render: me.elementTypes.textarea.render
604};
605
6061me.elementTypes.json = {
607 render: me.elementTypes.textarea.render
608};
609
6101me.elementTypes.email = {
611 render: function(field, value){
612 //var _field = copyProperties(field, {});
613 //_field.type = 'text';
6140 field.type = 'text';
6150 field.cls = (field.cls ? field.cls + ' ' : '') + 'email';
6160 me.elementTypes.textarea.render(field, value);
617 }
618};
619
6201me.elementTypes.address = {
621 render: me.elementTypes.fieldset.render,
622 defaultDefinition: {
623 tag: 'fieldset',
624 type: 'address',
625 children: [
626 {type: 'text', name: 'street', label: 'Street Address'},
627 {type: 'select', name: 'country', label: 'Country', options: me.countries},
628 {type: 'select', name: 'state', label: 'State', options: me.states["United States"]},
629 {type: 'text', name: 'postalcode', label: 'Postal Code'}
630 ]
631 }
632};
633
634
635/**
636 * Form Renderer, controls the overall creation of the form based on a form json object passed
637 * in as the first parameter. The structure of this object is as follows:
638 *
639 * form
640 * id : Unique ID that will become the form ID.
641 * title : Title to show at the top of the form.
642 * type : Type of form (not used at present).
643 * method: HTTP method to use.
644 * action : URL to submit form to.
645 * tabs : Should tabs be rendered for sections (default false).
646 * sections [*] : Optional - divide the form into sections.
647 * id : Unique identifier for a section.
648 * label : Description of section (appears as header or tab label)
649 * fields [*] : Array of fields in the section (see below).
650 * fields [*] : Form fields array - can be in form or section.
651 * label : Label for form field.
652 * name : Name of form element to be passed back with the value.
653 * type : Type of element, based on the form functions defined below.
654 * description : Description text to be rendered after the element in a div tag.
655 * buttons [*] : Array of buttons to be rendered at the bottom of the form.
656 * name : Name of button (for submission).
657 * type : Type of button.
658 * value : Value to submit when pressed.
659 *
660 * A complete example is shown below:
661 *
662 * var myForm = {id:'my-form',title:'Create My Thing...',type:'form',method:'POST',action:'/myaction',tabs:false,
663 * sections:[{
664 * id:'myform-section-1',
665 * label:'Section 1',
666 * fields:[
667 * {label:'Field A',name:'object[fieldA]',type:'text',description:'Description ... '},
668 * {label:'Field B',name:'object[fieldB]',type:'textarea',description:'Description ...'}
669 * ]
670 * },{
671 * id:'myform-section2',
672 * label:'Section 2',
673 * fields:[
674 * {label:'Select Field',name:'object[select]',type:'select',options:["option 1","option 2"],description:'Description...'},
675 * {label:'Date Field',name:'object[date]',type:'datetime',description:'Description...'},
676 * ]
677 * }
678 * ],
679 * fields:[
680 * {label:'',name:'hiddenField',type:'hidden'}
681 * ],
682 * buttons:[
683 * {name:'submit',type:'submit',value:'Save'}
684 * ]};
685 *
686 * The values of the form are passed through (optionally) as the second parameter. This allows you to re-use
687 * a form definition across different uses (e.g. CRU).
688 *
689 * @param item : the json object representing the form
690 * @param values : The values to initialise the form with
691 * @param next : Callback when done, pass markup as return val (TODO : deprecate this, then can use form.render in views)
692 */
6931me.render = function(formJson, values, req, next) {
694
6950 var self = this;
696
697 // Store local reference to the request for use during translation
6980 t = req.t;
699
700 // Emit a form pre-render event.
7010 calipso.e.custom_emit('FORM', formJson.id, formJson, function(formJson) {
702
7030 var form = (
704 self.start_form(formJson) +
705 self.render_sections(formJson, values) + // todo: deprecate - sections should be treated the same as any other field (container)
706 self.render_fields(formJson, values) +
707 self.render_buttons(formJson.buttons) + // todo: deprecate - buttons should be treated the same as any other field (container)
708 self.end_form(formJson)
709 );
710
711 // Save the form object in session, this enables us to use it later
712 // To parse the incoming data and validate against.
713 // Saving it in the session allows for per-user customisation of the form without
714 // impacting this code.
7150 saveFormInSession(formJson, req, function(err) {
7160 if(err) calipso.error(err.message);
7170 next(form);
718 })
719
720 });
721
722};
723
724/**
725 * Helper to save a form in the session to be used later when processing.
726 */
7271function saveFormInSession(form, req, next) {
728
729 // If we have a form id and a session, save it
7300 if(form.id && calipso.lib._.keys(req.session).length > 0) {
7310 calipso.silly("Saving form " + form.id + " in session.");
7320 req.session.forms = req.session.forms || {};
7330 req.session.forms[form.id] = form;
7340 req.session.save(next);
735 } else {
7360 next();
737 }
738}
739
740/**
741 * Deal with form tabs in jQuery UI style if required.
742 */
7431me.formTabs = function(sections) {
744
7450 if(!sections)
7460 return '';
747
7480 var tabOutput = '<nav><ul class="tabs">',
749 numSections = sections.length;
750
7510 sections.forEach( function(section, index) {
7520 var classes = 'form-tab';
7530 if (index === 0) {
7540 classes += ' first';
755 }
7560 if ((index + 1) === numSections) {
7570 classes += ' last';
758 }
7590 tabOutput += '<li class="' + classes + '"><a href="#' + section.id + '">' + t(section.label) + '</a></li>';
760 });
7610 return tabOutput + '</ul></nav>';
762
763};
764
765
766/**
767 * Render the initial form tag
768 *
769 * @param form
770 * @returns {String}
771 */
7721me.start_form = function(form) {
7730 return (
774 '<form id="' + form.id + '" name="' + form.id + '"' + (form.cls ? ' class="' + form.cls + '"' : "") +
775 ' method="' + form.method + '"' + ' enctype="' + (form.enctype ? form.enctype : "multipart/form-data") + '"' + ' action="' + form.action + '">' +
776 '<input type="hidden" value="' + form.id + '" name="form[id]"/>' +
777 '<header class="form-header">' +
778 '<h2>' + t(form.title) + '</h2>' +
779 '</header>' +
780 '<div class="form-container">' +
781 (form.tabs ? this.formTabs(form.sections) : '') +
782 '<div class="form-fields'+(form.tabs ? ' tab-container' : '')+'">'
783 );
784};
785
786/**
787 * Close the form
788 * @param form
789 * @returns {String}
790 */
7911me.end_form = function(form) {
7920 return '</div></div></form>';
793};
794
795
796
797/**
798 * Render the form sections, iterating through and then rendering
799 * each of the fields within a section.
800 */
8011me.render_sections = function(form, values) {
802
8030 var self = this;
8040 var sections = form.sections;
805
8060 if(!sections)
8070 return '';
808
8090 var sectionOutput = '';
810
8110 sections.forEach(function(section) {
8120 sectionOutput += (
813 '<section' + (form.tabs?' class="tab-content"':'') + ' id="' + section.id + '">' +
814 '<h3>' + t(section.label) + '</h3>' +
815 self.render_fields(section, values) +
816 '</section>'
817 );
818 });
8190 return sectionOutput;
820
821};
822
823
824/**
825 * Render the buttons on a form
826 * @param buttons
827 * @returns {String}
828 */
8291me.render_buttons = function(buttons) {
830
8310 var self = this;
8320 var buttonsOutput = '<div class="actions">';
833
8340 buttons.forEach(function(field) {
8350 buttonsOutput += self.elementTypes[field.tag || field.type].render(field);
836 });
837
8380 buttonsOutput += '</div>';
839
8400 return buttonsOutput;
841};
842
843
844
845/**
846 * Render the fields on a form
847 * @param fields
848 * @returns {String}
849 */
8501me.render_fields = function(fieldContainer, values) {
851
8520 var fields = fieldContainer.fields || fieldContainer.children;
8530 var self = this;
8540 var fieldOutput = '';
855
8560 if(!fields) {
8570 return '';
858 }
859
8600 fields.forEach( function(field) {
861
8620 var value = '';
8630 var fieldName = field.name;
864
865 // If we have a field name, lookup the value
8660 if(fieldName) {
8670 value = getValueForField(fieldName, values);
868 }
869
870 // if the 'field' is really just a container, pass the values on down
871 // todo: consider adding a property 'isContainer'
8720 if(field.type == 'section' || field.type == 'fieldset'){
8730 value = values;
874 }
875
876 // field.tag was introduced to allow for <button type="submit"> (without tag:button, that would be <input type="submit">)
8770 if(self.elementTypes[field.tag || field.type]){
8780 fieldOutput += self.elementTypes[field.tag || field.type].render(field, value, fieldContainer.tabs); //self.render_field(field, value);
879 } else {
8800 calipso.warn('No renderer for ', field);
881 }
882
883 });
884
8850 return fieldOutput;
886};
887
888/**
889 * Get the value for a form field from the values object
890 * @param from
891 * @param to
892 */
8931function getValueForField(field, values) {
894
8950 if(!values) return '';
896
897 // First of all, split the field name into keys
8980 var path = []
8990 if(field.match(/.*\]$/)) {
9000 path = field.replace(/\]/g,"").split("[");
901 } else {
9020 path = field.split(':');
903 }
904
9050 while (path.length > 0) {
906
9070 key = path.shift();
908
9090 if (!(values && key in values)) {
9100 if(values && (typeof values.get === "function")) {
9110 values = values.get(key);
912 } else {
9130 if(values && values[field]) {
9140 return values[field];
915 } else {
9160 return '';
917 }
918 }
919 } else {
9200 values = values[key];
921 }
922
9230 if (path.length === 0) {
9240 return (values || '');
925 }
926 }
927
928}
929
930
931
932
933/**
934 * Get the value for a form field from the values object
935 * @param from
936 * @param to
937 */
9381function setValueForField(field, values, value) {
939
9400 if(!values) return '';
941
942 // First of all, split the field name into keys
9430 var path = []
9440 if(field.match(/.*\]$/)) {
9450 path = field.replace(/\]/g,"").split("[");
946 } else {
9470 path = [field];
948 }
949
950 //
951 // Scope into the object to get the appropriate nested context
952 //
9530 while (path.length > 1) {
9540 key = path.shift();
9550 if (!values[key] || typeof values[key] !== 'object') {
9560 values[key] = {};
957 }
9580 values = values[key];
959 }
960
961 // Set the specified value in the nested JSON structure
9620 key = path.shift();
9630 values[key] = value;
9640 return true;
965
966}
967
968/**
969 * Recursive copy of object
970 * @param from
971 * @param to
972 */
9731function copyFormToObject(field, value, target) {
974
975 // First of all, split the field name into keys
9760 var path = []
9770 if(field.match(/.*\]$/)) {
978
9790 path = field.replace(/\]/g,"").split("[");
980
981 // Now, copy over
9820 while (path.length > 1) {
9830 key = path.shift();
9840 if (!target[key]) {
9850 target[key] = {};
986 }
9870 target = target[key];
988 }
989
990 // Shift one more time and set the value
9910 key = path.shift();
9920 target[key] = value;
993
994 } else {
995
996 // We are probably an nconf form, hence just copy over
9970 target[field] = value;
998
999 }
1000
1001
1002}
1003
1004/**
1005 * Process a field / section array from a contentType
1006 * And modify the form
1007 */
10081me.processFields = function(form, fields) {
1009
1010 // Process fields
10110 if(fields.fields) {
10120 processFieldArray(form,fields.fields);
1013 }
1014
10150 if(fields.sections) {
10160 fields.sections.forEach(function(section,key) {
1017 // Process fields
10180 if(section.label) {
10190 form.sections.push(section);
1020 }
1021 // Remove it
10220 if(section.hide) {
10230 form = removeSection(form,section.id);
1024 }
1025 });
1026 }
1027
10280 return form;
1029
1030};
1031
1032
1033// Helper function to process fields
10341function processFieldArray(form, fields) {
1035
10360 fields.forEach(function(field, key) {
1037 // Add it
10380 if(field.type) {
10390 form.fields.push(field);
1040 }
1041 // Remove it
10420 if(field.hide) {
10430 form = removeField(form, field.name);
1044 }
1045 });
1046
1047}
1048
1049/**
1050 * Remove a field from a form (any section)
1051 */
10521function removeField(form, fieldName) {
1053
1054 // Scan sections
10550 form.sections.forEach(function(section, key) {
10560 scanFields(section.fields, fieldName);
1057 });
1058
1059 // Form fields
10600 scanFields(form.fields, fieldName);
1061
10620 return form;
1063
1064}
1065
1066// Helper function for removeField
10671function scanFields(fieldArray, fieldName) {
10680 fieldArray.forEach(function(field, key) {
10690 if(field.name === fieldName) {
10700 fieldArray = fieldArray.splice(key, 1);
1071 }
1072 });
1073}
1074
1075/**
1076 * Remove a section from a form
1077 */
10781function removeSection(form, sectionId) {
1079
1080 // Scan sections
10810 form.sections.forEach(function(section,key) {
10820 if(section.id === sectionId) {
10830 form.sections.splice(key,1);
1084 }
1085 });
1086
10870 return form;
1088
1089}
1090
1091
1092/**
1093 * Simple object mapper, used to copy over form values to schemas
1094 */
10951me.mapFields = function(fields, record) {
1096
10970 var props = Object.getOwnPropertyNames(fields);
10980 props.forEach( function(name) {
1099 // If not private (e.g. _id), then copy
11000 if(!name.match(/^_.*/)) {
11010 record.set(name, fields[name]);
1102 }
1103 });
1104
1105};
1106
1107/**
1108 * Process the values submitted by a form and return a JSON
1109 * object representation (makes it simpler to then process a form submission
1110 * from within a module.
1111 */
11121me.process = function(req, next) {
1113
1114 // Fix until all modules refactored to use formData
11150 if(req.formProcessed) {
1116
11170 next(req.formData, req.uploadedFiles);
11180 return;
1119
1120 } else {
1121
1122 // Data parsed based on original form structure
11230 processFormData(req, function(err, formData) {
1124
11250 if(err) calipso.error(err);
1126
11270 req.formData = formData;
11280 req.formProcessed = true;
1129
11300 return next(req.formData, req.files);
1131
1132 });
1133
1134 }
1135
1136};
1137
1138
1139/**
1140 * This process the incoming form, if the form exists in session then use that
1141 * to validate and convert incoming data against.
1142 */
11431function processFormData(req, next) {
1144
11450 if(calipso.lib._.keys(req.body).length === 0) {
1146 // No data
11470 return next();
1148 }
1149
1150 // Get the form id and then remove from the response
11510 var formId = req.body.form ? req.body.form.id : '';
11520 delete req.body.form;
1153
1154 // Get the form and then delete the form from the user session to clean up
11550 var form = req.session.forms ? req.session.forms[formId] : null;
11560 var formData = req.body;
1157
11580 if(formId && form) {
1159
11600 processSectionData(form, formData, function(err, formData) {
1161
11620 delete req.session.forms[formId];
1163
11640 req.session.save(function(err) {
11650 if(err) calipso.error(err.message);
1166 }); // Doesn't matter that this is async, can happen in background
1167
11680 return next(err, formData);
1169
1170 });
1171
1172 } else {
1173
1174 // No form in session, do not process
11750 next(null, formData);
1176
1177 }
1178
1179}
1180
1181/**
1182 * Process form sections and fields.
1183 */
11841function processSectionData(form, formData, next) {
1185
1186 // Create a single array of all the form and section fields
11870 var fields = [];
1188
11890 if(form.sections) {
11900 form.sections.forEach(function(section) {
1191 // Ensure section isn't null
11920 if(section) {
11930 fields.push(section.fields);
1194 }
1195 });
1196 }
11970 if(form.fields) {
11980 fields.push(form.fields);
1199 }
1200
12010 calipso.lib.async.map(fields, function(section, cb) {
12020 processFieldData(section, formData, cb);
1203 }, function(err, result) {
12040 next(err, formData);
1205 })
1206
1207}
1208
1209/**
1210 * Process form fields.
1211 */
12121function processFieldData(fields, formData, next) {
1213
1214 // First create an array of all the fields (and field sets) to allow us to do an async.map
12150 var formFields = [];
1216
12170 for(var fieldName in fields) {
1218 // It is a section that contains fields
12190 var field = fields[fieldName];
12200 if(field.fields) {
1221 // This is a field set
12220 for(var subFieldName in field.fields) {
12230 formFields.push(field.fields[subFieldName]);
1224 }
1225 } else {
1226 // Just push the field
12270 formFields.push(field)
1228 }
1229 }
1230
12310 var iteratorFn = function(field, cb) {
1232
12330 var value = getValueForField(field.name, formData);
12340 processFieldValue(field, value, function(err, processedValue) {
12350 if(value !== processedValue) setValueForField(field.name, formData, processedValue);
12360 cb(err, true);
1237 });
1238
1239 }
1240
12410 calipso.lib.async.map(formFields, iteratorFn, next);
1242
1243}
1244
1245/**
1246 * Process submitted values against the original form.
1247 * TODO: This is where we would bolt on any validation.
1248 */
12491function processFieldValue(field, value, next) {
1250
1251 // Process each field
12520 if(field.type === 'checkbox') {
1253
12540 if(typeof value === 'object') {
1255 // The value has come in as ['off','on'] or [false,true]
1256 // So we always take the last value
12570 value = value[value.length - 1];
1258 }
1259
1260 // Deal with on off
12610 if(value === 'on') value = true;
12620 if(value === 'off') value = false;
1263
1264 }
1265
12660 if(field.type === 'select') {
1267
1268 // Deal with Yes / No > Boolean
12690 if(value === 'Yes') value = true;
12700 if(value === 'No') value = false;
1271
1272 }
1273
12740 if(field.type === 'datetime') {
1275
12760 if(value.hasOwnProperty('date') && value.hasOwnProperty('time')) {
12770 value = new Date(
1278 value.date + " " + value.time
1279 );
1280 }
1281
12820 if(value.hasOwnProperty('date') && value.hasOwnProperty('hours')) {
12830 value = new Date(
1284 value.date + " " + value.hours + ":" + value.minutes + ":00"
1285 );
1286 }
1287
12880 if(value.hasOwnProperty('year') && value.hasOwnProperty('hours')) {
1289
12900 var now = new Date();
1291
12920 value = new Date(
1293 (value.year || now.getFullYear()),
1294 (value.month || now.getMonth()),
1295 (value.day || now.getDate()),
1296 (value.hours || now.getHours()),
1297 (value.minutes || now.getMinutes()),
1298 (value.seconds || now.getSeconds())
1299 );
1300
1301 }
1302
1303 }
1304
13050 return next(null, value);
1306
1307}
1308
1309/**
1310 * Export an instance of our form object
1311 */
13121module.exports = f;

core/Helpers.js

66%
71
47
24
LineHitsSource
1/*!
2 * Calipso Core Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * Dynamic helpers for insertion into the templating engine
8 * They all need to take in a req,res pair, these are then
9 * interpreted during the request stack and passed into the
10 * view engine (so for example 'request' is accessible).
11 */
12
13/**
14 * removes any trailing query string or hash values
15 * @method stripUrlToConvert
16 * @param url {string} The url to convert
17 * @return {String} Converted url, if applicable
18 */
19
201function stripUrlToConvert(url) {
218 var qs = url.search(/\?|#/);
228 if (qs > -1) {
230 url = url.substring(0, qs);
24 }
258 return url;
26}
27
28/**
29 * Exports
30 */
311exports = module.exports = {
32
33 // Attach view Helpers to the request
34 getDynamicHelpers: function(req, res, calipso) {
354 var self = this;
364 req.helpers = {};
374 for (var helper in self.helpers) {
3884 req.helpers[helper] = self.helpers[helper](req, res, calipso);
39 }
40 },
41
42 // Add a new helper (e.g. so modules can add them)
43 addHelper: function(name, fn) {
440 var self = this;
450 self.helpers[name] = fn;
46 },
47
48 helpers: {
49 /**
50 * Request shortcut
51 */
52 request: function(req, res, calipso) {
534 return req;
54 },
55
56 /**
57 * Config shortcut
58 */
59 config: function(req, res, calipso) {
604 return calipso.config;
61 },
62
63 /**
64 * Translation shortcut
65 */
66 t: function(req, res, calipso) {
674 return req.t;
68 },
69
70 /**
71 * User shortcut
72 */
73 user: function(req, res, calipso) {
744 return req.session && req.session.user || {
75 username: '',
76 anonymous: true
77 };
78 },
79
80 /**
81 * Pretty date helper
82 */
83 prettyDate: function(req, res, calipso) {
84
854 var prettyFn = calipso.lib.prettyDate.prettyDate;
864 return prettyFn;
87
88 },
89
90 /**
91 * Pretty size helper
92 */
93 prettySize: function(req, res, calipso) {
94
954 var prettyFn = calipso.lib.prettySize.prettySize;
964 return prettyFn;
97 },
98
99 /**
100 * Hot date helper
101 */
102 hotDate: function(req, res, calipso) {
103
1044 var hotFn = calipso.lib.prettyDate.hotDate;
1054 return hotFn;
106
107 },
108
109 /**
110 * Get block data not included preloaded in the theme configuration (in blockData)
111 */
112 getBlock: function(req, res, calipso) {
113
1144 return function(block, next) {
115
116 // TODO : Allow block to be passed as a regex (e.g. to include all scripts.* blocks)
1178 var output = "";
1188 res.renderedBlocks.get(block, function(err, blocks) {
119
1208 blocks.forEach(function(content) {
1211 output += content;
122 });
123
12416 if (typeof next === 'function') next(null, output);
125
126 });
127
128 };
129 },
130
131 /**
132 * Get a menu html, synchronous
133 */
134 getMenu: function(req, res, calipso) {
135
1364 return function(menu, depth) {
137 // Render menu
13812 if (res.menu[menu]) {
13912 var output = res.menu[menu].render(req, depth);
14012 return output;
141 } else {
1420 return 'Menu ' + menu + ' does not exist!';
143 }
144
145 };
146 },
147
148 /**
149 * Directly call an exposed module function (e.g. over ride routing rules and inject it anywhere)
150 */
151 getModuleFn: function(req, res, calipso) {
152
1534 return function(req, moduleFunction, options, next) {
154
155 // Call an exposed module function
156 // e.g. user.loginForm(req, res, template, block, next)
157 // First see if function exists
1580 var moduleName = moduleFunction.split(".")[0];
1590 var functionName = moduleFunction.split(".")[1];
160
1610 if (calipso.modules[moduleName] && calipso.modules[moduleName].enabled && calipso.modules[moduleName].fn[functionName]) {
162
1630 var fn = calipso.modules[moduleName].fn[functionName];
164
165 // Get the template
1660 var template;
1670 if (options.template && calipso.modules[moduleName].templates[options.template]) {
1680 template = calipso.modules[moduleName].templates[options.template];
169 }
170
171 // Call the fn
1720 try {
1730 fn(req, res, template, null, next);
174 } catch (ex) {
1750 next(ex);
176 }
177
178 } else {
1790 next(null, "<div class='error'>Function " + moduleFunction + " requested via getModuleFn does not exist or module is not enabled.</div>");
180 }
181
182 };
183
184 },
185
186 /**
187 * Retrieves the params parsed during module routing
188 */
189 getParams: function(req, res, calipso) {
1904 return function() {
1910 return res.params;
192 };
193 },
194
195 /**
196 * Constructs individual classes based on the url request
197 */
198 getPageClasses: function(req, res, calipso) {
1994 var url = stripUrlToConvert(req.url);
2004 return url.split('/').join(' ');
201 },
202
203 /**
204 * Constructs a single id based on the url request
205 */
206 getPageId: function(req, res, calipso) {
2074 var url = stripUrlToConvert(req.url),
208 urlFrags = url.split('/');
2094 for (var i = 0, len = urlFrags.length; i < len; i++) {
2108 var frag = urlFrags[i];
2118 if (frag === '') {
2124 urlFrags.splice(i, 1);
213 }
214 }
2154 return urlFrags.join('-');
216 },
217
218
219 addScript: function(req, res, calipso) {
2204 return function(options) {
2210 res.client.addScript(options);
222 };
223 },
224
225 getScripts: function(req, res, calipso) {
2264 return function(next) {
2270 res.client.listScripts(next);
228 };
229 },
230
231
232 addStyle: function(req, res, calipso) {
2334 return function(options) {
2340 res.client.addStyle(options);
235 };
236 },
237
238 getStyles: function(req, res, calipso) {
2394 return function(next) {
2400 res.client.listStyles(next);
241 };
242 },
243
244 /**
245 * Flash message helpers
246 */
247 flashMessages: function(req, res, calipso) {
2484 return function() {
2490 return req.flash();
250 };
251 },
252
253 /**
254 * HTML helpers - form (formApi), table, link (for now)
255 */
256 formApi: function(req, res, calipso) {
2574 return function(form) {
2580 return calipso.form.render(form);
259 };
260 },
261 table: function(req, res, calipso) {
2624 return function(table) {
2630 return calipso.table.render(table);
264 };
265 },
266 link: function(req, res, calipso) {
2674 return function(link) {
2680 return calipso.link.render(link);
269 };
270 }
271 }
272
273};

core/Lib.js

100%
2
2
0
LineHitsSource
1/*!
2 * Calipso Imports
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This library is used to allow a single place to add new 3rd party libraries or utilities that
8 * are then automatically accessible via calipso.lib.library in any module.
9 *
10 */
111var rootpath = process.cwd() + '/';
12
131module.exports = {
14 fs: require('fs'),
15 path: require('path'),
16 express: require('express'),
17 step: require('step'),
18 util: require('util'),
19 mongoose: require('mongoose'),
20 url: require('url'),
21 ejs: require('ejs'),
22 pager: require(rootpath + 'utils/pager'),
23 prettyDate: require(rootpath + 'utils/prettyDate.js'),
24 prettySize: require(rootpath + 'utils/prettySize.js'),
25 crypto: require(rootpath + 'utils/crypto.js'),
26 connect: require('connect'),
27 _: require('underscore'),
28 async: require('async')
29};

core/Link.js

50%
12
6
6
LineHitsSource
1/*!
2 *
3 * Calipso Link Rendering Library
4 *
5 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
6 * MIT Licensed
7 *
8 * Loaded into calipso as a plugin, used to simplify rendering of links
9 *
10 */
11
121var rootpath = process.cwd() + '/',
13 path = require('path'),
14 calipso = require(path.join('..', 'calipso')),
15 qs = require('qs');
16
17// Global variable (in this context) for translation function
181var t;
19
20/**
21 * The default calipso link object, with default configuration values.
22 * Constructor
23 */
24
251function CalipsoLink() {
26
27 //TODO Allow over-ride
28}
29
30/**
31 * Export an instance of our link object
32 */
331module.exports = new CalipsoLink();
34
35
36/**
37 * Link Renderer, controls the overall creation of the tablle based on a form json object passed
38 * in as the first parameter. The structure of this object is as follows:
39 *
40 * link
41 * id : Unique ID that will become the link ID.
42 * title : Title to show (hover)
43 * target : target window
44 * label : label to show in link
45 * cls : css class
46 * url: the direct url to use, can be function (mandatory)
47 *
48 * @param item : the json object representing the form
49 * @param next : Callback when done, pass markup as return val.
50 */
511CalipsoLink.prototype.render = function(item) {
52
530 return (
54 this.render_link(item));
55
56};
57
58/**
59 * Render link
60 *
61 * @param link
62 * @returns {String}
63 */
641CalipsoLink.prototype.render_link = function(link) {
65
660 var url = "";
670 if (typeof link.url === 'function') {
680 url = link.url(link);
69 } else {
700 url = link.url;
71 }
72
730 return ('<a' + ' href="' + url + '"' + (link.id ? ' id=' + link.id + '"' : "") + (link.target ? ' target="' + link.target + '"' : "") + (link.title ? ' title="' + link.title + '"' : "") + (link.cls ? ' class="' + link.cls + '"' : "") + '>' + (link.label || "") + '</a>');
74};

core/Logging.js

84%
25
21
4
LineHitsSource
1/*!
2 * Calipso Core Logging Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This module exposes the functions that configures the logging of calipso.
8 * This is based entirely on Winston.
9 *
10 */
11
121var app, rootpath = process.cwd(),
13 path = require('path'),
14 winstong = require('winston'),
15 calipso = require(path.join('..', 'calipso'));
16
17
18/**
19 * Core export
20 */
211exports = module.exports = {
22 configureLogging: configureLogging
23};
24
25/**
26 * Configure winston to provide the logging services.
27 *
28 * TODO : This can be factored out into a module.
29 *
30 */
31
321function configureLogging(options) {
335 options = options || calipso.config.get('logging');
34
35 //Configure logging
365 var logMsg = "\x1b[36mLogging enabled: \x1b[0m",
37 winston = require("winston");
38
395 try {
405 winston.remove(winston.transports.File);
41 } catch (exFile) {
42 // Ignore the fault
43 }
44
455 if (options.file && options.file.enabled) {
460 winston.add(winston.transports.File, {
47 level: options.console.level,
48 timestamp: options.file.timestamp,
49 filename: options.file.filepath
50 });
510 logMsg += "File @ " + options.file.filepath + " ";
52 }
53
545 try {
555 winston.remove(winston.transports.Console);
56 } catch (exConsole) {
57 // Ignore the fault
58 }
59
605 if (options.console && options.console.enabled) {
610 winston.add(winston.transports.Console, {
62 level: options.console.level,
63 timestamp: options.console.timestamp,
64 colorize: options.console.colorize
65 });
660 logMsg += "Console ";
67 }
68
69 // Temporary data for form
705 calipso.data.loglevels = [];
715 for (var level in winston.config.npm.levels) {
7230 calipso.data.loglevels.push(level);
73 }
74
75 // Shortcuts to Default
765 calipso.log = winston.info; // Default function
77 // Shortcuts to NPM levels
785 calipso.silly = winston.silly;
795 calipso.verbose = winston.verbose;
805 calipso.info = winston.info;
815 calipso.warn = winston.warn;
825 calipso.debug = winston.debug;
835 calipso.error = winston.error;
84
85}

core/Menu.js

97%
148
145
3
LineHitsSource
1/*!
2 * Calipso Menu Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library provides the base functions to manage the creation of menus.
7 * A default renderer will be provided in this library, but this is intended to be over-ridden
8 * By menu modules (e.g. to export different structures), or even source menus from different locations.
9 *
10 */
11
12/**
13 * Includes
14 */
151var sys;
161try {
171 sys = require('util');
18} catch (e) {
190 sys = require('sys');
20}
211var rootpath = process.cwd() + '/',
22 path = require('path'),
23 utils = require('connect').utils,
24 merge = utils.merge,
25 calipso = require(path.join('..', 'calipso'));
26
27/**
28 * The default menu item object, with default configuration values.
29 * Constructor
30 */
31
321function CalipsoMenu(name, sort, type, options) {
33
34 // Basic menu options, used typically for root menu holder
3567 this.name = name || 'default'; // This should be mandatory
3667 this.type = type || 'root';
3767 this.sort = sort || 'name';
38
39 // Options for this menu item
4067 if (options) {
4151 this.setOptions(options);
42 }
43
44 // Child menu items
4567 this.children = {};
4667 this.sortedChildren = []; // Sorted array of prop names for recursion
47}
48
49/**
50 * Exports
51 */
521module.exports = CalipsoMenu;
53
54/**
55 * Wrapper to enable setting of menu options
56 */
571CalipsoMenu.prototype.setOptions = function(options) {
5852 merge(this, options);
59};
60
61/**
62 * Function to enable addition of a menu item to the menu.
63 *
64 * Menu Options:
65 * name: req.t('Admin') -- Label to display
66 * path: admin -- the menu heirarchy path, used for parent child.
67 * e.g. path: admin/config -- the menu heirarchy path, used for parent child.
68 * instruction: req.t('Administration Menu') -- tooltip label
69 * url: '/admin' -- Url to use as link
70 * security: [/admin/,"bob"] -- regex based on user role
71 */
721CalipsoMenu.prototype.addMenuItem = function(req, options) {
73
7430 var self = this;
75
76 // The req parameter was added in 0.3.0, if not passed, assuming options only
7730 if (options === undefined) calipso.error("Attempting to add menu item with invalid params, please update your module for the 0.3.0 api, path: " + req.path);
78
79 // Check security
8030 if (options.permit) {
81
8228 var permitFn = new calipso.permission.Filter(options, options.permit),
83 permit = permitFn.check(req);
84
8528 if (typeof permit !== "object") return;
8629 if (!permit.allow) return;
87 }
88 // Admin security is opposite to default
8929 if (self.name === 'admin') {
901 var isAdmin = req.session.user && req.session.user.isAdmin;
91 // Admin by default is not shown unless permitted
922 if (!options.permit && !isAdmin) return;
93 }
94
95 // Split the path, traverse items and add menuItems.
96 // If you add a child prior to parent, then create the parent.
9728 var newItem = self.createPath(options, options.path.split("/"));
98
99};
100
101/**
102 * Ensure that a full path provided is a valid menu tree
103 */
1041CalipsoMenu.prototype.createPath = function(options, path) {
105
10651 var self = this;
10751 var currentItem = path[0];
10851 var remainingItems = path.splice(1, path.length - 1);
109
11051 if (self.children[currentItem] && remainingItems.length > 0) {
111
112 // Recurse
11318 self.children[currentItem].createPath(options, remainingItems);
114
115 } else {
116
117 // If the current item does not yet exist
11833 if (!self.children[currentItem]) {
119
120 // Do we have children left, if so, mark this as a temporary node (e.g. we dont actually have its options)
12131 if (remainingItems.length > 0) {
1225 self.children[currentItem] = new CalipsoMenu('Child of ' + currentItem, self.sort, 'temporary', options);
123 } else {
12426 self.children[currentItem] = new CalipsoMenu('Child of ' + currentItem, self.sort, 'child', options);
125 }
12631 self.sortedChildren.push(currentItem); // Add to array for later sorting
127 }
128
129 // Check to see if we need to update a temporary node
13033 if (self.children[currentItem] && remainingItems.length === 0 && self.children[currentItem].type === 'temporary') {
1311 self.children[currentItem].type = 'child';
1321 self.children[currentItem].setOptions(options);
133 }
134
13533 if (remainingItems.length > 0) {
136 // Recurse
1375 self.children[currentItem].createPath(options, remainingItems);
138 }
139
140 }
141
142 // Sort the sorted array
14351 self.sortedChildren.sort(function(a, b) {
144
145 // a & b are strings, but both objects on the current children
1469 var diff;
1479 if (self.children[a][self.sort] && self.children[b][self.sort]) {
148
1498 if (typeof self.children[a][self.sort] === "string") {
1507 diff = self.children[a][self.sort].toLowerCase() > self.children[b][self.sort].toLowerCase();
151 } else {
1521 diff = self.children[a][self.sort] > self.children[b][self.sort];
153 }
154
155 } else {
1561 diff = self.children[a].name.toLowerCase() > self.children[b].name.toLowerCase();
157 }
158
1599 return diff;
160 });
161
162
163};
164
165
166/**
167 * Render the menu as a html list - this is the default.
168 * The idea is that this can be over-ridden (or the sub-function), to control
169 * HTML generation.
170 */
1711CalipsoMenu.prototype.render = function(req, depth) {
172
17313 var self = this;
174
175 // If the menu is empty, render nothing
17625 if (self.sortedChildren.length === 0) return '';
177
178 // Get selected items
1791 var selected = self.selected(req);
180
1811 var htmlOutput = '';
1821 htmlOutput += self.startTag();
183
1841 var renderUp = function(menu) {
1855 var selectedClass = '';
1865 if (contains(selected, menu.path)) {
1872 selectedClass = '-selected';
188 }
1895 var html = self.menuStartTag(menu, selectedClass) + self.menuLinkTag(req, menu, selectedClass);
1905 return html;
191 };
192
1931 var renderDown = function(menu) {
1945 var html = self.menuEndTag(menu);
1955 return html;
196 };
197
1981 var renderStart = function(menu) {
1993 var html = self.childrenStartTag(menu);
2003 return html;
201 };
202
2031 var renderFinish = function(menu) {
2043 var html = self.childrenEndTag(menu);
2053 return html;
206 };
207
2081 var output = [];
2091 self.fnRecurse(self, renderUp, renderDown, renderStart, renderFinish, depth, output);
210
2111 htmlOutput += output.join("");
2121 htmlOutput += self.endTag();
213
2141 return htmlOutput;
215
216};
217
218/**
219 * Specific tag rendering functions
220 * Over-write to enable custom menu rendering
221 */
2221CalipsoMenu.prototype.startTag = function() {
2231 return "<ul id='" + this.name + "-menu' class='menu" + (this.cls ? ' ' + this.cls : '') + "'>";
224};
2251CalipsoMenu.prototype.endTag = function() {
2261 return "</ul>";
227};
2281CalipsoMenu.prototype.menuStartTag = function(menu, selected) {
2295 var menuItemTagId = menu.path.replace(/\//g, '-') + "-menu-item";
2305 return "<li id='" + menuItemTagId + "' class='" + this.name + "-menu-item" + selected + "'>";
231};
2321CalipsoMenu.prototype.menuLinkTag = function(req, menu, selected) {
2335 var popup = menu.popup ? 'popupMenu' : '';
2345 return "<a href='" + menu.url + "' title='" + req.t(menu.description) + "' class='" + popup + " " + this.name + "-menu-link" + selected + (menu.cls ? " " + menu.cls : "") + "'>" + req.t(menu.name) + "</a>";
235};
2361CalipsoMenu.prototype.menuEndTag = function(menu) {
2375 return "</li>";
238};
2391CalipsoMenu.prototype.childrenStartTag = function() {
2403 return "<ul>";
241};
2421CalipsoMenu.prototype.childrenEndTag = function() {
2433 return "</ul>";
244};
245
246/**
247 * Locate selected paths based on current request
248 */
2491CalipsoMenu.prototype.selected = function(req) {
250
251 // Based on current url, create a regex string that can be used to test if a menu item
252 // Is selected during rendering
2532 var self = this;
2542 var output = [];
255
2562 var selectedFn = function(menu) {
257
25810 var menuSplit = menu.url.split("/");
25910 var reqSplit = req.url.split("/");
26010 var match = true;
261
26210 menuSplit.forEach(function(value, key) {
26336 match = match && (value === reqSplit[key]);
264 });
265
266 // Check if the url matches
26710 if (match) {
2684 return menu.path;
269 }
270
271 };
272
2732 self.fnRecurse(self, selectedFn, output);
274
2752 return output;
276
277};
278
279/**
280 * Helper function that can recurse the menu tree
281 * From a start point, execute a function and add the result to an output array
282 */
2831CalipsoMenu.prototype.fnRecurse = function(menu, fnUp, fnDown, fnStart, fnFinish, depth, output) {
284
28524 var self = this;
28624 var result;
28724 if (typeof fnDown != 'function') {
28818 output = fnDown;
289 }
29024 output = output || [];
291
292 // Recurse from menu item selected
29324 if (menu.type === 'root') {
294
295 // Functions don't run on root
2964 menu.sortedChildren.forEach(function(child) {
2974 self.fnRecurse(menu.children[child], fnUp, fnDown, fnStart, fnFinish, depth, output);
298 });
299
300 } else {
301
302 // Control depth of recursion
30320 depth = depth === undefined ? -1 : depth;
30420 if (depth > 0) {
3050 depth = depth - 1;
30620 } else if (depth === -1) {
307 // Recures infinitely
308 } else {
3090 return output;
310 }
311
312 // Count the number of children
31320 var childCount = menu.sortedChildren.length;
314
315 // Execute fn
31620 if (typeof fnUp === 'function') {
317
31820 result = fnUp(menu);
31920 if (result) {
32014 output.push(result);
321 }
322
32320 if (childCount > 0) {
32412 if (typeof fnStart === 'function') {
3253 result = fnStart(menu);
3263 if (result) {
3273 output.push(result);
328 }
329 }
330 }
331
332 }
333
334 // Recurse
33520 menu.sortedChildren.forEach(function(child) {
33616 self.fnRecurse(menu.children[child], fnUp, fnDown, fnStart, fnFinish, depth, output);
337 });
338
339 // Close
34020 if (typeof fnDown === 'function') {
341
3425 result = fnDown(menu);
3435 if (result) {
3445 output.push(result);
345 }
346
3475 if (childCount > 0) {
3483 if (typeof fnFinish === 'function') {
3493 result = fnFinish(menu);
3503 if (result) {
3513 output.push(result);
352 }
353 }
354 }
355 }
356
357 }
358
359};
360
361/**
362 * Return current menu as a JSON object, used for Ajax style menus.
363 * Path : root to return menu from, default is root (entire menu)
364 * Depth : How many levels to return menu
365 */
3661CalipsoMenu.prototype.getMenuJson = function(path, depth) {
367
368 // TODO
369};
370
371/**
372 * Private helper functions
373 */
374
3751function contains(a, obj) {
3765 var i = a.length;
3775 while (i--) {
3789 if (a[i] === obj) {
3792 return true;
380 }
381 }
3823 return false;
383}

core/Module.js

85%
262
225
37
LineHitsSource
11var app, rootpath = process.cwd() + '/',
2 path = require('path'),
3 calipso = require(path.join('..', 'calipso'));
4
5/**
6 * Route all of the modules based on the module event model
7 * This replaces an earlier version that only executed modules in
8 * parallel via step
9 */
10
111function eventRouteModules(req, res, next) {
12
13 // Ignore static content
14 // TODO : Make this more connect friendly or at least configurable
15 // STATIC content may or may not be static. We should route it normally for now.
16 //if (req.url.match(/^\/images|^\/js|^\/css|^\/favicon.ico|png$|jpg$|gif$|css$|js$/)) {
17 // return next();
18 //}
19
204 req.timeStart = new Date();
214 var end = res.end;
224 res.calipsoEndCalled = false;
234 res.end = function () {
240 res.calipsoEndCalled = true;
250 end.apply(res, arguments);
26 }
27
28 // Attach our event listener to this request
294 attachRequestEvents(req, res);
30
31 // Initialise the response re. matches
32 // Later used to decide to show 404
334 res.routeMatched = false;
34
35 // Store our callback here
364 req.routeComplete = function(res) {
378 if(!res.calipsoEndCalled) next();
38 };
39
40 // Route 'first' modules that fire before all others
41 // These first modules can stop the routing of all others
424 doFirstModules(req, res, function(err) {
43
444 var iterator = function(module, cb) {
4516 routeModule(req, res, module, false, false, cb);
46 }
47
484 calipso.lib.async.map(calipso.lib._.keys(calipso.modules), iterator, function(err, result) {
49 // Not important
50 })
51
52 });
53
54}
55
56/**
57 * Attach module event emitters and request event listener
58 * to this request instance.
59 * This will only last for the context of a current request
60 */
61
621function attachRequestEvents(req, res) {
63
64 // Create a request event listener for this request, pass in functions
65 // to enable testing.
664 req.event = new calipso.event.RequestEventListener({
67 notifyDependencyFn: notifyDependenciesOfRoute,
68 registerDependenciesFn: registerDependencies
69 });
70
71 //
724 var maxListeners = calipso.config.get('server:events:maxListeners');
73
74 // Attach all the modules to it
754 for (var module in calipso.modules) {
7616 req.event.registerModule(req, res, module, {maxListeners: maxListeners});
77 }
78
79}
80
81/**
82 * Helper to register dependent modules that should be checked by a module when
83 * routing, the parent module's emitter is passed in.
84 */
851function registerDependencies(moduleEmitter, moduleName) {
86
87 // Register depends on parent
8816 if (calipso.modules[moduleName].fn && calipso.modules[moduleName].fn.depends) {
894 calipso.modules[moduleName].fn.depends.forEach(function(dependentModule) {
904 moduleEmitter.modules[moduleName].check[dependentModule] = false;
91 });
92 }
93}
94
95/**
96 * Route a specific module
97 * Called by both the eventRouteModules but also by when dependencies trigger
98 * a module to be routed
99 *
100 * req, res : request/resposne
101 * module : the module to route
102 * depends : has this route been triggered by an event based on dependencies being met
103 * last : final modules, after all others have routed
104 *
105 */
106
1071function routeModule(req, res, moduleName, depends, last, next) {
108
10920 var module = calipso.modules[moduleName];
110
111 // If module is enabled and has no dependencies, or if we are explicitly triggering this via depends
112 // Ignore modules that are specified as post route only
11320 if (module.enabled && (depends || !module.fn.depends) && (last || !module.fn.last) && !module.fn.first) {
114
115 // Fire event to start
1168 req.event.modules[moduleName].route_start();
117
118 // Route
1198 module.fn.route(req, res, module, calipso.app, function(err, moduleName) {
120
121 // Gracefully deal with errors
1228 if (err) {
1230 res.statusCode = 500;
1240 calipso.error(err.message);
1250 res.errorMessage = "Module " + moduleName + ", error: " + err.message + err.stack;
126 }
127
128 // Expose configuration if module has it
1298 if (module.fn && module.fn.config) {
1300 var modulePermit = calipso.permission.Helper.hasPermission("admin:module:configuration");
1310 res.menu.admin.addMenuItem(req, {
132 name: moduleName,
133 path: 'admin/modules/' + moduleName,
134 url: '/admin/modules?module=' + moduleName,
135 description: 'Manage ' + moduleName + ' settings ...',
136 permit: modulePermit
137 });
138 }
139
140 // Finish event
1418 req.event.modules[moduleName].route_finish();
142
143 // Check to see if we have completed routing all modules
1448 if (!last) {
1458 checkAllModulesRouted(req, res);
146 }
147
1488 next();
149
150 });
151
152 } else {
153
15412 checkAllModulesRouted(req, res);
155
15612 next();
157
158 }
159
160}
161
162/**
163 * Check that all enabled modules have been initialised
164 * Don't check disabled modules or modules that are setup for postRoute only
165 */
1661function checkAllModulesRouted(req, res) {
167
16820 var allRouted = true;
169
17020 for (var module in req.event.modules) {
17180 var moduleRouted = (req.event.modules[module].routed || (calipso.modules[module].enabled && (calipso.modules[module].fn.last || calipso.modules[module].fn.first)) || !calipso.modules[module].enabled);
17280 allRouted = moduleRouted && allRouted;
173 }
174
17520 if (allRouted && !req.event.routeComplete) {
1764 req.event.routeComplete = true;
1774 doLastModules(req, res, function() {
1784 req.timeFinish = new Date();
1794 req.timeDuration = req.timeFinish - req.timeStart;
1804 calipso.silly("All modules routed in " + req.timeDuration + " ms");
1814 doResponse(req, res);
182 });
183 }
184
185}
186
187
188/**
189 * RUn any modules that are defined as first routing modules
190 * via first: true, dependencies are ignored for these.
191 */
1921function doFirstModules(req, res, next) {
193
194 // Get all the postRoute modules
1954 var firstModules = [];
1964 for (var moduleName in calipso.modules) {
19716 if (calipso.modules[moduleName].enabled && calipso.modules[moduleName].fn.first) {
1984 firstModules.push(calipso.modules[moduleName]);
199 }
200 }
201
202
2034 if(firstModules.length === 0) return next();
204
205 // Execute their routing functions
2064 calipso.lib.step(
207
208 function doFirstModules() {
2094 var group = this.group();
2104 firstModules.forEach(function(module) {
2114 module.fn.route(req, res, module, calipso.app, group());
212 });
213 }, function done(err) {
214
215 // Gracefully deal with errors
2164 if (err) {
2170 res.statusCode = 500;
2180 console.log(err.message);
2190 res.errorMessage = err.message + err.stack;
220 }
221
2224 next();
223
224 });
225
226}
227
228
229/**
230 * RUn any modules that are defined as last routing modules
231 * via last: true, dependencies are ignored for these atm.
232 */
233
2341function doLastModules(req, res, next) {
235
236 // Get all the postRoute modules
2374 var lastModules = [];
2384 for (var moduleName in calipso.modules) {
23916 if (calipso.modules[moduleName].enabled && calipso.modules[moduleName].fn.last) {
2404 lastModules.push(calipso.modules[moduleName]);
241 }
242 }
243
244
2454 if(lastModules.length === 0) return next();
246
247 // Execute their routing functions
2484 calipso.lib.step(
249
250 function doLastModules() {
2514 var group = this.group();
2524 lastModules.forEach(function(module) {
2534 module.fn.route(req, res, module, calipso.app, group());
254 });
255 }, function done(err) {
256
257 // Gracefully deal with errors
2584 if (err) {
2590 res.statusCode = 500;
2600 console.log(err.message);
2610 res.errorMessage = err.message + err.stack;
262 }
263
2644 next();
265
266 });
267
268}
269
270/**
271 * Standard response to all modules completing their routing
272 */
273
2741function doResponse(req, res, next) {
275
276 // If we are in install mode, and are not in the installation process, then redirect
2774 if (!calipso.config.get('installed') && !req.url.match(/^\/admin\/install/)) {
2780 calipso.silly("Redirecting to admin/install ...");
2790 calipso.app.doingInstall = true;
2800 res.redirect("/admin/install");
2810 return;
282 }
283
284 // If nothing could be matched ...
2854 if (!res.routeMatched) {
2861 calipso.log("No Calipso module routes matched the current URL.");
2871 res.statusCode = 404;
288 }
289
290 // Render statuscodes dealt with by themeing engine
291 // TODO - this is not very clean
2924 calipso.silly("Responding with statusCode: " + res.statusCode);
2934 if (res.statusCode === 404 || res.statusCode === 500 || res.statusCode === 200 || res.statusCode === 403) {
294
2953 calipso.theme.render(req, res, function(err, content) {
296
2973 if (err) {
298
299 // Something went wrong at the layout, cannot use layout to render.
3000 res.statusCode = 500;
3010 res.send(500, "<html><h2>A fatal error occurred!</h2>" + "<p>" + (err.xMessage ? err.xMessage : err.message) + "</p>" + "<pre>" + err.stack + "</pre></html>");
3020 req.routeComplete(res);
303
304 } else {
305
3063 res.setHeader('Content-Type', 'text/html');
307 // Who am I?
3083 res.setHeader('X-Powered-By', 'Calipso');
309
310 // render
3113 res.send(content);
312
313 // Callback
3143 req.routeComplete(res);
315
316 }
317
318 });
319
320 } else {
321
322 // Otherwise, provided we haven't already issued a redirect, then pass back to Express
3231 req.routeComplete(res);
324
325 }
326
327}
328
329
330/**
331 * Initialise the modules currently enabled.
332 * This iterates through the modules loaded by loadModules (it places them in an array in the calipso object),
333 * and calls the 'init' function exposed by each module (in parallel controlled via step).
334 */
335
3361function initModules() {
337
338 // Reset
3392 calipso.initComplete = false;
340
341 // Create a list of all our enabled modules
3422 var enabledModules = [];
3432 for (var module in calipso.modules) {
3448 if (calipso.modules[module].enabled) {
3458 enabledModules.push(module);
346 }
347 }
348
349 // Initialise them all
3502 enabledModules.forEach(function(module) {
3518 initModule(module, false);
352 });
353
354}
355
356/**
357 * Init a specific module, called by event listeners re. dependent modules
358 */
359
3601function initModule(module, depends) {
361
362
363 // If the module has no dependencies, kick start it
36410 if (depends || !calipso.modules[module].fn.depends) {
365
366 // Init start event
3678 calipso.modules[module].event.init_start();
368
369 // Next run any init functions
3708 calipso.modules[module].fn.init(calipso.modules[module], calipso.app, function(err) {
371
372 // Init finish event
3738 calipso.modules[module].inited = true;
3748 calipso.modules[module].event.init_finish();
375
376 // Now, load any routes to go along with it
3778 if (calipso.modules[module].fn.routes && calipso.modules[module].fn.routes.length > 0) {
3782 calipso.lib.async.map(calipso.modules[module].fn.routes, function(options, next) {
3792 calipso.modules[module].router.addRoute(options, next);
380 }, function(err, data) {
3812 if (err) calipso.error(err);
3822 checkAllModulesInited();
383 });
384 } else {
3856 checkAllModulesInited();
386 }
387
388 });
389
390 }
391
392}
393
394/**
395 * Check that all enabled modules have been initialised
396 * If they have been initialised, then call the callback supplied on initialisation
397 */
398
3991function checkAllModulesInited() {
400
4018 var allLoaded = true;
4028 for (var module in calipso.modules) {
40332 allLoaded = (calipso.modules[module].inited || !calipso.modules[module].enabled) && allLoaded;
404 }
405
4068 if (allLoaded && !calipso.initComplete) {
4072 calipso.initComplete = true;
4082 calipso.initCallback();
409 }
410
411}
412
413/**
414 * Load the modules from the file system, into a 'modules' array
415 * that can be managed and iterated.
416 *
417 * The first level folder is the module type (e.g. core, contrib, ui).
418 * It doesn't actually change the processing, but that folder structure is
419 * now stored as a property of the module (so makes admin easier).
420 *
421 * It will take in an options object that holds the configuration parameters
422 * for the modules (e.g. if they are enabled or not).
423 * If they are switching (e.g. enabled > disabled) it will run the disable hook.
424 *
425 */
426
4271function loadModules(next) {
428
4292 var configuredModules = calipso.config.get('modules') || {};
430
431 // Run any disable hooks
4322 for (var module in calipso.modules) {
433 // Check to see if the module is currently enabled, if we are disabling it.
4344 if (calipso.modules[module].enabled && configuredModules[module].enabled === false && typeof calipso.modules[module].fn.disable === 'function') {
4350 calipso.modules[module].fn.disable();
436 }
437 }
438
439 // Clear the modules object (not sure if this is required, but was getting strange errors initially)
4402 delete calipso.modules; // 'Delete' it.
4412 calipso.modules = {}; // Always reset it
442
4432 var moduleBasePath = path.join(rootpath, calipso.config.get('server:modulePath'));
444
445 // Read the modules in from the file system, sync is fine as we do it once on load.
4462 calipso.lib.fs.readdirSync(moduleBasePath).forEach(function(type) {
447
448 // Check for all files or folder starting with "." so that we can handle ".svn", ".git" and so on without problems.
449
4502 if (type != "README" && type[0] != '.') { // Ignore the readme file and .DS_Store file for Macs
4512 calipso.lib.fs.readdirSync(path.join(moduleBasePath, type)).forEach(function(moduleFolderName) {
452
4538 if (moduleFolderName != "README" && moduleFolderName[0] != '.') { // Ignore the readme file and .DS_Store file for Macs
454
4558 var modulePath = path.join(moduleBasePath, type, moduleFolderName);
456
4578 var module = {
458 name: moduleFolderName,
459 folder: moduleFolderName,
460 library: moduleFolderName,
461 type: type,
462 path: modulePath,
463 enabled: false,
464 inited: false
465 };
466
467 // Add about info to it
4688 loadAbout(module, modulePath, 'package.json');
469
470 // Set the module name to what is in the package.json, default to folder name
4718 module.name = (module.about && module.about.name) ? module.about.name : moduleFolderName;
472
473 // Now set the module
4748 calipso.modules[module.name] = module;
475
476 // Set if it is enabled or not
4778 module.enabled = configuredModules[module.name] ? configuredModules[module.name].enabled : false;
478
4798 if (module.enabled) {
480
481 // Load the module itself via require
4828 requireModule(calipso.modules[module.name], modulePath);
483
484 // Load the templates (factored out so it can be recalled by watcher)
4858 loadModuleTemplates(calipso.modules[module.name], path.join(modulePath,'templates'));
486
487 }
488
489 }
490
491 });
492 }
493
494 });
495
496 // Now that all are loaded, attach events & depends
4972 attachModuleEventsAndDependencies();
498
499 // Save configuration changes (if required)
5002 if (calipso.config.dirty) {
5010 calipso.config.save(next);
502 } else {
5032 return next();
504 }
505
506}
507
508/**
509 * Load data from package.json or theme.json
510 */
511
5121function loadAbout(obj, fromPath, file) {
513
51411 var fs = calipso.lib.fs;
515
51611 var packageFile = calipso.lib.path.join(fromPath, file);
517
51811 if ((fs.existsSync || path.existsSync)(packageFile)) {
51911 var json = fs.readFileSync(packageFile);
52011 try {
52111 obj.about = JSON.parse(json.toString());
52211 if (obj.about && obj.about.name) {
52311 obj.library = obj.about.name;
524 } else {
5250 obj.library = obj.name;
526 }
527 } catch (ex) {
5280 obj.about = {
529 description: 'Invalid ' + file
530 };
531 }
532 }
533
534}
535
536/**
537 * Connect up events and dependencies
538 * Must come after all modules are loaded
539 */
540
5411function attachModuleEventsAndDependencies() {
542
5432 var options = {maxListeners: calipso.config.get('server:events:maxListeners'), notifyDependencyFn: notifyDependenciesOfInit};
544
5452 for (var module in calipso.modules) {
546
547 // Register dependencies
5488 registerModuleDependencies(calipso.modules[module]);
549
550 // Attach event listener
5518 calipso.event.addModuleEventListener(calipso.modules[module], options);
552
553 }
554
555 // Sweep through the dependency tree and make sure any broken dependencies are disabled
5562 disableBrokenDependencies();
557
558}
559
560/**
561 * Ensure dependencies are mapped and registered against parent and child
562 */
563
5641function registerModuleDependencies(module) {
565
5668 if (module.fn && module.fn.depends && module.enabled) {
567
568 // Create object to hold dependent status
5692 module.check = {};
570
571 // Register depends on parent
5722 module.fn.depends.forEach(function(dependentModule) {
573
5742 module.check[dependentModule] = false;
575
5762 if (calipso.modules[dependentModule] && calipso.modules[dependentModule].enabled) {
577
578 // Create a notification array to allow this module to notify modules that depend on it
5792 calipso.modules[dependentModule].notify = calipso.modules[dependentModule].notify || [];
5802 calipso.modules[dependentModule].notify.push(module.name);
581
582 } else {
583
5840 calipso.modules[module.name].error = "Module " + module.name + " depends on " + dependentModule + ", but it does not exist or is disabled - this module will not load.";
5850 calipso.error(calipso.modules[module.name].error);
5860 calipso.modules[module.name].enabled = false;
587
588 }
589
590 });
591
592 }
593
594}
595
596
597/**
598 * Disable everythign in a broken dependency tree
599 */
600
6011function disableBrokenDependencies() {
602
6032 var disabled = 0;
6042 for (var moduleName in calipso.modules) {
6058 var module = calipso.modules[moduleName];
6068 if (module.enabled && module.fn && module.fn.depends) {
6072 module.fn.depends.forEach(function(dependentModule) {
6082 if (!calipso.modules[dependentModule].enabled) {
6090 calipso.modules[module.name].error = "Module " + module.name + " depends on " + dependentModule + ", but it does not exist or is disabled - this module will not load.";
6100 calipso.error(calipso.modules[module.name].error);
6110 calipso.modules[module.name].enabled = false;
6120 disabled = disabled + 1;
613 }
614 });
615 }
616 }
617
618 // Recursive
6192 if (disabled > 0) disableBrokenDependencies();
620
621}
622
623/**
624 * Notify dependencies for initialisation
625 */
626
6271function notifyDependenciesOfInit(moduleName, options) {
628
6298 var module = calipso.modules[moduleName];
6308 if (module.notify) {
6312 module.notify.forEach(function(notifyModuleName) {
6322 notifyDependencyOfInit(moduleName, notifyModuleName, options);
633 });
634 }
635
636}
637
638
639/**
640 * Notify dependencies for routing
641 */
642
6431function notifyDependenciesOfRoute(req, res, moduleName, reqModules) {
644
6458 var module = calipso.modules[moduleName];
6468 if (module.notify) {
6474 module.notify.forEach(function(notifyModuleName) {
6484 notifyDependencyOfRoute(req, res, moduleName, notifyModuleName);
649 });
650 }
651
652}
653
654/**
655 * Notify dependency
656 * moduleName - module that has init'd
657 * notifyModuleName - module to tell
658 */
659
6601function notifyDependencyOfInit(moduleName, notifyModuleName, options) {
661
662 // Set it to true
6632 var module = calipso.modules[notifyModuleName];
6642 module.check[moduleName] = true;
6652 checkInit(module);
666
667}
668
669
670/**
671 * Notify dependency
672 * req - request
673 * res - response
674 * moduleName - module that has init'd
675 * notifyModuleName - module to tell
676 */
677
6781function notifyDependencyOfRoute(req, res, moduleName, notifyModuleName) {
679
6804 var module = req.event.modules[notifyModuleName];
6814 module.check[moduleName] = true;
6824 checkRouted(req, res, moduleName, notifyModuleName);
683
684}
685
686/**
687 * Check if all dependencies are met and we should init the module
688 */
689
6901function checkInit(module, next) {
691
6922 var doInit = true;
6932 for (var check in module.check) {
6942 doInit = doInit & module.check[check];
695 }
6962 if (doInit) {
697 // Initiate the module, no req for callback
6982 initModule(module.name, true, function() {});
699 }
700
701}
702
703/**
704 * Check if all dependencies are met and we should route the module
705 */
706
7071function checkRouted(req, res, moduleName, notifyModuleName) {
708
7094 var doRoute = true;
710
7114 for (var check in req.event.modules[notifyModuleName].check) {
7124 doRoute = doRoute && req.event.modules[notifyModuleName].check[check];
713 }
714
7154 if (doRoute) {
716 // Initiate the module, no req for callback
717 // initModule(module.name,true,function() {});
7184 routeModule(req, res, notifyModuleName, true, false, function() {});
719 }
720
721}
722
723/**
724 * Load the module itself, refactored out to enable watch / reload
725 * Note, while it was refactored out, you can't currently reload
726 * a module, will patch in node-supervisor to watch the js files and restart
727 * the whole server (only option :())
728 */
729
7301function requireModule(module, modulePath, reload, next) {
731
7328 var fs = calipso.lib.fs;
7338 var moduleFile = path.join(modulePath + '/' + module.name);
734
7358 try {
736
737 // Require the module
7388 module.fn = require(moduleFile);
739
740 // Attach a router - legacy check for default routes
7418 module.router = new calipso.router(module.name, modulePath);
742
743 // Load the routes if specified as either array or function
7448 if (typeof module.fn.routes === "function") module.fn.routes = module.fn.routes();
7458 module.fn.routes = module.fn.routes || [];
746
747 // Ensure the defaultConfig exists (e.g. if it hasn't been required before)
748 // This is saved in the wider loadModules loop to ensure only one config save action (if required)
7498 if (module.fn.config && !calipso.config.getModuleConfig(module.name, '')) {
7500 calipso.config.setDefaultModuleConfig(module.name, module.fn.config);
751 }
752
753 } catch (ex) {
754
7550 calipso.error("Module " + module.name + " has been disabled because " + ex.message);
7560 calipso.modules[module.name].enabled = false;
757
758 }
759
760}
761
762/**
763 * Pre load all the templates in a module, synch, but only happens on app start up and config reload
764 * This is attached to the templates attribute so used later.
765 *
766 * @param calipso
767 * @param moduleTemplatePath
768 * @returns template object
769 */
770
7711function loadModuleTemplates(module, moduleTemplatePath) {
772
7738 var templates = {};
774
775 // Default the template to any loaded in the theme (overrides)
7768 var fs = calipso.lib.fs;
777
7788 if (!(fs.existsSync || calipso.lib.path.existsSync)(moduleTemplatePath)) {
7796 return null;
780 }
781
7822 fs.readdirSync(moduleTemplatePath).forEach(function(name) {
783
784 // Template paths and functions
7852 var templatePath = moduleTemplatePath + "/" + name;
7862 var templateExtension = templatePath.match(/([^\.]+)$/)[0];
7872 var template = fs.readFileSync(templatePath, 'utf8');
7882 var templateName = name.replace(/\.([^\.]+)$/, '');
789
790 // Load the template - only if not already loaded by theme (e.g. overriden)
7912 var hasTemplate = calipso.utils.hasProperty('theme.cache.modules.' + module.name + '.templates.' + templateName, calipso);
792
7932 if (hasTemplate) {
794
795 // Use the theme version
7960 templates[templateName] = calipso.theme.cache.modules[module.name].templates[templateName];
797
798 } else {
799
800 // Else load it
8012 if (template) {
802 // calipso.theme.compileTemplate => ./Theme.js
8032 templates[templateName] = calipso.theme.compileTemplate(template, templatePath, templateExtension);
804
805 // Watch / unwatch files - always unwatch (e.g. around config changes)
8062 if (calipso.config.get('performance:watchFiles')) {
807
8082 fs.unwatchFile(templatePath); // Always unwatch first due to recursive behaviour
8092 fs.watchFile(templatePath, {
810 persistent: true,
811 interval: 200
812 }, function(curr, prev) {
8130 loadModuleTemplates(module, moduleTemplatePath);
8140 calipso.silly("Module " + module.name + " template " + name + " reloaded.");
815 });
816
817 }
818
819 }
820 }
821 });
822
8232 module.templates = templates;
824
825}
826
827/**
828 * Exports
829 */
8301module.exports = {
831 loadModules: loadModules,
832 initModules: initModules,
833 eventRouteModules: eventRouteModules,
834 notifyDependenciesOfInit: notifyDependenciesOfInit,
835 notifyDependenciesOfRoute: notifyDependenciesOfRoute,
836 registerDependencies: registerDependencies,
837 loadAbout: loadAbout
838};

core/Permission.js

55%
61
34
27
LineHitsSource
1/*!
2 * Calipso Permissions Class
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library adds a permissions class to the router, defining functions that are used by the router to control access.
7 *
8 */
9
101var rootpath = process.cwd() + '/',
11 path = require('path'),
12 calipso = require(path.join('..', 'calipso'));
13
14/**
15 * A set of helper functions to simplify the application of filters, as well as store
16 * the in memory map of roles to permissions (in memory for performance reasons)
17 */
181var PermissionHelpers = {
19
20 // Holder of defined permissions
21 permissions: {},
22 sortedPermissions: [],
23 structuredPermissions: {},
24
25 // Clear all oaded permissions
26 clearPermissionRoles: function() {
27
280 var self = this;
290 for (var perm in self.permissions) {
300 delete self.permissions[perm].roles;
310 self.permissions[perm].roles = [];
32 }
33
34 },
35
36 // Add a permission
37 addPermission: function(permission, description, isCrud) {
38
393 var self = this;
40
41 // if Crud, automatically add level below
423 if (isCrud) {
430 calipso.lib._.map(["view", "create", "update", "delete"], function(crudAction) {
440 var crudPermission = permission + ":" + crudAction;
450 self.permissions[crudPermission] = {
46 roles: [],
47 queries: [],
48 description: crudAction + " " + description
49 };
500 self.sortedPermissions.push(crudPermission);
51 });
52 } else {
53
54 // Add Permission always resets it if it already exists
553 self.permissions[permission] = {
56 roles: [],
57 queries: [],
58 description: description
59 };
603 self.sortedPermissions.push(permission);
61
62 }
63
64 },
65
66 structureAndSort: function() {
67
680 var self = this;
69
70 // This could be done by the permissions module
710 self.sortedPermissions.sort(function(a, b) {
720 return a < b;
73 });
74
75 // Now we need to create our permissions object structure
760 self.sortedPermissions.forEach(function(value) {
77
780 var path = value.split(":"),
79 target = self.structuredPermissions,
80 counter = 0;
81
820 while (path.length > 1) {
830 key = path.shift();
840 if (!target[key] || typeof target[key] !== 'object') {
850 target[key] = {};
86 }
870 target = target[key];
88 }
89
90 // Set the specified value in the nested JSON structure
910 key = path.shift();
920 if (typeof target[key] !== "object") {
930 target[key] = self.permissions[value].roles;
94 }
95
96 });
97
98 },
99
100 // Add a map between role / permission (this is loaded via the user module)
101 addPermissionRole: function(permission, role) {
102
1033 var self = this;
104
105 // Store this as a simple in memory map
1063 if (self.permissions[permission]) {
1073 self.permissions[permission].roles.push(role);
1083 return true;
109 } else {
1100 calipso.warn("Attempted to map role: " + role + " to a permission: " + permission + " that does not exist (perhaps related to a disabled module?).");
1110 return false;
112 }
113
114 },
115
116 // Does a user have a role
117 hasRole: function(role) {
118 // Curried filter
1190 return function(user) {
1200 var isAllowed = user.roles.indexOf(role) >= 0 ? true : false,
121 message = isAllowed ? ('User has role ' + role) : 'You dont have the appropriate roles to view that page!';
1220 return {
123 allow: isAllowed,
124 msg: message
125 };
126 };
127 },
128
129 // Does a user have a permission
130 hasPermission: function(permission) {
131
1325 var self = this;
133
134 // Curried filter
1355 return function(user) {
136
137 // Check if the user has a role that maps to the permission
13826 var userRoles = user.roles,
139 permissionRoles = self.permissions[permission] ? self.permissions[permission].roles : [];
140
141 // Check if allowed based on intersection of user roles and roles that have permission
14226 var isAllowed = calipso.lib._.intersect(permissionRoles, userRoles).length > 0,
143 message = isAllowed ? ('User has permission ' + permission) : 'You do not have any of the roles required to perform that action.';
144
145
14626 return {
147 allow: isAllowed,
148 msg: message
149 };
150
151 };
152
153 }
154
155};
156
157
158/**
159 * The default calipso permission filter, this is attached to every route, and processed as part of the route matching.
160 */
1611function PermissionFilter(options, permit) {
162
163 // Store the options
16436 var self = this;
16536 self.options = options;
166
16736 if(permit) {
16830 if(typeof permit === 'function') {
169 // permit is already a fn created by a helper
17028 self.permit = permit;
171 } else {
172 // permit is a string - e.g. 'admin:core:configuration'
1732 self.permit = calipso.permission.Helper.hasPermission(permit);
174 }
175 }
176
177}
178
1791PermissionFilter.prototype.check = function(req) {
180
18130 var self = this;
18230 if (!self.permit && self.options.permit) self.permit = self.options.permit;
18330 if (self.permit) {
184
18530 var user = req.session.user;
18630 var isAdmin = req.session.user && req.session.user.isAdmin;
187
18832 if (isAdmin) return {
189 allow: true
190 }; // Admins always access everything
191 // Else check for a specific permission
19228 if (user) {
19326 return self.permit(user);
194 } else {
1952 return {
196 allow: false,
197 msg: 'You must be a logged in user to view that page'
198 };
199 }
200
201 } else {
2020 return {
203 allow: true
204 };
205 }
206
207};
208
209
210/**
211 * Export an instance of our object
212 */
2131exports.Filter = PermissionFilter;
2141exports.Helper = PermissionHelpers;

core/Router.js

75%
90
68
22
LineHitsSource
1/*!
2 * Calipso Core Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * The Calipso Router provides a router object to each module that enables each module to register
7 * its own functions to respond to URL patterns (as per the typical Express approach). Note
8 * that Calipso itself does not respond to any URL outside of those exposed by a module, if all are disabled
9 * the application will do nothing.
10 *
11 * Borrowed liberally from Connect / ExpressJS itself for this, thanks for the great work!
12 */
13
14/**
15 * Includes
16 */
171var rootpath = process.cwd() + '/',
18 path = require('path'),
19 calipso = require(path.join('..', 'calipso')),
20 url = require('url'),
21 fs = require('fs'),
22 PermissionFilter = require('./Permission').Filter,
23 PermissionHelper = require('./Permission').Helper,
24 blocks = require('./Blocks');
25
26/**
27 * Core router object, use the return model to ensure
28 * that we always return a new instance when called.
29 *
30 * A Router is attached to each module, and allows each module to effectively
31 * act as its own controller in a mini MVC model.
32 *
33 * This class exposes:
34 *
35 * addRoutes: function, to add Routes to a module.
36 * route: iterate through the routes, match, and then call the matched function in the module.
37 *
38 */
391var Router = function(moduleName, modulePath) {
40
418 return {
42
43 moduleName: moduleName,
44 modulePath: modulePath,
45 routes: [],
46
47 /**
48 * A route is defined by three parameters:
49 *
50 * path: a string in the form 'GET /url' where the first piece is the HTTP method to respond to.
51 * OR
52 * a regex function (it matches only on GET requests).
53 * fn: the function in the module to call if the route matches.
54 * options: additional configuration options, specifically:
55 * end - deprecated. TODO CLEANUP
56 * admin - is the route an administrative route (user must have isAdmin = true).
57 */
58 addRoute: function(options, next, legacy_options, legacy_next) {
59
60 // Default options
618 var self = this,
62 defaults = {
63 end: true,
64 admin: false,
65 user: false,
66 cache: false,
67 permit: null
68 };
69
70 // Deal with legacy, this will eventually just be options, next to enable simpler invocation
71 // And to make it more extensible
728 if (typeof legacy_next === "function") {
73
746 var routePath = options,
75 fn = next;
76
77 // Set the variables
786 options = legacy_options || {};
796 options.path = routePath;
806 options.fn = fn;
81
826 next = legacy_next;
83
84 }
85
86 // Default options
878 options = calipso.lib._.extend(defaults, options);
888 options.permitFn = new PermissionFilter(options, options.permit);
89
908 self.routes.push(options);
91
928 next();
93
94 },
95
96 /**
97 * Module routing function, iterates through the configured routes and trys to match.
98 * This has been borrowed from the Express core routing module and refactored slightly
99 * to deal with the fact this is much more specific.
100 */
101 route: function(req, res, next) {
102
10316 var self = this,
104 requestUrl = url.parse(req.url, true),
105 routes = this.routes;
106
107 // Use step to enable parallel execution
10816 calipso.lib.step(
109
110 function matchRoutes() {
111
112 // Emit event to indicate starting
11316 var i, l, group = this.group();
114
11516 for (i = 0, l = routes.length; i < l; i = i + 1) {
116
11716 var keys = [],
118 route = routes[i],
119 templateFn = null,
120 block = "",
121 routeMethod = "",
122 routeRegEx, j, paramLen, param, allPages = false;
123
12416 if (typeof route.path === "string") {
1258 routeMethod = route.path.split(" ")[0];
1268 routeRegEx = normalizePath(route.path.split(" ")[1], keys);
127 } else {
1288 routeRegEx = route.path;
1298 allPages = true; // Is a regex
130 }
131
13216 var captures = requestUrl.pathname.match(routeRegEx);
133
13416 if (captures && (!routeMethod || req.method === routeMethod)) {
135
136 // Check to see if we matched a non /.*/ route to flag a 404 later
13711 res.routeMatched = !allPages || res.routeMatched;
138
139 // If admin, then set the route
14011 if (route.admin) {
1412 res.layout = "admin";
1422 if(!route.permit){
1430 calipso.debug("Route has admin only but no permit is defined!");
1440 route.permit = calipso.permission.Helper.hasPermission("admin");
145 }
146 }
147
148 // TODO
14911 var isAdmin = req.session.user && req.session.user.isAdmin;
150
151 // Check to see if it requires logged in user access
15211 if (route.permit) {
153
1542 var permit = route.permitFn.check(req);
155
1562 if (typeof permit !== "object") permit = {
157 allow: false,
158 msg: 'You don\'t have the appropriate permissions to view that page.'
159 };
1602 if (!permit.allow) {
1611 if (!allPages) {
1621 if (!req.cookies.logout) {
1631 req.flash('error', req.t(permit.msg));
1641 res.statusCode = 401;
165 }
1661 res.redirect("/");
1671 return group()();
168 } else {
169 // Simply ignore silently
1700 return group()();
171 }
172 }
173 }
174
175 // Debugging - only when logged in as admin user
176 // calipso.silly("Module " + router.moduleName + " matched route: " + requestUrl.pathname + " / " + routeRegEx.toString() + " [" + res.routeMatched + "]");
177 // Lookup the template for this route
17810 if (route.template) {
1791 templateFn = calipso.modules[self.moduleName].templates[route.template];
1801 if (!templateFn && route.template) {
1810 var err = new Error("The specified template: " + route.template + " does not exist in the module: " + self.modulePath);
1820 return group()(err);
183 } else {
1841 calipso.silly("Using template: " + route.template + " for module: " + self.modulePath);
185 }
1861 route.templateFn = templateFn;
187 }
188
189 // Set the object to hold the rendered blocks if it hasn't been created already
19010 if (!res.renderedBlocks) {
1911 res.renderedBlocks = new blocks.RenderedBlocks(calipso.cacheService);
192 }
193
194 // Copy over any params that make sense from the url
19510 req.moduleParams = {};
19610 for (j = 1, paramLen = captures.length; j < paramLen; j = j + 1) {
1970 var key = keys[j - 1],
198 val = typeof captures[j] === 'string' ? decodeURIComponent(captures[j]) : captures[j];
1990 if (key) {
2000 req.moduleParams[key] = val;
201 } else {
202 // Comes from custom regex, no key
203 // req.moduleParams["regex"] = val;
204 }
205 }
206
207 // Convert any url parameters if we are not a .* match
20810 if (requestUrl.query && !allPages) {
2092 for (param in requestUrl.query) {
2100 if (requestUrl.query.hasOwnProperty(param)) {
2110 req.moduleParams[param] = requestUrl.query[param];
212 }
213 }
214 }
215
216 // Store the params for use outside the router
21710 res.params = res.params || {};
21810 calipso.lib._.extend(res.params, req.moduleParams);
219
220 // Set if we should cache this block - do not cache by default, do not cache admins
22110 var cacheBlock = res.renderedBlocks.contentCache[block] = route.cache && !isAdmin;
22210 var cacheEnabled = calipso.config.get('performance:cache:enabled');
223
22410 if (route.block && cacheBlock && cacheEnabled) {
225
2260 var cacheKey = calipso.cacheService.getCacheKey(['block', route.block], res.params);
227
2280 calipso.cacheService.check(cacheKey, function(err, isCached) {
2290 if (isCached) {
230 // Set the block from the cache, set layout if needed
2310 res.renderedBlocks.getCache(cacheKey, route.block, function(err, layout) {
2320 if (layout) res.layout = layout;
2330 group()(err);
234 });
235 } else {
236
237 // Execute the module route function and cache the result
2380 self.routeFn(req, res, route, group());
239
240 }
241 });
242
243 } else {
244
24510 self.routeFn(req, res, route, group());
246
247 }
248
249 }
250
251 }
252
253 },
254
255 function allMatched(err) {
256
257 // Once all functions have been called, log the error and pass it back up the tree.
25816 if (err) {
259 // Enrich the error message with info on the module
260 // calipso.error("Error in module " + this.moduleName + ", of " + err.message);
2610 err.message = err.message + " Calipso Module: " + self.moduleName;
262 }
263
264 // Emit routed event
26516 next(err, self.moduleName);
266
267 });
268
269 },
270
271 // Wrapper for router
272 // This deals with legacy modules pre 0.3.0 (will remove later)
273 routeFn: function(req, res, route, next) {
274
27510 if (typeof route.fn !== "function") console.dir(route);
276
27710 var legacyRouteFn = route.fn.length === 5 ? true : false;
27810 if (legacyRouteFn) {
2790 route.fn(req, res, route.templateFn, route.block, next);
280 } else {
28110 route.fn(req, res, route, next);
282 }
283
284 }
285
286 };
287
288 };
289
290
291/**
292 * Normalize the given path string,
293 * returning a regular expression.
294 *
295 * An empty array should be passed,
296 * which will contain the placeholder
297 * key names. For example "/user/:id" will
298 * then contain ["id"].
299 *
300 * BORROWED FROM Connect
301 *
302 * @param {String} path
303 * @param {Array} keys
304 * @return {RegExp}
305 * @api private
306 */
307
3081function normalizePath(path, keys) {
3098 path = path.concat('/?').replace(/\/\(/g, '(?:/').replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional) {
3100 keys.push(key);
3110 slash = slash || '';
3120 return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format || '') + (capture || '([^/]+?)') + ')' + (optional || '');
313 }).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)');
314
3158 return new RegExp('^' + path + '$', 'i');
316}
317
318/**
319 * Exports
320 */
3211module.exports = Router;

core/Storage.js

58%
41
24
17
LineHitsSource
1/*!
2 * Calipso MongoDB Storage Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library provides a few simple functions that can be used to help manage MongoDB and Mongoose.
7 */
8
91var rootpath = process.cwd(),
10 path = require('path'),
11 events = require('events'),
12 mongoStore = require('connect-mongodb'),
13 mongoose = require('mongoose'),
14 calipso = require(path.join('..', 'calipso'));
15
161function Storage() {
17 // Store running map reduce functions
181 this.mr = {};
19}
20
21/**
22 * Check that the mongodb instance specified in the configuration is valid.
23 */
241Storage.prototype.mongoConnect = function(dbUri, checkInstalling, next) {
25
26 // Test the mongodb configuration
272 var isInstalled = calipso.config.get('installed');
28
29 // If first option is callback, ste dbUri to config value
302 if (typeof dbUri === "function") {
310 next = dbUri;
320 dbUri = calipso.config.get('database:uri');
330 checkInstalling = false;
34 }
35
36 // Check we are installing ...
372 if (checkInstalling) {
380 var db = mongoose.createConnection(dbUri, function(err) {
390 next(err, false);
40 });
410 return;
42 }
43
442 if (isInstalled) {
45
46 // Always disconnect first just in case any left overs from installation
472 mongoose.disconnect(function() {
48
49 // TODO - what the hell is going on with mongoose?
502 calipso.db = mongoose.createConnection(dbUri, function(err) {
51
522 if (err) {
53
540 calipso.error("Unable to connect to the specified database ".red + dbUri + ", the problem was: ".red + err.message);
550 mongoose.disconnect(function() {
560 return next(err, false);
57 });
58
59 } else {
60
612 calipso.silly("Database connection to " + dbUri + " was successful.");
62
63 // Replace the inmemory session with mongodb backed one
642 var foundMiddleware = false, mw;
65
662 calipso.app.stack.forEach(function(middleware, key) {
676 if (middleware.handle.tag === 'session') {
682 foundMiddleware = true;
692 var maxAge = calipso.config.get('session:maxAge');
702 if (maxAge) {
710 try {
720 maxAge = Number(maxAge) * 1000;
73 }
74 catch (e) {
750 calipso.error('MaxAge value ' + maxAge + ' is not a numeric string');
760 maxAge = undefined;
77 }
78 }
792 mw = calipso.lib.express.session({
80 secret: calipso.config.get('session:secret'),
81 store: calipso.app.sessionStore = new mongoStore({
82 db: calipso.db.db
83 }),
84 cookie: { maxAge: maxAge }
85 });
862 mw.tag = 'session';
872 calipso.app.stack[key].handle = mw;
88 }
89 });
90
912 if (!foundMiddleware) {
920 return next(new Error("Unable to load the MongoDB backed session, please check your session and db configuration"), false);
93 }
94
952 return next(null, true);
96
97 }
98 });
99 });
100
101 } else {
102
1030 calipso.silly("Database connection not attempted to " + dbUri + " as in installation mode.");
104
105 // Create a dummy connection to enable models to be defined
1060 calipso.db = mongoose.createConnection('');
107
1080 next(null, false);
109
110 }
111
112};
113
1141module.exports = new Storage();

core/Table.js

70%
65
46
19
LineHitsSource
1/*!
2 *
3 * Calipso Table Rendering Library
4 *
5 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
6 * MIT Licensed
7 *
8 * Loaded into calipso as a plugin, used to simplify the rendering of tabular data.
9 * Including things such as rendering table sorting elements etc.
10 * TODO: validation, redisplay of submitted values
11 *
12 */
13
141var rootpath = process.cwd() + '/',
15 path = require('path'),
16 calipso = require(path.join('..', 'calipso')),
17 qs = require('qs'),
18 pager = require(path.join(rootpath, 'utils/pager')),
19 merge = require('connect').utils.merge;
20
21// Global variable (in this context) for translation function
221var t;
23
24/**
25 * The default calipso table object, with default configuration values.
26 * Constructor
27 */
28
291function CalipsoTable() {
30
31 //TODO Allow over-ride
32}
33
34/**
35 * Export an instance of our table object
36 */
371module.exports = new CalipsoTable();
38
39
40/**
41 * Table Renderer, controls the overall creation of the tablle based on a form json object passed
42 * in as the first parameter. The structure of this object is as follows:
43 *
44 * table
45 * id : Unique ID that will become the form ID.
46 * title : Title to show at the top of the form.
47 * cls : css class
48 * columns [*] : Form fields array - can be in form or section.
49 * label : Label for form field.
50 * name : Name of form element to be passed back with the value.
51 * type : Type of element, based on the form functions defined below.
52 * sortable : true / false
53 * fn : Function to apply to the row
54 * data [*] : Array of buttons to be rendered at the bottom of the form.
55 * view : COntrols display of this form
56 * pager : show pager
57 * from : from page
58 * to : to page
59 * url : base url for links
60 * sort : {} of sort field name:dir (asc|desc)
61 *
62 * This is synchronous so that it can be called from views.
63 *
64 * @param item : the json object representing the table
65 * @param req : The request object
66 */
671CalipsoTable.prototype.render = function(req, item) {
68
69 // Store local reference to the request for use during translation
701 t = req.t;
71
721 return (
73 this.start_table(item) + this.render_headers(item) + this.render_data(item, req) + this.end_table(item) + this.render_pager(item, item.view.url));
74
75};
76
77/**
78 * Render the initial table tag
79 *
80 * @param form
81 * @returns {String}
82 */
831CalipsoTable.prototype.start_table = function(table) {
841 return ('<table id="' + table.id + '"' + (table.cls ? ' class="' + table.cls + '"' : "") + '>');
85};
86
87/**
88 * Close the table
89 * @param table
90 * @returns {String}
91 */
921CalipsoTable.prototype.end_table = function(table) {
931 return '</table>';
94};
95
96
97/**
98 * Render headers
99 * @param table
100 * @returns {String}
101 */
1021CalipsoTable.prototype.render_headers = function(table) {
103
104 // If there are no columns, return
1051 if (table.columns.length === 0) throw new Error("You must define columns to render a table.");
106
107 // Test
1081 var output = "<thead><tr>";
109
110 // Iterate
1111 table.columns.forEach(function(column, key) {
112
113 // set the class
114 // Check to see if we are sorting by this column
1153 var cls = getHeaderClass(table, column);
116
1173 output += "<th" + (' class="' + cls + '"') + (column.sort ? ' name="' + column.sort + '"' : (column.name ? ' name="' + column.name + '"' : "")) + ">";
1183 output += column.label;
1193 output += "</th>";
120
121 });
122
1231 output += "</tr></thead>";
124
1251 return output;
126
127};
128
129/**
130 * Helper function to determine column header sort class
131 */
132
1331function getHeaderClass(table, column) {
134
135 // Default class
1363 var cls = column.cls || '';
137 // Sortable
1383 cls += column.sortable === false ? '' : 'sortable';
139
1403 if (table.view && table.view.sort && (table.view.sort[column.name] || table.view.sort[column.sort])) {
1410 cls += ' sorted-' + (table.view.sort[column.sort] || table.view.sort[column.name]);
142 } else {
143 // Leave as is
144 }
1453 return cls;
146
147}
148
149/**
150 * Convert a sortBy parameter into mongo sort queries
151 */
1521CalipsoTable.prototype.sortQuery = function(qry, sortBy) {
153
1540 if (typeof sortBy === 'string') sortBy = [sortBy];
1550 if (!sortBy || sortBy.length === 0) return qry;
156
1570 sortBy.forEach(function(sort) {
1580 var sortArr = sort.split(",");
1590 if (sortArr.length === 2) {
1600 var dir = sortArr[1] === 'asc' ? 1 : (sortArr[1] === 'desc' ? -1 : 0);
1610 qry = qry.sort(sortArr[0], dir);
162 }
163 });
164
1650 return qry;
166};
167
168
169/**
170 * Convert a sort by form param into a view sort object
171 */
1721CalipsoTable.prototype.parseSort = function(sortBy) {
173
1740 var options = {};
175
1760 if (typeof sortBy === 'string') sortBy = [sortBy];
1770 if (!sortBy || sortBy.length === 0) return options;
178
1790 sortBy.forEach(function(sort) {
1800 var sortArr = sort.split(",");
1810 if (sortArr.length === 2) {
1820 options[sortArr[0]] = sortArr[1];
183 }
184 });
185
1860 return options;
187};
188
189
190/**
191 * Render headers
192 * @param table
193 * @returns {String}
194 */
1951CalipsoTable.prototype.render_data = function(table, req) {
196
197 // If there are no columns, return
1981 if (table.columns.length === 0) throw new Error("You must define columns to render a table.");
199
200 // Test
2011 var output = "<tbody>";
202
203 // Iterate
2041 table.data.forEach(function(row) {
2052 output += "<tr>";
206 // Iterate over the columns
2072 table.columns.forEach(function(column) {
2086 output += "<td>";
2096 if (column.name in row) {
2106 if (typeof column.fn === "function") {
2110 output += column.fn(req, row);
212 } else {
2136 output += row[column.name];
214 }
215 } else {
2160 output += "Invalid: " + column.name;
217 }
2186 output += "</td>";
219 });
2202 output += "</tr>";
221 });
222
2231 return output + "</tbody>";
224
225};
226
227/**
228 * Render headers
229 * @param table
230 * @returns {String}
231 */
2321CalipsoTable.prototype.render_pager = function(table, url) {
233
234 // Test
2351 var output = "";
236
2371 if (table.view && table.view.pager) {
2381 output += pager.render(table.view.from, table.view.limit, table.view.total, url);
239 }
240
2411 return output;
242
243};

core/Themes.js

65%
238
155
83
LineHitsSource
1/*!
2 * Calipso theme library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This library provides all of the template loading, caching and rendering functions used by Calipso.
8 *
9 * The idea is that modules only ever provide generic, unstyled html (or json), where all layout and styling is completely
10 * controlled by the theme. Themes should be able to be reloaded on configuration change, and the theme engine
11 * will watch for changes to templates (based on config) to speed up development.
12 *
13 * Additionally, configuration of layouts has been extracted out into a theme configuration file, enabling control of
14 * the 'wiring' to an extent.
15 *
16 * Decision was made to not use the default express view renderers as it didn't give enough control over caching templates,
17 * Interacting with the view libraries directly,
18 *
19 * Borrowed liberally from Connect / ExpressJS itself for this, thanks for the great work!
20 *
21 */
22
23/**
24 * Includes
25 */
26
271var rootpath = process.cwd() + '/',
28 path = require('path'),
29 calipso = require(path.join('..', 'calipso')),
30 fs = require('fs'),
31 utils = require('connect').utils,
32 merge = utils.merge;
33
34/**
35 * The theme object itself, instantiated within calipso
36 */
371module.exports.Theme = function(theme, next) {
38
39 // Defaults
402 var themeName = theme.name;
412 var themePath = theme.path;
42
43 /**
44 * Load a theme
45 */
462 loadTheme(themeName, themePath, function(err, themeConfig) {
47
482 if (err) {
490 next(err);
500 return;
51 }
52
532 cacheTheme(themeConfig, themePath, function(err, themeCache) {
54
552 if (err) {
560 next(err);
570 return;
58 }
59
60 // Load the theme configuration file.
612 var theme = {
62 theme: themeName,
63 cache: themeCache,
64 config: themeConfig,
65 compileTemplate: function(data, templatePath, templateExtension) {
66 // expose private function for module to use
672 return compileTemplate(data, templatePath, templateExtension);
68 },
69
70 // Render a module
71 // Changed in 0.1.1 to be asynch
72 renderItem: function(req, res, template, block, options, next) {
73
741 var output = "";
75
761 if (template) {
77
781 var themeOptions = createOptions(req, res, options);
79
801 if (typeof template === 'function') {
811 try {
821 output = template.call({}, themeOptions);
83 } catch (ex) {
840 output = "Block: " + block + " failed to render because " + ex.message + ex.stack;
85 }
86
87 } else {
88 // Assume template is processed HTML
890 output = template;
90 }
91
921 if (block) {
93 // Store the block and layout
941 res.renderedBlocks.set(block, output, res.layout, res.params, next);
95 } else {
96 // Just return back to the calling function
970 next(null, output);
98 }
99
100 }
101
102 },
103 render: function(req, res, next) {
104
1053 var cache = this.cache, theme = this, layout = res.layout ? res.layout : "default", content, themeOptions, err;
106
1073 calipso.silly("Using layout " + layout);
108
1093 if (!theme.config.layouts[layout]) {
1100 layout = "default";
1110 if (!theme.config.layouts[layout]) {
1120 calipso.error("Default layout is not defined within the current theme, exiting.");
1130 res.send("");
1140 return;
115 }
116 }
117
1183 processTheme(req, res, layout, theme, function(err) {
119
120 // If something went wrong ...
1213 if (err) {
1220 next(err, null);
1230 return;
124 }
125
126 // Now, process the layout template itself
1273 themeOptions = createOptions(req, res, res.bufferedOutput);
128
1293 try {
1303 content = theme.cache[layout].template.call({}, themeOptions);
131 } catch (ex) {
1320 err = ex;
133 }
134
1353 return next(err, content);
136
137
138 });
139
140 },
141 getLayoutsArray: function() {
142
1430 var theme = this;
1440 var layouts = [];
1450 for (var layout in theme.config.layouts) {
1460 layouts.push(layout);
147 }
1480 return layouts;
149
150 }
151
152 };
153
1542 next(null, theme);
155
156 });
157
158
159 });
160
161};
162
163/**
164 *Process a theme section
165 */
166
1671function processSection(req, res, section, sectionPath, layoutConfig, theme, next) {
168
16918 var themeOptions, sectionCache = theme.cache[sectionPath];
170
171 // Check the theme cache
17218 if (!sectionCache) {
1730 calipso.error("Unable to find template for " + sectionPath);
1740 next();
1750 return;
176 }
177
17818 var blockData = "";
179
18018 if (!sectionCache.template) {
181 // Use the default
1820 sectionPath = "default." + section;
1830 sectionCache = theme.cache[sectionPath];
184 }
185
186 // should there be more than just these two error codes?
187 // if more than just these two, then this would have to happen later on:
188 // templates.push({name:"500", templatePath:"templates/500.html"});
189 // Override with a 404 (not found) page
19018 if (section === "body" && res.statusCode === 404) {
1911 if (!theme.cache.hasOwnProperty("404")) {
1920 localNext(new Error("You must define a 404 template in the error folder e.g. error/404.html"));
1930 return;
194 }
1951 sectionCache = theme.cache["404"];
196 }
197
198 // Override with a 403 (no permissions) page
19918 if(section === "body" && res.statusCode === 403) {
2000 if(!theme.cache.hasOwnProperty("403")) {
2010 localNext(new Error("You must define a 403 template in the error folder e.g. error/403.html"));
2020 return;
203 }
2040 sectionCache = theme.cache["403"];
205 }
206
207 // Override with a 500 (error) page
20818 if (section === "body" && res.statusCode === 500) {
2090 if (!theme.cache.hasOwnProperty("500")) {
2100 localNext(new Error("You must define a 500 template in the error folder e.g. error/500.html"));
2110 return;
212 }
2130 sectionCache = theme.cache["500"];
2140 blockData = res.errorMessage ? res.errorMessage : "";
215 }
216
217 // Retrieve any backing function
21818 var sectionCacheFn = sectionCache.fn;
219
220 // Clear any buffered output for this section
22118 res.bufferedOutput[section] = "";
222
223 // Get the basic theme options
22418 themeOptions = createOptions(req, res, {
225 blockData: blockData
226 });
227
228 // Add any custom functions
22918 if (typeof sectionCacheFn === "function") {
230
2318 sectionCacheFn(req, themeOptions, function(err, fnOptions) {
232
2338 if (err) {
2340 err.xMessage = "Issue executing the theme function for section " + section + ", check " + sectionPath.replace(".", "/") + ".js";
2350 localNext(err);
2360 return;
237 }
238
2398 themeOptions = merge(themeOptions, fnOptions);
2408 try {
2418 res.bufferedOutput[section] += sectionCache.template.call({}, themeOptions);
2428 localNext();
243 } catch (ex) {
244 // Augment the exception
2450 ex.xMessage = "Issue processing theme section " + section + ", path: " + sectionPath;
2460 localNext(ex);
247 }
248
249 });
250
251 } else {
25210 try {
25310 res.bufferedOutput[section] += sectionCache.template.call({}, themeOptions);
25410 localNext();
255 } catch (ex) {
2560 ex.xMessage = "Issue processing theme section: " + section + ", theme: " + sectionPath;
2570 localNext(ex);
258 }
259
260 }
261
262 // Local next function to enable proxying of callback
263
26418 function localNext(err) {
26518 next(err);
266 }
267
268
269}
270
271/**
272 * Copy the current block data over to options to render
273 * @param res
274 * @param config
275 */
276
2771function processTheme(req, res, layout, theme, next) {
278
2793 var layoutConfig, copyConfig, copySection, sectionExists, disable, sections = [],
280 section;
281
2823 delete res.bufferedOutput;
2833 res.bufferedOutput = {};
284
285 // Scan through each layout
2863 try {
2873 layoutConfig = theme.config.layouts[layout].layout;
288 } catch (ex) {
2890 next(ex.message);
2900 return;
291 }
292
293 // Check to see if this layout copies default
2943 if (layoutConfig.copyFrom && layout != "default") {
295
2961 copyConfig = theme.config.layouts[layoutConfig.copyFrom].layout;
2971 layoutConfig.sections = layoutConfig.sections || {};
298
299 // Copy over any missing sections from default
3001 for (copySection in copyConfig.sections) {
301
3026 sectionExists = layoutConfig.sections && layoutConfig.sections[copySection];
3036 disable = layoutConfig.sections && layoutConfig.sections[copySection] && layoutConfig.sections[copySection].disable;
3046 if (!sectionExists && !disable) {
3056 layoutConfig.sections[copySection] = copyConfig.sections[copySection];
3066 layoutConfig.sections[copySection].layout = "default"; // Flag override as below
307 }
308
309 }
310
311 }
312
313 // Create a section array
3143 for (section in layoutConfig.sections) {
31518 disable = layoutConfig.sections[section].disable;
31618 if (!disable) {
31718 sections.push(section);
318 }
319 }
3203 var totalCount = sections.length;
3213 var totalDone = 0;
322
323 // Now, process all the sections
324 // This is done via a localNext to give us full control
325 // and better ability to debug
326
3273 function localNext(err) {
32818 totalDone += 1;
329
33018 if (totalDone == totalCount) {
3313 next();
332 }
333
334 }
335
3363 for (section in layoutConfig.sections) {
337
338 // Check to see if we are overriding
33918 var currentSection = section;
34018 var layoutOverride = layoutConfig.sections[section].layout;
34118 var sectionPath = layoutOverride ? layoutOverride + "." + section : layout + "." + section;
34218 var cache = layoutConfig.sections[section].cache;
34318 var params = layoutConfig.sections[section].varyParams;
34418 var cacheEnabled = calipso.config.get('performance:cache:enabled');
34518 var isAdmin = req.session.user && req.session.user.isAdmin;
346
34718 disable = layoutConfig.sections[section].disable;
348
349 // Sections are cacheable
35018 if (!disable) {
35118 if (cache && cacheEnabled && !isAdmin) {
3520 var keys = [layout, 'section', currentSection];
3530 var cacheKey = calipso.cacheService.getCacheKey(keys, params);
3540 sectionCache(req, res, cacheKey, section, sectionPath, layoutConfig, theme, localNext);
355 } else {
35618 processSection(req, res, section, sectionPath, layoutConfig, theme, localNext);
357 }
358 }
359
360 }
361
362}
363
364/**
365 * Interact with sections via the cache
366 */
367
3681function sectionCache(req, res, cacheKey, section, templateName, layoutConfig, theme, next) {
369
3700 calipso.cacheService.check(cacheKey, function(err, isCached) {
3710 if (isCached) {
3720 calipso.silly("Cache hit for " + cacheKey + ", section " + section);
3730 calipso.cacheService.get(cacheKey, function(err, cache) {
3740 if (!err) {
3750 res.bufferedOutput[section] = cache.content;
376 }
3770 next(err);
378 });
379 } else {
3800 calipso.silly("Cache miss for " + cacheKey + ", section " + section);
3810 processSection(req, res, section, templateName, layoutConfig, theme, function(err) {
3820 if (!err) {
3830 var content = res.bufferedOutput[section];
3840 calipso.cacheService.set(cacheKey, {
385 content: content
386 }, null, next);
387 } else {
3880 next(err);
389 }
390 });
391 }
392 });
393}
394
395
396/**
397 * Load a theme
398 */
399
4001function loadTheme(theme, themePath, next) {
401
4022 var themeFile = calipso.lib.path.join(themePath, "theme.json");
403
4042 (fs.exists || path.exists)(themeFile, function(exists) {
4052 if(exists) {
4062 fs.readFile(themeFile, 'utf8', function(err, data) {
4072 if (!err) {
4082 var jsonData;
4092 try {
4102 jsonData = JSON.parse(data);
4112 next(null, jsonData);
412 } catch (ex) {
4130 next(new Error("Error parsing theme configuration: " + ex.message + " stack, " + ex.stack));
414 }
415 } else {
4160 next(err);
417 }
418 });
419 } else {
4200 next(new Error("Can't find specified theme configuration " + themeFile));
421 }
422 });
423}
424
425/**
426 * Load all of the theme templates into the theme
427 * @param theme
428 */
429
4301function cacheTheme(themeConfig, themePath, next) {
431
4322 var templates = [],
433 templateCache = {},
434 layout, layoutConfig, section, template, module, templateFiles, errorCodeTemplates;
435
436 // Scan through each layout
4372 if (themeConfig) {
438
4392 for (layout in themeConfig.layouts) {
440
441 // Scan through each layout
4426 layoutConfig = themeConfig.layouts[layout].layout;
443
444 // Add the layout template
4456 templates.push({
446 name: layout,
447 templatePath: calipso.lib.path.join("templates", layoutConfig.template)
448 });
449
450
451 // Add the templates
4526 for (section in layoutConfig.sections) {
45314 template = layoutConfig.sections[section].template;
45414 if (template) {
45514 templates.push({
456 name: layout + "." + section,
457 templatePath: calipso.lib.path.join("templates", layout, template)
458 });
459 }
460 }
461
462 // Check to see if the theme overrides any module templates
4636 if (layoutConfig.modules) {
4640 for (module in layoutConfig.modules) {
4650 for (template in layoutConfig.modules[module]) {
4660 loadModuleOverrideTemplate(templateCache, module, template, path.join(themePath, layoutConfig.modules[module][template]));
467 }
468 }
469 }
470 }
471
472 // Push error message templates
4732 templateFiles = calipso.lib.fs.readdirSync(calipso.lib.path.join(themePath, 'templates', 'error'));
4742 errorCodeTemplates = calipso.lib._.select(templateFiles, function(filename) {
475 // Select files that start with 3 digits, indicating an error code
4764 return filename.match(/^\d{3}./);
477 });
478
4792 calipso.lib._.each(errorCodeTemplates, function(filename) {
4804 templates.push({
481 name: filename.match(/^\d{3}/)[0],
482 templatePath: calipso.lib.path.join("templates", "error", filename)
483 });
484 });
485
4862 var templateIterator = function(templateName, cb) {
48724 loadTemplate(templateCache, templateName, themePath, cb);
488 };
489
4902 calipso.lib.async.map(templates, templateIterator, function(err, result) {
4912 if (err) {
492 // May not be a problem as missing templates default to default
4930 calipso.error("Error loading templates, msg: " + err.message + ", stack: " + err.stack);
4940 next(err);
495 } else {
4962 next(null, templateCache);
497 }
498 });
499
500 }
501
502}
503
504/**
505 * Load a template that overrides a module template
506 * fired from cacheTheme(),
507 */
508
5091function loadModuleOverrideTemplate(templateCache, module, template, path) {
510
5110 var templatePath = path,
512 templateExtension = templatePath.match(/([^\.]+)$/)[0],
513 templateFn = fs.readFileSync(templatePath, 'utf8'),
514 templateFnCompiled = compileTemplate(templateFn, templatePath, templateExtension);
515
516 // Initialise the objects
5170 templateCache.modules = templateCache.modules || {};
5180 templateCache.modules[module] = templateCache.modules[module] || {};
5190 templateCache.modules[module].templates = templateCache.modules[module].templates || {};
520
521 // allow hook for listening for module events?
522 // Load the function
5230 templateCache.modules[module].templates[template] = templateFnCompiled;
524
525}
526
527/**
528 * Load a template
529 */
530
5311function loadTemplate(templateCache, template, themePath, next) {
532
533 // Reset / default
53448 if (!templateCache[template.name]) templateCache[template.name] = {};
535
536 // Template paths and functions
53724 var templatePath = calipso.lib.path.join(themePath, template.templatePath),
538 templateExtension = template.templatePath.match(/([^\.]+)$/)[0],
539 templateFnPath = calipso.lib.path.join(themePath, template.templatePath.replace("." + templateExtension, ".js"));
540
54124 (fs.exists || path.exists)(templatePath,function(exists) {
542
54324 if (exists) {
544
54524 var templateData = '';
546
54724 try {
54824 templateData = fs.readFileSync(templatePath, 'utf8');
549 } catch (err) {
5500 calipso.error('Failed to open template ' + templatePath + ' ...');
551 }
552
55324 if (calipso.config.get('performance:watchFiles')) {
554
55524 try {
55624 fs.unwatchFile(templatePath);
55724 fs.watchFile(templatePath, {
558 persistent: true,
559 interval: 200
560 }, function(curr, prev) {
5610 loadTemplate(templateCache, template, themePath, function() {
5620 calipso.silly("Template " + templatePath + " reloaded ...");
563 });
564 });
565 } catch (ex) {
5660 calipso.error('Failed to watch template ' + templatePath + ' ...');
567 }
568
569 }
570
571 // Precompile the view into our cache
57224 templateCache[template.name].template = compileTemplate(templateData, templatePath, templateExtension);
573
574 // See if we have a template fn
57524 if ((fs.existsSync || path.existsSync)(templateFnPath)) {
576
5778 if (exists) {
5788 try {
5798 templateCache[template.name].fn = require(templateFnPath);
580 } catch (ex) {
5810 calipso.error(ex);
582 }
583
584 }
585
586 }
587
58824 return next(null, template);
589
590 } else {
591
5920 next(new Error('Path does not exist: ' + templatePath));
593
594 }
595
596 });
597
598}
599
600/**
601 * Pre-compile a template based on its extension.
602 * If the required view engine does not exist, exit gracefully and let
603 * them know that this is the case.
604 */
605
6061function compileTemplate(template, templatePath, templateExtension) {
607
60826 var compiledTemplate = function() {},
609 templateEngine;
61026 var options = {
611 filename: templatePath
612 };
613
614 // If we get html, replace with ejs
61552 if (templateExtension === "html") templateExtension = "ejs";
616
617 // Load a template engine based on the extension
61826 try {
61926 templateEngine = require(templateExtension);
620 } catch (ex) {
6210 calipso.warn("No view rendering engine exists that matches: " + templateExtension + ", so using EJS!");
6220 templateEngine = require("ejs");
623 }
624
625 // Return our compiled template
62626 try {
62726 compiledTemplate = templateEngine.compile(template, options);
628 } catch (ex) {
6290 calipso.error("Error compiling template : " + templatePath + ", message: " + ex.message);
630 }
631
63226 return compiledTemplate;
633
634}
635
636/**
637 * Merge options together
638 */
639
6401function createOptions(req, res, options) {
641
642 // Merge options with helpers
64322 options = merge(options, req.helpers);
644
645 // Merge options with application data
64622 if (calipso.data) {
64722 options = merge(options, calipso.data);
648 }
649
65022 return options;
651
652}

core/Utils.js

37%
27
10
17
LineHitsSource
1/**
2 * General utility methods
3 */
41var _ = require('underscore');
5
61module.exports = {
7 /**
8 * Basically like getProperty, different return
9 * @method hasProperty
10 * @param ns {string} A period delimited string of the namespace to find, sans root object
11 * @param obj {object} The root object to search
12 * @return {boolean} true if property exists, false otherwise
13 */
14 hasProperty: function(ns, obj) {
152 if (!ns) {
160 return obj;
17 }
182 var nsArray = ns.split('.'),
19 nsLen = nsArray.length,
20 newNs;
21
22 // if nsLen === 0, then obj is just returned
232 while (nsLen > 0) {
246 newNs = nsArray.shift();
256 if (obj[newNs]) {
264 obj = obj[newNs];
27 } else {
282 return false;
29 }
304 nsLen = nsArray.length;
31 }
320 return true;
33 },
34 /**
35 * Find a namespaced property
36 * @method getProperty
37 * @param ns {string} A period delimited string of the namespace to find, sans root object
38 * @param obj {object} The root object to search
39 * @return {object} the object, either the namespaced obejct or the root object
40 */
41 getProperty: function(ns, obj) {
420 if (!ns) {
430 return obj;
44 }
450 var nsArray = ns.split('.'),
46 nsLen = nsArray.length,
47 newNs;
48
49 // if nsLen === 0, then obj is just returned
500 while (nsLen > 0) {
510 newNs = nsArray.shift();
520 if (obj[newNs]) {
530 obj = obj[newNs];
54 }
550 nsLen = nsArray.length;
56 }
570 return obj;
58 },
59
60 /**
61 * Simple mongo object copier, used to do a shallow copy of objects
62 */
63 copyMongoObject: function(object, copy, schema) {
64
650 var fields = _.keys(schema.paths);
660 _.each(fields, function(key) {
670 if (key !== '_id') copy.set(key, object.get(key));
68 });
69
70 },
71 escapeHtmlQuotes: function (string) {
720 if (string && string.replace) {
730 return string.replace(/\"/g, '&quot;').replace(/\'/g, '&apos;');
74 }
75 else {
760 return string;
77 }
78 }
79};

client/Client.js

100%
44
44
0
LineHitsSource
1/**
2 * This library provides a wrapper to enable modules to load javascript and styles into an
3 * array, that can then be rendered into a theme in the appropriate location.
4 *
5 * Styles and JS are all indexed by key, so you could write functions that over-wrote them in the theme as the
6 * last update will always stick.
7 *
8 */
9
101var rootpath = process.cwd() + '/',
11 path = require('path'),
12 calipso = require(path.join('..', 'calipso')),
13 fs = require('fs');
14
15/**
16 * Client Object - handle CSS and JS loading for modules out to themes
17 */
181var Client = module.exports = function Client(options) {
19
2014 this.options = options || {
21 'minified-script': 'media/calipso-main'
22 };
23
2414 this.scripts = [];
2514 this.styles = [];
26
27 // Shortcuts to core, must be included somewhere (module or theme) to be rendered
2814 this.coreScripts = {
29 'jquery': {key:'jquery', url:'jquery-1.8.2.min.js', weight: -100},
30 'calipso': {key:'calipso', url:'calipso.js', weight: -50}
31 }
32
33 };
34
351Client.prototype.addScript = function(options) {
36
378 var self = this;
38
39 // Convert our options over with flexible defaults
408 if (typeof options === "string") {
412 if (this.coreScripts[options]) {
421 options = this.coreScripts[options];
43 } else {
441 options = {
45 name: options,
46 url: options,
47 weight: 0
48 };
49 }
50 }
5112 if (!options.name) options.name = options.url;
52
53 // Add the script
548 self._add('scripts', options.name, options);
55
56};
57
58/**
59 * Create simple list of all client JS
60 */
611Client.prototype.listScripts = function(next) {
62
63 // TODO - this should be updated to use LABjs by default (?)
641 var self = this;
651 var output = "<!-- Calipso Module Scripts -->";
661 self.scripts.forEach(function(value) {
672 output += '\r\n<script title="' + value.name + '" src="' + value.url + '"></script>';
68 });
691 output += "<!-- End of Calipso Module Scripts -->";
701 next(null, output);
71
72};
73
741Client.prototype.addStyle = function(options) {
75
765 var self = this;
77
78 // Convert our options over with flexible defaults
795 if (typeof options === "string") {
801 options = {
81 name: options,
82 url: options,
83 weight: 0
84 };
85 }
868 if (!options.name) options.name = options.url;
87
88 // Add the script
895 self._add('styles', options.name, options);
90
91};
92
93/**
94 * Compile together all of the client side scripts
95 */
961Client.prototype.listStyles = function(next) {
97
98 // TODO - this should be updated to use LABjs by default (?)
991 var self = this;
1001 var output = "<!-- Calipso Module Styles -->";
101
1021 self.styles.forEach(function(value) {
1032 output += '\r\n<link rel="stylesheet" title="' + value.name + '" href="' + value.url + '"/>';
104 });
1051 output += "<!-- End of Calipso Module Styles -->";
1061 next(null, output);
107
108};
109
110
111/**
112 * Helper to add unique elements to an array
113 */
1141Client.prototype._add = function(arrName, name, options) {
115
11613 var self = this;
11713 self[arrName] = self[arrName] || [];
118
119 // Find first match
12013 var found = calipso.lib._.find(self[arrName], function(value) {
1213 return (value.name && value.name === name) ? true : false;
122 });
123
12413 if (found) {
125 // Replace - this means we never get duplicates (e.g. of JQuery, JQueryUI)
1261 self[arrName].splice(found, 1, options);
127 } else {
128 // Push
12912 self[arrName].push(options);
130 }
131
132 // Sort - TODO, this can probably be more efficient by placing the new item smarter
13313 self[arrName].sort(function(a, b) {
1342 return a.weight > b.weight;
135 });
136
137};
138
139
140/**
141 * Compile together all of the client side scripts
142 * TODO - this is currently not used, needs to be worked on and thought through.
143 *
144Client.prototype.compile = function(next) {
145
146 var self = this;
147
148 try {
149
150 var scriptFile = path.join(rootpath,self.options.script),
151 scriptStream = fs.createWriteStream(scriptFile, {'flags': 'a'});
152
153 } catch(ex) {
154
155 console.dir(ex);
156
157 }
158
159 var grabFile = function(item, callback) {
160
161 // TODO - allow referential
162 var filePath = path.join(rootpath, item.url);
163
164 // Check to see if the file has changed
165 var stat = fs.lstatSync(filePath);
166
167 fs.readFile(filePath, 'utf8', function(err, contents) {
168
169 if(err) {
170
171 return callback(new Error("Unable to locate file for ClientJS creation: " + filePath));
172
173 } else {
174
175 var drain;
176 drain = scriptStream.write(contents);
177 callback(null, stat.mtime);
178
179 }
180 });
181
182 }
183
184 // Callback wrapper to close the streams
185 var done = function(err, data) {
186 scriptStream.end();
187 next(err, data);
188 }
189
190 // var contents = fs.readFileSync(config.out, 'utf8');
191 calipso.lib.async.mapSeries(self.scripts, grabFile, function(err, scripts) {
192
193 if(err) return done(err);
194
195 var reduce = function(context, memo, value, index, list) {
196 return (value > memo) ? value : memo;
197 };
198
199 var maxmtime = calipso.lib._.reduce(scripts, reduce);
200
201 console.dir(maxmtime);
202
203 var script = '<!-- ' + maxmtime + ' -->'
204
205 done(null, script);
206
207 })
208
209}
210**/

/Users/andy/calipso/test/helpers/calipsoHelper.js

85%
49
42
7
LineHitsSource
1/**
2 * Setup the bare minimum required for a fully functioning 'calipso' object
3 */
41var jsc = require('jscoverage'),
5 require = jsc.require(module), // rewrite require function
6 calipso = require('./require', true)('calipso'),
7 path = require('path'),
8 fs = require('fs'),
9 colors = require('colors'),
10 rootpath = process.cwd() + '/',
11 Config = require('./require', true)('core/Configuration'),
12 http = require('http'),
13 mochaConfig = path.join(rootpath,'tmp','mocha.json');
14
15// Create the tmp folder if it doesnt' exist
163try { fs.mkdirSync(path.join(rootpath,'tmp')) } catch(ex) {};
17
18/**
19 * Mock application object
20 */
211function MockApp(next) {
22
231 var self = this;
24
25 // Configuration - always start with default
261 var defaultConfig = path.join(rootpath, 'test', 'helpers', 'defaultConfig.json');
27
281 var statusMsg = '\r\nBase path: '.grey + rootpath.cyan + '\r\nUsing config: '.grey + defaultConfig.cyan + '\r\nIn environment: '.grey + (process.env.NODE_ENV || 'development').cyan;
291 if(!process.env.CALIPSO_COV) console.log(statusMsg);
30
31 // Always delete any left over config
322 try { fs.unlinkSync(mochaConfig); } catch(ex) { /** ignore **/ }
33
34 // Create new
351 self.config = new Config({
36 'env': 'mocha',
37 'path': path.join(rootpath, 'tmp'),
38 'defaultConfig': defaultConfig
39 });
40
41 // Middleware helpers
421 self.mwHelpers = {
430 staticMiddleware: function() { return {} },
440 stylusMiddleware: function() { return {} }
45 }
46
47 // Pseudo stack - only middleware that is later overloaded
481 self.stack = [{
49 handle: {
50 name: 'sessionDefault',
51 tag: 'session'
52 }
53 }, {
54 handle: {
55 name: 'static',
56 tag: 'theme.static'
57 }
58 }, {
59 handle: {
60 name: 'stylus',
61 tag: 'theme.stylus'
62 }
63 }];
64
65 // Initialise and return
661 self.config.init(function (err) {
67
681 if(err) console.log('Config error: '.grey + err.message.red);
691 if(!process.env.CALIPSO_COV) console.log('Config loaded: '.grey + self.config.file.cyan);
701 next(self);
71
72 })
73
74}
75
76/**
77 * Test permissions
78 */
791calipso.permission.Helper.addPermission("test:permission", "Simple permission for testing purposes.");
801calipso.permission.Helper.addPermissionRole("test:permission", "Test");
81
82/**
83 * Setup logging
84 */
851var loggingConfig = {
86 "console": {
87 "enabled": false,
88 "level": "error",
89 "timestamp": true,
90 "colorize": true
91 }
92};
931calipso.logging.configureLogging(loggingConfig);
94
95/**
96 * Request
97 */
981require('express/lib/request');
991require('express/lib/response');
100
1011var Request = http.IncomingMessage,
102 Response = http.OutgoingMessage;
103
1041Request.prototype.t = function (str) {
10514 return str
106};
107
1081function CreateRequest(url, method, session) {
1093 var req = new Request();
1103 req.method = method || 'GET';
1113 req.url = url || '/';
1123 req.session = session || {};
1133 req.flashMsgs = [];
1143 req.flash = function (type, msg) {
1150 req.flashMsgs.push({
116 type: type,
117 msg: msg
118 });
119 }
1203 return req;
121}
122
123
1241function CreateResponse() {
1251 var res = new Response();
1261 res.redirectQueue = [];
1271 res.redirect = function (url) {
1280 res.redirectQueue.push(url);
1290 res.finished = false;
130 }
1311 res.end = function(content, type) {
1320 res.body = content;
133 }
1341 res.send = function(content) {
1350 res.body = content;
136 }
1371 return res;
138}
139/**
140 * Default requests and users
141 */
1421var requests = {
143 anonUser: CreateRequest('/', 'GET'),
144 testUser: CreateRequest('/', 'GET', {
145 user: {
146 isAdmin: false,
147 roles: ['Test']
148 }
149 }),
150 adminUser: CreateRequest('/secured', 'GET', {
151 user: {
152 isAdmin: true,
153 roles: ['Administrator']
154 }
155 })
156}
157
158/**
159 * Initialise everything and then export
160 */
1611new MockApp(function (app) {
1621 module.exports = {
163 app: app,
164 calipso: calipso,
165 testPermit: calipso.permission.Helper.hasPermission("test:permission"),
166 requests: requests,
167 response: CreateResponse()
168 }
169})
\ No newline at end of file + padding-left: 15px; + line-height: 15px; + white-space: pre; + font: 12px monaco, monospace; +} + +code .comment { + color: #ddd +} + +code .init { + color: #2F6FAD +} + +code .string { + color: #5890AD +} + +code .keyword { + color: #8A6343 +} + +code .number { + color: #2F6FAD +} + + + +

Coverage

+ + +
+
63%
+
1966
+
1242
+
724
+
+
+

core/Configuration.js

+ +
+
96%
+
58
+
56
+
2
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/**
2 * Configuration library
3 */
4
51var rootpath = process.cwd() + '/',
6 path = require('path'),
7 step = require('step'),
8 _ = require('underscore'),
9 fs = require('fs');
10
11/**
12 * Config object, wrapper for nconf
13 * @type
14 * @options
15 */
16
171function Configuration(options) {
18
19 // Defaults
2011 this.type = options && options.type ? options.type : 'file';
2111 this.env = options && options.env ? options.env : (process.env.NODE_ENV + || 'development'); +
2211 this.path = options && options.path ? options.path : path.join(rootpath, + 'conf'); +
2311 this.defaultConfig = options && options.defaultConfig ? + options.defaultConfig : path.join(rootpath, 'lib', 'conf', 'default.json'); +
2411 this.file = path.join(this.path, this.env + '.json');
25
26 // Track if changes have been made to config
2711 this.dirty = false;
28
29}
30
311Configuration.prototype.init = function(next) {
3210 if (typeof next !== 'function') next = function(err) {
330 if (err) console.error(err.message)
34 };
3510 var Provider = require('nconf').Provider;
3610 this.nconf = new Provider();
3710 this.load(next);
38}
39
40/**
41 * Check to see if configuration for this environment
42 * doesn't exist, if so, it loads the default from default.json.
43 */
441Configuration.prototype.check = function() {
45
4611 if (!(fs.existsSync || path.existsSync)(this.file)) {
478 try {
488 var defaultFile = fs.readFileSync(this.defaultConfig);
49 // Parse it to make sure there are no errors
507 defaultFile = JSON.stringify(JSON.parse(defaultFile), true);
517 fs.writeFileSync(this.file, defaultFile);
52 } catch (ex) {
531 return ex.message;
54 }
557 return;
56 } else {
573 return;
58 }
59
60}
61
62/**
63 * Load the configuration
64 */
651Configuration.prototype.load = function(next) {
66
67 // Check if config exists for this environment or default it
6811 var checkConfig = this.check();
6911 if (!checkConfig) {
70
71 // Initialise nconf
7210 try {
7310 this.nconf.use(this.type, this);
74 } catch (ex) {
750 return next(ex);
76 }
77
7810 this.nconf.load(next);
79
80 } else {
81
821 next(new Error("Unable to load configuration defined in " + this.env + + ".json, there may be a problem with the default configuration in " + + this.defaultConfig + ", reason: " + checkConfig)); +
83
84 }
85
86}
87
88/**
89 * Get config - wrapper
90 */
911Configuration.prototype.get = function(key) {
9294 return this.nconf.get(key);
93}
94
95/**
96 * Get config for module - wrapper
97 */
981Configuration.prototype.getModuleConfig = function(moduleName, key) {
992 var moduleKey = 'modules:' + moduleName + ':config' + (key ? ':' + key : ''); +
1002 return this.nconf.get(moduleKey);
101}
102
103
104/**
105 * Set config
106 */
1071Configuration.prototype.set = function(key, value) {
1084 this.dirty = true;
1094 this.nconf.set(key, value);
110}
111
112/**
113 * Set config for module - wrapper
114 */
1151Configuration.prototype.setModuleConfig = function(moduleName, key, value) {
1161 var moduleKey = 'modules:' + moduleName + ':config' + (key ? ':' + key : ''); +
1171 this.dirty = true;
1181 this.nconf.set(moduleKey, value);
119}
120
121/**
122 * Set default config for module - wrapper
123 */
1241Configuration.prototype.setDefaultModuleConfig = function(moduleName, config) { +
125
1261 var moduleKey = 'modules:' + moduleName + ':config';
1271 this.dirty = true;
128
129 // Extract the defaults from the config
1301 var defaultConfig = _.reduce(_.keys(config), function(memo, key) {
1311 memo[key] = config[key].
132 default;
1331 return memo;
134 }, {})
135
1361 this.nconf.set(moduleKey, defaultConfig);
137
138}
139
140/**
141 * Save config
142 */
1431Configuration.prototype.save = function(next) {
1442 this.dirty = false;
1452 this.nconf.save(next);
146}
147
148/**
149 * Set & save config
150 */
151
1521Configuration.prototype.setSave = function(key, value, next) {
1531 this.set(key, value);
1541 this.dirty = false;
1551 this.save(next);
156}
157
158/**
159 * Export the config object
160 */
1611module.exports = Configuration;
+
+

+ /Users/andy/calipso/test/helpers/require.js

+ +
+
66%
+
6
+
4
+
2
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1
2/**
3 * This helper allows us to include files that either have or haven't been marked + up by jscoverage. +
4 * All modules under test should be included via;
5 *
6 * library = require('./helpers/require')('core/Config.js');
7 *
8 * The path is always relative to the lib folder, and this approach only works + for core Calipso libraries. +
9 *
10 */
116if (process.env.CALIPSO_COV) {
126 var jsc = require('jscoverage'),
13 require = jsc.require(module); // rewrite require function
146 module.exports = function (library) {
1524 return require('../../lib-cov/' + library);
16 }
17} else {
180 module.exports = function (library) {
190 return require('../../lib/' + library);
20 }
21}
+
+

calipso.js

+ +
+
81%
+
92
+
75
+
17
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Core Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This is the core Calipso middleware that controls the bootstrapping, and core + routing functions, required for +
8 * Calipso to function. This is loaded into an Express application via:
9 *
10 * app.use(calipso.calipsoRouter(next);
11 *
12 * Further detail is contained in comments for each function and object.
13 *
14 */
151var rootpath = process.cwd() + '/',
16 path = require('path'),
17 fs = require('fs'),
18 events = require('events');
19
20// Core object
211var calipso = module.exports = {
22
23 // Router and initialisation
24 routingFn: routingFn,
25 init: init,
26
27 // Configuration exposed
28 reloadConfig: reloadConfig,
29
30 // Core objects - themes, data, modules
31 theme: {},
32 data: {},
33 modules: {}
34
35};
36
37// Load libraries in the core folder
381loadCore(calipso);
39
401function loadCore(calipso) {
41
421 fs.readdirSync(__dirname + '/core').forEach(function(library) {
4319 var isLibrary = library.split(".").length > 0 && + library.split(".")[1] === 'js', +
44 libName = library.split(".")[0].toLowerCase();
4537 if (isLibrary) calipso[libName] = require(__dirname + '/core/' + library);
46 });
47
48}
491module.exports.loaded = true;
50
51/**
52 * Calipso initialisation
53 */
54
551function init(app, initCallback) {
56
571 calipso.app = app;
58
59 // Load the calipso package.json into app.about
601 calipso.module.loadAbout(app, rootpath, 'package.json');
61
62 // config is the actual instance of loaded config, configuration is the + library. +
631 calipso.config = app.config;
64
65 // Store the callback function for later
661 calipso.initCallback = function() {
671 initCallback();
68 };
69
70 // Configure the cache
711 calipso.cacheService = calipso.cache.Cache({
72 ttl: calipso.config.get('performance:cache:ttl')
73 });
74
75 // Create our calipso event emitter
761 calipso.e = new calipso.event.CalipsoEventEmitter({maxListeners: + calipso.config.get('server:events:maxListeners')}); +
77
78 // Load configuration
791 initialiseCalipso();
80
81}
82
83/**
84 * Core router function.
85 *
86 * Returns a connect middleware function that manages the roucting
87 * of requests to modules.
88 *
89 * Expects Calipso to be initialised.
90 */
91
921function routingFn() {
93
94 // Return the function that manages the routing
95 // Ok being non-synchro
964 return function(req, res, next) {
97
98 // Default menus and blocks for each request
99 // More of these can be added in modules, these are jsut the defaults
1004 res.menu = {
101 admin: new calipso.menu('admin', 'weight', 'root', {
102 cls: 'admin'
103 }),
104 adminToolbar: new calipso.menu('adminToolbar', 'weight', 'root', {
105 cls: 'admin-toolbar toolbar'
106 }),
107 // TODO - Configurable!
108 userToolbar: new calipso.menu('userToolbar', 'weight', 'root', {
109 cls: 'user-toolbar toolbar'
110 }),
111 primary: new calipso.menu('primary', 'name', 'root', {
112 cls: 'primary'
113 }),
114 secondary: new calipso.menu('secondary', 'name', 'root', {
115 cls: 'secondary'
116 })
117 };
118
119
120 // Initialise our clientJS library linked to this request
1214 var Client = require('./client/Client');
1224 res.client = new Client();
123
124 // Initialise helpers - first pass
1254 calipso.helpers.getDynamicHelpers(req, res, calipso);
126
127 // Route the modules
1284 calipso.module.eventRouteModules(req, res, next);
129
130 };
131
132}
133
134/**
135 * Load the application configuration
136 * Configure the logging
137 * Configure the theme
138 * Load the modules
139 * Initialise the modules
140 *
141 * @argument config
142 *
143 */
144
1451function initialiseCalipso(reloadConfig) {
146
147 // Check if we need to reload the config from disk (e.g. from cluster mode)
1482 if (reloadConfig) {
1491 calipso.config.load();
150 }
151
152 // Clear Event listeners
1532 calipso.e.init();
154
155 // Configure the logging
1562 calipso.logging.configureLogging();
157
158 // Check / Connect Mongo
1592 calipso.storage.mongoConnect(calipso.config.get('database:uri'), false, + function(err, connected) { +
160
1612 if (err) {
1620 console.log("There was an error connecting to the database: " + + err.message); +
1630 process.exit();
164 }
165
166 // Load all the themes
1672 loadThemes(function() {
168
169 // Initialise the modules and theming engine
1702 configureTheme(function() {
171
172 // Load all the modules
1732 calipso.module.loadModules(function() {
174
175 // Initialise, callback via calipso.initCallback
1762 calipso.module.initModules();
177
178 });
179
180 });
181
182 });
183
184 });
185
186}
187
188/**
189 * Called both via a hook.io event as
190 * well as via the server that initiated it.
191 */
1921function reloadConfig(event, data, next) {
193
194 // Create a callback
1951 calipso.initCallback = function(err) {
196 // If called via event emitter rather than hook
1972 if (typeof next === "function") next(err);
198 };
1991 return initialiseCalipso(true);
200
201}
202
203/**
204 * Load the available themes into the calipso.themes object
205 */
206
2071function loadThemes(next) {
208
2092 var themeBasePath = calipso.config.get('server:themePath'),
210 themePath, legacyTheme, themes;
211
212 // Load the available themes
2132 calipso.availableThemes = calipso.availableThemes || {};
214
2152 calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, + themeBasePath)).forEach(function(folder) { +
216
2172 if (folder != "README" && folder[0] != '.') {
218
2192 themes = calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, + themeBasePath, folder)); +
220
221 // First scan for legacy themes
2222 legacyTheme = false;
2232 themes.forEach(function(theme) {
2242 if (theme === "theme.json") {
2250 legacyTheme = true;
2260 console.log("Themes are now stored in sub-folders under the themes folder, + please move: " + folder + " (e.g. to custom/" + folder + ").\r\n"); +
227 }
228 });
229
230 // Process
2312 if (!legacyTheme) {
2322 themes.forEach(function(theme) {
233
2342 if (theme != "README" && theme[0] != '.') {
2352 themePath = calipso.lib.path.join(rootpath, themeBasePath, folder, theme);
236 // Create the theme object
2372 calipso.availableThemes[theme] = {
238 name: theme,
239 path: themePath
240 };
241 // Load the about info from package.json
2422 calipso.module.loadAbout(calipso.availableThemes[theme], themePath, + 'theme.json'); +
243 }
244 });
245 }
246 }
247 });
248
2492 next();
250
251}
252
253/**
254 * Configure a theme using the theme library.
255 */
256
2571function configureTheme(next, overrideTheme) {
258
2592 var defaultTheme = calipso.config.get("theme:default");
2602 var themeName = overrideTheme ? overrideTheme : + calipso.config.get('theme:front'); +
2612 var themeConfig = calipso.availableThemes[themeName]; // Reference to + theme.json +
2622 if (themeConfig) {
263
264 // Themes is the library
2652 calipso.themes.Theme(themeConfig, function(err, loadedTheme) {
266
267 // Current theme is always in calipso.theme
2682 calipso.theme = loadedTheme;
269
2702 if (err) {
2710 calipso.error(err.message);
272 }
273
2742 if (!calipso.theme) {
275
2760 if (loadedTheme.name === defaultTheme) {
2770 calipso.error('There has been a failure loading the default theme, calipso + cannot start until this is fixed, terminating.'); +
2780 process.exit();
2790 return;
280 } else {
2810 calipso.error('The `' + themeName + '` theme failed to load, attempting to use + the default theme: `' + defaultTheme + '`'); +
2820 configureTheme(next, defaultTheme);
2830 return;
284 }
285
286 } else {
287
288 // Search for middleware that already has themeStatic tag
2892 var foundMiddleware = false,
290 mw;
2912 calipso.app.stack.forEach(function(middleware, key) {
292
2936 if (middleware.handle.tag === 'theme.stylus') {
2941 foundMiddleware = true;
2951 mw = calipso.app.mwHelpers.stylusMiddleware(themeConfig.path);
2961 calipso.app.stack[key].handle = mw;
297 }
298
2996 if (middleware.handle.tag === 'theme.static') {
3001 foundMiddleware = true;
3011 mw = calipso.app.mwHelpers.staticMiddleware(themeConfig.path);
3021 calipso.app.stack[key].handle = mw;
303 }
304
305 });
306
3072 next();
308
309 }
310
311 });
312
313 } else {
314
3150 if (themeName === defaultTheme) {
3160 console.error("Unable to locate the theme: " + themeName + ", + terminating."); +
3170 process.exit();
318 } else {
3190 calipso.error('The `' + themeName + '` theme is missing, trying the defaul + theme: `' + defaultTheme + '`'); +
3200 configureTheme(next, defaultTheme);
321 }
322
323 }
324
325}
+
+

core/Blocks.js

+ +
+
70%
+
30
+
21
+
9
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Core Library - Storage of Rendered Blocks
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This class controls the storage and retrieval of blocks rendered via the + Router, e.g. specific pieces of output. +
7 *
8 */
9
101var rootpath = process.cwd() + '/',
11 path = require('path'),
12 calipso = require(path.join('..', 'calipso'));
13
14/**
15 * Holder for rendered blocks (get / set)
16 * Idea is that this will give us an opportunity
17 * to cache expensive sections of a page.
18 */
19
201function RenderedBlocks(cache) {
21
22 // Store the content rendered by modules
231 this.content = {};
24
25 // Flags to indicate if it should be cached
261 this.contentCache = {};
27
28 // The cache itself
291 this.cache = cache;
30
31}
32
33/**
34 * Set block content
35 */
361RenderedBlocks.prototype.set = function(block, content, layout, params, next) { +
37
381 var cacheKey = calipso.cacheService.getCacheKey(['block', block], params);
39
401 this.content[block] = this.content[block] || [];
411 this.content[block].push(content);
42
43 // If we are caching, then cache it.
441 if (this.contentCache[block]) {
450 calipso.silly("Cache set for " + cacheKey);
460 this.cache.set(cacheKey, {
47 content: content,
48 layout: layout
49 }, null, next);
50 } else {
511 next();
52 }
53
54};
55
56/**
57 * Get block content
58 */
591RenderedBlocks.prototype.get = function(key, next) {
60
61 // Check to see if the key is a regex, for 0.4 and 0.5 nodej
628 if (typeof key === 'object' || typeof key === "function") {
638 var item, items = [];
648 for (item in this.content) {
653 if (this.content.hasOwnProperty(item)) {
663 if (item.match(key)) {
671 items.push(this.content[item]);
68 }
69 }
70 }
718 next(null, items);
72 } else {
730 next(null, this.content[key] || []);
74 }
75
76};
77
78/**
79 * Get content from cache and load into block
80 */
811RenderedBlocks.prototype.getCache = function(key, block, next) {
82
830 calipso.silly("Cache hit for block " + key);
84
850 var self = this;
860 this.cache.get(key, function(err, cache) {
87
880 self.content[block] = self.content[block] || [];
890 self.content[block].push(cache.content);
900 next(err, cache.layout);
91
92 });
93
94};
95
961module.exports.RenderedBlocks = RenderedBlocks;
+
+

core/Cache.js

+ +
+
100%
+
7
+
7
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Core Caching Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This is the core Calipso library that enables caching to be turned on
8 * that will cache both module and full page output.
9 *
10 * The idea is to have a pluggable cache storage module, copied liberally
11 * from concepts behind the express session module.
12 *
13 *
14 *
15 */
161var rootpath = process.cwd() + '/',
17 path = require('path'),
18 calipso = require(path.join('..', 'calipso')),
19 MemoryStore = require('./cacheAdapters/memory'),
20 Store = require('./cacheAdapters/store');
21
22// Exports
231exports.Cache = Cache;
241exports.Store = Store;
251exports.MemoryStore = MemoryStore;
26
27/**
28 * Very simple wrapper that
29 * Enables pluggability of cache store, defaulting to in Memory
30 *
31 * cache.set('cc','RAH!',500,function() {
32 * cache.get('cc',function(err,item) {
33 * console.log(item);
34 * });
35 * });
36 *
37 */
38
391function Cache(options) {
40
411 var options = options || {},
42 store = store || new MemoryStore(options);
43
441 return store;
45
46}
+
+

core/cacheAdapters/memory.js

+ +
+
30%
+
39
+
12
+
27
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1
2/*!
3 * Calipso - cache - Memory Store
4 * Approach copied from Connect session store
5 * MIT Licensed
6 */
7
8/**
9 * Module dependencies.
10 */
11
121var Store = require('./store');
13
14/**
15 * Initialize a new `MemoryStore`.
16 *
17 * @api public
18 */
19
201var MemoryStore = module.exports = function MemoryStore(options) {
211 this.cache = {};
221 this.options = options || {};
23};
24
25/**
26 * Inherit from `Store.prototype`.
27 */
281MemoryStore.prototype.__proto__ = Store.prototype;
29
30/**
31 * Attempt to fetch cache by the given `key'.
32 *
33 * @param {String} key
34 * @param {Function} fn
35 * @api public
36 */
37
381MemoryStore.prototype.get = function(key, fn){
390 var self = this;
40
41 //process.nextTick(function(){
420 var cache = self.cache[key];
430 if (cache) {
440 fn(null, cache.item);
45 } else {
460 fn(new Error('Cache miss: ' + key));
47 }
48 //});
49};
50
51/**
52 * Check cache by the given `key'.
53 *
54 * @param {String} key
55 * @param {Function} fn
56 * @api public
57 */
58
591MemoryStore.prototype.check = function(key, fn){
60
610 var self = this;
620 var cache = self.cache[key];
63
640 if (cache) {
65 // Check to see if it has expired
660 if (!cache.expires || Date.now() < cache.expires) { // TODO
670 fn(null, true);
68 } else {
690 self.destroy(key, fn);
70 }
71 } else {
720 fn(null,false);
73 }
74
75};
76
77/**
78 * Add an item to the cache, referenced by key
79 * with expires
80 *
81 * @param {String} key
82 * @param {String} item
83 * @param {Number} expires (milliseconds)
84 * @param {Function} fn
85 * @api public
86 */
87
881MemoryStore.prototype.set = function(key, item, ttl, fn){
890 var self = this;
90 //process.nextTick(function(){
910 ttl = ttl || (self.options.ttl || 600);
920 var expires = Date.now() + ttl;
930 self.cache[key] = {item:item, expires:expires}
940 fn && fn();
95 //});
96};
97
98/**
99 * Destroy the session associated with the given `key`.
100 *
101 * @param {String} key
102 * @api public
103 */
104
1051MemoryStore.prototype.destroy = function(key, fn){
1060 var self = this;
107 //process.nextTick(function(){
1080 delete self.cache[key];
1090 fn && fn();
110 //});
111};
112
113/**
114 * Invoke the given callback `fn` with all active sessions.
115 *
116 * @param {Function} fn
117 * @api public
118 */
119
1201MemoryStore.prototype.all = function(fn){
1210 var arr = []
122 , keys = Object.keys(this.cache);
1230 for (var i = 0, len = keys.length; i < len; ++i) {
1240 arr.push(this.cache[keys[i]]);
125 }
1260 fn(null, arr);
127};
128
129/**
130 * Clear cache
131 *
132 * @param {Function} fn
133 * @api public
134 */
135
1361MemoryStore.prototype.clear = function(fn){
1370 this.cache = {};
1380 fn && fn();
139};
140
141/**
142 * Fetch number of cache items
143 *
144 * @param {Function} fn
145 * @api public
146 */
147
1481MemoryStore.prototype.length = function(fn){
1490 fn(null, Object.keys(this.cache).length);
150};
+
+

core/cacheAdapters/store.js

+ +
+
75%
+
16
+
12
+
4
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1
2/*!
3 * Calipso - cache - Store
4 * Concepts taken from connect session
5 * MIT Licensed
6 */
7
8/**
9 * Initialize abstract `Store`.
10 *
11 * @api private
12 */
131var rootpath = process.cwd() + '/',
14 path = require('path'),
15 calipso = require(path.join('..', '..', 'calipso'));
16
17/**
18 * Store object - options:
19 * prefix - a prefix to attach to all cache keys, defaults to calipso.
20 */
211var Store = module.exports = function Store(options){
22
230 this.options = options || {};
24
25};
26
27/**
28* Generate a cache key - applies to all store types
29*/
301Store.prototype.getCacheKey = function(keys, params) {
31
321 var prefix = this.options.prefix || "calipso";
33
34 // Append the theme, allows for theme change
351 var cacheKey = prefix + "::" + calipso.theme.theme, paramCount = 0; +
36
37 // Create the key from the keys
381 keys.forEach(function(value) {
392 cacheKey += "::" + value;
40 })
41
421 var qs = require("querystring");
43
441 if(params) {
451 cacheKey += "::";
461 calipso.lib._.each(params,function(param,key) {
470 if(param) {
480 cacheKey += (paramCount > 0 ? "::" : "") + (param ? (key + + "=" + qs.escape(param)) : ""); +
490 paramCount += 1;
50 }
51 });
52 }
53
541 return cacheKey;
55}
+
+

core/Date.js

+ +
+
13%
+
149
+
20
+
129
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/**
2 * This is a generic date parsing and formatting library, to avoid any + confusion +
3 * about how dates are handled across both the back and front end (assuming + jQuery UI will be) +
4 * the default.
5 *
6 * These functions are extracted from the jQuery UI Datepicker (see below).
7 */
8
9/**
10 * jQuery UI Datepicker
11 *
12 *
13 * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)
14 * Dual licensed under the MIT or GPL Version 2 licenses.
15 * - License http://jquery.org/license
16 * - Original Source http://docs.jquery.com/UI/Datepicker
17 */
18
191function CalipsoDate() {
20
211 this.regional = []; // Available regional settings, indexed by language code +
221 this.regional[''] = { // Default regional settings
23 closeText: 'Done',
24 // Display text for close link
25 prevText: 'Prev',
26 // Display text for previous month link
27 nextText: 'Next',
28 // Display text for next month link
29 currentText: 'Today',
30 // Display text for current month link
31 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', + 'August', 'September', 'October', 'November', 'December'], +
32 // Names of months for drop-down and formatting
33 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec'], +
34 // For formatting
35 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', + 'Saturday'], +
36 // For formatting
37 dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
38 // For formatting
39 dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
40 // Column headings for days starting at Sunday
41 weekHeader: 'Wk',
42 // Column header for week of the year
43 dateFormat: 'mm/dd/yy',
44 // See format options on parseDate
45 firstDay: 0,
46 // The first day of the week, Sun = 0, Mon = 1, ...
47 isRTL: false,
48 // True if right-to-left language, false if left-to-right
49 showMonthAfterYear: false,
50 // True if the year select precedes month, false for month then year
51 yearSuffix: '' // Additional text to append to the year in the month headers +
52 };
53
541 this._defaults = this.regional[''];
55
56 // Standard date formats.
571 this.ATOM = 'yy-mm-dd'; // RFC 3339 (ISO 8601)
581 this.COOKIE = 'D, dd M yy';
591 this.ISO_8601 = 'yy-mm-dd';
601 this.RFC_822 = 'D, d M y';
611 this.RFC_850 = 'DD, dd-M-y';
621 this.RFC_1036 = 'D, d M y';
631 this.RFC_1123 = 'D, d M yy';
641 this.RFC_2822 = 'D, d M yy';
651 this.RSS = 'D, d M y'; // RFC 822
661 this.TICKS = '!';
671 this.TIMESTAMP = '@';
681 this.W3C = 'yy-mm-dd'; // ISO 8601
691 this._ticksTo1970 = (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 + / 100) + Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000); +
70
71}
72
73/* Parse a string value into a date object.
74 See formatDate below for the possible formats.
75
76 @param format string - the expected format of the date
77 @param value string - the date in the above format
78 @param settings Object - attributes include:
79 shortYearCutoff number - the cutoff year for determining the century + (optional) +
80 dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) +
81 dayNames string[7] - names of the days from Sunday (optional)
82 monthNamesShort string[12] - abbreviated names of the months (optional)
83 monthNames string[12] - names of the months (optional)
84 @return Date - the extracted date value or null if value is blank */
851CalipsoDate.prototype.parseDate = function(format, value, settings) {
860 if (format == null || value == null) throw 'Invalid arguments';
870 value = (typeof value == 'object' ? value.toString() : value + '');
880 if (value == '') return null;
890 var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || + this._defaults.shortYearCutoff; +
900 shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : new + Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); +
910 var dayNamesShort = (settings ? settings.dayNamesShort : null) || + this._defaults.dayNamesShort; +
920 var dayNames = (settings ? settings.dayNames : null) || + this._defaults.dayNames; +
930 var monthNamesShort = (settings ? settings.monthNamesShort : null) || + this._defaults.monthNamesShort; +
940 var monthNames = (settings ? settings.monthNames : null) || + this._defaults.monthNames; +
950 var year = -1;
960 var month = -1;
970 var day = -1;
980 var doy = -1;
990 var literal = false;
100 // Check whether a format character is doubled
1010 var lookAhead = function(match) {
1020 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + + 1) == match); +
1030 if (matches) iFormat++;
1040 return matches;
105 };
106 // Extract a number from the string value
1070 var getNumber = function(match) {
1080 var isDoubled = lookAhead(match);
1090 var size = (match == '@' ? 14 : (match == '!' ? 20 : (match == 'y' && + isDoubled ? 4 : (match == 'o' ? 3 : 2)))); +
1100 var digits = new RegExp('^\\d{1,' + size + '}');
1110 var num = value.substring(iValue).match(digits);
1120 if (!num) throw 'Missing number at position ' + iValue;
1130 iValue += num[0].length;
1140 return parseInt(num[0], 10);
115 };
116 // Extract a name from the string value and convert to an index
1170 var getName = function(match, shortNames, longNames) {
1180 var names = $.map(lookAhead(match) ? longNames : shortNames, function(v, k) { +
1190 return [[k, v]];
120 }).sort(function(a, b) {
1210 return -(a[1].length - b[1].length);
122 });
1230 var index = -1;
1240 $.each(names, function(i, pair) {
1250 var name = pair[1];
1260 if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) { +
1270 index = pair[0];
1280 iValue += name.length;
1290 return false;
130 }
131 });
1320 if (index != -1) return index + 1;
1330 else throw 'Unknown name at position ' + iValue;
134 };
135 // Confirm that a literal character matches the string value
1360 var checkLiteral = function() {
1370 if (value.charAt(iValue) != format.charAt(iFormat)) throw 'Unexpected literal at + position ' + iValue; +
1380 iValue++;
139 };
1400 var iValue = 0;
1410 for (var iFormat = 0; iFormat < format.length; iFormat++) {
1420 if (literal) if (format.charAt(iFormat) == "'" && !lookAhead("'")) + literal = false; +
1430 else checkLiteral();
1440 else switch (format.charAt(iFormat)) {
145 case 'd':
1460 day = getNumber('d');
1470 break;
148 case 'D':
1490 getName('D', dayNamesShort, dayNames);
1500 break;
151 case 'o':
1520 doy = getNumber('o');
1530 break;
154 case 'm':
1550 month = getNumber('m');
1560 break;
157 case 'M':
1580 month = getName('M', monthNamesShort, monthNames);
1590 break;
160 case 'y':
1610 year = getNumber('y');
1620 break;
163 case '@':
1640 var date = new Date(getNumber('@'));
1650 year = date.getFullYear();
1660 month = date.getMonth() + 1;
1670 day = date.getDate();
1680 break;
169 case '!':
1700 var date = new Date((getNumber('!') - this._ticksTo1970) / 10000);
1710 year = date.getFullYear();
1720 month = date.getMonth() + 1;
1730 day = date.getDate();
1740 break;
175 case "'":
1760 if (lookAhead("'")) checkLiteral();
1770 else literal = true;
1780 break;
179 default:
1800 checkLiteral();
181 }
182 }
1830 if (year == -1) year = new Date().getFullYear();
1840 else if (year < 100) year += new Date().getFullYear() - new + Date().getFullYear() % 100 + (year <= shortYearCutoff ? 0 : -100); +
1850 if (doy > -1) {
1860 month = 1;
1870 day = doy;
1880 do {
1890 var dim = this._getDaysInMonth(year, month - 1);
1900 if (day <= dim) break;
1910 month++;
1920 day -= dim;
193 } while (true);
194 }
1950 var date = this._daylightSavingAdjust(new Date(year, month - 1, day));
1960 if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() + != day) throw 'Invalid date'; // E.g. 31/02/00 +
1970 return date;
198}
199
200/*
201 Format a date object into a string value.
202
203 The format can be combinations of the following
204
205 d - day of month (no leading zero)
206 dd - day of month (two digit)
207 o - day of year (no leading zeros)
208 oo - day of year (three digit)
209 D - day name short
210 DD - day name long
211 m - month of year (no leading zero)
212 mm - month of year (two digit)
213 M - month name short
214 MM - month name long
215 y - year (two digit)
216 yy - year (four digit)
217 @ - Unix timestamp (ms since 01/01/1970)
218 ! - Windows ticks (100ns since 01/01/0001)
219 '...' - literal text
220 '' - single quote
221
222 @param format string - the desired format of the date
223 @param date Date - the date value to format
224 @param settings Object - attributes include:
225 dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) +
226 dayNames string[7] - names of the days from Sunday (optional)
227 monthNamesShort string[12] - abbreviated names of the months (optional)
228 monthNames string[12] - names of the months (optional)
229 @return string - the date in the above format */
230
2311CalipsoDate.prototype.formatDate = function(format, date, settings) {
2320 if (!date) return '';
2330 var dayNamesShort = (settings ? settings.dayNamesShort : null) || + this._defaults.dayNamesShort; +
2340 var dayNames = (settings ? settings.dayNames : null) || + this._defaults.dayNames; +
2350 var monthNamesShort = (settings ? settings.monthNamesShort : null) || + this._defaults.monthNamesShort; +
2360 var monthNames = (settings ? settings.monthNames : null) || + this._defaults.monthNames; +
237 // Check whether a format character is doubled
2380 var lookAhead = function(match) {
2390 var matches = (iFormat + 1 < format.length && format.charAt(iFormat + + 1) == match); +
2400 if (matches) iFormat++;
2410 return matches;
242 };
243 // Format a number, with leading zero if necessary
2440 var formatNumber = function(match, value, len) {
2450 var num = '' + value;
2460 if (lookAhead(match)) while (num.length < len)
2470 num = '0' + num;
2480 return num;
249 };
250 // Format a name, short or long as requested
2510 var formatName = function(match, value, shortNames, longNames) {
2520 return (lookAhead(match) ? longNames[value] : shortNames[value]);
253 };
2540 var output = '';
2550 var literal = false;
2560 if (date) for (var iFormat = 0; iFormat < format.length; iFormat++) {
2570 if (literal) if (format.charAt(iFormat) == "'" && !lookAhead("'")) + literal = false; +
2580 else output += format.charAt(iFormat);
2590 else switch (format.charAt(iFormat)) {
260 case 'd':
2610 output += formatNumber('d', date.getDate(), 2);
2620 break;
263 case 'D':
2640 output += formatName('D', date.getDay(), dayNamesShort, dayNames);
2650 break;
266 case 'o':
2670 output += formatNumber('o', (date.getTime() - new Date(date.getFullYear(), 0, + 0).getTime()) / 86400000, 3); +
2680 break;
269 case 'm':
2700 output += formatNumber('m', date.getMonth() + 1, 2);
2710 break;
272 case 'M':
2730 output += formatName('M', date.getMonth(), monthNamesShort, monthNames);
2740 break;
275 case 'y':
2760 output += (lookAhead('y') ? date.getFullYear() : (date.getYear() % 100 < 10 ? + '0' : '') + date.getYear() % 100); +
2770 break;
278 case '@':
2790 output += date.getTime();
2800 break;
281 case '!':
2820 output += date.getTime() * 10000 + this._ticksTo1970;
2830 break;
284 case "'":
2850 if (lookAhead("'")) output += "'";
2860 else literal = true;
2870 break;
288 default:
2890 output += format.charAt(iFormat);
290 }
291 }
2920 return output;
293}
294
295/**
296 * Export an instance of our date object
297 */
2981module.exports = new CalipsoDate();
+
+

core/Event.js

+ +
+
95%
+
132
+
126
+
6
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Module Event Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library provides an event emitter for modules that is created on each + request, +
7 * to provide the ability for module dependencies to be managed, as well as + enable modules +
8 * to ensure that they run after all other modules have emitted certain events + (e.g. menu rendering). +
9 *
10 *
11 */
12
13/**
14 * Includes
15 */
161var rootpath = process.cwd() + '/',
17 path = require('path'),
18 util = require('util'),
19 events = require('events'),
20 calipso = require(path.join('..', 'calipso'));
21
221exports = module.exports = {
23 CalipsoEventEmitter: CalipsoEventEmitter,
24 RequestEventListener: RequestEventListener,
25 addModuleEventListener: addModuleEventListener,
26 // Module & Routing Event constants
27 ROUTE_START: 'route_s',
28 ROUTE_FINISH: 'route_f',
29 INIT_START: 'init_s',
30 INIT_FINISH: 'init_f'
31};
32
33
34/**
35 * Calipso event emitter, object that enables calipso to emit events.
36 * Events are always triggered at server scope, and cannot be used to
37 * Execute functions in request scope
38 */
39
401function CalipsoEventEmitter(options) {
41
425 var self = this;
43
44 // Initialise options
455 this.options = options || {};
46
47 // Create an emitter to drive events
485 this.emitter = new events.EventEmitter();
495 this.emitter.setMaxListeners(this.options.maxListeners || 100);
50
51 // Holder for events, enable debugging of module events
525 this.events = {};
53
54 // Clear all existing listeners
555 this.init = function() {
56
57 // Clear down the event emitters
587 for (var event in self.events) {
59
603 self.emitter.removeAllListeners("PRE_" + event);
613 self.emitter.removeAllListeners("POST_" + event);
62
633 if (self.events[event].custom) {
643 for (var key in self.events[event].custom) {
650 self.emitter.removeAllListeners(event + "_" + key);
66 }
67 }
68
69 }
70
71 // Add core events not created by modules
727 this.addEvent('FORM');
73
74 };
75
76 // Wrapper for event emitter, enable turn on / off
775 this.addEvent = function(event, options) {
78
7911 options = calipso.lib._.extend({
80 enabled: true
81 }, options);
82
8311 this.events[event] = options;
84 // Enable tracking of attached listeners for debugging purposes
8511 this.events[event].preListeners = {
86 '#': 0
87 };
8811 this.events[event].postListeners = {
89 '#': 0
90 };
9111 this.events[event].custom = {};
92 };
93
94 // Pre and post event prefixes
955 var pre_prefix = 'PRE_',
96 post_prefix = 'POST_';
97
98 // Register a pre listener
995 this.pre = function(event, listener, fn) {
100
1012 self.emitter.on(pre_prefix + event, fn);
1022 this.events[event].preListeners[listener] = + this.events[event].preListeners[listener] || []; +
1032 this.events[event].preListeners[listener].push({
104 name: fn.name
105 });
1062 this.events[event].preListeners['#'] += 1;
107 };
108
109 // Register a post listener
1105 this.post = function(event, listener, fn) {
1112 self.emitter.on(post_prefix + event, fn);
1122 this.events[event].postListeners[listener] = + this.events[event].postListeners[listener] || []; +
1132 this.events[event].postListeners[listener].push({
114 name: fn.name
115 });
1162 this.events[event].postListeners['#'] += 1;
117 };
118
119 // Register a custom event listener
1205 this.custom = function(event, key, listener, fn) {
121
1222 self.emitter.on(event + '_' + key, fn);
123
124 // Register under key
1252 this.events[event].custom[key] = this.events[event].custom[key] || {
126 customListeners: {
127 '#': 0
128 }
129 };
130
131 // Register
1322 this.events[event].custom[key].customListeners[listener] = + this.events[event].custom[key].customListeners[listener] || []; +
1332 this.events[event].custom[key].customListeners[listener].push({
134 name: fn.name
135 });
1362 this.events[event].custom[key].customListeners['#'] += 1;
137
138 };
139
140 // Emit a pre event
1415 this.pre_emit = function(event, data, next) {
142
1432 var cb;
144
145 // Create a callback to track completion of all events (only if next exists) +
1462 if (typeof next === "function") {
1471 cb = createCallback(this.events[event].preListeners['#'], data, next);
148 } else {
1491 cb = function() {};
150 }
151
1522 if (this.events[event] && this.events[event].enabled) {
1532 self.emitter.emit(pre_prefix + event, pre_prefix + event, data, cb);
154 }
155
156 };
157
158 // Emit a post event
1595 this.post_emit = function(event, data, next) {
160
1612 var cb;
162
163 // Create a callback to track completion of all events (only if next exists) +
1642 if (typeof next === "function") {
1651 cb = createCallback(this.events[event].postListeners['#'], data, next);
166 } else {
1671 cb = function() {};
168 }
169
1702 if (this.events[event] && this.events[event].enabled) {
1712 self.emitter.emit(post_prefix + event, post_prefix + event, data, cb);
172 }
173
174 };
175
176 // Emit a custom event
1775 this.custom_emit = function(event, key, data, next) {
178
1792 var cb;
180
1812 if (this.events[event] && this.events[event].custom[key] && + this.events[event].enabled) { +
182
183 // Create a callback to track completion of all events (only if next exists) +
1842 if (typeof next === "function") {
1852 cb = createCallback(this.events[event].custom[key].customListeners['#'], data, + next); +
186 } else {
1870 cb = function() {};
188 }
189
1902 self.emitter.emit(event + '_' + key, event + '_' + key, data, cb);
191
192 } else {
1930 next(data);
194 }
195
196 };
197
198 // Create a curried callback function for use in the emit code
199
2005 function createCallback(total, data, callback) {
201
2024 var count = 0,
203 total = total,
204 outputStack = [];
205
2068 if (data) outputStack.push(data);
207
208 // No listeners, so callback immediately
2094 if (total === 0) {
2100 callback(data);
2110 return;
212 }
213
2144 return function(data) {
215
2164 count += 1;
217
2188 if (data) outputStack.push(data);
219
220 // Merge the outputs from the stack
2214 if (count === total) {
2224 callback(mergeArray(outputStack));
223 }
224
225 };
226
227 }
228
229}
230
231/**
232 * Module event emitter, object that enables modules to emit events.
233 * This contains both server and request scope event emitters, though clearly +
234 * an instance of an object only emits one or the other depending on
235 * where it is instantiated.
236 */
237
2381function ModuleInitEventEmitter(moduleName, options) {
239
2409 events.EventEmitter.call(this);
241
2429 var self = this;
243
2449 self.options = options || {};
2459 this.moduleName = moduleName;
246
247 // Set the max listeners
2489 var maxListeners = self.options.maxListeners || 100;
2499 this.setMaxListeners(maxListeners);
250
2519 this.init_start = function(options) {
2528 self.emit(exports.INIT_START, self.moduleName, options);
253 };
254
2559 this.init_finish = function(options) {
2568 self.emit(exports.INIT_FINISH, self.moduleName, options);
257 };
258
259}
260
261
262/**
263 * Event listener linked to the module itself
264 * This is for server events (e.g. init, reload)
265 * No events here can sit within the request context as
266 * they will apply to all requests
267 */
268
2691function addModuleEventListener(module, options) {
270
2719 options = options || {};
272
2739 var moduleEventEmitter = module.event = new ModuleInitEventEmitter(module.name, + options), +
274 notifyDependencyFn = options.notifyDependencyFn || function() {};
275
276 // Link events
2779 moduleEventEmitter.once(exports.INIT_START, function(moduleName, options) {
278 // Do nothing
279 });
280
2819 moduleEventEmitter.once(exports.INIT_FINISH, function(moduleName, options) { +
282 // Check for dependent modules, init them
2838 notifyDependencyFn(moduleName, options);
284 });
285
286}
287
288/**
289 * Module event emitter, object that enables modules to emit events.
290 * This contains both server and request scope event emitters, though clearly +
291 * an instance of an object only emits one or the other depending on
292 * where it is instantiated.
293 */
2941function ModuleRequestEventEmitter(moduleName, options) {
295
29617 events.EventEmitter.call(this);
297
298 // Refresh the require
29917 var self = this;
30017 self.options = options || {};
30117 self.moduleName = moduleName;
302
303 // Set the max listeners
30417 var maxListeners = self.options.maxListeners || 100;
30517 this.setMaxListeners(maxListeners);
306
30717 this.route_start = function(options) {
3089 self.emit(exports.ROUTE_START, self.moduleName, options);
309 };
310
31117 this.route_finish = function(options) {
3129 self.emit(exports.ROUTE_FINISH, self.moduleName, options);
313 };
314
315}
316
317/**
318 * Event listener linked to the request object
319 * This is the object that will listen to each module event emitter
320 * and call other modules or perform other defined functions
321 */
322
3231function RequestEventListener(options) {
324
3255 options = options || {};
326
327 // Register a module, listen to its events
3285 var self = this,
329 notifyDependencyFn = options.notifyDependencyFn || function() {},
330 registerDependenciesFn = options.registerDependenciesFn || function() {};
331
332 // Local hash of module event emitters, used to track routing status
3335 this.modules = {};
334
335 // Register a module
3365 this.registerModule = function(req, res, moduleName, options) {
337
338 // Register event emitter
33917 var moduleEventEmitter = self.modules[moduleName] = new + ModuleRequestEventEmitter(moduleName, options); +
340
341 // Configure event listener
34217 self.modules[moduleName].routed = false; // Is it done
34317 self.modules[moduleName].check = {}; // Hash of dependent modules to check if + initialised +
344
345 // Curried function to notify dependent modules that we have finished
34617 var notifyDependencies = function(moduleName) {
3479 notifyDependencyFn(req, res, moduleName, self.modules);
348 };
349
35017 registerDependenciesFn(self, moduleName);
351
352 // Start
35317 moduleEventEmitter.once(exports.ROUTE_START, function(moduleName, options) { +
3549 self.modules[moduleName].start = new Date();
355 });
356
357 // Finish
35817 moduleEventEmitter.once(exports.ROUTE_FINISH, function(moduleName, options) { +
359
3609 self.modules[moduleName].finish = new Date();
3619 self.modules[moduleName].duration = self.modules[moduleName].finish - + self.modules[moduleName].start; +
3629 self.modules[moduleName].routed = true;
363
364 // Callback to Calipso to notify dependent objects of route
365 // calipso.notifyDependenciesOfRoute(req, res, moduleName, self.modules);
3669 notifyDependencies(moduleName);
367
368 });
369
370 };
371
372}
373
374/**
375 * Inherits
376 */
3771util.inherits(ModuleInitEventEmitter, events.EventEmitter);
3781util.inherits(ModuleRequestEventEmitter, events.EventEmitter);
379
380
381/**
382 * Helper functions TODO CONSOLIDATE!
383 */
384
3851function mergeArray(arr, first) {
3864 var output = {};
3874 arr.forEach(function(value, key) {
3888 if (first) {
3890 output = merge(value, output);
390 } else {
3918 output = merge(output, value);
392 }
393 });
3944 return output;
395}
396
3971function merge(a, b) {
3988 if (a && b) {
3998 for (var key in b) {
40016 a[key] = b[key];
401 }
402 }
4038 return a;
404}
+
+

core/Form.js

+ +
+
13%
+
302
+
40
+
262
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!a
2 * Calipso Form Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * Core form generation module.
8 *
9 * This is loaded by calipso as a plugin, so can be replaced by modules.
10 * Module must expose a single object as below, with a single
11 * function that is called by other modules to generate form markup.
12 *
13 * This is most likely a synchronous call, but has been written asynch just
14 * in case a module author wants to make an asynch version (for some reason!). +
15 *
16 * TODO: validation, redisplay of submitted values
17 *
18 */
19
20
211var rootpath = process.cwd() + '/',
22 path = require('path'),
23 calipso = require(path.join('..', 'calipso')),
24 qs = require('qs'),
25 merge = require('connect').utils.merge;
26
27// Global variable (in this context) for translation function
281var t;
29
30/**
31 * The default calipso form object, with default configuration values.
32 * Constructor
33 */
341function Form() {
35
36 // TODO - tagStyle should also affect whether attributes can be minimised + ('selected' vs. 'selected="selected"') +
37
38 // tagStyle should be one of [html, xhtml, xml]
391 this.tagStyle = "html";
40
41 // adjust the way tags are closed based on the given tagStyle.
421 this.tagClose = this.tagStyle == "html" ? '>' : ' />';
43
44 // cheap way of ensuring unique radio ids
451 this.radioCount = 0;
46
47}
48
491var f = new Form();
50
511var me = Form.prototype;
52
53// instead of referring to the singleton (`f`), we could implement a function +
54// that would give us the current instance, for a more sure `this`
55// but it would be a little bit slower, due to the function call
56//me.getInstance = function(){
57// return this;
58//};
59//me.getContext = function(){
60// return this;
61//};
62
63/* just an idea.
64function getAttributeString(el){
65 var validAttrs = ['type','name','id','class','value','disabled'];
66 var output = '';
67 validAttrs.forEach(function(i, attrName){
68 if(el[attrName]){
69 output += ' ' + attrName + '="' + el.attr[attrName] + '"';
70 }
71 });
72 return output;
73}
74*/
75
76// if complete for every country, this will be a lot of data and should
77// probably be broken out to a separate file.
781me.countries = [
79 "Afghanistan", "Albania", "Algeria", "Andorra", + "Angola", +
80 "Antigua and Barbuda", "Argentina", "Armenia", + "Australia", "Austria", +
81 "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", + "Barbados", "Belarus", +
82 "Belgium", "Belize", "Benin", "Bhutan", + "Bolivia", "Bosnia and Herzegovina", +
83 "Botswana", "Brazil", "Brunei", "Bulgaria", + "Burkina Faso", "Burundi", +
84 "Cambodia", "Cameroon", "Canada", "Cape + Verde", "Central African Republic", +
85 "Chad", "Chile", "China (People's Republic of China)", + "Colombia", "Comoros", +
86 "Democratic Republic of the Congo", "Republic of the Congo",
87 "Costa Rica", "Côte d'Ivoire", "Croatia", + "Cuba", "Cyprus", +
88 "Czech Republic", "Denmark, the Kingdom of", "Djibouti", + "Dominica", +
89 "Dominican Republic", "East Timor", "Ecuador", + "Egypt", "El Salvador", +
90 "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", + "Fiji", +
91 "Finland", "France", "Gabon", "Gambia", + "Georgia", "Germany", "Ghana", +
92 "Greece", "Grenada", "Guatemala", "Guinea", + "Guinea-Bissau", "Guyana", +
93 "Haiti", "Honduras", "Hungary", "Iceland", + "India", "Indonesia", "Iran", +
94 "Iraq", "Ireland", "Israel", "Italy", + "Jamaica", "Japan", "Jordan", +
95 "Kazakhstan", "Kenya", "Kiribati", "North + Korea", "South Korea", +
96 "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", + "Latvia", "Lebanon", +
97 "Lesotho", "Liberia", "Libya", "Liechtenstein", + "Lithuania", "Luxembourg", +
98 "Macedonia", "Madagascar", "Malawi", "Malaysia", + "Maldives", "Mali", +
99 "Malta", "Marshall Islands", "Mauritania", "Mauritius", + "Mexico", +
100 "Federated States of Micronesia", "Moldova", "Monaco", + "Mongolia", +
101 "Montenegro", "Morocco", "Mozambique", "Myanmar", + "Namibia", "Nauru", +
102 "Nepal", "Netherlands, the Kingdom of", "New Zealand", + "Nicaragua", "Niger", +
103 "Nigeria", "Norway", "Oman", "Pakistan", + "Palau", "Palestinian territories", +
104 "Panama", "Papua New Guinea", "Paraguay", "Peru", + "Philippines", "Poland", +
105 "Portugal", "Qatar", "Romania", "Russia", + "Rwanda", "Saint Kitts and Nevis", +
106 "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", + "San Marino", +
107 "São Tomé and Príncipe", "Saudi + Arabia", "Senegal", "Serbia", "Seychelles", +
108 "Sierra Leone", "Singapore", "Slovakia", "Slovenia", + "Solomon Islands", +
109 "Somalia", "South Africa", "South Sudan", "Spain", + "Sri Lanka", "Sudan", +
110 "Suriname", "Swaziland", "Sweden", "Switzerland", + "Syria", +
111 "Taiwan (Republic of China)", "Tajikistan", "Tanzania", + "Thailand", "Togo", +
112 "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", + "Turkmenistan", +
113 "Tuvalu", "Uganda", "Ukraine", "United Arab + Emirates", "United Kingdom", +
114 "United States", "Uruguay", "Uzbekistan", "Vanuatu", + "Vatican City", +
115 "Venezuela", "Vietnam", "Western Sahara", "Yemen", + "Zambia", "Zimbabwe"
116];
117
1181me.states = {
119 "United States": {
120 AL:"Alabama",
121 AK:"Alaska",
122 AZ:"Arizona",
123 AR:"Arkansas",
124 CA:"California",
125 CO:"Colorado",
126 CT:"Connecticut",
127 DE:"Delaware",
128 DC:"District Of Columbia",
129 FL:"Florida",
130 GA:"Georgia",
131 HI:"Hawaii",
132 ID:"Idaho",
133 IL:"Illinois",
134 IN:"Indiana",
135 IA:"Iowa",
136 KS:"Kansas",
137 KY:"Kentucky",
138 LA:"Louisiana",
139 ME:"Maine",
140 MD:"Maryland",
141 MA:"Massachusetts",
142 MI:"Michigan",
143 MN:"Minnesota",
144 MS:"Mississippi",
145 MO:"Missouri",
146 MT:"Montana",
147 NE:"Nebraska",
148 NV:"Nevada",
149 NH:"New Hampshire",
150 NJ:"New Jersey",
151 NM:"New Mexico",
152 NY:"New York",
153 NC:"North Carolina",
154 ND:"North Dakota",
155 OH:"Ohio",
156 OK:"Oklahoma",
157 OR:"Oregon",
158 PA:"Pennsylvania",
159 RI:"Rhode Island",
160 SC:"South Carolina",
161 SD:"South Dakota",
162 TN:"Tennessee",
163 TX:"Texas",
164 UT:"Utah",
165 VT:"Vermont",
166 VA:"Virginia",
167 WA:"Washington",
168 WV:"West Virginia",
169 WI:"Wisconsin",
170 WY:"Wyoming"
171 }
172};
173
174
175/**
176 * Functions for each tag type, these are now exposed directly on the object
177 * so that they can be redefined within modules (e.g. in a module that provides +
178 * a rich text editor), or a module can add new types specific to that module. +
179 *
180 * Current field types available are:
181 *
182 * text : default text field (used if no function matches field type)
183 * textarea : default textarea, can set rows in form definition to control rows + in a textarea field item. +
184 * hidden : hidden field
185 * select : single select box, requires values to be set (as array or function) +
186 * submit : submit button
187 * button : general button
188 * date : date input control (very rudimentary)
189 * time : time input controls
190 * datetime : combination of date and time controls
191 * crontime : crontime editor (6 small text boxes)
192 * password : password field
193 * checkbox : checkbox field
194 * radio : radio button
195 * file : file field
196 *
197**/
198
1991me.defaultTagRenderer = function(field, value, bare){
2000 var isCheckable = field.type == 'radio' || field.type == 'checkbox';
2010 var checked = field.checked || (isCheckable && value && + (field.value == value || value===true)); +
202
203 //console.log('... field: ', field, value);
2040 var tagOutput = "";
205
2060 if(field.type == 'checkbox' && !field.readonly && + !field.disabled){ +
207 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a + hidden field with the value +
2080 tagOutput += '<input type="hidden" name="' + field.name + '" + value="false" />'; +
209 }
210
2110 tagOutput += '<input type="' + field.type + '"'
212 + ' class="'+ field.type + (field.cls ? ' ' + field.cls : "") + + (field.labelFirst ? ' labelFirst' : '') + '"' +
213 + ' name="' + field.name + '"'
214 + (field.href ? ' onClick=\'window.location="' + field.href + '"\';' : + '') +
215 + ' id="' + (field.id ? field.id : field.name + (field.type=='radio' ? + (++f.radioCount) : '')) + '"' +
216 + (field.src ? ' src="' + field.src + '"' : '') // for input + type=image .. which should be avoided anyway. +
217 + (field.multiple ? ' multiple="' + field.multiple + '"' : '') // for + input type=file +
218 + (field.directory ? ' mozdirectory webkitdirectory directory' : '') //for input + type=file +
219 + ' value="' + calipso.utils.escapeHtmlQuotes(value || field.value || + (isCheckable && 'on') || '') + '"' +
220 + (field.readonly || field.disabled ? ' disabled' : '')
221 + (checked ? ' checked' : '')
222 + f.tagClose;
2230 if(field.readonly || field.disabled){
224 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a + hidden field with the value +
2250 tagOutput += '<input type="hidden" name="' + field.name + '" + value="' + (checked ? 'true' : 'false') + '" />'; +
226 }
227
2280 return bare ? tagOutput : me.decorateField(field, tagOutput);
229
230};
231
2321me.decorateField = function(field, tagHTML){
2330 calipso.silly('FORM: decorateField, field: ', field);
2340 var isCheckable = !field.labelFirst && (field.type == "checkbox" + || field.type == "radio"); +
2350 var labelHTML = field.label ? (
236 '<label' + (isCheckable ? ' class="for-checkable"' : '')
237 + ' for="' + field.name + (field.type == 'radio' ? f.radioCount : '')
238 + '">' + t(field.label) + (isCheckable ? '' : ':') + '</label>' +
239 ) : '';
240
2410 var wrapperId = (
242 field.name.replace(/\[/g, '_').replace(/\]/g, '')
243 + (field.type == 'radio' ? '-radio' + me.radioCount : '')
244 );
245
2460 return field.label && field.label.length > 0 ? (
247 '<div class="form-item field-type-' + field.type + '" id="' + + wrapperId + '-wrapper">' + +
248 '<div class="form-field">' +
249 // put checkboxes and radios ("checkables") before their labels, + unless field.labelFirst is true +
250 (isCheckable ? tagHTML + labelHTML : labelHTML + tagHTML) +
251 '</div>' +
252 (field.description ? '<span class="description ' + field.type + + '-description">' + t(field.description) + '</span>' : '') + +
253 '</div>'
254 ) : tagHTML;
255};
256
257// if there is no `canContain` or `cannotContain`, then the element is not a + container. +
258// the following psuedofunction should suffice:
259// x.canContain(y) =
260// ((x.canContain && y in x.canContain) || (x.cannotContain && + !(y in x.cannotContain))) +
261// && (!y.canBeContainedBy || x in y.canBeContainedBy)
2621me.elementTypes = {
263
264 'page': {
265 cannotContain: ['page'],
266 render: function(el){}
267 },
268
269 'section': {
270 cannotContain: ['page'],
271 isTab : false,
272 render: function(el, values, isTabs){
2730 return (
274 '<section' + (el.isTab || isTabs ? ' class="tab-content"':'') + ' + id="' + el.id + '">' + +
275 (el.label ? '<h3>' + t(el.label) + '</h3>' : '') +
276 (el.description ? '<p>' + el.description + '</p>' : '') +
277 '<div class="section-fields">' +
278 me.render_fields(el, values) +
279 '</div>' +
280 '</section>'
281 );
282 }
283 },
284
285 // todo: allow for pre-rendered markup for the description, or other renderers + (such as markdown) +
286 'fieldset': {
287 cannotContain: ['section', 'page'],
288 render: function(el, values){
2890 if(!el.label) el.label = el.legend;
2900 return (
291 '<fieldset class="' + (el.type != 'fieldset' ? el.type + '-fieldset' : + 'fieldset') + '">' + +
292 // <legend> is preferable, but legends are not fully stylable, so 'label' + = <h4>
293 (el.label ? '<h4>' + t(el.label) + '</h4>' : '') +
294 (el.description ? '<p>' + el.description + '</p>' : '') +
295 '<div class="fieldset-fields">' +
296 me.render_fields(el, values) +
297 '</div>' +
298 '</fieldset>'
299 );
300 }
301 },
302
303 // special .. might also be used as a container (i.e., depending on what radio + is active, elements 'under' it are active?) +
304 // special - have to share ids .. are part of a set - TODO - allow for more than + one radio group (already done?) +
305 'radios': { // it's a container because radios must belong to a 'set' .. also, + sometimes a form uses radios kindof like tabs.... +
306 canContain: ['option'],
307 render: function(field, values){
3080 return me.elementTypes.fieldset.render(field, values);
309 }
310 },
311
312 // special .. might also be used as a container (i.e., depending on whether a + checkbox is checked, elements 'under' it are active?) +
313 'checkboxes': {
314 canContain: ['option'],
315 render: function(field, values){
3160 return me.elementTypes.fieldset.render(field, values);
317 }
318 },
319
320 'select': { // it's a container because it contains options
321 canContain: ['options','optgroup'],
322 render: function(field, value){
323
3240 var tagOutput = '<select'
325 + ' class="select ' + (field.cls ? field.cls : "") + '"' +
326 + ' name="' + field.name + '"'
327 + ' id="' + field.name + '"'
328 + (field.multiple ? ' multiple="multiple"' : '')
329 + '>';
330
3310 var options = typeof field.options === 'function' ? field.options() : + field.options; +
332
3330 if(field.optgroups){
3340 field.optgroups.forEach(function(optgroup){
3350 tagOutput += '<optgroup label="' + optgroup.label + '">';
3360 optgroup.options.forEach(function(option){
3370 tagOutput += me.elementTypes.option.render(option, value, 'select');
338 });
3390 tagOutput += '</optgroup>';
340 });
341 } else {
3420 options.forEach(function(option){
3430 tagOutput += me.elementTypes.option.render(option, value, 'select');
344 });
345 }
3460 tagOutput += '</select>';
347
3480 return me.decorateField(field, tagOutput);
349 }
350 },
351
352 'optgroup': {
353 canBeContainedBy: ['select'],
354 canContain: ['option']
355 },
356
357 'options': {
358 canBeContainedBy: ['select'],
359 canContain: ['option']
360 },
361
362 // an "option" can be an <option> or a radio or a checkbox.
363 'option': {
364 canBeContainedBy: ['radios','checkboxes','select','optgroup'],
365 // container determines render method.
366 render: function(option, value, containerType){
3670 if(containerType == 'select'){
3680 var displayText = option.label || option;
3690 var optionValue = option.value || option;
3700 return (
371 '<option'
372 + ' value="' + optionValue + '"'
373 + (value === optionValue ? ' selected' : '')
374 + (option.cls ? ' class="' + option.cls + '"' : '')
375 + '>'
376 + displayText
377 + '</option>'
378 );
379 } else {
3800 return me.defaultTagRenderer(option, value);
381 }
382 }
383 },
384
385 // type: 'radio' should become type: option, and be in a {type: radios}
386 'radio': {
387 render: me.defaultTagRenderer
388 },
389
390 // type: 'checkbox' should become type: option, and be in a {type: checkboxes} +
391 'checkbox': {
392 render: function(field, value, bare) {
393
394 // Quickly flip values to true/false if on/off
3950 value = (value === "on" ? true : (value === "off" ? false : + value)); +
396
397 // Now set the checked variable
3980 var checked = (value ? true : (field.value ? true : (field.checked ? true : + false))); +
399
4000 var tagOutput = "";
401
4020 if(!field.readonly && !field.disabled){
403 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a + hidden field with the value +
4040 tagOutput += '<input type="hidden" name="' + field.name + '" + value="off" />'; +
405 }
406
4070 tagOutput += '<input type="' + field.type + '"'
408 + ' class="'+ field.type + (field.cls ? ' ' + field.cls : "") + + (field.labelFirst ? ' labelFirst' : '') + '"' +
409 + ' name="' + field.name + '"'
410 + ' id="' + field.name + '"'
411 + (field.readonly || field.disabled ? ' disabled' : '')
412 + (checked ? ' checked' : '')
413 + f.tagClose;
414
4150 if(field.readonly || field.disabled){
416 // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a + hidden field with the value +
4170 tagOutput += '<input type="hidden" name="' + field.name + '" + value="' + (checked ? "on" : "off") + '" />'; +
418 }
4190 return bare ? tagOutput : me.decorateField(field, tagOutput);
420 }
421 },
422
423 'text': {
424 render: me.defaultTagRenderer
425 },
426
427 'textarea': {
428 render: function(field, value){
4290 return me.decorateField(field, '<textarea'
430 + ' class="textarea ' + (field.cls ? field.cls : "") + '"' +
431 + ' rows="' + (field.rows ? field.rows : "10") + '"'
432 + ' name="' + field.name + '"'
433 + ' id="' + field.name + '"'
434 + (field.required ? ' required' : '')
435 + '>'
436 + value
437 + '</textarea>');
438 }
439 },
440
441 'hidden': {
442 render: me.defaultTagRenderer
443 },
444
445 'password': { // can be special, if there is a 'verify'
446 render: me.defaultTagRenderer
447 },
448
449 // might allow file to take a url
450 'file': {
451 render: me.defaultTagRenderer
452 },
453
454 'buttons': {
455 canContain: ['submit','image','reset','button','link']
456 },
457
458 // buttons should only be able to be added to the 'action set'
459 // button can be [submit, reset, cancel (a link), button (generic), link + (generic)] +
460 // if form has pages, 'previous' and 'next' buttons should interpolate until the + last page, 'submit' +
461 'button': {
462 render: me.defaultTagRenderer
463 },
464
465 'submit': {
466 render: me.defaultTagRenderer
467 },
468
469 'image': {
470 render: me.defaultTagRenderer
471 },
472
473 'reset': {
474 render: me.defaultTagRenderer
475 },
476
477 // a link is not really a form control, but is provided here for convenience +
478 // it also doesn't really make sense for it to have a value.
479 // a link should have an href and text, and optionally, cls ('class'), id
480 'link': {
481 render: function(field, value){
4820 var id = field.id || field.name;
4830 var text = field.text || field.value;
4840 return '<a href="' + field.href + '"'
485 + ' class="form-link' + (field.cls ? ' ' + field.cls : "") + '"'
486 + (id ? ' id="' + id + '"' : '')
487 + '>' + text + '</a>';
488 }
489 },
490
491 'date': {
492 render: function(field, value, bare){
493
4940 if(!value) {
4950 value = new Date();
496 }
497
498 // TODO - use user's Locale
4990 var monthNames = calipso.date.regional[''].monthNamesShort;
500
5010 var tagOutput = '<input type="text"'
502 + ' class="date date-day' + (field.cls ? ' date-day-'+field.cls : '') + '"'
503 + ' name="' + field.name + '[day]"'
504 + ' value="' + value.getDate() + '"'
505 + (field.required ? ' required' : '')
506 + f.tagClose;
507
5080 tagOutput += ' ';
509
5100 tagOutput += '<select class="date date-month' + (field.cls ? ' + date-month-'+field.cls : '') + '"' +
511 + (field.required ? ' required' : '')
512 + ' name="' + field.name + '[month]">';
5130 for(var monthNameCounter=0; monthNameCounter<12; monthNameCounter++) {
5140 tagOutput += (
515 '<option value="'+monthNameCounter+'"' + (value.getMonth() === + monthNameCounter ? ' selected' : '') + '>' +
516 + monthNames[monthNameCounter]
517 + '</option>'
518 );
519 }
5200 tagOutput += '</select>';
521
5220 tagOutput += ' ';
523
5240 tagOutput += '<input type="text"'
525 + ' class="date date-year' + (field.cls ? ' date-year-'+field.cls : '') + '"'
526 + ' name="' + field.name + '[year]"'
527 + ' value="' + value.getFullYear() + '"'
528 + (field.required ? ' required' : '')
529 + f.tagClose;
530
5310 return bare ? tagOutput : me.decorateField(field, tagOutput);
532 }
533 },
534
535 'time': {
536 render: function(field, value, bare) {
537
538 // TODO
5390 if(!value) {
5400 value = new Date(); // why 1900? why not 'now'?
541 }
542
5430 var tagOutput = '<input type="text" class="time time-hours' + + (field.cls ? ' time-hours-'+field.cls : '') + '"' +
544 + ' name="' + field.name + '[hours]"'
545 + ' value="' + value.getHours() + '"'
546 + (field.required ? ' required' : '')
547 + f.tagClose;
548
5490 tagOutput += ' ';
550
5510 tagOutput += '<input type="text" class="time time-minutes' + + (field.cls ? ' time-minutes-'+field.cls : '') + '"' +
552 + ' name="' + field.name + '[minutes]"'
553 + ' value="' + value.getMinutes() + '"'
554 + (field.required ? ' required' : '')
555 + f.tagClose;
556
5570 return bare ? tagOutput : me.decorateField(field, tagOutput);
558
559 }
560 },
561
562 'datetime': {
563 render: function(field, value) {
564 // Call both types
5650 return me.decorateField(field,
566 me.elementTypes.date.render({
567 name: field.name,
568 type: "date",
569 required: field.required
570 }, value, true) +
571 ' ' +
572 me.elementTypes.time.render({
573 name: field.name,
574 type: "time",
575 required: field.required
576 }, value, true)
577 );
578 }
579 },
580
581 'crontime': {
582 render: function(field, value) {
5830 var tagOutput = '';
5840 var cronTimeValues = value ? value.split(/\s/) : ['*','*','*','*','*','*'];
5850 for(var cronTimeInputCounter = 0; cronTimeInputCounter < 6; + cronTimeInputCounter++) { +
5860 tagOutput += (
587 '<input type="text" class="text crontime" value="' + + +
588 cronTimeValues[cronTimeInputCounter] +
589 '" name="job[cronTime' + cronTimeInputCounter + ']"' +
590 (field.required ? ' required' : '') +
591 f.tagClose
592 );
593 }
5940 return me.decorateField(field, tagOutput);
595 }
596 }
597
598};
599
600// any element types that reference other element types have to be declared + afterward +
601// so that the references exist.
6021me.elementTypes.richtext = {
603 render: me.elementTypes.textarea.render
604};
605
6061me.elementTypes.json = {
607 render: me.elementTypes.textarea.render
608};
609
6101me.elementTypes.email = {
611 render: function(field, value){
612 //var _field = copyProperties(field, {});
613 //_field.type = 'text';
6140 field.type = 'text';
6150 field.cls = (field.cls ? field.cls + ' ' : '') + 'email';
6160 me.elementTypes.textarea.render(field, value);
617 }
618};
619
6201me.elementTypes.address = {
621 render: me.elementTypes.fieldset.render,
622 defaultDefinition: {
623 tag: 'fieldset',
624 type: 'address',
625 children: [
626 {type: 'text', name: 'street', label: 'Street Address'},
627 {type: 'select', name: 'country', label: 'Country', options: me.countries},
628 {type: 'select', name: 'state', label: 'State', options: me.states["United + States"]}, +
629 {type: 'text', name: 'postalcode', label: 'Postal Code'}
630 ]
631 }
632};
633
634
635/**
636 * Form Renderer, controls the overall creation of the form based on a form json + object passed +
637 * in as the first parameter. The structure of this object is as follows:
638 *
639 * form
640 * id : Unique ID that will become the form ID.
641 * title : Title to show at the top of the form.
642 * type : Type of form (not used at present).
643 * method: HTTP method to use.
644 * action : URL to submit form to.
645 * tabs : Should tabs be rendered for sections (default false).
646 * sections [*] : Optional - divide the form into sections.
647 * id : Unique identifier for a section.
648 * label : Description of section (appears as header or tab label)
649 * fields [*] : Array of fields in the section (see below).
650 * fields [*] : Form fields array - can be in form or section.
651 * label : Label for form field.
652 * name : Name of form element to be passed back with the value.
653 * type : Type of element, based on the form functions defined below.
654 * description : Description text to be rendered after the element in a div + tag. +
655 * buttons [*] : Array of buttons to be rendered at the bottom of the form.
656 * name : Name of button (for submission).
657 * type : Type of button.
658 * value : Value to submit when pressed.
659 *
660 * A complete example is shown below:
661 *
662 * var myForm = {id:'my-form',title:'Create My + Thing...',type:'form',method:'POST',action:'/myaction',tabs:false, +
663 * sections:[{
664 * id:'myform-section-1',
665 * label:'Section 1',
666 * fields:[
667 * {label:'Field A',name:'object[fieldA]',type:'text',description:'Description + ... '}, +
668 * {label:'Field + B',name:'object[fieldB]',type:'textarea',description:'Description ...'} +
669 * ]
670 * },{
671 * id:'myform-section2',
672 * label:'Section 2',
673 * fields:[
674 * {label:'Select Field',name:'object[select]',type:'select',options:["option + 1","option 2"],description:'Description...'}, +
675 * {label:'Date + Field',name:'object[date]',type:'datetime',description:'Description...'}, +
676 * ]
677 * }
678 * ],
679 * fields:[
680 * {label:'',name:'hiddenField',type:'hidden'}
681 * ],
682 * buttons:[
683 * {name:'submit',type:'submit',value:'Save'}
684 * ]};
685 *
686 * The values of the form are passed through (optionally) as the second + parameter. This allows you to re-use +
687 * a form definition across different uses (e.g. CRU).
688 *
689 * @param item : the json object representing the form
690 * @param values : The values to initialise the form with
691 * @param next : Callback when done, pass markup as return val (TODO : deprecate + this, then can use form.render in views) +
692 */
6931me.render = function(formJson, values, req, next) {
694
6950 var self = this;
696
697 // Store local reference to the request for use during translation
6980 t = req.t;
699
700 // Emit a form pre-render event.
7010 calipso.e.custom_emit('FORM', formJson.id, formJson, function(formJson) {
702
7030 var form = (
704 self.start_form(formJson) +
705 self.render_sections(formJson, values) + // todo: deprecate - sections should be + treated the same as any other field (container) +
706 self.render_fields(formJson, values) +
707 self.render_buttons(formJson.buttons) + // todo: deprecate - buttons should be + treated the same as any other field (container) +
708 self.end_form(formJson)
709 );
710
711 // Save the form object in session, this enables us to use it later
712 // To parse the incoming data and validate against.
713 // Saving it in the session allows for per-user customisation of the form + without +
714 // impacting this code.
7150 saveFormInSession(formJson, req, function(err) {
7160 if(err) calipso.error(err.message);
7170 next(form);
718 })
719
720 });
721
722};
723
724/**
725 * Helper to save a form in the session to be used later when processing.
726 */
7271function saveFormInSession(form, req, next) {
728
729 // If we have a form id and a session, save it
7300 if(form.id && calipso.lib._.keys(req.session).length > 0) {
7310 calipso.silly("Saving form " + form.id + " in session."); +
7320 req.session.forms = req.session.forms || {};
7330 req.session.forms[form.id] = form;
7340 req.session.save(next);
735 } else {
7360 next();
737 }
738}
739
740/**
741 * Deal with form tabs in jQuery UI style if required.
742 */
7431me.formTabs = function(sections) {
744
7450 if(!sections)
7460 return '';
747
7480 var tabOutput = '<nav><ul class="tabs">',
749 numSections = sections.length;
750
7510 sections.forEach( function(section, index) {
7520 var classes = 'form-tab';
7530 if (index === 0) {
7540 classes += ' first';
755 }
7560 if ((index + 1) === numSections) {
7570 classes += ' last';
758 }
7590 tabOutput += '<li class="' + classes + '"><a href="#' + + section.id + '">' + t(section.label) + '</a></li>'; +
760 });
7610 return tabOutput + '</ul></nav>';
762
763};
764
765
766/**
767 * Render the initial form tag
768 *
769 * @param form
770 * @returns {String}
771 */
7721me.start_form = function(form) {
7730 return (
774 '<form id="' + form.id + '" name="' + form.id + '"' + + (form.cls ? ' class="' + form.cls + '"' : "") + +
775 ' method="' + form.method + '"' + ' enctype="' + (form.enctype ? + form.enctype : "multipart/form-data") + '"' + ' action="' + form.action + '">' + + +
776 '<input type="hidden" value="' + form.id + '" name="form[id]"/>' + + +
777 '<header class="form-header">' +
778 '<h2>' + t(form.title) + '</h2>' +
779 '</header>' +
780 '<div class="form-container">' +
781 (form.tabs ? this.formTabs(form.sections) : '') +
782 '<div class="form-fields'+(form.tabs ? ' tab-container' : '')+'">'
783 );
784};
785
786/**
787 * Close the form
788 * @param form
789 * @returns {String}
790 */
7911me.end_form = function(form) {
7920 return '</div></div></form>';
793};
794
795
796
797/**
798 * Render the form sections, iterating through and then rendering
799 * each of the fields within a section.
800 */
8011me.render_sections = function(form, values) {
802
8030 var self = this;
8040 var sections = form.sections;
805
8060 if(!sections)
8070 return '';
808
8090 var sectionOutput = '';
810
8110 sections.forEach(function(section) {
8120 sectionOutput += (
813 '<section' + (form.tabs?' class="tab-content"':'') + ' id="' + + section.id + '">' + +
814 '<h3>' + t(section.label) + '</h3>' +
815 self.render_fields(section, values) +
816 '</section>'
817 );
818 });
8190 return sectionOutput;
820
821};
822
823
824/**
825 * Render the buttons on a form
826 * @param buttons
827 * @returns {String}
828 */
8291me.render_buttons = function(buttons) {
830
8310 var self = this;
8320 var buttonsOutput = '<div class="actions">';
833
8340 buttons.forEach(function(field) {
8350 buttonsOutput += self.elementTypes[field.tag || field.type].render(field);
836 });
837
8380 buttonsOutput += '</div>';
839
8400 return buttonsOutput;
841};
842
843
844
845/**
846 * Render the fields on a form
847 * @param fields
848 * @returns {String}
849 */
8501me.render_fields = function(fieldContainer, values) {
851
8520 var fields = fieldContainer.fields || fieldContainer.children;
8530 var self = this;
8540 var fieldOutput = '';
855
8560 if(!fields) {
8570 return '';
858 }
859
8600 fields.forEach( function(field) {
861
8620 var value = '';
8630 var fieldName = field.name;
864
865 // If we have a field name, lookup the value
8660 if(fieldName) {
8670 value = getValueForField(fieldName, values);
868 }
869
870 // if the 'field' is really just a container, pass the values on down
871 // todo: consider adding a property 'isContainer'
8720 if(field.type == 'section' || field.type == 'fieldset'){
8730 value = values;
874 }
875
876 // field.tag was introduced to allow for <button type="submit"> + (without tag:button, that would be <input type="submit">) +
8770 if(self.elementTypes[field.tag || field.type]){
8780 fieldOutput += self.elementTypes[field.tag || field.type].render(field, value, + fieldContainer.tabs); //self.render_field(field, value); +
879 } else {
8800 calipso.warn('No renderer for ', field);
881 }
882
883 });
884
8850 return fieldOutput;
886};
887
888/**
889 * Get the value for a form field from the values object
890 * @param from
891 * @param to
892 */
8931function getValueForField(field, values) {
894
8950 if(!values) return '';
896
897 // First of all, split the field name into keys
8980 var path = []
8990 if(field.match(/.*\]$/)) {
9000 path = field.replace(/\]/g,"").split("[");
901 } else {
9020 path = field.split(':');
903 }
904
9050 while (path.length > 0) {
906
9070 key = path.shift();
908
9090 if (!(values && key in values)) {
9100 if(values && (typeof values.get === "function")) {
9110 values = values.get(key);
912 } else {
9130 if(values && values[field]) {
9140 return values[field];
915 } else {
9160 return '';
917 }
918 }
919 } else {
9200 values = values[key];
921 }
922
9230 if (path.length === 0) {
9240 return (values || '');
925 }
926 }
927
928}
929
930
931
932
933/**
934 * Get the value for a form field from the values object
935 * @param from
936 * @param to
937 */
9381function setValueForField(field, values, value) {
939
9400 if(!values) return '';
941
942 // First of all, split the field name into keys
9430 var path = []
9440 if(field.match(/.*\]$/)) {
9450 path = field.replace(/\]/g,"").split("[");
946 } else {
9470 path = [field];
948 }
949
950 //
951 // Scope into the object to get the appropriate nested context
952 //
9530 while (path.length > 1) {
9540 key = path.shift();
9550 if (!values[key] || typeof values[key] !== 'object') {
9560 values[key] = {};
957 }
9580 values = values[key];
959 }
960
961 // Set the specified value in the nested JSON structure
9620 key = path.shift();
9630 values[key] = value;
9640 return true;
965
966}
967
968/**
969 * Recursive copy of object
970 * @param from
971 * @param to
972 */
9731function copyFormToObject(field, value, target) {
974
975 // First of all, split the field name into keys
9760 var path = []
9770 if(field.match(/.*\]$/)) {
978
9790 path = field.replace(/\]/g,"").split("[");
980
981 // Now, copy over
9820 while (path.length > 1) {
9830 key = path.shift();
9840 if (!target[key]) {
9850 target[key] = {};
986 }
9870 target = target[key];
988 }
989
990 // Shift one more time and set the value
9910 key = path.shift();
9920 target[key] = value;
993
994 } else {
995
996 // We are probably an nconf form, hence just copy over
9970 target[field] = value;
998
999 }
1000
1001
1002}
1003
1004/**
1005 * Process a field / section array from a contentType
1006 * And modify the form
1007 */
10081me.processFields = function(form, fields) {
1009
1010 // Process fields
10110 if(fields.fields) {
10120 processFieldArray(form,fields.fields);
1013 }
1014
10150 if(fields.sections) {
10160 fields.sections.forEach(function(section,key) {
1017 // Process fields
10180 if(section.label) {
10190 form.sections.push(section);
1020 }
1021 // Remove it
10220 if(section.hide) {
10230 form = removeSection(form,section.id);
1024 }
1025 });
1026 }
1027
10280 return form;
1029
1030};
1031
1032
1033// Helper function to process fields
10341function processFieldArray(form, fields) {
1035
10360 fields.forEach(function(field, key) {
1037 // Add it
10380 if(field.type) {
10390 form.fields.push(field);
1040 }
1041 // Remove it
10420 if(field.hide) {
10430 form = removeField(form, field.name);
1044 }
1045 });
1046
1047}
1048
1049/**
1050 * Remove a field from a form (any section)
1051 */
10521function removeField(form, fieldName) {
1053
1054 // Scan sections
10550 form.sections.forEach(function(section, key) {
10560 scanFields(section.fields, fieldName);
1057 });
1058
1059 // Form fields
10600 scanFields(form.fields, fieldName);
1061
10620 return form;
1063
1064}
1065
1066// Helper function for removeField
10671function scanFields(fieldArray, fieldName) {
10680 fieldArray.forEach(function(field, key) {
10690 if(field.name === fieldName) {
10700 fieldArray = fieldArray.splice(key, 1);
1071 }
1072 });
1073}
1074
1075/**
1076 * Remove a section from a form
1077 */
10781function removeSection(form, sectionId) {
1079
1080 // Scan sections
10810 form.sections.forEach(function(section,key) {
10820 if(section.id === sectionId) {
10830 form.sections.splice(key,1);
1084 }
1085 });
1086
10870 return form;
1088
1089}
1090
1091
1092/**
1093 * Simple object mapper, used to copy over form values to schemas
1094 */
10951me.mapFields = function(fields, record) {
1096
10970 var props = Object.getOwnPropertyNames(fields);
10980 props.forEach( function(name) {
1099 // If not private (e.g. _id), then copy
11000 if(!name.match(/^_.*/)) {
11010 record.set(name, fields[name]);
1102 }
1103 });
1104
1105};
1106
1107/**
1108 * Process the values submitted by a form and return a JSON
1109 * object representation (makes it simpler to then process a form submission
1110 * from within a module.
1111 */
11121me.process = function(req, next) {
1113
1114 // Fix until all modules refactored to use formData
11150 if(req.formProcessed) {
1116
11170 next(req.formData, req.uploadedFiles);
11180 return;
1119
1120 } else {
1121
1122 // Data parsed based on original form structure
11230 processFormData(req, function(err, formData) {
1124
11250 if(err) calipso.error(err);
1126
11270 req.formData = formData;
11280 req.formProcessed = true;
1129
11300 return next(req.formData, req.files);
1131
1132 });
1133
1134 }
1135
1136};
1137
1138
1139/**
1140 * This process the incoming form, if the form exists in session then use that +
1141 * to validate and convert incoming data against.
1142 */
11431function processFormData(req, next) {
1144
11450 if(calipso.lib._.keys(req.body).length === 0) {
1146 // No data
11470 return next();
1148 }
1149
1150 // Get the form id and then remove from the response
11510 var formId = req.body.form ? req.body.form.id : '';
11520 delete req.body.form;
1153
1154 // Get the form and then delete the form from the user session to clean up
11550 var form = req.session.forms ? req.session.forms[formId] : null;
11560 var formData = req.body;
1157
11580 if(formId && form) {
1159
11600 processSectionData(form, formData, function(err, formData) {
1161
11620 delete req.session.forms[formId];
1163
11640 req.session.save(function(err) {
11650 if(err) calipso.error(err.message);
1166 }); // Doesn't matter that this is async, can happen in background
1167
11680 return next(err, formData);
1169
1170 });
1171
1172 } else {
1173
1174 // No form in session, do not process
11750 next(null, formData);
1176
1177 }
1178
1179}
1180
1181/**
1182 * Process form sections and fields.
1183 */
11841function processSectionData(form, formData, next) {
1185
1186 // Create a single array of all the form and section fields
11870 var fields = [];
1188
11890 if(form.sections) {
11900 form.sections.forEach(function(section) {
1191 // Ensure section isn't null
11920 if(section) {
11930 fields.push(section.fields);
1194 }
1195 });
1196 }
11970 if(form.fields) {
11980 fields.push(form.fields);
1199 }
1200
12010 calipso.lib.async.map(fields, function(section, cb) {
12020 processFieldData(section, formData, cb);
1203 }, function(err, result) {
12040 next(err, formData);
1205 })
1206
1207}
1208
1209/**
1210 * Process form fields.
1211 */
12121function processFieldData(fields, formData, next) {
1213
1214 // First create an array of all the fields (and field sets) to allow us to do an + async.map +
12150 var formFields = [];
1216
12170 for(var fieldName in fields) {
1218 // It is a section that contains fields
12190 var field = fields[fieldName];
12200 if(field.fields) {
1221 // This is a field set
12220 for(var subFieldName in field.fields) {
12230 formFields.push(field.fields[subFieldName]);
1224 }
1225 } else {
1226 // Just push the field
12270 formFields.push(field)
1228 }
1229 }
1230
12310 var iteratorFn = function(field, cb) {
1232
12330 var value = getValueForField(field.name, formData);
12340 processFieldValue(field, value, function(err, processedValue) {
12350 if(value !== processedValue) setValueForField(field.name, formData, + processedValue); +
12360 cb(err, true);
1237 });
1238
1239 }
1240
12410 calipso.lib.async.map(formFields, iteratorFn, next);
1242
1243}
1244
1245/**
1246 * Process submitted values against the original form.
1247 * TODO: This is where we would bolt on any validation.
1248 */
12491function processFieldValue(field, value, next) {
1250
1251 // Process each field
12520 if(field.type === 'checkbox') {
1253
12540 if(typeof value === 'object') {
1255 // The value has come in as ['off','on'] or [false,true]
1256 // So we always take the last value
12570 value = value[value.length - 1];
1258 }
1259
1260 // Deal with on off
12610 if(value === 'on') value = true;
12620 if(value === 'off') value = false;
1263
1264 }
1265
12660 if(field.type === 'select') {
1267
1268 // Deal with Yes / No > Boolean
12690 if(value === 'Yes') value = true;
12700 if(value === 'No') value = false;
1271
1272 }
1273
12740 if(field.type === 'datetime') {
1275
12760 if(value.hasOwnProperty('date') && value.hasOwnProperty('time')) {
12770 value = new Date(
1278 value.date + " " + value.time
1279 );
1280 }
1281
12820 if(value.hasOwnProperty('date') && value.hasOwnProperty('hours')) {
12830 value = new Date(
1284 value.date + " " + value.hours + ":" + value.minutes + + ":00"
1285 );
1286 }
1287
12880 if(value.hasOwnProperty('year') && value.hasOwnProperty('hours')) {
1289
12900 var now = new Date();
1291
12920 value = new Date(
1293 (value.year || now.getFullYear()),
1294 (value.month || now.getMonth()),
1295 (value.day || now.getDate()),
1296 (value.hours || now.getHours()),
1297 (value.minutes || now.getMinutes()),
1298 (value.seconds || now.getSeconds())
1299 );
1300
1301 }
1302
1303 }
1304
13050 return next(null, value);
1306
1307}
1308
1309/**
1310 * Export an instance of our form object
1311 */
13121module.exports = f;
+
+

core/Helpers.js

+ +
+
66%
+
71
+
47
+
24
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Core Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * Dynamic helpers for insertion into the templating engine
8 * They all need to take in a req,res pair, these are then
9 * interpreted during the request stack and passed into the
10 * view engine (so for example 'request' is accessible).
11 */
12
13/**
14 * removes any trailing query string or hash values
15 * @method stripUrlToConvert
16 * @param url {string} The url to convert
17 * @return {String} Converted url, if applicable
18 */
19
201function stripUrlToConvert(url) {
218 var qs = url.search(/\?|#/);
228 if (qs > -1) {
230 url = url.substring(0, qs);
24 }
258 return url;
26}
27
28/**
29 * Exports
30 */
311exports = module.exports = {
32
33 // Attach view Helpers to the request
34 getDynamicHelpers: function(req, res, calipso) {
354 var self = this;
364 req.helpers = {};
374 for (var helper in self.helpers) {
3884 req.helpers[helper] = self.helpers[helper](req, res, calipso);
39 }
40 },
41
42 // Add a new helper (e.g. so modules can add them)
43 addHelper: function(name, fn) {
440 var self = this;
450 self.helpers[name] = fn;
46 },
47
48 helpers: {
49 /**
50 * Request shortcut
51 */
52 request: function(req, res, calipso) {
534 return req;
54 },
55
56 /**
57 * Config shortcut
58 */
59 config: function(req, res, calipso) {
604 return calipso.config;
61 },
62
63 /**
64 * Translation shortcut
65 */
66 t: function(req, res, calipso) {
674 return req.t;
68 },
69
70 /**
71 * User shortcut
72 */
73 user: function(req, res, calipso) {
744 return req.session && req.session.user || {
75 username: '',
76 anonymous: true
77 };
78 },
79
80 /**
81 * Pretty date helper
82 */
83 prettyDate: function(req, res, calipso) {
84
854 var prettyFn = calipso.lib.prettyDate.prettyDate;
864 return prettyFn;
87
88 },
89
90 /**
91 * Pretty size helper
92 */
93 prettySize: function(req, res, calipso) {
94
954 var prettyFn = calipso.lib.prettySize.prettySize;
964 return prettyFn;
97 },
98
99 /**
100 * Hot date helper
101 */
102 hotDate: function(req, res, calipso) {
103
1044 var hotFn = calipso.lib.prettyDate.hotDate;
1054 return hotFn;
106
107 },
108
109 /**
110 * Get block data not included preloaded in the theme configuration (in + blockData) +
111 */
112 getBlock: function(req, res, calipso) {
113
1144 return function(block, next) {
115
116 // TODO : Allow block to be passed as a regex (e.g. to include all scripts.* + blocks) +
1178 var output = "";
1188 res.renderedBlocks.get(block, function(err, blocks) {
119
1208 blocks.forEach(function(content) {
1211 output += content;
122 });
123
12416 if (typeof next === 'function') next(null, output);
125
126 });
127
128 };
129 },
130
131 /**
132 * Get a menu html, synchronous
133 */
134 getMenu: function(req, res, calipso) {
135
1364 return function(menu, depth) {
137 // Render menu
13812 if (res.menu[menu]) {
13912 var output = res.menu[menu].render(req, depth);
14012 return output;
141 } else {
1420 return 'Menu ' + menu + ' does not exist!';
143 }
144
145 };
146 },
147
148 /**
149 * Directly call an exposed module function (e.g. over ride routing rules and + inject it anywhere) +
150 */
151 getModuleFn: function(req, res, calipso) {
152
1534 return function(req, moduleFunction, options, next) {
154
155 // Call an exposed module function
156 // e.g. user.loginForm(req, res, template, block, next)
157 // First see if function exists
1580 var moduleName = moduleFunction.split(".")[0];
1590 var functionName = moduleFunction.split(".")[1];
160
1610 if (calipso.modules[moduleName] && calipso.modules[moduleName].enabled + && calipso.modules[moduleName].fn[functionName]) { +
162
1630 var fn = calipso.modules[moduleName].fn[functionName];
164
165 // Get the template
1660 var template;
1670 if (options.template && + calipso.modules[moduleName].templates[options.template]) { +
1680 template = calipso.modules[moduleName].templates[options.template];
169 }
170
171 // Call the fn
1720 try {
1730 fn(req, res, template, null, next);
174 } catch (ex) {
1750 next(ex);
176 }
177
178 } else {
1790 next(null, "<div class='error'>Function " + moduleFunction + + " requested via getModuleFn does not exist or module is not enabled.</div>"); +
180 }
181
182 };
183
184 },
185
186 /**
187 * Retrieves the params parsed during module routing
188 */
189 getParams: function(req, res, calipso) {
1904 return function() {
1910 return res.params;
192 };
193 },
194
195 /**
196 * Constructs individual classes based on the url request
197 */
198 getPageClasses: function(req, res, calipso) {
1994 var url = stripUrlToConvert(req.url);
2004 return url.split('/').join(' ');
201 },
202
203 /**
204 * Constructs a single id based on the url request
205 */
206 getPageId: function(req, res, calipso) {
2074 var url = stripUrlToConvert(req.url),
208 urlFrags = url.split('/');
2094 for (var i = 0, len = urlFrags.length; i < len; i++) {
2108 var frag = urlFrags[i];
2118 if (frag === '') {
2124 urlFrags.splice(i, 1);
213 }
214 }
2154 return urlFrags.join('-');
216 },
217
218
219 addScript: function(req, res, calipso) {
2204 return function(options) {
2210 res.client.addScript(options);
222 };
223 },
224
225 getScripts: function(req, res, calipso) {
2264 return function(next) {
2270 res.client.listScripts(next);
228 };
229 },
230
231
232 addStyle: function(req, res, calipso) {
2334 return function(options) {
2340 res.client.addStyle(options);
235 };
236 },
237
238 getStyles: function(req, res, calipso) {
2394 return function(next) {
2400 res.client.listStyles(next);
241 };
242 },
243
244 /**
245 * Flash message helpers
246 */
247 flashMessages: function(req, res, calipso) {
2484 return function() {
2490 return req.flash();
250 };
251 },
252
253 /**
254 * HTML helpers - form (formApi), table, link (for now)
255 */
256 formApi: function(req, res, calipso) {
2574 return function(form) {
2580 return calipso.form.render(form);
259 };
260 },
261 table: function(req, res, calipso) {
2624 return function(table) {
2630 return calipso.table.render(table);
264 };
265 },
266 link: function(req, res, calipso) {
2674 return function(link) {
2680 return calipso.link.render(link);
269 };
270 }
271 }
272
273};
+
+

core/Lib.js

+ +
+
100%
+
2
+
2
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Imports
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This library is used to allow a single place to add new 3rd party libraries or + utilities that +
8 * are then automatically accessible via calipso.lib.library in any module.
9 *
10 */
111var rootpath = process.cwd() + '/';
12
131module.exports = {
14 fs: require('fs'),
15 path: require('path'),
16 express: require('express'),
17 step: require('step'),
18 util: require('util'),
19 mongoose: require('mongoose'),
20 url: require('url'),
21 ejs: require('ejs'),
22 pager: require(rootpath + 'utils/pager'),
23 prettyDate: require(rootpath + 'utils/prettyDate.js'),
24 prettySize: require(rootpath + 'utils/prettySize.js'),
25 crypto: require(rootpath + 'utils/crypto.js'),
26 connect: require('connect'),
27 _: require('underscore'),
28 async: require('async')
29};
+
+

core/Link.js

+ +
+
50%
+
12
+
6
+
6
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 *
3 * Calipso Link Rendering Library
4 *
5 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
6 * MIT Licensed
7 *
8 * Loaded into calipso as a plugin, used to simplify rendering of links
9 *
10 */
11
121var rootpath = process.cwd() + '/',
13 path = require('path'),
14 calipso = require(path.join('..', 'calipso')),
15 qs = require('qs');
16
17// Global variable (in this context) for translation function
181var t;
19
20/**
21 * The default calipso link object, with default configuration values.
22 * Constructor
23 */
24
251function CalipsoLink() {
26
27 //TODO Allow over-ride
28}
29
30/**
31 * Export an instance of our link object
32 */
331module.exports = new CalipsoLink();
34
35
36/**
37 * Link Renderer, controls the overall creation of the tablle based on a form + json object passed +
38 * in as the first parameter. The structure of this object is as follows:
39 *
40 * link
41 * id : Unique ID that will become the link ID.
42 * title : Title to show (hover)
43 * target : target window
44 * label : label to show in link
45 * cls : css class
46 * url: the direct url to use, can be function (mandatory)
47 *
48 * @param item : the json object representing the form
49 * @param next : Callback when done, pass markup as return val.
50 */
511CalipsoLink.prototype.render = function(item) {
52
530 return (
54 this.render_link(item));
55
56};
57
58/**
59 * Render link
60 *
61 * @param link
62 * @returns {String}
63 */
641CalipsoLink.prototype.render_link = function(link) {
65
660 var url = "";
670 if (typeof link.url === 'function') {
680 url = link.url(link);
69 } else {
700 url = link.url;
71 }
72
730 return ('<a' + ' href="' + url + '"' + (link.id ? ' id=' + link.id + + '"' : "") + (link.target ? ' target="' + link.target + '"' : "") + + (link.title ? ' title="' + link.title + '"' : "") + (link.cls ? ' class="' + + link.cls + '"' : "") + '>' + (link.label || "") + '</a>'); +
74};
+
+

core/Logging.js

+ +
+
84%
+
25
+
21
+
4
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Core Logging Library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This module exposes the functions that configures the logging of calipso.
8 * This is based entirely on Winston.
9 *
10 */
11
121var app, rootpath = process.cwd(),
13 path = require('path'),
14 winstong = require('winston'),
15 calipso = require(path.join('..', 'calipso'));
16
17
18/**
19 * Core export
20 */
211exports = module.exports = {
22 configureLogging: configureLogging
23};
24
25/**
26 * Configure winston to provide the logging services.
27 *
28 * TODO : This can be factored out into a module.
29 *
30 */
31
321function configureLogging(options) {
335 options = options || calipso.config.get('logging');
34
35 //Configure logging
365 var logMsg = "\x1b[36mLogging enabled: \x1b[0m",
37 winston = require("winston");
38
395 try {
405 winston.remove(winston.transports.File);
41 } catch (exFile) {
42 // Ignore the fault
43 }
44
455 if (options.file && options.file.enabled) {
460 winston.add(winston.transports.File, {
47 level: options.console.level,
48 timestamp: options.file.timestamp,
49 filename: options.file.filepath
50 });
510 logMsg += "File @ " + options.file.filepath + " ";
52 }
53
545 try {
555 winston.remove(winston.transports.Console);
56 } catch (exConsole) {
57 // Ignore the fault
58 }
59
605 if (options.console && options.console.enabled) {
610 winston.add(winston.transports.Console, {
62 level: options.console.level,
63 timestamp: options.console.timestamp,
64 colorize: options.console.colorize
65 });
660 logMsg += "Console ";
67 }
68
69 // Temporary data for form
705 calipso.data.loglevels = [];
715 for (var level in winston.config.npm.levels) {
7230 calipso.data.loglevels.push(level);
73 }
74
75 // Shortcuts to Default
765 calipso.log = winston.info; // Default function
77 // Shortcuts to NPM levels
785 calipso.silly = winston.silly;
795 calipso.verbose = winston.verbose;
805 calipso.info = winston.info;
815 calipso.warn = winston.warn;
825 calipso.debug = winston.debug;
835 calipso.error = winston.error;
84
85}
+
+

core/Menu.js

+ +
+
97%
+
148
+
145
+
3
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Menu Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library provides the base functions to manage the creation of menus.
7 * A default renderer will be provided in this library, but this is intended to + be over-ridden +
8 * By menu modules (e.g. to export different structures), or even source menus + from different locations. +
9 *
10 */
11
12/**
13 * Includes
14 */
151var sys;
161try {
171 sys = require('util');
18} catch (e) {
190 sys = require('sys');
20}
211var rootpath = process.cwd() + '/',
22 path = require('path'),
23 utils = require('connect').utils,
24 merge = utils.merge,
25 calipso = require(path.join('..', 'calipso'));
26
27/**
28 * The default menu item object, with default configuration values.
29 * Constructor
30 */
31
321function CalipsoMenu(name, sort, type, options) {
33
34 // Basic menu options, used typically for root menu holder
3567 this.name = name || 'default'; // This should be mandatory
3667 this.type = type || 'root';
3767 this.sort = sort || 'name';
38
39 // Options for this menu item
4067 if (options) {
4151 this.setOptions(options);
42 }
43
44 // Child menu items
4567 this.children = {};
4667 this.sortedChildren = []; // Sorted array of prop names for recursion
47}
48
49/**
50 * Exports
51 */
521module.exports = CalipsoMenu;
53
54/**
55 * Wrapper to enable setting of menu options
56 */
571CalipsoMenu.prototype.setOptions = function(options) {
5852 merge(this, options);
59};
60
61/**
62 * Function to enable addition of a menu item to the menu.
63 *
64 * Menu Options:
65 * name: req.t('Admin') -- Label to display
66 * path: admin -- the menu heirarchy path, used for parent child.
67 * e.g. path: admin/config -- the menu heirarchy path, used for parent child. +
68 * instruction: req.t('Administration Menu') -- tooltip label
69 * url: '/admin' -- Url to use as link
70 * security: [/admin/,"bob"] -- regex based on user role
71 */
721CalipsoMenu.prototype.addMenuItem = function(req, options) {
73
7430 var self = this;
75
76 // The req parameter was added in 0.3.0, if not passed, assuming options only +
7730 if (options === undefined) calipso.error("Attempting to add menu item with + invalid params, please update your module for the 0.3.0 api, path: " + req.path); +
78
79 // Check security
8030 if (options.permit) {
81
8228 var permitFn = new calipso.permission.Filter(options, options.permit),
83 permit = permitFn.check(req);
84
8528 if (typeof permit !== "object") return;
8629 if (!permit.allow) return;
87 }
88 // Admin security is opposite to default
8929 if (self.name === 'admin') {
901 var isAdmin = req.session.user && req.session.user.isAdmin;
91 // Admin by default is not shown unless permitted
922 if (!options.permit && !isAdmin) return;
93 }
94
95 // Split the path, traverse items and add menuItems.
96 // If you add a child prior to parent, then create the parent.
9728 var newItem = self.createPath(options, options.path.split("/"));
98
99};
100
101/**
102 * Ensure that a full path provided is a valid menu tree
103 */
1041CalipsoMenu.prototype.createPath = function(options, path) {
105
10651 var self = this;
10751 var currentItem = path[0];
10851 var remainingItems = path.splice(1, path.length - 1);
109
11051 if (self.children[currentItem] && remainingItems.length > 0) {
111
112 // Recurse
11318 self.children[currentItem].createPath(options, remainingItems);
114
115 } else {
116
117 // If the current item does not yet exist
11833 if (!self.children[currentItem]) {
119
120 // Do we have children left, if so, mark this as a temporary node (e.g. we dont + actually have its options) +
12131 if (remainingItems.length > 0) {
1225 self.children[currentItem] = new CalipsoMenu('Child of ' + currentItem, + self.sort, 'temporary', options); +
123 } else {
12426 self.children[currentItem] = new CalipsoMenu('Child of ' + currentItem, + self.sort, 'child', options); +
125 }
12631 self.sortedChildren.push(currentItem); // Add to array for later sorting
127 }
128
129 // Check to see if we need to update a temporary node
13033 if (self.children[currentItem] && remainingItems.length === 0 && + self.children[currentItem].type === 'temporary') { +
1311 self.children[currentItem].type = 'child';
1321 self.children[currentItem].setOptions(options);
133 }
134
13533 if (remainingItems.length > 0) {
136 // Recurse
1375 self.children[currentItem].createPath(options, remainingItems);
138 }
139
140 }
141
142 // Sort the sorted array
14351 self.sortedChildren.sort(function(a, b) {
144
145 // a & b are strings, but both objects on the current children
1469 var diff;
1479 if (self.children[a][self.sort] && self.children[b][self.sort]) {
148
1498 if (typeof self.children[a][self.sort] === "string") {
1507 diff = self.children[a][self.sort].toLowerCase() > + self.children[b][self.sort].toLowerCase(); +
151 } else {
1521 diff = self.children[a][self.sort] > self.children[b][self.sort];
153 }
154
155 } else {
1561 diff = self.children[a].name.toLowerCase() > + self.children[b].name.toLowerCase(); +
157 }
158
1599 return diff;
160 });
161
162
163};
164
165
166/**
167 * Render the menu as a html list - this is the default.
168 * The idea is that this can be over-ridden (or the sub-function), to control +
169 * HTML generation.
170 */
1711CalipsoMenu.prototype.render = function(req, depth) {
172
17313 var self = this;
174
175 // If the menu is empty, render nothing
17625 if (self.sortedChildren.length === 0) return '';
177
178 // Get selected items
1791 var selected = self.selected(req);
180
1811 var htmlOutput = '';
1821 htmlOutput += self.startTag();
183
1841 var renderUp = function(menu) {
1855 var selectedClass = '';
1865 if (contains(selected, menu.path)) {
1872 selectedClass = '-selected';
188 }
1895 var html = self.menuStartTag(menu, selectedClass) + self.menuLinkTag(req, menu, + selectedClass); +
1905 return html;
191 };
192
1931 var renderDown = function(menu) {
1945 var html = self.menuEndTag(menu);
1955 return html;
196 };
197
1981 var renderStart = function(menu) {
1993 var html = self.childrenStartTag(menu);
2003 return html;
201 };
202
2031 var renderFinish = function(menu) {
2043 var html = self.childrenEndTag(menu);
2053 return html;
206 };
207
2081 var output = [];
2091 self.fnRecurse(self, renderUp, renderDown, renderStart, renderFinish, depth, + output); +
210
2111 htmlOutput += output.join("");
2121 htmlOutput += self.endTag();
213
2141 return htmlOutput;
215
216};
217
218/**
219 * Specific tag rendering functions
220 * Over-write to enable custom menu rendering
221 */
2221CalipsoMenu.prototype.startTag = function() {
2231 return "<ul id='" + this.name + "-menu' class='menu" + + (this.cls ? ' ' + this.cls : '') + "'>"; +
224};
2251CalipsoMenu.prototype.endTag = function() {
2261 return "</ul>";
227};
2281CalipsoMenu.prototype.menuStartTag = function(menu, selected) {
2295 var menuItemTagId = menu.path.replace(/\//g, '-') + "-menu-item";
2305 return "<li id='" + menuItemTagId + "' class='" + + this.name + "-menu-item" + selected + "'>"; +
231};
2321CalipsoMenu.prototype.menuLinkTag = function(req, menu, selected) {
2335 var popup = menu.popup ? 'popupMenu' : '';
2345 return "<a href='" + menu.url + "' title='" + + req.t(menu.description) + "' class='" + popup + " " + this.name + "-menu-link" + + selected + (menu.cls ? " " + menu.cls : "") + "'>" + + req.t(menu.name) + "</a>"; +
235};
2361CalipsoMenu.prototype.menuEndTag = function(menu) {
2375 return "</li>";
238};
2391CalipsoMenu.prototype.childrenStartTag = function() {
2403 return "<ul>";
241};
2421CalipsoMenu.prototype.childrenEndTag = function() {
2433 return "</ul>";
244};
245
246/**
247 * Locate selected paths based on current request
248 */
2491CalipsoMenu.prototype.selected = function(req) {
250
251 // Based on current url, create a regex string that can be used to test if a + menu item +
252 // Is selected during rendering
2532 var self = this;
2542 var output = [];
255
2562 var selectedFn = function(menu) {
257
25810 var menuSplit = menu.url.split("/");
25910 var reqSplit = req.url.split("/");
26010 var match = true;
261
26210 menuSplit.forEach(function(value, key) {
26336 match = match && (value === reqSplit[key]);
264 });
265
266 // Check if the url matches
26710 if (match) {
2684 return menu.path;
269 }
270
271 };
272
2732 self.fnRecurse(self, selectedFn, output);
274
2752 return output;
276
277};
278
279/**
280 * Helper function that can recurse the menu tree
281 * From a start point, execute a function and add the result to an output array +
282 */
2831CalipsoMenu.prototype.fnRecurse = function(menu, fnUp, fnDown, fnStart, fnFinish, + depth, output) { +
284
28524 var self = this;
28624 var result;
28724 if (typeof fnDown != 'function') {
28818 output = fnDown;
289 }
29024 output = output || [];
291
292 // Recurse from menu item selected
29324 if (menu.type === 'root') {
294
295 // Functions don't run on root
2964 menu.sortedChildren.forEach(function(child) {
2974 self.fnRecurse(menu.children[child], fnUp, fnDown, fnStart, fnFinish, depth, + output); +
298 });
299
300 } else {
301
302 // Control depth of recursion
30320 depth = depth === undefined ? -1 : depth;
30420 if (depth > 0) {
3050 depth = depth - 1;
30620 } else if (depth === -1) {
307 // Recures infinitely
308 } else {
3090 return output;
310 }
311
312 // Count the number of children
31320 var childCount = menu.sortedChildren.length;
314
315 // Execute fn
31620 if (typeof fnUp === 'function') {
317
31820 result = fnUp(menu);
31920 if (result) {
32014 output.push(result);
321 }
322
32320 if (childCount > 0) {
32412 if (typeof fnStart === 'function') {
3253 result = fnStart(menu);
3263 if (result) {
3273 output.push(result);
328 }
329 }
330 }
331
332 }
333
334 // Recurse
33520 menu.sortedChildren.forEach(function(child) {
33616 self.fnRecurse(menu.children[child], fnUp, fnDown, fnStart, fnFinish, depth, + output); +
337 });
338
339 // Close
34020 if (typeof fnDown === 'function') {
341
3425 result = fnDown(menu);
3435 if (result) {
3445 output.push(result);
345 }
346
3475 if (childCount > 0) {
3483 if (typeof fnFinish === 'function') {
3493 result = fnFinish(menu);
3503 if (result) {
3513 output.push(result);
352 }
353 }
354 }
355 }
356
357 }
358
359};
360
361/**
362 * Return current menu as a JSON object, used for Ajax style menus.
363 * Path : root to return menu from, default is root (entire menu)
364 * Depth : How many levels to return menu
365 */
3661CalipsoMenu.prototype.getMenuJson = function(path, depth) {
367
368 // TODO
369};
370
371/**
372 * Private helper functions
373 */
374
3751function contains(a, obj) {
3765 var i = a.length;
3775 while (i--) {
3789 if (a[i] === obj) {
3792 return true;
380 }
381 }
3823 return false;
383}
+
+

core/Module.js

+ +
+
85%
+
262
+
225
+
37
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
11var app, rootpath = process.cwd() + '/',
2 path = require('path'),
3 calipso = require(path.join('..', 'calipso'));
4
5/**
6 * Route all of the modules based on the module event model
7 * This replaces an earlier version that only executed modules in
8 * parallel via step
9 */
10
111function eventRouteModules(req, res, next) {
12
13 // Ignore static content
14 // TODO : Make this more connect friendly or at least configurable
15 // STATIC content may or may not be static. We should route it normally for + now. +
16 //if + (req.url.match(/^\/images|^\/js|^\/css|^\/favicon.ico|png$|jpg$|gif$|css$|js$/)) { +
17 // return next();
18 //}
19
204 req.timeStart = new Date();
214 var end = res.end;
224 res.calipsoEndCalled = false;
234 res.end = function () {
240 res.calipsoEndCalled = true;
250 end.apply(res, arguments);
26 }
27
28 // Attach our event listener to this request
294 attachRequestEvents(req, res);
30
31 // Initialise the response re. matches
32 // Later used to decide to show 404
334 res.routeMatched = false;
34
35 // Store our callback here
364 req.routeComplete = function(res) {
378 if(!res.calipsoEndCalled) next();
38 };
39
40 // Route 'first' modules that fire before all others
41 // These first modules can stop the routing of all others
424 doFirstModules(req, res, function(err) {
43
444 var iterator = function(module, cb) {
4516 routeModule(req, res, module, false, false, cb);
46 }
47
484 calipso.lib.async.map(calipso.lib._.keys(calipso.modules), iterator, + function(err, result) { +
49 // Not important
50 })
51
52 });
53
54}
55
56/**
57 * Attach module event emitters and request event listener
58 * to this request instance.
59 * This will only last for the context of a current request
60 */
61
621function attachRequestEvents(req, res) {
63
64 // Create a request event listener for this request, pass in functions
65 // to enable testing.
664 req.event = new calipso.event.RequestEventListener({
67 notifyDependencyFn: notifyDependenciesOfRoute,
68 registerDependenciesFn: registerDependencies
69 });
70
71 //
724 var maxListeners = calipso.config.get('server:events:maxListeners');
73
74 // Attach all the modules to it
754 for (var module in calipso.modules) {
7616 req.event.registerModule(req, res, module, {maxListeners: maxListeners});
77 }
78
79}
80
81/**
82 * Helper to register dependent modules that should be checked by a module when +
83 * routing, the parent module's emitter is passed in.
84 */
851function registerDependencies(moduleEmitter, moduleName) {
86
87 // Register depends on parent
8816 if (calipso.modules[moduleName].fn && + calipso.modules[moduleName].fn.depends) { +
894 calipso.modules[moduleName].fn.depends.forEach(function(dependentModule) {
904 moduleEmitter.modules[moduleName].check[dependentModule] = false;
91 });
92 }
93}
94
95/**
96 * Route a specific module
97 * Called by both the eventRouteModules but also by when dependencies trigger +
98 * a module to be routed
99 *
100 * req, res : request/resposne
101 * module : the module to route
102 * depends : has this route been triggered by an event based on dependencies + being met +
103 * last : final modules, after all others have routed
104 *
105 */
106
1071function routeModule(req, res, moduleName, depends, last, next) {
108
10920 var module = calipso.modules[moduleName];
110
111 // If module is enabled and has no dependencies, or if we are explicitly + triggering this via depends +
112 // Ignore modules that are specified as post route only
11320 if (module.enabled && (depends || !module.fn.depends) && (last + || !module.fn.last) && !module.fn.first) { +
114
115 // Fire event to start
1168 req.event.modules[moduleName].route_start();
117
118 // Route
1198 module.fn.route(req, res, module, calipso.app, function(err, moduleName) {
120
121 // Gracefully deal with errors
1228 if (err) {
1230 res.statusCode = 500;
1240 calipso.error(err.message);
1250 res.errorMessage = "Module " + moduleName + ", error: " + + err.message + err.stack; +
126 }
127
128 // Expose configuration if module has it
1298 if (module.fn && module.fn.config) {
1300 var modulePermit = calipso.permission.Helper.hasPermission("admin:module:configuration");
1310 res.menu.admin.addMenuItem(req, {
132 name: moduleName,
133 path: 'admin/modules/' + moduleName,
134 url: '/admin/modules?module=' + moduleName,
135 description: 'Manage ' + moduleName + ' settings ...',
136 permit: modulePermit
137 });
138 }
139
140 // Finish event
1418 req.event.modules[moduleName].route_finish();
142
143 // Check to see if we have completed routing all modules
1448 if (!last) {
1458 checkAllModulesRouted(req, res);
146 }
147
1488 next();
149
150 });
151
152 } else {
153
15412 checkAllModulesRouted(req, res);
155
15612 next();
157
158 }
159
160}
161
162/**
163 * Check that all enabled modules have been initialised
164 * Don't check disabled modules or modules that are setup for postRoute only
165 */
1661function checkAllModulesRouted(req, res) {
167
16820 var allRouted = true;
169
17020 for (var module in req.event.modules) {
17180 var moduleRouted = (req.event.modules[module].routed || + (calipso.modules[module].enabled && (calipso.modules[module].fn.last || + calipso.modules[module].fn.first)) || !calipso.modules[module].enabled); +
17280 allRouted = moduleRouted && allRouted;
173 }
174
17520 if (allRouted && !req.event.routeComplete) {
1764 req.event.routeComplete = true;
1774 doLastModules(req, res, function() {
1784 req.timeFinish = new Date();
1794 req.timeDuration = req.timeFinish - req.timeStart;
1804 calipso.silly("All modules routed in " + req.timeDuration + " ms");
1814 doResponse(req, res);
182 });
183 }
184
185}
186
187
188/**
189 * RUn any modules that are defined as first routing modules
190 * via first: true, dependencies are ignored for these.
191 */
1921function doFirstModules(req, res, next) {
193
194 // Get all the postRoute modules
1954 var firstModules = [];
1964 for (var moduleName in calipso.modules) {
19716 if (calipso.modules[moduleName].enabled && + calipso.modules[moduleName].fn.first) { +
1984 firstModules.push(calipso.modules[moduleName]);
199 }
200 }
201
202
2034 if(firstModules.length === 0) return next();
204
205 // Execute their routing functions
2064 calipso.lib.step(
207
208 function doFirstModules() {
2094 var group = this.group();
2104 firstModules.forEach(function(module) {
2114 module.fn.route(req, res, module, calipso.app, group());
212 });
213 }, function done(err) {
214
215 // Gracefully deal with errors
2164 if (err) {
2170 res.statusCode = 500;
2180 console.log(err.message);
2190 res.errorMessage = err.message + err.stack;
220 }
221
2224 next();
223
224 });
225
226}
227
228
229/**
230 * RUn any modules that are defined as last routing modules
231 * via last: true, dependencies are ignored for these atm.
232 */
233
2341function doLastModules(req, res, next) {
235
236 // Get all the postRoute modules
2374 var lastModules = [];
2384 for (var moduleName in calipso.modules) {
23916 if (calipso.modules[moduleName].enabled && + calipso.modules[moduleName].fn.last) { +
2404 lastModules.push(calipso.modules[moduleName]);
241 }
242 }
243
244
2454 if(lastModules.length === 0) return next();
246
247 // Execute their routing functions
2484 calipso.lib.step(
249
250 function doLastModules() {
2514 var group = this.group();
2524 lastModules.forEach(function(module) {
2534 module.fn.route(req, res, module, calipso.app, group());
254 });
255 }, function done(err) {
256
257 // Gracefully deal with errors
2584 if (err) {
2590 res.statusCode = 500;
2600 console.log(err.message);
2610 res.errorMessage = err.message + err.stack;
262 }
263
2644 next();
265
266 });
267
268}
269
270/**
271 * Standard response to all modules completing their routing
272 */
273
2741function doResponse(req, res, next) {
275
276 // If we are in install mode, and are not in the installation process, then + redirect +
2774 if (!calipso.config.get('installed') && + !req.url.match(/^\/admin\/install/)) { +
2780 calipso.silly("Redirecting to admin/install ...");
2790 calipso.app.doingInstall = true;
2800 res.redirect("/admin/install");
2810 return;
282 }
283
284 // If nothing could be matched ...
2854 if (!res.routeMatched) {
2861 calipso.log("No Calipso module routes matched the current URL.");
2871 res.statusCode = 404;
288 }
289
290 // Render statuscodes dealt with by themeing engine
291 // TODO - this is not very clean
2924 calipso.silly("Responding with statusCode: " + res.statusCode);
2934 if (res.statusCode === 404 || res.statusCode === 500 || res.statusCode === 200 + || res.statusCode === 403) { +
294
2953 calipso.theme.render(req, res, function(err, content) {
296
2973 if (err) {
298
299 // Something went wrong at the layout, cannot use layout to render.
3000 res.statusCode = 500;
3010 res.send(500, "<html><h2>A fatal error occurred!</h2>" + + "<p>" + (err.xMessage ? err.xMessage : err.message) + "</p>" + + "<pre>" + err.stack + "</pre></html>"); +
3020 req.routeComplete(res);
303
304 } else {
305
3063 res.setHeader('Content-Type', 'text/html');
307 // Who am I?
3083 res.setHeader('X-Powered-By', 'Calipso');
309
310 // render
3113 res.send(content);
312
313 // Callback
3143 req.routeComplete(res);
315
316 }
317
318 });
319
320 } else {
321
322 // Otherwise, provided we haven't already issued a redirect, then pass back to + Express +
3231 req.routeComplete(res);
324
325 }
326
327}
328
329
330/**
331 * Initialise the modules currently enabled.
332 * This iterates through the modules loaded by loadModules (it places them in an + array in the calipso object), +
333 * and calls the 'init' function exposed by each module (in parallel controlled + via step). +
334 */
335
3361function initModules() {
337
338 // Reset
3392 calipso.initComplete = false;
340
341 // Create a list of all our enabled modules
3422 var enabledModules = [];
3432 for (var module in calipso.modules) {
3448 if (calipso.modules[module].enabled) {
3458 enabledModules.push(module);
346 }
347 }
348
349 // Initialise them all
3502 enabledModules.forEach(function(module) {
3518 initModule(module, false);
352 });
353
354}
355
356/**
357 * Init a specific module, called by event listeners re. dependent modules
358 */
359
3601function initModule(module, depends) {
361
362
363 // If the module has no dependencies, kick start it
36410 if (depends || !calipso.modules[module].fn.depends) {
365
366 // Init start event
3678 calipso.modules[module].event.init_start();
368
369 // Next run any init functions
3708 calipso.modules[module].fn.init(calipso.modules[module], calipso.app, + function(err) { +
371
372 // Init finish event
3738 calipso.modules[module].inited = true;
3748 calipso.modules[module].event.init_finish();
375
376 // Now, load any routes to go along with it
3778 if (calipso.modules[module].fn.routes && + calipso.modules[module].fn.routes.length > 0) { +
3782 calipso.lib.async.map(calipso.modules[module].fn.routes, function(options, next) + { +
3792 calipso.modules[module].router.addRoute(options, next);
380 }, function(err, data) {
3812 if (err) calipso.error(err);
3822 checkAllModulesInited();
383 });
384 } else {
3856 checkAllModulesInited();
386 }
387
388 });
389
390 }
391
392}
393
394/**
395 * Check that all enabled modules have been initialised
396 * If they have been initialised, then call the callback supplied on + initialisation +
397 */
398
3991function checkAllModulesInited() {
400
4018 var allLoaded = true;
4028 for (var module in calipso.modules) {
40332 allLoaded = (calipso.modules[module].inited || !calipso.modules[module].enabled) + && allLoaded; +
404 }
405
4068 if (allLoaded && !calipso.initComplete) {
4072 calipso.initComplete = true;
4082 calipso.initCallback();
409 }
410
411}
412
413/**
414 * Load the modules from the file system, into a 'modules' array
415 * that can be managed and iterated.
416 *
417 * The first level folder is the module type (e.g. core, contrib, ui).
418 * It doesn't actually change the processing, but that folder structure is
419 * now stored as a property of the module (so makes admin easier).
420 *
421 * It will take in an options object that holds the configuration parameters
422 * for the modules (e.g. if they are enabled or not).
423 * If they are switching (e.g. enabled > disabled) it will run the disable + hook. +
424 *
425 */
426
4271function loadModules(next) {
428
4292 var configuredModules = calipso.config.get('modules') || {};
430
431 // Run any disable hooks
4322 for (var module in calipso.modules) {
433 // Check to see if the module is currently enabled, if we are disabling it.
4344 if (calipso.modules[module].enabled && configuredModules[module].enabled + === false && typeof calipso.modules[module].fn.disable === 'function') { +
4350 calipso.modules[module].fn.disable();
436 }
437 }
438
439 // Clear the modules object (not sure if this is required, but was getting + strange errors initially) +
4402 delete calipso.modules; // 'Delete' it.
4412 calipso.modules = {}; // Always reset it
442
4432 var moduleBasePath = path.join(rootpath, + calipso.config.get('server:modulePath')); +
444
445 // Read the modules in from the file system, sync is fine as we do it once on + load. +
4462 calipso.lib.fs.readdirSync(moduleBasePath).forEach(function(type) {
447
448 // Check for all files or folder starting with "." so that we can + handle ".svn", ".git" and so on without problems. +
449
4502 if (type != "README" && type[0] != '.') { // Ignore the readme + file and .DS_Store file for Macs +
4512 calipso.lib.fs.readdirSync(path.join(moduleBasePath, + type)).forEach(function(moduleFolderName) { +
452
4538 if (moduleFolderName != "README" && moduleFolderName[0] != + '.') { // Ignore the readme file and .DS_Store file for Macs +
454
4558 var modulePath = path.join(moduleBasePath, type, moduleFolderName);
456
4578 var module = {
458 name: moduleFolderName,
459 folder: moduleFolderName,
460 library: moduleFolderName,
461 type: type,
462 path: modulePath,
463 enabled: false,
464 inited: false
465 };
466
467 // Add about info to it
4688 loadAbout(module, modulePath, 'package.json');
469
470 // Set the module name to what is in the package.json, default to folder name +
4718 module.name = (module.about && module.about.name) ? module.about.name : + moduleFolderName; +
472
473 // Now set the module
4748 calipso.modules[module.name] = module;
475
476 // Set if it is enabled or not
4778 module.enabled = configuredModules[module.name] ? + configuredModules[module.name].enabled : false; +
478
4798 if (module.enabled) {
480
481 // Load the module itself via require
4828 requireModule(calipso.modules[module.name], modulePath);
483
484 // Load the templates (factored out so it can be recalled by watcher)
4858 loadModuleTemplates(calipso.modules[module.name], + path.join(modulePath,'templates')); +
486
487 }
488
489 }
490
491 });
492 }
493
494 });
495
496 // Now that all are loaded, attach events & depends
4972 attachModuleEventsAndDependencies();
498
499 // Save configuration changes (if required)
5002 if (calipso.config.dirty) {
5010 calipso.config.save(next);
502 } else {
5032 return next();
504 }
505
506}
507
508/**
509 * Load data from package.json or theme.json
510 */
511
5121function loadAbout(obj, fromPath, file) {
513
51411 var fs = calipso.lib.fs;
515
51611 var packageFile = calipso.lib.path.join(fromPath, file);
517
51811 if ((fs.existsSync || path.existsSync)(packageFile)) {
51911 var json = fs.readFileSync(packageFile);
52011 try {
52111 obj.about = JSON.parse(json.toString());
52211 if (obj.about && obj.about.name) {
52311 obj.library = obj.about.name;
524 } else {
5250 obj.library = obj.name;
526 }
527 } catch (ex) {
5280 obj.about = {
529 description: 'Invalid ' + file
530 };
531 }
532 }
533
534}
535
536/**
537 * Connect up events and dependencies
538 * Must come after all modules are loaded
539 */
540
5411function attachModuleEventsAndDependencies() {
542
5432 var options = {maxListeners: calipso.config.get('server:events:maxListeners'), + notifyDependencyFn: notifyDependenciesOfInit}; +
544
5452 for (var module in calipso.modules) {
546
547 // Register dependencies
5488 registerModuleDependencies(calipso.modules[module]);
549
550 // Attach event listener
5518 calipso.event.addModuleEventListener(calipso.modules[module], options);
552
553 }
554
555 // Sweep through the dependency tree and make sure any broken dependencies are + disabled +
5562 disableBrokenDependencies();
557
558}
559
560/**
561 * Ensure dependencies are mapped and registered against parent and child
562 */
563
5641function registerModuleDependencies(module) {
565
5668 if (module.fn && module.fn.depends && module.enabled) {
567
568 // Create object to hold dependent status
5692 module.check = {};
570
571 // Register depends on parent
5722 module.fn.depends.forEach(function(dependentModule) {
573
5742 module.check[dependentModule] = false;
575
5762 if (calipso.modules[dependentModule] && + calipso.modules[dependentModule].enabled) { +
577
578 // Create a notification array to allow this module to notify modules that + depend on it +
5792 calipso.modules[dependentModule].notify = + calipso.modules[dependentModule].notify || []; +
5802 calipso.modules[dependentModule].notify.push(module.name);
581
582 } else {
583
5840 calipso.modules[module.name].error = "Module " + module.name + " + depends on " + dependentModule + ", but it does not exist or is disabled - this module + will not load."; +
5850 calipso.error(calipso.modules[module.name].error);
5860 calipso.modules[module.name].enabled = false;
587
588 }
589
590 });
591
592 }
593
594}
595
596
597/**
598 * Disable everythign in a broken dependency tree
599 */
600
6011function disableBrokenDependencies() {
602
6032 var disabled = 0;
6042 for (var moduleName in calipso.modules) {
6058 var module = calipso.modules[moduleName];
6068 if (module.enabled && module.fn && module.fn.depends) {
6072 module.fn.depends.forEach(function(dependentModule) {
6082 if (!calipso.modules[dependentModule].enabled) {
6090 calipso.modules[module.name].error = "Module " + module.name + " + depends on " + dependentModule + ", but it does not exist or is disabled - this module + will not load."; +
6100 calipso.error(calipso.modules[module.name].error);
6110 calipso.modules[module.name].enabled = false;
6120 disabled = disabled + 1;
613 }
614 });
615 }
616 }
617
618 // Recursive
6192 if (disabled > 0) disableBrokenDependencies();
620
621}
622
623/**
624 * Notify dependencies for initialisation
625 */
626
6271function notifyDependenciesOfInit(moduleName, options) {
628
6298 var module = calipso.modules[moduleName];
6308 if (module.notify) {
6312 module.notify.forEach(function(notifyModuleName) {
6322 notifyDependencyOfInit(moduleName, notifyModuleName, options);
633 });
634 }
635
636}
637
638
639/**
640 * Notify dependencies for routing
641 */
642
6431function notifyDependenciesOfRoute(req, res, moduleName, reqModules) {
644
6458 var module = calipso.modules[moduleName];
6468 if (module.notify) {
6474 module.notify.forEach(function(notifyModuleName) {
6484 notifyDependencyOfRoute(req, res, moduleName, notifyModuleName);
649 });
650 }
651
652}
653
654/**
655 * Notify dependency
656 * moduleName - module that has init'd
657 * notifyModuleName - module to tell
658 */
659
6601function notifyDependencyOfInit(moduleName, notifyModuleName, options) {
661
662 // Set it to true
6632 var module = calipso.modules[notifyModuleName];
6642 module.check[moduleName] = true;
6652 checkInit(module);
666
667}
668
669
670/**
671 * Notify dependency
672 * req - request
673 * res - response
674 * moduleName - module that has init'd
675 * notifyModuleName - module to tell
676 */
677
6781function notifyDependencyOfRoute(req, res, moduleName, notifyModuleName) {
679
6804 var module = req.event.modules[notifyModuleName];
6814 module.check[moduleName] = true;
6824 checkRouted(req, res, moduleName, notifyModuleName);
683
684}
685
686/**
687 * Check if all dependencies are met and we should init the module
688 */
689
6901function checkInit(module, next) {
691
6922 var doInit = true;
6932 for (var check in module.check) {
6942 doInit = doInit & module.check[check];
695 }
6962 if (doInit) {
697 // Initiate the module, no req for callback
6982 initModule(module.name, true, function() {});
699 }
700
701}
702
703/**
704 * Check if all dependencies are met and we should route the module
705 */
706
7071function checkRouted(req, res, moduleName, notifyModuleName) {
708
7094 var doRoute = true;
710
7114 for (var check in req.event.modules[notifyModuleName].check) {
7124 doRoute = doRoute && req.event.modules[notifyModuleName].check[check]; +
713 }
714
7154 if (doRoute) {
716 // Initiate the module, no req for callback
717 // initModule(module.name,true,function() {});
7184 routeModule(req, res, notifyModuleName, true, false, function() {});
719 }
720
721}
722
723/**
724 * Load the module itself, refactored out to enable watch / reload
725 * Note, while it was refactored out, you can't currently reload
726 * a module, will patch in node-supervisor to watch the js files and restart
727 * the whole server (only option :())
728 */
729
7301function requireModule(module, modulePath, reload, next) {
731
7328 var fs = calipso.lib.fs;
7338 var moduleFile = path.join(modulePath + '/' + module.name);
734
7358 try {
736
737 // Require the module
7388 module.fn = require(moduleFile);
739
740 // Attach a router - legacy check for default routes
7418 module.router = new calipso.router(module.name, modulePath);
742
743 // Load the routes if specified as either array or function
7448 if (typeof module.fn.routes === "function") module.fn.routes = + module.fn.routes(); +
7458 module.fn.routes = module.fn.routes || [];
746
747 // Ensure the defaultConfig exists (e.g. if it hasn't been required before)
748 // This is saved in the wider loadModules loop to ensure only one config save + action (if required) +
7498 if (module.fn.config && !calipso.config.getModuleConfig(module.name, + '')) { +
7500 calipso.config.setDefaultModuleConfig(module.name, module.fn.config);
751 }
752
753 } catch (ex) {
754
7550 calipso.error("Module " + module.name + " has been disabled + because " + ex.message); +
7560 calipso.modules[module.name].enabled = false;
757
758 }
759
760}
761
762/**
763 * Pre load all the templates in a module, synch, but only happens on app start + up and config reload +
764 * This is attached to the templates attribute so used later.
765 *
766 * @param calipso
767 * @param moduleTemplatePath
768 * @returns template object
769 */
770
7711function loadModuleTemplates(module, moduleTemplatePath) {
772
7738 var templates = {};
774
775 // Default the template to any loaded in the theme (overrides)
7768 var fs = calipso.lib.fs;
777
7788 if (!(fs.existsSync || calipso.lib.path.existsSync)(moduleTemplatePath)) {
7796 return null;
780 }
781
7822 fs.readdirSync(moduleTemplatePath).forEach(function(name) {
783
784 // Template paths and functions
7852 var templatePath = moduleTemplatePath + "/" + name;
7862 var templateExtension = templatePath.match(/([^\.]+)$/)[0];
7872 var template = fs.readFileSync(templatePath, 'utf8');
7882 var templateName = name.replace(/\.([^\.]+)$/, '');
789
790 // Load the template - only if not already loaded by theme (e.g. overriden)
7912 var hasTemplate = calipso.utils.hasProperty('theme.cache.modules.' + module.name + + '.templates.' + templateName, calipso); +
792
7932 if (hasTemplate) {
794
795 // Use the theme version
7960 templates[templateName] = + calipso.theme.cache.modules[module.name].templates[templateName]; +
797
798 } else {
799
800 // Else load it
8012 if (template) {
802 // calipso.theme.compileTemplate => ./Theme.js
8032 templates[templateName] = calipso.theme.compileTemplate(template, templatePath, + templateExtension); +
804
805 // Watch / unwatch files - always unwatch (e.g. around config changes)
8062 if (calipso.config.get('performance:watchFiles')) {
807
8082 fs.unwatchFile(templatePath); // Always unwatch first due to recursive + behaviour +
8092 fs.watchFile(templatePath, {
810 persistent: true,
811 interval: 200
812 }, function(curr, prev) {
8130 loadModuleTemplates(module, moduleTemplatePath);
8140 calipso.silly("Module " + module.name + " template " + name + + " reloaded."); +
815 });
816
817 }
818
819 }
820 }
821 });
822
8232 module.templates = templates;
824
825}
826
827/**
828 * Exports
829 */
8301module.exports = {
831 loadModules: loadModules,
832 initModules: initModules,
833 eventRouteModules: eventRouteModules,
834 notifyDependenciesOfInit: notifyDependenciesOfInit,
835 notifyDependenciesOfRoute: notifyDependenciesOfRoute,
836 registerDependencies: registerDependencies,
837 loadAbout: loadAbout
838};
+
+

core/Permission.js

+ +
+
55%
+
61
+
34
+
27
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Permissions Class
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library adds a permissions class to the router, defining functions that + are used by the router to control access. +
7 *
8 */
9
101var rootpath = process.cwd() + '/',
11 path = require('path'),
12 calipso = require(path.join('..', 'calipso'));
13
14/**
15 * A set of helper functions to simplify the application of filters, as well as + store +
16 * the in memory map of roles to permissions (in memory for performance + reasons) +
17 */
181var PermissionHelpers = {
19
20 // Holder of defined permissions
21 permissions: {},
22 sortedPermissions: [],
23 structuredPermissions: {},
24
25 // Clear all oaded permissions
26 clearPermissionRoles: function() {
27
280 var self = this;
290 for (var perm in self.permissions) {
300 delete self.permissions[perm].roles;
310 self.permissions[perm].roles = [];
32 }
33
34 },
35
36 // Add a permission
37 addPermission: function(permission, description, isCrud) {
38
393 var self = this;
40
41 // if Crud, automatically add level below
423 if (isCrud) {
430 calipso.lib._.map(["view", "create", "update", + "delete"], function(crudAction) { +
440 var crudPermission = permission + ":" + crudAction;
450 self.permissions[crudPermission] = {
46 roles: [],
47 queries: [],
48 description: crudAction + " " + description
49 };
500 self.sortedPermissions.push(crudPermission);
51 });
52 } else {
53
54 // Add Permission always resets it if it already exists
553 self.permissions[permission] = {
56 roles: [],
57 queries: [],
58 description: description
59 };
603 self.sortedPermissions.push(permission);
61
62 }
63
64 },
65
66 structureAndSort: function() {
67
680 var self = this;
69
70 // This could be done by the permissions module
710 self.sortedPermissions.sort(function(a, b) {
720 return a < b;
73 });
74
75 // Now we need to create our permissions object structure
760 self.sortedPermissions.forEach(function(value) {
77
780 var path = value.split(":"),
79 target = self.structuredPermissions,
80 counter = 0;
81
820 while (path.length > 1) {
830 key = path.shift();
840 if (!target[key] || typeof target[key] !== 'object') {
850 target[key] = {};
86 }
870 target = target[key];
88 }
89
90 // Set the specified value in the nested JSON structure
910 key = path.shift();
920 if (typeof target[key] !== "object") {
930 target[key] = self.permissions[value].roles;
94 }
95
96 });
97
98 },
99
100 // Add a map between role / permission (this is loaded via the user module)
101 addPermissionRole: function(permission, role) {
102
1033 var self = this;
104
105 // Store this as a simple in memory map
1063 if (self.permissions[permission]) {
1073 self.permissions[permission].roles.push(role);
1083 return true;
109 } else {
1100 calipso.warn("Attempted to map role: " + role + " to a + permission: " + permission + " that does not exist (perhaps related to a disabled + module?)."); +
1110 return false;
112 }
113
114 },
115
116 // Does a user have a role
117 hasRole: function(role) {
118 // Curried filter
1190 return function(user) {
1200 var isAllowed = user.roles.indexOf(role) >= 0 ? true : false,
121 message = isAllowed ? ('User has role ' + role) : 'You dont have the appropriate + roles to view that page!'; +
1220 return {
123 allow: isAllowed,
124 msg: message
125 };
126 };
127 },
128
129 // Does a user have a permission
130 hasPermission: function(permission) {
131
1325 var self = this;
133
134 // Curried filter
1355 return function(user) {
136
137 // Check if the user has a role that maps to the permission
13826 var userRoles = user.roles,
139 permissionRoles = self.permissions[permission] ? + self.permissions[permission].roles : []; +
140
141 // Check if allowed based on intersection of user roles and roles that have + permission +
14226 var isAllowed = calipso.lib._.intersect(permissionRoles, userRoles).length > + 0, +
143 message = isAllowed ? ('User has permission ' + permission) : 'You do not have + any of the roles required to perform that action.'; +
144
145
14626 return {
147 allow: isAllowed,
148 msg: message
149 };
150
151 };
152
153 }
154
155};
156
157
158/**
159 * The default calipso permission filter, this is attached to every route, and + processed as part of the route matching. +
160 */
1611function PermissionFilter(options, permit) {
162
163 // Store the options
16436 var self = this;
16536 self.options = options;
166
16736 if(permit) {
16830 if(typeof permit === 'function') {
169 // permit is already a fn created by a helper
17028 self.permit = permit;
171 } else {
172 // permit is a string - e.g. 'admin:core:configuration'
1732 self.permit = calipso.permission.Helper.hasPermission(permit);
174 }
175 }
176
177}
178
1791PermissionFilter.prototype.check = function(req) {
180
18130 var self = this;
18230 if (!self.permit && self.options.permit) self.permit = + self.options.permit; +
18330 if (self.permit) {
184
18530 var user = req.session.user;
18630 var isAdmin = req.session.user && req.session.user.isAdmin;
187
18832 if (isAdmin) return {
189 allow: true
190 }; // Admins always access everything
191 // Else check for a specific permission
19228 if (user) {
19326 return self.permit(user);
194 } else {
1952 return {
196 allow: false,
197 msg: 'You must be a logged in user to view that page'
198 };
199 }
200
201 } else {
2020 return {
203 allow: true
204 };
205 }
206
207};
208
209
210/**
211 * Export an instance of our object
212 */
2131exports.Filter = PermissionFilter;
2141exports.Helper = PermissionHelpers;
+
+

core/Router.js

+ +
+
75%
+
90
+
68
+
22
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso Core Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * The Calipso Router provides a router object to each module that enables each + module to register +
7 * its own functions to respond to URL patterns (as per the typical Express + approach). Note +
8 * that Calipso itself does not respond to any URL outside of those exposed by a + module, if all are disabled +
9 * the application will do nothing.
10 *
11 * Borrowed liberally from Connect / ExpressJS itself for this, thanks for the + great work! +
12 */
13
14/**
15 * Includes
16 */
171var rootpath = process.cwd() + '/',
18 path = require('path'),
19 calipso = require(path.join('..', 'calipso')),
20 url = require('url'),
21 fs = require('fs'),
22 PermissionFilter = require('./Permission').Filter,
23 PermissionHelper = require('./Permission').Helper,
24 blocks = require('./Blocks');
25
26/**
27 * Core router object, use the return model to ensure
28 * that we always return a new instance when called.
29 *
30 * A Router is attached to each module, and allows each module to effectively +
31 * act as its own controller in a mini MVC model.
32 *
33 * This class exposes:
34 *
35 * addRoutes: function, to add Routes to a module.
36 * route: iterate through the routes, match, and then call the matched function + in the module. +
37 *
38 */
391var Router = function(moduleName, modulePath) {
40
418 return {
42
43 moduleName: moduleName,
44 modulePath: modulePath,
45 routes: [],
46
47 /**
48 * A route is defined by three parameters:
49 *
50 * path: a string in the form 'GET /url' where the first piece is the HTTP method + to respond to. +
51 * OR
52 * a regex function (it matches only on GET requests).
53 * fn: the function in the module to call if the route matches.
54 * options: additional configuration options, specifically:
55 * end - deprecated. TODO CLEANUP
56 * admin - is the route an administrative route (user must have isAdmin = + true). +
57 */
58 addRoute: function(options, next, legacy_options, legacy_next) {
59
60 // Default options
618 var self = this,
62 defaults = {
63 end: true,
64 admin: false,
65 user: false,
66 cache: false,
67 permit: null
68 };
69
70 // Deal with legacy, this will eventually just be options, next to enable + simpler invocation +
71 // And to make it more extensible
728 if (typeof legacy_next === "function") {
73
746 var routePath = options,
75 fn = next;
76
77 // Set the variables
786 options = legacy_options || {};
796 options.path = routePath;
806 options.fn = fn;
81
826 next = legacy_next;
83
84 }
85
86 // Default options
878 options = calipso.lib._.extend(defaults, options);
888 options.permitFn = new PermissionFilter(options, options.permit);
89
908 self.routes.push(options);
91
928 next();
93
94 },
95
96 /**
97 * Module routing function, iterates through the configured routes and trys to + match. +
98 * This has been borrowed from the Express core routing module and refactored + slightly +
99 * to deal with the fact this is much more specific.
100 */
101 route: function(req, res, next) {
102
10316 var self = this,
104 requestUrl = url.parse(req.url, true),
105 routes = this.routes;
106
107 // Use step to enable parallel execution
10816 calipso.lib.step(
109
110 function matchRoutes() {
111
112 // Emit event to indicate starting
11316 var i, l, group = this.group();
114
11516 for (i = 0, l = routes.length; i < l; i = i + 1) {
116
11716 var keys = [],
118 route = routes[i],
119 templateFn = null,
120 block = "",
121 routeMethod = "",
122 routeRegEx, j, paramLen, param, allPages = false;
123
12416 if (typeof route.path === "string") {
1258 routeMethod = route.path.split(" ")[0];
1268 routeRegEx = normalizePath(route.path.split(" ")[1], keys);
127 } else {
1288 routeRegEx = route.path;
1298 allPages = true; // Is a regex
130 }
131
13216 var captures = requestUrl.pathname.match(routeRegEx);
133
13416 if (captures && (!routeMethod || req.method === routeMethod)) {
135
136 // Check to see if we matched a non /.*/ route to flag a 404 later
13711 res.routeMatched = !allPages || res.routeMatched;
138
139 // If admin, then set the route
14011 if (route.admin) {
1412 res.layout = "admin";
1422 if(!route.permit){
1430 calipso.debug("Route has admin only but no permit is defined!");
1440 route.permit = calipso.permission.Helper.hasPermission("admin");
145 }
146 }
147
148 // TODO
14911 var isAdmin = req.session.user && req.session.user.isAdmin;
150
151 // Check to see if it requires logged in user access
15211 if (route.permit) {
153
1542 var permit = route.permitFn.check(req);
155
1562 if (typeof permit !== "object") permit = {
157 allow: false,
158 msg: 'You don\'t have the appropriate permissions to view that page.'
159 };
1602 if (!permit.allow) {
1611 if (!allPages) {
1621 if (!req.cookies.logout) {
1631 req.flash('error', req.t(permit.msg));
1641 res.statusCode = 401;
165 }
1661 res.redirect("/");
1671 return group()();
168 } else {
169 // Simply ignore silently
1700 return group()();
171 }
172 }
173 }
174
175 // Debugging - only when logged in as admin user
176 // calipso.silly("Module " + router.moduleName + " matched route: + " + requestUrl.pathname + " / " + routeRegEx.toString() + " [" + + res.routeMatched + "]"); +
177 // Lookup the template for this route
17810 if (route.template) {
1791 templateFn = calipso.modules[self.moduleName].templates[route.template];
1801 if (!templateFn && route.template) {
1810 var err = new Error("The specified template: " + route.template + + " does not exist in the module: " + self.modulePath); +
1820 return group()(err);
183 } else {
1841 calipso.silly("Using template: " + route.template + " for module: + " + self.modulePath); +
185 }
1861 route.templateFn = templateFn;
187 }
188
189 // Set the object to hold the rendered blocks if it hasn't been created + already +
19010 if (!res.renderedBlocks) {
1911 res.renderedBlocks = new blocks.RenderedBlocks(calipso.cacheService);
192 }
193
194 // Copy over any params that make sense from the url
19510 req.moduleParams = {};
19610 for (j = 1, paramLen = captures.length; j < paramLen; j = j + 1) {
1970 var key = keys[j - 1],
198 val = typeof captures[j] === 'string' ? decodeURIComponent(captures[j]) : + captures[j]; +
1990 if (key) {
2000 req.moduleParams[key] = val;
201 } else {
202 // Comes from custom regex, no key
203 // req.moduleParams["regex"] = val;
204 }
205 }
206
207 // Convert any url parameters if we are not a .* match
20810 if (requestUrl.query && !allPages) {
2092 for (param in requestUrl.query) {
2100 if (requestUrl.query.hasOwnProperty(param)) {
2110 req.moduleParams[param] = requestUrl.query[param];
212 }
213 }
214 }
215
216 // Store the params for use outside the router
21710 res.params = res.params || {};
21810 calipso.lib._.extend(res.params, req.moduleParams);
219
220 // Set if we should cache this block - do not cache by default, do not cache + admins +
22110 var cacheBlock = res.renderedBlocks.contentCache[block] = route.cache && + !isAdmin; +
22210 var cacheEnabled = calipso.config.get('performance:cache:enabled');
223
22410 if (route.block && cacheBlock && cacheEnabled) {
225
2260 var cacheKey = calipso.cacheService.getCacheKey(['block', route.block], + res.params); +
227
2280 calipso.cacheService.check(cacheKey, function(err, isCached) {
2290 if (isCached) {
230 // Set the block from the cache, set layout if needed
2310 res.renderedBlocks.getCache(cacheKey, route.block, function(err, layout) {
2320 if (layout) res.layout = layout;
2330 group()(err);
234 });
235 } else {
236
237 // Execute the module route function and cache the result
2380 self.routeFn(req, res, route, group());
239
240 }
241 });
242
243 } else {
244
24510 self.routeFn(req, res, route, group());
246
247 }
248
249 }
250
251 }
252
253 },
254
255 function allMatched(err) {
256
257 // Once all functions have been called, log the error and pass it back up the + tree. +
25816 if (err) {
259 // Enrich the error message with info on the module
260 // calipso.error("Error in module " + this.moduleName + ", of + " + err.message); +
2610 err.message = err.message + " Calipso Module: " + self.moduleName; +
262 }
263
264 // Emit routed event
26516 next(err, self.moduleName);
266
267 });
268
269 },
270
271 // Wrapper for router
272 // This deals with legacy modules pre 0.3.0 (will remove later)
273 routeFn: function(req, res, route, next) {
274
27510 if (typeof route.fn !== "function") console.dir(route);
276
27710 var legacyRouteFn = route.fn.length === 5 ? true : false;
27810 if (legacyRouteFn) {
2790 route.fn(req, res, route.templateFn, route.block, next);
280 } else {
28110 route.fn(req, res, route, next);
282 }
283
284 }
285
286 };
287
288 };
289
290
291/**
292 * Normalize the given path string,
293 * returning a regular expression.
294 *
295 * An empty array should be passed,
296 * which will contain the placeholder
297 * key names. For example "/user/:id" will
298 * then contain ["id"].
299 *
300 * BORROWED FROM Connect
301 *
302 * @param {String} path
303 * @param {Array} keys
304 * @return {RegExp}
305 * @api private
306 */
307
3081function normalizePath(path, keys) {
3098 path = path.concat('/?').replace(/\/\(/g, + '(?:/').replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, + optional) { +
3100 keys.push(key);
3110 slash = slash || '';
3120 return '' + (optional ? '' : slash) + '(?:' + (optional ? slash : '') + (format + || '') + (capture || '([^/]+?)') + ')' + (optional || ''); +
313 }).replace(/([\/.])/g, '\\$1').replace(/\*/g, '(.+)');
314
3158 return new RegExp('^' + path + '$', 'i');
316}
317
318/**
319 * Exports
320 */
3211module.exports = Router;
+
+

core/Storage.js

+ +
+
58%
+
41
+
24
+
17
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso MongoDB Storage Library
3 * Copyright(c) 2011 Clifton Cunningham
4 * MIT Licensed
5 *
6 * This library provides a few simple functions that can be used to help manage + MongoDB and Mongoose. +
7 */
8
91var rootpath = process.cwd(),
10 path = require('path'),
11 events = require('events'),
12 mongoStore = require('connect-mongodb'),
13 mongoose = require('mongoose'),
14 calipso = require(path.join('..', 'calipso'));
15
161function Storage() {
17 // Store running map reduce functions
181 this.mr = {};
19}
20
21/**
22 * Check that the mongodb instance specified in the configuration is valid.
23 */
241Storage.prototype.mongoConnect = function(dbUri, checkInstalling, next) {
25
26 // Test the mongodb configuration
272 var isInstalled = calipso.config.get('installed');
28
29 // If first option is callback, ste dbUri to config value
302 if (typeof dbUri === "function") {
310 next = dbUri;
320 dbUri = calipso.config.get('database:uri');
330 checkInstalling = false;
34 }
35
36 // Check we are installing ...
372 if (checkInstalling) {
380 var db = mongoose.createConnection(dbUri, function(err) {
390 next(err, false);
40 });
410 return;
42 }
43
442 if (isInstalled) {
45
46 // Always disconnect first just in case any left overs from installation
472 mongoose.disconnect(function() {
48
49 // TODO - what the hell is going on with mongoose?
502 calipso.db = mongoose.createConnection(dbUri, function(err) {
51
522 if (err) {
53
540 calipso.error("Unable to connect to the specified database ".red + + dbUri + ", the problem was: ".red + err.message); +
550 mongoose.disconnect(function() {
560 return next(err, false);
57 });
58
59 } else {
60
612 calipso.silly("Database connection to " + dbUri + " was + successful."); +
62
63 // Replace the inmemory session with mongodb backed one
642 var foundMiddleware = false, mw;
65
662 calipso.app.stack.forEach(function(middleware, key) {
676 if (middleware.handle.tag === 'session') {
682 foundMiddleware = true;
692 var maxAge = calipso.config.get('session:maxAge');
702 if (maxAge) {
710 try {
720 maxAge = Number(maxAge) * 1000;
73 }
74 catch (e) {
750 calipso.error('MaxAge value ' + maxAge + ' is not a numeric string');
760 maxAge = undefined;
77 }
78 }
792 mw = calipso.lib.express.session({
80 secret: calipso.config.get('session:secret'),
81 store: calipso.app.sessionStore = new mongoStore({
82 db: calipso.db.db
83 }),
84 cookie: { maxAge: maxAge }
85 });
862 mw.tag = 'session';
872 calipso.app.stack[key].handle = mw;
88 }
89 });
90
912 if (!foundMiddleware) {
920 return next(new Error("Unable to load the MongoDB backed session, please + check your session and db configuration"), false); +
93 }
94
952 return next(null, true);
96
97 }
98 });
99 });
100
101 } else {
102
1030 calipso.silly("Database connection not attempted to " + dbUri + " + as in installation mode."); +
104
105 // Create a dummy connection to enable models to be defined
1060 calipso.db = mongoose.createConnection('');
107
1080 next(null, false);
109
110 }
111
112};
113
1141module.exports = new Storage();
+
+

core/Table.js

+ +
+
70%
+
65
+
46
+
19
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 *
3 * Calipso Table Rendering Library
4 *
5 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
6 * MIT Licensed
7 *
8 * Loaded into calipso as a plugin, used to simplify the rendering of tabular + data. +
9 * Including things such as rendering table sorting elements etc.
10 * TODO: validation, redisplay of submitted values
11 *
12 */
13
141var rootpath = process.cwd() + '/',
15 path = require('path'),
16 calipso = require(path.join('..', 'calipso')),
17 qs = require('qs'),
18 pager = require(path.join(rootpath, 'utils/pager')),
19 merge = require('connect').utils.merge;
20
21// Global variable (in this context) for translation function
221var t;
23
24/**
25 * The default calipso table object, with default configuration values.
26 * Constructor
27 */
28
291function CalipsoTable() {
30
31 //TODO Allow over-ride
32}
33
34/**
35 * Export an instance of our table object
36 */
371module.exports = new CalipsoTable();
38
39
40/**
41 * Table Renderer, controls the overall creation of the tablle based on a form + json object passed +
42 * in as the first parameter. The structure of this object is as follows:
43 *
44 * table
45 * id : Unique ID that will become the form ID.
46 * title : Title to show at the top of the form.
47 * cls : css class
48 * columns [*] : Form fields array - can be in form or section.
49 * label : Label for form field.
50 * name : Name of form element to be passed back with the value.
51 * type : Type of element, based on the form functions defined below.
52 * sortable : true / false
53 * fn : Function to apply to the row
54 * data [*] : Array of buttons to be rendered at the bottom of the form.
55 * view : COntrols display of this form
56 * pager : show pager
57 * from : from page
58 * to : to page
59 * url : base url for links
60 * sort : {} of sort field name:dir (asc|desc)
61 *
62 * This is synchronous so that it can be called from views.
63 *
64 * @param item : the json object representing the table
65 * @param req : The request object
66 */
671CalipsoTable.prototype.render = function(req, item) {
68
69 // Store local reference to the request for use during translation
701 t = req.t;
71
721 return (
73 this.start_table(item) + this.render_headers(item) + this.render_data(item, req) + + this.end_table(item) + this.render_pager(item, item.view.url)); +
74
75};
76
77/**
78 * Render the initial table tag
79 *
80 * @param form
81 * @returns {String}
82 */
831CalipsoTable.prototype.start_table = function(table) {
841 return ('<table id="' + table.id + '"' + (table.cls ? ' class="' + + table.cls + '"' : "") + '>'); +
85};
86
87/**
88 * Close the table
89 * @param table
90 * @returns {String}
91 */
921CalipsoTable.prototype.end_table = function(table) {
931 return '</table>';
94};
95
96
97/**
98 * Render headers
99 * @param table
100 * @returns {String}
101 */
1021CalipsoTable.prototype.render_headers = function(table) {
103
104 // If there are no columns, return
1051 if (table.columns.length === 0) throw new Error("You must define columns to + render a table."); +
106
107 // Test
1081 var output = "<thead><tr>";
109
110 // Iterate
1111 table.columns.forEach(function(column, key) {
112
113 // set the class
114 // Check to see if we are sorting by this column
1153 var cls = getHeaderClass(table, column);
116
1173 output += "<th" + (' class="' + cls + '"') + (column.sort + ? ' name="' + column.sort + '"' : (column.name ? ' name="' + column.name + '"' + : "")) + ">"; +
1183 output += column.label;
1193 output += "</th>";
120
121 });
122
1231 output += "</tr></thead>";
124
1251 return output;
126
127};
128
129/**
130 * Helper function to determine column header sort class
131 */
132
1331function getHeaderClass(table, column) {
134
135 // Default class
1363 var cls = column.cls || '';
137 // Sortable
1383 cls += column.sortable === false ? '' : 'sortable';
139
1403 if (table.view && table.view.sort && + (table.view.sort[column.name] || table.view.sort[column.sort])) { +
1410 cls += ' sorted-' + (table.view.sort[column.sort] || + table.view.sort[column.name]); +
142 } else {
143 // Leave as is
144 }
1453 return cls;
146
147}
148
149/**
150 * Convert a sortBy parameter into mongo sort queries
151 */
1521CalipsoTable.prototype.sortQuery = function(qry, sortBy) {
153
1540 if (typeof sortBy === 'string') sortBy = [sortBy];
1550 if (!sortBy || sortBy.length === 0) return qry;
156
1570 sortBy.forEach(function(sort) {
1580 var sortArr = sort.split(",");
1590 if (sortArr.length === 2) {
1600 var dir = sortArr[1] === 'asc' ? 1 : (sortArr[1] === 'desc' ? -1 : 0);
1610 qry = qry.sort(sortArr[0], dir);
162 }
163 });
164
1650 return qry;
166};
167
168
169/**
170 * Convert a sort by form param into a view sort object
171 */
1721CalipsoTable.prototype.parseSort = function(sortBy) {
173
1740 var options = {};
175
1760 if (typeof sortBy === 'string') sortBy = [sortBy];
1770 if (!sortBy || sortBy.length === 0) return options;
178
1790 sortBy.forEach(function(sort) {
1800 var sortArr = sort.split(",");
1810 if (sortArr.length === 2) {
1820 options[sortArr[0]] = sortArr[1];
183 }
184 });
185
1860 return options;
187};
188
189
190/**
191 * Render headers
192 * @param table
193 * @returns {String}
194 */
1951CalipsoTable.prototype.render_data = function(table, req) {
196
197 // If there are no columns, return
1981 if (table.columns.length === 0) throw new Error("You must define columns to + render a table."); +
199
200 // Test
2011 var output = "<tbody>";
202
203 // Iterate
2041 table.data.forEach(function(row) {
2052 output += "<tr>";
206 // Iterate over the columns
2072 table.columns.forEach(function(column) {
2086 output += "<td>";
2096 if (column.name in row) {
2106 if (typeof column.fn === "function") {
2110 output += column.fn(req, row);
212 } else {
2136 output += row[column.name];
214 }
215 } else {
2160 output += "Invalid: " + column.name;
217 }
2186 output += "</td>";
219 });
2202 output += "</tr>";
221 });
222
2231 return output + "</tbody>";
224
225};
226
227/**
228 * Render headers
229 * @param table
230 * @returns {String}
231 */
2321CalipsoTable.prototype.render_pager = function(table, url) {
233
234 // Test
2351 var output = "";
236
2371 if (table.view && table.view.pager) {
2381 output += pager.render(table.view.from, table.view.limit, table.view.total, + url); +
239 }
240
2411 return output;
242
243};
+
+

core/Themes.js

+ +
+
65%
+
238
+
155
+
83
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/*!
2 * Calipso theme library
3 *
4 * Copyright(c) 2011 Clifton Cunningham <clifton.cunningham@gmail.com>
5 * MIT Licensed
6 *
7 * This library provides all of the template loading, caching and rendering + functions used by Calipso. +
8 *
9 * The idea is that modules only ever provide generic, unstyled html (or json), + where all layout and styling is completely +
10 * controlled by the theme. Themes should be able to be reloaded on configuration + change, and the theme engine +
11 * will watch for changes to templates (based on config) to speed up + development. +
12 *
13 * Additionally, configuration of layouts has been extracted out into a theme + configuration file, enabling control of +
14 * the 'wiring' to an extent.
15 *
16 * Decision was made to not use the default express view renderers as it didn't + give enough control over caching templates, +
17 * Interacting with the view libraries directly,
18 *
19 * Borrowed liberally from Connect / ExpressJS itself for this, thanks for the + great work! +
20 *
21 */
22
23/**
24 * Includes
25 */
26
271var rootpath = process.cwd() + '/',
28 path = require('path'),
29 calipso = require(path.join('..', 'calipso')),
30 fs = require('fs'),
31 utils = require('connect').utils,
32 merge = utils.merge;
33
34/**
35 * The theme object itself, instantiated within calipso
36 */
371module.exports.Theme = function(theme, next) {
38
39 // Defaults
402 var themeName = theme.name;
412 var themePath = theme.path;
42
43 /**
44 * Load a theme
45 */
462 loadTheme(themeName, themePath, function(err, themeConfig) {
47
482 if (err) {
490 next(err);
500 return;
51 }
52
532 cacheTheme(themeConfig, themePath, function(err, themeCache) {
54
552 if (err) {
560 next(err);
570 return;
58 }
59
60 // Load the theme configuration file.
612 var theme = {
62 theme: themeName,
63 cache: themeCache,
64 config: themeConfig,
65 compileTemplate: function(data, templatePath, templateExtension) {
66 // expose private function for module to use
672 return compileTemplate(data, templatePath, templateExtension);
68 },
69
70 // Render a module
71 // Changed in 0.1.1 to be asynch
72 renderItem: function(req, res, template, block, options, next) {
73
741 var output = "";
75
761 if (template) {
77
781 var themeOptions = createOptions(req, res, options);
79
801 if (typeof template === 'function') {
811 try {
821 output = template.call({}, themeOptions);
83 } catch (ex) {
840 output = "Block: " + block + " failed to render because " + + ex.message + ex.stack; +
85 }
86
87 } else {
88 // Assume template is processed HTML
890 output = template;
90 }
91
921 if (block) {
93 // Store the block and layout
941 res.renderedBlocks.set(block, output, res.layout, res.params, next);
95 } else {
96 // Just return back to the calling function
970 next(null, output);
98 }
99
100 }
101
102 },
103 render: function(req, res, next) {
104
1053 var cache = this.cache, theme = this, layout = res.layout ? res.layout : "default", + content, themeOptions, err; +
106
1073 calipso.silly("Using layout " + layout);
108
1093 if (!theme.config.layouts[layout]) {
1100 layout = "default";
1110 if (!theme.config.layouts[layout]) {
1120 calipso.error("Default layout is not defined within the current theme, + exiting."); +
1130 res.send("");
1140 return;
115 }
116 }
117
1183 processTheme(req, res, layout, theme, function(err) {
119
120 // If something went wrong ...
1213 if (err) {
1220 next(err, null);
1230 return;
124 }
125
126 // Now, process the layout template itself
1273 themeOptions = createOptions(req, res, res.bufferedOutput);
128
1293 try {
1303 content = theme.cache[layout].template.call({}, themeOptions);
131 } catch (ex) {
1320 err = ex;
133 }
134
1353 return next(err, content);
136
137
138 });
139
140 },
141 getLayoutsArray: function() {
142
1430 var theme = this;
1440 var layouts = [];
1450 for (var layout in theme.config.layouts) {
1460 layouts.push(layout);
147 }
1480 return layouts;
149
150 }
151
152 };
153
1542 next(null, theme);
155
156 });
157
158
159 });
160
161};
162
163/**
164 *Process a theme section
165 */
166
1671function processSection(req, res, section, sectionPath, layoutConfig, theme, + next) { +
168
16918 var themeOptions, sectionCache = theme.cache[sectionPath];
170
171 // Check the theme cache
17218 if (!sectionCache) {
1730 calipso.error("Unable to find template for " + sectionPath);
1740 next();
1750 return;
176 }
177
17818 var blockData = "";
179
18018 if (!sectionCache.template) {
181 // Use the default
1820 sectionPath = "default." + section;
1830 sectionCache = theme.cache[sectionPath];
184 }
185
186 // should there be more than just these two error codes?
187 // if more than just these two, then this would have to happen later on:
188 // templates.push({name:"500", templatePath:"templates/500.html"});
189 // Override with a 404 (not found) page
19018 if (section === "body" && res.statusCode === 404) {
1911 if (!theme.cache.hasOwnProperty("404")) {
1920 localNext(new Error("You must define a 404 template in the error folder + e.g. error/404.html")); +
1930 return;
194 }
1951 sectionCache = theme.cache["404"];
196 }
197
198 // Override with a 403 (no permissions) page
19918 if(section === "body" && res.statusCode === 403) {
2000 if(!theme.cache.hasOwnProperty("403")) {
2010 localNext(new Error("You must define a 403 template in the error folder + e.g. error/403.html")); +
2020 return;
203 }
2040 sectionCache = theme.cache["403"];
205 }
206
207 // Override with a 500 (error) page
20818 if (section === "body" && res.statusCode === 500) {
2090 if (!theme.cache.hasOwnProperty("500")) {
2100 localNext(new Error("You must define a 500 template in the error folder + e.g. error/500.html")); +
2110 return;
212 }
2130 sectionCache = theme.cache["500"];
2140 blockData = res.errorMessage ? res.errorMessage : "";
215 }
216
217 // Retrieve any backing function
21818 var sectionCacheFn = sectionCache.fn;
219
220 // Clear any buffered output for this section
22118 res.bufferedOutput[section] = "";
222
223 // Get the basic theme options
22418 themeOptions = createOptions(req, res, {
225 blockData: blockData
226 });
227
228 // Add any custom functions
22918 if (typeof sectionCacheFn === "function") {
230
2318 sectionCacheFn(req, themeOptions, function(err, fnOptions) {
232
2338 if (err) {
2340 err.xMessage = "Issue executing the theme function for section " + + section + ", check " + sectionPath.replace(".", "/") + ".js"; +
2350 localNext(err);
2360 return;
237 }
238
2398 themeOptions = merge(themeOptions, fnOptions);
2408 try {
2418 res.bufferedOutput[section] += sectionCache.template.call({}, themeOptions); +
2428 localNext();
243 } catch (ex) {
244 // Augment the exception
2450 ex.xMessage = "Issue processing theme section " + section + ", + path: " + sectionPath; +
2460 localNext(ex);
247 }
248
249 });
250
251 } else {
25210 try {
25310 res.bufferedOutput[section] += sectionCache.template.call({}, themeOptions); +
25410 localNext();
255 } catch (ex) {
2560 ex.xMessage = "Issue processing theme section: " + section + ", + theme: " + sectionPath; +
2570 localNext(ex);
258 }
259
260 }
261
262 // Local next function to enable proxying of callback
263
26418 function localNext(err) {
26518 next(err);
266 }
267
268
269}
270
271/**
272 * Copy the current block data over to options to render
273 * @param res
274 * @param config
275 */
276
2771function processTheme(req, res, layout, theme, next) {
278
2793 var layoutConfig, copyConfig, copySection, sectionExists, disable, sections = + [], +
280 section;
281
2823 delete res.bufferedOutput;
2833 res.bufferedOutput = {};
284
285 // Scan through each layout
2863 try {
2873 layoutConfig = theme.config.layouts[layout].layout;
288 } catch (ex) {
2890 next(ex.message);
2900 return;
291 }
292
293 // Check to see if this layout copies default
2943 if (layoutConfig.copyFrom && layout != "default") {
295
2961 copyConfig = theme.config.layouts[layoutConfig.copyFrom].layout;
2971 layoutConfig.sections = layoutConfig.sections || {};
298
299 // Copy over any missing sections from default
3001 for (copySection in copyConfig.sections) {
301
3026 sectionExists = layoutConfig.sections && + layoutConfig.sections[copySection]; +
3036 disable = layoutConfig.sections && layoutConfig.sections[copySection] + && layoutConfig.sections[copySection].disable; +
3046 if (!sectionExists && !disable) {
3056 layoutConfig.sections[copySection] = copyConfig.sections[copySection];
3066 layoutConfig.sections[copySection].layout = "default"; // Flag + override as below +
307 }
308
309 }
310
311 }
312
313 // Create a section array
3143 for (section in layoutConfig.sections) {
31518 disable = layoutConfig.sections[section].disable;
31618 if (!disable) {
31718 sections.push(section);
318 }
319 }
3203 var totalCount = sections.length;
3213 var totalDone = 0;
322
323 // Now, process all the sections
324 // This is done via a localNext to give us full control
325 // and better ability to debug
326
3273 function localNext(err) {
32818 totalDone += 1;
329
33018 if (totalDone == totalCount) {
3313 next();
332 }
333
334 }
335
3363 for (section in layoutConfig.sections) {
337
338 // Check to see if we are overriding
33918 var currentSection = section;
34018 var layoutOverride = layoutConfig.sections[section].layout;
34118 var sectionPath = layoutOverride ? layoutOverride + "." + section : + layout + "." + section; +
34218 var cache = layoutConfig.sections[section].cache;
34318 var params = layoutConfig.sections[section].varyParams;
34418 var cacheEnabled = calipso.config.get('performance:cache:enabled');
34518 var isAdmin = req.session.user && req.session.user.isAdmin;
346
34718 disable = layoutConfig.sections[section].disable;
348
349 // Sections are cacheable
35018 if (!disable) {
35118 if (cache && cacheEnabled && !isAdmin) {
3520 var keys = [layout, 'section', currentSection];
3530 var cacheKey = calipso.cacheService.getCacheKey(keys, params);
3540 sectionCache(req, res, cacheKey, section, sectionPath, layoutConfig, theme, + localNext); +
355 } else {
35618 processSection(req, res, section, sectionPath, layoutConfig, theme, + localNext); +
357 }
358 }
359
360 }
361
362}
363
364/**
365 * Interact with sections via the cache
366 */
367
3681function sectionCache(req, res, cacheKey, section, templateName, layoutConfig, + theme, next) { +
369
3700 calipso.cacheService.check(cacheKey, function(err, isCached) {
3710 if (isCached) {
3720 calipso.silly("Cache hit for " + cacheKey + ", section " + + section); +
3730 calipso.cacheService.get(cacheKey, function(err, cache) {
3740 if (!err) {
3750 res.bufferedOutput[section] = cache.content;
376 }
3770 next(err);
378 });
379 } else {
3800 calipso.silly("Cache miss for " + cacheKey + ", section " + + section); +
3810 processSection(req, res, section, templateName, layoutConfig, theme, + function(err) { +
3820 if (!err) {
3830 var content = res.bufferedOutput[section];
3840 calipso.cacheService.set(cacheKey, {
385 content: content
386 }, null, next);
387 } else {
3880 next(err);
389 }
390 });
391 }
392 });
393}
394
395
396/**
397 * Load a theme
398 */
399
4001function loadTheme(theme, themePath, next) {
401
4022 var themeFile = calipso.lib.path.join(themePath, "theme.json");
403
4042 (fs.exists || path.exists)(themeFile, function(exists) {
4052 if(exists) {
4062 fs.readFile(themeFile, 'utf8', function(err, data) {
4072 if (!err) {
4082 var jsonData;
4092 try {
4102 jsonData = JSON.parse(data);
4112 next(null, jsonData);
412 } catch (ex) {
4130 next(new Error("Error parsing theme configuration: " + ex.message + + " stack, " + ex.stack)); +
414 }
415 } else {
4160 next(err);
417 }
418 });
419 } else {
4200 next(new Error("Can't find specified theme configuration " + + themeFile)); +
421 }
422 });
423}
424
425/**
426 * Load all of the theme templates into the theme
427 * @param theme
428 */
429
4301function cacheTheme(themeConfig, themePath, next) {
431
4322 var templates = [],
433 templateCache = {},
434 layout, layoutConfig, section, template, module, templateFiles, + errorCodeTemplates; +
435
436 // Scan through each layout
4372 if (themeConfig) {
438
4392 for (layout in themeConfig.layouts) {
440
441 // Scan through each layout
4426 layoutConfig = themeConfig.layouts[layout].layout;
443
444 // Add the layout template
4456 templates.push({
446 name: layout,
447 templatePath: calipso.lib.path.join("templates", + layoutConfig.template) +
448 });
449
450
451 // Add the templates
4526 for (section in layoutConfig.sections) {
45314 template = layoutConfig.sections[section].template;
45414 if (template) {
45514 templates.push({
456 name: layout + "." + section,
457 templatePath: calipso.lib.path.join("templates", layout, template) +
458 });
459 }
460 }
461
462 // Check to see if the theme overrides any module templates
4636 if (layoutConfig.modules) {
4640 for (module in layoutConfig.modules) {
4650 for (template in layoutConfig.modules[module]) {
4660 loadModuleOverrideTemplate(templateCache, module, template, path.join(themePath, + layoutConfig.modules[module][template])); +
467 }
468 }
469 }
470 }
471
472 // Push error message templates
4732 templateFiles = calipso.lib.fs.readdirSync(calipso.lib.path.join(themePath, + 'templates', 'error')); +
4742 errorCodeTemplates = calipso.lib._.select(templateFiles, function(filename) { +
475 // Select files that start with 3 digits, indicating an error code
4764 return filename.match(/^\d{3}./);
477 });
478
4792 calipso.lib._.each(errorCodeTemplates, function(filename) {
4804 templates.push({
481 name: filename.match(/^\d{3}/)[0],
482 templatePath: calipso.lib.path.join("templates", "error", + filename) +
483 });
484 });
485
4862 var templateIterator = function(templateName, cb) {
48724 loadTemplate(templateCache, templateName, themePath, cb);
488 };
489
4902 calipso.lib.async.map(templates, templateIterator, function(err, result) {
4912 if (err) {
492 // May not be a problem as missing templates default to default
4930 calipso.error("Error loading templates, msg: " + err.message + ", + stack: " + err.stack); +
4940 next(err);
495 } else {
4962 next(null, templateCache);
497 }
498 });
499
500 }
501
502}
503
504/**
505 * Load a template that overrides a module template
506 * fired from cacheTheme(),
507 */
508
5091function loadModuleOverrideTemplate(templateCache, module, template, path) {
510
5110 var templatePath = path,
512 templateExtension = templatePath.match(/([^\.]+)$/)[0],
513 templateFn = fs.readFileSync(templatePath, 'utf8'),
514 templateFnCompiled = compileTemplate(templateFn, templatePath, + templateExtension); +
515
516 // Initialise the objects
5170 templateCache.modules = templateCache.modules || {};
5180 templateCache.modules[module] = templateCache.modules[module] || {};
5190 templateCache.modules[module].templates = + templateCache.modules[module].templates || {}; +
520
521 // allow hook for listening for module events?
522 // Load the function
5230 templateCache.modules[module].templates[template] = templateFnCompiled;
524
525}
526
527/**
528 * Load a template
529 */
530
5311function loadTemplate(templateCache, template, themePath, next) {
532
533 // Reset / default
53448 if (!templateCache[template.name]) templateCache[template.name] = {};
535
536 // Template paths and functions
53724 var templatePath = calipso.lib.path.join(themePath, template.templatePath),
538 templateExtension = template.templatePath.match(/([^\.]+)$/)[0],
539 templateFnPath = calipso.lib.path.join(themePath, template.templatePath.replace("." + + templateExtension, ".js")); +
540
54124 (fs.exists || path.exists)(templatePath,function(exists) {
542
54324 if (exists) {
544
54524 var templateData = '';
546
54724 try {
54824 templateData = fs.readFileSync(templatePath, 'utf8');
549 } catch (err) {
5500 calipso.error('Failed to open template ' + templatePath + ' ...');
551 }
552
55324 if (calipso.config.get('performance:watchFiles')) {
554
55524 try {
55624 fs.unwatchFile(templatePath);
55724 fs.watchFile(templatePath, {
558 persistent: true,
559 interval: 200
560 }, function(curr, prev) {
5610 loadTemplate(templateCache, template, themePath, function() {
5620 calipso.silly("Template " + templatePath + " reloaded + ..."); +
563 });
564 });
565 } catch (ex) {
5660 calipso.error('Failed to watch template ' + templatePath + ' ...');
567 }
568
569 }
570
571 // Precompile the view into our cache
57224 templateCache[template.name].template = compileTemplate(templateData, + templatePath, templateExtension); +
573
574 // See if we have a template fn
57524 if ((fs.existsSync || path.existsSync)(templateFnPath)) {
576
5778 if (exists) {
5788 try {
5798 templateCache[template.name].fn = require(templateFnPath);
580 } catch (ex) {
5810 calipso.error(ex);
582 }
583
584 }
585
586 }
587
58824 return next(null, template);
589
590 } else {
591
5920 next(new Error('Path does not exist: ' + templatePath));
593
594 }
595
596 });
597
598}
599
600/**
601 * Pre-compile a template based on its extension.
602 * If the required view engine does not exist, exit gracefully and let
603 * them know that this is the case.
604 */
605
6061function compileTemplate(template, templatePath, templateExtension) {
607
60826 var compiledTemplate = function() {},
609 templateEngine;
61026 var options = {
611 filename: templatePath
612 };
613
614 // If we get html, replace with ejs
61552 if (templateExtension === "html") templateExtension = + "ejs"; +
616
617 // Load a template engine based on the extension
61826 try {
61926 templateEngine = require(templateExtension);
620 } catch (ex) {
6210 calipso.warn("No view rendering engine exists that matches: " + + templateExtension + ", so using EJS!"); +
6220 templateEngine = require("ejs");
623 }
624
625 // Return our compiled template
62626 try {
62726 compiledTemplate = templateEngine.compile(template, options);
628 } catch (ex) {
6290 calipso.error("Error compiling template : " + templatePath + ", + message: " + ex.message); +
630 }
631
63226 return compiledTemplate;
633
634}
635
636/**
637 * Merge options together
638 */
639
6401function createOptions(req, res, options) {
641
642 // Merge options with helpers
64322 options = merge(options, req.helpers);
644
645 // Merge options with application data
64622 if (calipso.data) {
64722 options = merge(options, calipso.data);
648 }
649
65022 return options;
651
652}
+
+

core/Utils.js

+ +
+
37%
+
27
+
10
+
17
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/**
2 * General utility methods
3 */
41var _ = require('underscore');
5
61module.exports = {
7 /**
8 * Basically like getProperty, different return
9 * @method hasProperty
10 * @param ns {string} A period delimited string of the namespace to find, sans + root object +
11 * @param obj {object} The root object to search
12 * @return {boolean} true if property exists, false otherwise
13 */
14 hasProperty: function(ns, obj) {
152 if (!ns) {
160 return obj;
17 }
182 var nsArray = ns.split('.'),
19 nsLen = nsArray.length,
20 newNs;
21
22 // if nsLen === 0, then obj is just returned
232 while (nsLen > 0) {
246 newNs = nsArray.shift();
256 if (obj[newNs]) {
264 obj = obj[newNs];
27 } else {
282 return false;
29 }
304 nsLen = nsArray.length;
31 }
320 return true;
33 },
34 /**
35 * Find a namespaced property
36 * @method getProperty
37 * @param ns {string} A period delimited string of the namespace to find, sans + root object +
38 * @param obj {object} The root object to search
39 * @return {object} the object, either the namespaced obejct or the root object +
40 */
41 getProperty: function(ns, obj) {
420 if (!ns) {
430 return obj;
44 }
450 var nsArray = ns.split('.'),
46 nsLen = nsArray.length,
47 newNs;
48
49 // if nsLen === 0, then obj is just returned
500 while (nsLen > 0) {
510 newNs = nsArray.shift();
520 if (obj[newNs]) {
530 obj = obj[newNs];
54 }
550 nsLen = nsArray.length;
56 }
570 return obj;
58 },
59
60 /**
61 * Simple mongo object copier, used to do a shallow copy of objects
62 */
63 copyMongoObject: function(object, copy, schema) {
64
650 var fields = _.keys(schema.paths);
660 _.each(fields, function(key) {
670 if (key !== '_id') copy.set(key, object.get(key));
68 });
69
70 },
71 escapeHtmlQuotes: function (string) {
720 if (string && string.replace) {
730 return string.replace(/\"/g, '&quot;').replace(/\'/g, '&apos;'); +
74 }
75 else {
760 return string;
77 }
78 }
79};
+
+

client/Client.js

+ +
+
100%
+
44
+
44
+
0
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/**
2 * This library provides a wrapper to enable modules to load javascript and + styles into an +
3 * array, that can then be rendered into a theme in the appropriate location. +
4 *
5 * Styles and JS are all indexed by key, so you could write functions that + over-wrote them in the theme as the +
6 * last update will always stick.
7 *
8 */
9
101var rootpath = process.cwd() + '/',
11 path = require('path'),
12 calipso = require(path.join('..', 'calipso')),
13 fs = require('fs');
14
15/**
16 * Client Object - handle CSS and JS loading for modules out to themes
17 */
181var Client = module.exports = function Client(options) {
19
2014 this.options = options || {
21 'minified-script': 'media/calipso-main'
22 };
23
2414 this.scripts = [];
2514 this.styles = [];
26
27 // Shortcuts to core, must be included somewhere (module or theme) to be + rendered +
2814 this.coreScripts = {
29 'jquery': {key:'jquery', url:'jquery-1.8.2.min.js', weight: -100},
30 'calipso': {key:'calipso', url:'calipso.js', weight: -50}
31 }
32
33 };
34
351Client.prototype.addScript = function(options) {
36
378 var self = this;
38
39 // Convert our options over with flexible defaults
408 if (typeof options === "string") {
412 if (this.coreScripts[options]) {
421 options = this.coreScripts[options];
43 } else {
441 options = {
45 name: options,
46 url: options,
47 weight: 0
48 };
49 }
50 }
5112 if (!options.name) options.name = options.url;
52
53 // Add the script
548 self._add('scripts', options.name, options);
55
56};
57
58/**
59 * Create simple list of all client JS
60 */
611Client.prototype.listScripts = function(next) {
62
63 // TODO - this should be updated to use LABjs by default (?)
641 var self = this;
651 var output = "<!-- Calipso Module Scripts -->";
661 self.scripts.forEach(function(value) {
672 output += '\r\n<script title="' + value.name + '" src="' + + value.url + '"></script>'; +
68 });
691 output += "<!-- End of Calipso Module Scripts -->";
701 next(null, output);
71
72};
73
741Client.prototype.addStyle = function(options) {
75
765 var self = this;
77
78 // Convert our options over with flexible defaults
795 if (typeof options === "string") {
801 options = {
81 name: options,
82 url: options,
83 weight: 0
84 };
85 }
868 if (!options.name) options.name = options.url;
87
88 // Add the script
895 self._add('styles', options.name, options);
90
91};
92
93/**
94 * Compile together all of the client side scripts
95 */
961Client.prototype.listStyles = function(next) {
97
98 // TODO - this should be updated to use LABjs by default (?)
991 var self = this;
1001 var output = "<!-- Calipso Module Styles -->";
101
1021 self.styles.forEach(function(value) {
1032 output += '\r\n<link rel="stylesheet" title="' + value.name + + '" href="' + value.url + '"/>'; +
104 });
1051 output += "<!-- End of Calipso Module Styles -->";
1061 next(null, output);
107
108};
109
110
111/**
112 * Helper to add unique elements to an array
113 */
1141Client.prototype._add = function(arrName, name, options) {
115
11613 var self = this;
11713 self[arrName] = self[arrName] || [];
118
119 // Find first match
12013 var found = calipso.lib._.find(self[arrName], function(value) {
1213 return (value.name && value.name === name) ? true : false;
122 });
123
12413 if (found) {
125 // Replace - this means we never get duplicates (e.g. of JQuery, JQueryUI)
1261 self[arrName].splice(found, 1, options);
127 } else {
128 // Push
12912 self[arrName].push(options);
130 }
131
132 // Sort - TODO, this can probably be more efficient by placing the new item + smarter +
13313 self[arrName].sort(function(a, b) {
1342 return a.weight > b.weight;
135 });
136
137};
138
139
140/**
141 * Compile together all of the client side scripts
142 * TODO - this is currently not used, needs to be worked on and thought + through. +
143 *
144Client.prototype.compile = function(next) {
145
146 var self = this;
147
148 try {
149
150 var scriptFile = path.join(rootpath,self.options.script),
151 scriptStream = fs.createWriteStream(scriptFile, {'flags': 'a'});
152
153 } catch(ex) {
154
155 console.dir(ex);
156
157 }
158
159 var grabFile = function(item, callback) {
160
161 // TODO - allow referential
162 var filePath = path.join(rootpath, item.url);
163
164 // Check to see if the file has changed
165 var stat = fs.lstatSync(filePath);
166
167 fs.readFile(filePath, 'utf8', function(err, contents) {
168
169 if(err) {
170
171 return callback(new Error("Unable to locate file for ClientJS creation: + " + filePath)); +
172
173 } else {
174
175 var drain;
176 drain = scriptStream.write(contents);
177 callback(null, stat.mtime);
178
179 }
180 });
181
182 }
183
184 // Callback wrapper to close the streams
185 var done = function(err, data) {
186 scriptStream.end();
187 next(err, data);
188 }
189
190 // var contents = fs.readFileSync(config.out, 'utf8');
191 calipso.lib.async.mapSeries(self.scripts, grabFile, function(err, scripts) { +
192
193 if(err) return done(err);
194
195 var reduce = function(context, memo, value, index, list) {
196 return (value > memo) ? value : memo;
197 };
198
199 var maxmtime = calipso.lib._.reduce(scripts, reduce);
200
201 console.dir(maxmtime);
202
203 var script = '<!-- ' + maxmtime + ' -->'
204
205 done(null, script);
206
207 })
208
209}
210**/
+
+

+ /Users/andy/calipso/test/helpers/calipsoHelper.js

+ +
+
85%
+
49
+
42
+
7
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LineHitsSource
1/**
2 * Setup the bare minimum required for a fully functioning 'calipso' object
3 */
41var jsc = require('jscoverage'),
5 require = jsc.require(module), // rewrite require function
6 calipso = require('./require', true)('calipso'),
7 path = require('path'),
8 fs = require('fs'),
9 colors = require('colors'),
10 rootpath = process.cwd() + '/',
11 Config = require('./require', true)('core/Configuration'),
12 http = require('http'),
13 mochaConfig = path.join(rootpath,'tmp','mocha.json');
14
15// Create the tmp folder if it doesnt' exist
163try { fs.mkdirSync(path.join(rootpath,'tmp')) } catch(ex) {};
17
18/**
19 * Mock application object
20 */
211function MockApp(next) {
22
231 var self = this;
24
25 // Configuration - always start with default
261 var defaultConfig = path.join(rootpath, 'test', 'helpers', + 'defaultConfig.json'); +
27
281 var statusMsg = '\r\nBase path: '.grey + rootpath.cyan + '\r\nUsing config: + '.grey + defaultConfig.cyan + '\r\nIn environment: '.grey + (process.env.NODE_ENV || + 'development').cyan; +
291 if(!process.env.CALIPSO_COV) console.log(statusMsg);
30
31 // Always delete any left over config
322 try { fs.unlinkSync(mochaConfig); } catch(ex) { /** ignore **/ }
33
34 // Create new
351 self.config = new Config({
36 'env': 'mocha',
37 'path': path.join(rootpath, 'tmp'),
38 'defaultConfig': defaultConfig
39 });
40
41 // Middleware helpers
421 self.mwHelpers = {
430 staticMiddleware: function() { return {} },
440 stylusMiddleware: function() { return {} }
45 }
46
47 // Pseudo stack - only middleware that is later overloaded
481 self.stack = [{
49 handle: {
50 name: 'sessionDefault',
51 tag: 'session'
52 }
53 }, {
54 handle: {
55 name: 'static',
56 tag: 'theme.static'
57 }
58 }, {
59 handle: {
60 name: 'stylus',
61 tag: 'theme.stylus'
62 }
63 }];
64
65 // Initialise and return
661 self.config.init(function (err) {
67
681 if(err) console.log('Config error: '.grey + err.message.red);
691 if(!process.env.CALIPSO_COV) console.log('Config loaded: '.grey + + self.config.file.cyan); +
701 next(self);
71
72 })
73
74}
75
76/**
77 * Test permissions
78 */
791calipso.permission.Helper.addPermission("test:permission", "Simple + permission for testing purposes."); +
801calipso.permission.Helper.addPermissionRole("test:permission", "Test");
81
82/**
83 * Setup logging
84 */
851var loggingConfig = {
86 "console": {
87 "enabled": false,
88 "level": "error",
89 "timestamp": true,
90 "colorize": true
91 }
92};
931calipso.logging.configureLogging(loggingConfig);
94
95/**
96 * Request
97 */
981require('express/lib/request');
991require('express/lib/response');
100
1011var Request = http.IncomingMessage,
102 Response = http.OutgoingMessage;
103
1041Request.prototype.t = function (str) {
10514 return str
106};
107
1081function CreateRequest(url, method, session) {
1093 var req = new Request();
1103 req.method = method || 'GET';
1113 req.url = url || '/';
1123 req.session = session || {};
1133 req.flashMsgs = [];
1143 req.flash = function (type, msg) {
1150 req.flashMsgs.push({
116 type: type,
117 msg: msg
118 });
119 }
1203 return req;
121}
122
123
1241function CreateResponse() {
1251 var res = new Response();
1261 res.redirectQueue = [];
1271 res.redirect = function (url) {
1280 res.redirectQueue.push(url);
1290 res.finished = false;
130 }
1311 res.end = function(content, type) {
1320 res.body = content;
133 }
1341 res.send = function(content) {
1350 res.body = content;
136 }
1371 return res;
138}
139/**
140 * Default requests and users
141 */
1421var requests = {
143 anonUser: CreateRequest('/', 'GET'),
144 testUser: CreateRequest('/', 'GET', {
145 user: {
146 isAdmin: false,
147 roles: ['Test']
148 }
149 }),
150 adminUser: CreateRequest('/secured', 'GET', {
151 user: {
152 isAdmin: true,
153 roles: ['Administrator']
154 }
155 })
156}
157
158/**
159 * Initialise everything and then export
160 */
1611new MockApp(function (app) {
1621 module.exports = {
163 app: app,
164 calipso: calipso,
165 testPermit: calipso.permission.Helper.hasPermission("test:permission"),
166 requests: requests,
167 response: CreateResponse()
168 }
169})
+
+
+
+ + \ No newline at end of file diff --git a/i18n/language.ru.js b/i18n/language.ru.js index 08f6ea07d..64fe5809b 100644 --- a/i18n/language.ru.js +++ b/i18n/language.ru.js @@ -3,148 +3,148 @@ * Approved by jBugman */ exports.language = { - "About You": "О ВаÑ", - "Add your keyword to the url to view other streams ... e.g.": "Добавить ключевое Ñлово в URL, чтобы проÑмотреть другие потоки ... например,", - "Admin":"Admin", - "Args": "Args", - "Arguments": "Ðргументы", - "Author":"Ðвтор", - "Breaking News":"ПоÑледние новоÑти", - "CRON Time": "Ð’Ñ€ÐµÐ¼Ñ CRON", - "Categorisation":"КатегоризациÑ", - "Click to create":"Ðажмите, чтобы Ñоздать", - "Content": "Материал", - "Content Type": "Тип контента", - "Content Types": "Типы контента", - "Could not save content because {msg}.": "Ðе удалоÑÑŒ Ñохранить контент, поÑкольку {msg}.", - "Could not save content type because {msg}.": "Ðе удалоÑÑŒ Ñохранить тип контента, так как {msg}.", - "Could not save job because {msg}.": "Ðе удалоÑÑŒ Ñохранить job, потому что {msg}.", - "Could not update content because {msg}.": "Ðе удалоÑÑŒ обновить контент, так как {msg}.", - "Could not update content type because {msg}.": "Ðе удалоÑÑŒ обновить тип контента, так как {msg}.", - "Could not update job because {msg}.": "Ðе удалоÑÑŒ обновить job, потому что {msg}.", - "Create Content ...": "Создать материал", - "Create Content Type": "Создание типа контента", - "Create New Job": "Создать job", - "Date to appear as published.": "ÐžÑ‚Ð¾Ð±Ñ€Ð°Ð¶Ð°ÐµÐ¼Ð°Ñ Ð´Ð°Ñ‚Ð° публикации.", - "Date to be published (if scheduled).": "Ð—Ð°Ð¿Ð»Ð°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð´Ð°Ñ‚Ð° публикации (еÑли предуÑмотрена).", - "Delete Content": "Удалить материал", - "Delete Content Type": "Удалить тип контента", - "Delete Job": "Удалить job", - "Description": "ОпиÑание", - "Disable All":"Отключить вÑе", - "Double click to edit content block ...": "Кликните дважды Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ", - "Edit Content": "Редактировать материал", - "Edit Content ...": "Редактировать материал", - "Edit Content Type": "Редактировать тип контента", - "Edit Job": "Редактировать job", - "Edit Profile": "Редактировать профиль", - "Email": "Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°", - "Enable All":"Включить вÑе", - "Enable or disable the job": "Включить или выключить задание", - "Enabled":"Включено", - "Enter a unique name for the job": "Введите уникальное Ð¸Ð¼Ñ Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð¸Ñ", - "Enter comma delimited tags to help manage this content.": "Введите теги через запÑтую, чтобы облегчить управление материалами.", - "Enter some short text that describes the content, appears in lists.": "Rороткий текÑÑ‚, который опиÑывает Ñодержимое. ПоÑвлÑетÑÑ Ð² ÑпиÑках.", - "Enter the arguments (as per the job function)": "Введите аргументы (как у job function)", - "Enter the full content text.": "Введите полный текÑÑ‚ материала.", - "Enter the menu heirarchy, e.g. \"welcome/about\"": "Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ Ð¼ÐµÐ½ÑŽ, например, \"О компании/ÐšÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ\"", - "Is Public": "ЯвлÑетÑÑ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼", - "Jade Template": "Шаблон Jade", - "Job": "Job", - "Job Function": "Job Function", - "Language": "Язык", - "Languages": "Языки", - "Last Updated": "ПоÑледнее обновление", - "Layout":"Макет", - "Log In": "Войти", - "Login": "Войти", - "Method": "Метод", - "Module": "Модуль", - "Name": "Ðазвание", - "Navigation": "ÐавигациÑ", - "New Content": "Ðовый материал", - "New Content Type": "Ðовый тип контента", - "New Job": "Ðовое задание", - "No jobs defined": "Jobs не определены", - "Password": "Пароль", - "Permanent URL / Alias": "ПоÑтоÑнный URL / Alias", - "Permanent url (no spaces or invalid html characters), if left blank is generated from title.": "ПоÑтоÑнный URL (без пробелов или недейÑтвительным HTML Ñимволов), еÑли оÑтавить пуÑтым, ÑгенерируетÑÑ Ð¸Ð· названиÑ.", - "Public": "Публично", - "Published": "Опубликовано", - "Register": "РегиÑтрациÑ", - "Save Content": "Сохранить", - "Save Content Type": "Сохранить", - "Save Job": "Сохранить", - "Save Profile":"Сохранить", - "Schedule": "РаÑпиÑание", - "Scheduled": "Запланированное", - "Scheduled Jobs": "Запланированные заданиÑ", - "Scheduler": "Планировщик", - "Select the job function to run as per this schedule": "Выберите job function Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка в ÑоответÑтвии Ñ Ñтим графиком", - "Select the status (published is visible to all public).": "Выберите ÑÑ‚Ð°Ñ‚ÑƒÑ (published — видно вÑем).", - "Select the type, this impacts custom fields and page display.":"ВлиÑет на наÑтраиваемые Ð¿Ð¾Ð»Ñ Ð¸ отображение Ñтраниц.", - "Status": "СтатуÑ", - "Tag Cloud": "Облако тегов", - "Tags": "Теги", - "Taxonomy": "ТакÑономиÑ", - "Teaser": "ÐннотациÑ", - "Template": "Шаблон", - "The content has now been deleted.": "Материал был удален.", - "The content type has now been deleted.": "Тип Ñодержимого был удален.", - "Title": "Ðазвание", - "Title to appear for this piece of content.": "Заголовок Ñтого материала", - "Tweet Stream": "Твиты", - "Type": "Тип", - "Update Profile": "Редактировать профиль", - "Updated": "Обновлено", - "User": "Пользователь", - "Users": "Пользователи", - "Username": "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ", - "You may have entered an incorrect username or password, please try again.": "Возможно, вы ввели неверное Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль, пожалуйÑта, попробуйте еще раз.", - "You need to add a \"breaking\" tag to content to have it appear here.":"Вам необходимо добавить тег \"breaking\" к материалу, чтобы он поÑвитÑÑ Ð·Ð´ÐµÑÑŒ.", - "for": "длÑ", - "{name} Profile":"{name} профиль", - "Profile": "Профиль", - "Full Name": "Полное имÑ", - "Logout": "Выход", - "Old Password": "Старый пароль", - "New Password": "Ðовый пароль", - "Repeat Password": "Повторите пароль", - "Leave blank if not changing password.": "Ðичего не вводите, еÑли не хотите менÑÑ‚ÑŒ пароль.", - "Privacy": "ÐаÑтройки приватноÑти", - "Show Full Name": "Показывать полное имÑ", - "Show Email": "Показывать Ñлектронную почту", - "Never": "Ðикогда", - "Registered Users Only": "Только зарегиÑтрированным пользователÑм", - "Public": "Публичный", - "Roles": "Роли", - "User Roles": "Роли пользователÑ", - "Administration": "ÐдминиÑтрирование", - "Configuration Options": "ÐаÑтройки", - "Core Configuration ...": "ÐаÑтройки", - "View Languages": "Языки", - "Languages ...": "Языки", - "Create": "Создать", - "Create Content ...": "Создать материал", - "Content Management": "Управление контентом", - "Manage content": "Управление контентом", - "Manage content ...": "Управление контентом", - "Calipso administration ...": "ÐдминиÑтрирование Calipso", - "Manage content types ...": "Управление типами контента", - "Development": "Разаработчику", - "Default Language": "Язык по умолчанию", - "Watch Template Files": "Ðвтообновление шаблонов", - "Theme": "Тема", - "Modules": "Модули", - "Logging": "Логи", - "Console Logging": "Вывод в конÑоль", - "File Logging": "Вывод в файл", - "Return": "Ðазад", - "File Log Path": "Путь к файлу лога", - "Lock": "Заблокировать", - "Delete": "Удалить", - "Edit": "Редактировать", - "List": "СпиÑок", - "Register New User": "Добавление пользователÑ", - "Register new user ...": "Добавление пользователÑ", + "About You":"О ВаÑ", + "Add your keyword to the url to view other streams ... e.g.":"Добавить ключевое Ñлово в URL, чтобы проÑмотреть другие потоки ... например,", + "Admin":"Admin", + "Args":"Args", + "Arguments":"Ðргументы", + "Author":"Ðвтор", + "Breaking News":"ПоÑледние новоÑти", + "CRON Time":"Ð’Ñ€ÐµÐ¼Ñ CRON", + "Categorisation":"КатегоризациÑ", + "Click to create":"Ðажмите, чтобы Ñоздать", + "Content":"Материал", + "Content Type":"Тип контента", + "Content Types":"Типы контента", + "Could not save content because {msg}.":"Ðе удалоÑÑŒ Ñохранить контент, поÑкольку {msg}.", + "Could not save content type because {msg}.":"Ðе удалоÑÑŒ Ñохранить тип контента, так как {msg}.", + "Could not save job because {msg}.":"Ðе удалоÑÑŒ Ñохранить job, потому что {msg}.", + "Could not update content because {msg}.":"Ðе удалоÑÑŒ обновить контент, так как {msg}.", + "Could not update content type because {msg}.":"Ðе удалоÑÑŒ обновить тип контента, так как {msg}.", + "Could not update job because {msg}.":"Ðе удалоÑÑŒ обновить job, потому что {msg}.", + "Create Content ...":"Создать материал", + "Create Content Type":"Создание типа контента", + "Create New Job":"Создать job", + "Date to appear as published.":"ÐžÑ‚Ð¾Ð±Ñ€Ð°Ð¶Ð°ÐµÐ¼Ð°Ñ Ð´Ð°Ñ‚Ð° публикации.", + "Date to be published (if scheduled).":"Ð—Ð°Ð¿Ð»Ð°Ð½Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð´Ð°Ñ‚Ð° публикации (еÑли предуÑмотрена).", + "Delete Content":"Удалить материал", + "Delete Content Type":"Удалить тип контента", + "Delete Job":"Удалить job", + "Description":"ОпиÑание", + "Disable All":"Отключить вÑе", + "Double click to edit content block ...":"Кликните дважды Ð´Ð»Ñ Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ", + "Edit Content":"Редактировать материал", + "Edit Content ...":"Редактировать материал", + "Edit Content Type":"Редактировать тип контента", + "Edit Job":"Редактировать job", + "Edit Profile":"Редактировать профиль", + "Email":"Ð­Ð»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð°Ñ Ð¿Ð¾Ñ‡Ñ‚Ð°", + "Enable All":"Включить вÑе", + "Enable or disable the job":"Включить или выключить задание", + "Enabled":"Включено", + "Enter a unique name for the job":"Введите уникальное Ð¸Ð¼Ñ Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ð½Ð¸Ñ", + "Enter comma delimited tags to help manage this content.":"Введите теги через запÑтую, чтобы облегчить управление материалами.", + "Enter some short text that describes the content, appears in lists.":"Rороткий текÑÑ‚, который опиÑывает Ñодержимое. ПоÑвлÑетÑÑ Ð² ÑпиÑках.", + "Enter the arguments (as per the job function)":"Введите аргументы (как у job function)", + "Enter the full content text.":"Введите полный текÑÑ‚ материала.", + "Enter the menu heirarchy, e.g. \"welcome/about\"":"Ð˜ÐµÑ€Ð°Ñ€Ñ…Ð¸Ñ Ð¼ÐµÐ½ÑŽ, например, \"О компании/ÐšÐ¾Ð½Ñ‚Ð°ÐºÑ‚Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ\"", + "Is Public":"ЯвлÑетÑÑ Ð¿ÑƒÐ±Ð»Ð¸Ñ‡Ð½Ñ‹Ð¼", + "Jade Template":"Шаблон Jade", + "Job":"Job", + "Job Function":"Job Function", + "Language":"Язык", + "Languages":"Языки", + "Last Updated":"ПоÑледнее обновление", + "Layout":"Макет", + "Log In":"Войти", + "Login":"Войти", + "Method":"Метод", + "Module":"Модуль", + "Name":"Ðазвание", + "Navigation":"ÐавигациÑ", + "New Content":"Ðовый материал", + "New Content Type":"Ðовый тип контента", + "New Job":"Ðовое задание", + "No jobs defined":"Jobs не определены", + "Password":"Пароль", + "Permanent URL / Alias":"ПоÑтоÑнный URL / Alias", + "Permanent url (no spaces or invalid html characters), if left blank is generated from title.":"ПоÑтоÑнный URL (без пробелов или недейÑтвительным HTML Ñимволов), еÑли оÑтавить пуÑтым, ÑгенерируетÑÑ Ð¸Ð· названиÑ.", + "Public":"Публично", + "Published":"Опубликовано", + "Register":"РегиÑтрациÑ", + "Save Content":"Сохранить", + "Save Content Type":"Сохранить", + "Save Job":"Сохранить", + "Save Profile":"Сохранить", + "Schedule":"РаÑпиÑание", + "Scheduled":"Запланированное", + "Scheduled Jobs":"Запланированные заданиÑ", + "Scheduler":"Планировщик", + "Select the job function to run as per this schedule":"Выберите job function Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка в ÑоответÑтвии Ñ Ñтим графиком", + "Select the status (published is visible to all public).":"Выберите ÑÑ‚Ð°Ñ‚ÑƒÑ (published — видно вÑем).", + "Select the type, this impacts custom fields and page display.":"ВлиÑет на наÑтраиваемые Ð¿Ð¾Ð»Ñ Ð¸ отображение Ñтраниц.", + "Status":"СтатуÑ", + "Tag Cloud":"Облако тегов", + "Tags":"Теги", + "Taxonomy":"ТакÑономиÑ", + "Teaser":"ÐннотациÑ", + "Template":"Шаблон", + "The content has now been deleted.":"Материал был удален.", + "The content type has now been deleted.":"Тип Ñодержимого был удален.", + "Title":"Ðазвание", + "Title to appear for this piece of content.":"Заголовок Ñтого материала", + "Tweet Stream":"Твиты", + "Type":"Тип", + "Update Profile":"Редактировать профиль", + "Updated":"Обновлено", + "User":"Пользователь", + "Users":"Пользователи", + "Username":"Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ", + "You may have entered an incorrect username or password, please try again.":"Возможно, вы ввели неверное Ð¸Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð¸Ð»Ð¸ пароль, пожалуйÑта, попробуйте еще раз.", + "You need to add a \"breaking\" tag to content to have it appear here.":"Вам необходимо добавить тег \"breaking\" к материалу, чтобы он поÑвитÑÑ Ð·Ð´ÐµÑÑŒ.", + "for":"длÑ", + "{name} Profile":"{name} профиль", + "Profile":"Профиль", + "Full Name":"Полное имÑ", + "Logout":"Выход", + "Old Password":"Старый пароль", + "New Password":"Ðовый пароль", + "Repeat Password":"Повторите пароль", + "Leave blank if not changing password.":"Ðичего не вводите, еÑли не хотите менÑÑ‚ÑŒ пароль.", + "Privacy":"ÐаÑтройки приватноÑти", + "Show Full Name":"Показывать полное имÑ", + "Show Email":"Показывать Ñлектронную почту", + "Never":"Ðикогда", + "Registered Users Only":"Только зарегиÑтрированным пользователÑм", + "Public":"Публичный", + "Roles":"Роли", + "User Roles":"Роли пользователÑ", + "Administration":"ÐдминиÑтрирование", + "Configuration Options":"ÐаÑтройки", + "Core Configuration ...":"ÐаÑтройки", + "View Languages":"Языки", + "Languages ...":"Языки", + "Create":"Создать", + "Create Content ...":"Создать материал", + "Content Management":"Управление контентом", + "Manage content":"Управление контентом", + "Manage content ...":"Управление контентом", + "Calipso administration ...":"ÐдминиÑтрирование Calipso", + "Manage content types ...":"Управление типами контента", + "Development":"Разаработчику", + "Default Language":"Язык по умолчанию", + "Watch Template Files":"Ðвтообновление шаблонов", + "Theme":"Тема", + "Modules":"Модули", + "Logging":"Логи", + "Console Logging":"Вывод в конÑоль", + "File Logging":"Вывод в файл", + "Return":"Ðазад", + "File Log Path":"Путь к файлу лога", + "Lock":"Заблокировать", + "Delete":"Удалить", + "Edit":"Редактировать", + "List":"СпиÑок", + "Register New User":"Добавление пользователÑ", + "Register new user ...":"Добавление пользователÑ", } \ No newline at end of file diff --git a/i18n/translate.js b/i18n/translate.js index cd8b0f862..340b6bdc8 100644 --- a/i18n/translate.js +++ b/i18n/translate.js @@ -10,13 +10,13 @@ var rootpath = process.cwd() + '/', * Query string parameters will always take precedent over user session * */ -module.exports.translate = function(configLanguage,enabledLanguages,addMode) { +module.exports.translate = function (configLanguage, enabledLanguages, addMode) { // Default to english var languages = enabledLanguages || ['en']; // Always contains english var languageCache = cacheLanguages([], languages); - return function(req, res, next) { + return function (req, res, next) { // Set the request language req.language = configLanguage || "en"; @@ -25,10 +25,10 @@ module.exports.translate = function(configLanguage,enabledLanguages,addMode) { req.languages = languages; // Add the translate function to the request object - req.t = req.translate = function(englishString, values) { + req.t = req.translate = function (englishString, values) { // Check the user session - if(req.session && req.session.user) { + if (req.session && req.session.user) { // Set the language to the user configuration if it exists; req.language = req.session.user.language || req.language; } @@ -37,10 +37,10 @@ module.exports.translate = function(configLanguage,enabledLanguages,addMode) { req.language = req.moduleParams ? req.moduleParams.language || req.language : req.language; // Translate - if(languageCache[req.language]) { + if (languageCache[req.language]) { return doTranslation(englishString, languageCache[req.language], values, addMode); } else { - if(addMode) { + if (addMode) { // Add the language languageCache[req.language] = {}; return doTranslation(englishString, languageCache[req.language], values, addMode); @@ -51,7 +51,7 @@ module.exports.translate = function(configLanguage,enabledLanguages,addMode) { } - if(addMode) { + if (addMode) { req.languageCache = languageCache; } @@ -71,9 +71,9 @@ function cacheLanguages(languages, loadedLanguages) { // Read the language files, sync is ok as this is done once on load var fs = require("fs"); - fs.readdirSync("i18n").forEach(function(file){ + fs.readdirSync("i18n").forEach(function (file) { var languageFile = file.split("."); - if(languageFile[0] === "language") { + if (languageFile[0] === "language") { languageCache[languageFile[1]] = require(path.join(rootpath, "i18n/", file)).language; loadedLanguages.push(languageFile[1]); } @@ -88,7 +88,7 @@ function cacheLanguages(languages, loadedLanguages) { */ function doTranslation(englishString, languageCache, values, addMode) { - if(addMode) { + if (addMode) { // Add the string if appropriate languageCache[englishString] = languageCache[englishString] || englishString; return replaceValues(languageCache[englishString], values); @@ -111,9 +111,9 @@ function doTranslation(englishString, languageCache, values, addMode) { * Output: * "Hello Clifton" */ -function replaceValues(string,values) { +function replaceValues(string, values) { - return string.replace(/{[^{}]+}/g, function(key) { + return string.replace(/{[^{}]+}/g, function (key) { return values[key.replace(/[{}]+/g, "")] || ""; }); diff --git a/jasmine-1.1.0.js b/jasmine-1.1.0.js new file mode 100644 index 000000000..78c758320 --- /dev/null +++ b/jasmine-1.1.0.js @@ -0,0 +1,2525 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) { + exports.jasmine = jasmine; +} +/** + * @private + */ +jasmine.unimplementedMethod_ = function () { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function () { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function (base, name) { + var original = base[name]; + if (original.apply) { + return function () { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function (values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function () { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) { + text += " "; + } + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function (params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function () { + return jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function (value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function (value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function (value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function (typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function (value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function (obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function (clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function (name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function () { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function () { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function (value) { + this.plan = function () { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function (exceptionMsg) { + this.plan = function () { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function (fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function () { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function (name) { + + var spyObj = function () { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object:this, args:args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function (putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function (baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function () { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function (obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) { + exports.spyOn = spyOn; +} + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function (desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) { + exports.it = it; +} + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function (desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) { + exports.xit = xit; +} + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function (actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) { + exports.expect = expect; +} + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function (func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) { + exports.runs = runs; +} + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function (timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) { + exports.waits = waits; +} + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function (latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) { + exports.waitsFor = waitsFor; +} + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function (beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) { + exports.beforeEach = beforeEach; +} + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function (afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) { + exports.afterEach = afterEach; +} + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function (description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) { + exports.describe = describe; +} + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function (description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) { + exports.xdescribe = xdescribe; +} + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function () { + function tryIt(f) { + try { + return f(); + } catch (e) { + } + return null; + } + + var xhr = tryIt(function () { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function () { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function () { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function () { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) { + throw new Error("This browser does not support XMLHttpRequest."); + } + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function (childClass, parentClass) { + /** + * @private + */ + var subclass = function () { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function (e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function (str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function (args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; +}; + +jasmine.util.extend = function (destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function () { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function () { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function () { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function () { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function (reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function () { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function (description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function () { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function (beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function (afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function (desc, specDefinitions) { + return { + execute:function () { + } + }; +}; + +jasmine.Env.prototype.it = function (description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function (desc, func) { + return { + id:this.nextSpecId(), + runs:function () { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function (a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function (obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') { + continue; + } + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function (a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) { + return result; + } + } + + if (a === b) { + return true; + } + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function (haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) { + return true; + } + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function (equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function () { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function (runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function (runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function (suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function (spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function (spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function (str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function (env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function (onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function () { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function (runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function () { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function (suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id:suiteOrSpec.id, + name:suiteOrSpec.description, + type:isSuite ? 'suite' : 'spec', + children:[] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function () { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function (specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function (runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function (suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function (spec) { + this.results_[spec.id] = { + messages:spec.results().getItems(), + result:spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function (str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function (specIds) { + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function (result) { + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text:resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed:resultMessage.passed ? resultMessage.passed() : true, + type:resultMessage.type, + message:resultMessage.message, + trace:{ + stack:resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result:result.result, + messages:summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function (env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function (str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function (result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function (prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') { + continue; + } + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function (matcherName, matcherFunction) { + return function () { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) { + return result; + } + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function (s) { + return ' ' + s.toLowerCase(); + }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) { + message += ","; + } + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName:matcherName, + passed:result, + expected:matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual:this.actual, + message:message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function (expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function (expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function (expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function (expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function (expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function (expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function () { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function () { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function () { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function () { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function () { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function () { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function () { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function () { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function () { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function () { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function () { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function () { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function () { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function (expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function (expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function (expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function (expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function (expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function (expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function () { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function (expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function (other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function () { + return ''; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function () { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function (reporter) { + this.subReporters_.push(reporter); +}; + +(function () { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function (functionName) { + return function () { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function () { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function (result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function (values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function () { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function (result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function () { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function () { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function (value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function (obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') { + continue; + } + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function () { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function (value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function (value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function (array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function (obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function (property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function (value) { + this.string += value; +}; +jasmine.Queue = function (env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function (block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function (block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function (block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function (onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function () { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function () { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function () { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function () { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function (env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function () { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function (beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0, 0, beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function (afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0, 0, afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function () { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function (suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function (block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function () { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function () { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function () { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function (env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function () { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function () { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function () { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function (result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function (actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function (timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function (latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed:false, + message:e ? jasmine.util.formatException(e) : 'Exception', + trace:{ stack:e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function () { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function (matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function () { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function () { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function (onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function (doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function (onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function () { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function () { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function (obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function () { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function (env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function () { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function (onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function (beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function (afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function () { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function (suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function () { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function () { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function () { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function (onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function (env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function (env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name:'timeout', + message:message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function () { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function () { + this.reset(); + + var self = this; + self.setTimeout = function (funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function (funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function (timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function (timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function () { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function (millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function (oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function (a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch (e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function (timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis:this.nowMillis + millis, + funcToCall:funcToCall, + recurring:recurring, + timeoutKey:timeoutKey, + millis:millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer:new jasmine.FakeTimer(), + + reset:function () { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick:function (millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange:function (oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction:function (timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock:function () { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock:function () { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock:function () { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real:{ + setTimeout:jasmine.getGlobal().setTimeout, + clearTimeout:jasmine.getGlobal().clearTimeout, + setInterval:jasmine.getGlobal().setInterval, + clearInterval:jasmine.getGlobal().clearInterval + }, + + assertInstalled:function () { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled:function () { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed:null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function (funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function (funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function (timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function (timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +jasmine.version_ = { + "major":1, + "minor":1, + "build":0, + "revision":1315677058 +}; diff --git a/jsTestDriver.conf b/jsTestDriver.conf new file mode 100644 index 000000000..4b5cd8678 --- /dev/null +++ b/jsTestDriver.conf @@ -0,0 +1,15 @@ +server: http://localhost:4224 + +load: + - lib/*.js + +test: + - test/*.js + +plugin: + - name: "coverage" + jar: "lib/jstestdriver/coverage.jar" + module: "com.google.jstestdriver.coverage.CoverageModule" + +timeout: 90 + diff --git a/lib/calipso-cli.js b/lib/calipso-cli.js index 6e7004937..7845a5898 100755 --- a/lib/calipso-cli.js +++ b/lib/calipso-cli.js @@ -4,15 +4,15 @@ */ /** -* Dependencies on this script -*/ + * Dependencies on this script + */ var fs = require('fs'), - os = require('os'), - nodepath = require('path'), - exec = require('child_process').exec, - logo = require('../logo'), - colors = require('colors'), - cluster = null; + os = require('os'), + nodepath = require('path'), + exec = require('child_process').exec, + logo = require('../logo'), + colors = require('colors'), + cluster = null; try { cluster = require('cluster'); } @@ -22,15 +22,13 @@ catch (e) { /** * Optimist configuration */ -console.log(process.argv); var argv = require('optimist') - .default('src', false) - .default('port', 3000) - .alias('p', 'port') - .alias('s', 'src') - .boolean('s') - .argv; -console.log(argv); + .default('src', false) + .default('port', 3000) + .alias('p', 'port') + .alias('s', 'src') + .boolean('s') + .argv; /** * Paths @@ -49,14 +47,14 @@ var step = require('step'); * Defaults to display the help script */ var appLauncher = { - command:argv._[0] ? argv._[0] : 'help', - server: { port:argv.port }, - src:argv.src, - script: { - name:'help', - params: argv._.splice(1) - } - }; + command:argv._[0] ? argv._[0] : 'help', + server:{ port:argv.port }, + src:argv.src, + script:{ + name:'help', + params:argv._.splice(1) + } +}; runLauncher(appLauncher); @@ -71,17 +69,17 @@ function runLauncher(appLauncher) { console.log('Calipso directory: '.cyan.bold + calipsoPath.white); // Check if this is a calipso src folder - if(isLibrary() && !appLauncher.src) { + if (isLibrary() && !appLauncher.src) { console.log('\r\nWARNING:'.yellow.bold + ' You are running this from a Calipso source folder.'.white.bold); } // Check if this is a calipso site - if(!isCalipso() && appLauncher.command != 'site' && !isLibrary()) { + if (!isCalipso() && appLauncher.command != 'site' && !isLibrary()) { console.log('\x1b[1mThis is not a Calipso site - you must run:\x1b[0m calipso site SiteName\r\n'); return; } } - switch(appLauncher.command) { + switch (appLauncher.command) { case 'test': runTests(appLauncher.script); break; @@ -92,7 +90,7 @@ function runLauncher(appLauncher) { runCluster(appLauncher.server.port); break; case 'site': - createApplication(path,appLauncher.script.params); + createApplication(path, appLauncher.script.params); break; case 'install': runInstall(path); @@ -102,12 +100,12 @@ function runLauncher(appLauncher) { //require.paths.unshift(path); //make local paths accessible require(path + '/app').boot(false, function (app) { var modules = require(calipsoPath + '/lib/cli/Modules') - modules.moduleRouter(path,appLauncher.script.params,true,function(err) { - if(err) { - console.log("\r\n" + err.message.red.bold + "\r\n"); - } - process.exit(); - }); + modules.moduleRouter(path, appLauncher.script.params, true, function (err) { + if (err) { + console.log("\r\n" + err.message.red.bold + "\r\n"); + } + process.exit(); + }); }); break; case 'themes': @@ -115,12 +113,12 @@ function runLauncher(appLauncher) { //require.paths.unshift(path); //make local paths accessible require(path + '/app').boot(false, function (app) { var themes = require(calipsoPath + '/lib/cli/Themes') - themes.themeRouter(path,appLauncher.script.params,true,function(err) { - if(err) { - console.log("\r\n" + err.message.red.bold + "\r\n"); - } - process.exit(); - }); + themes.themeRouter(path, appLauncher.script.params, true, function (err) { + if (err) { + console.log("\r\n" + err.message.red.bold + "\r\n"); + } + process.exit(); + }); }); break; default: @@ -152,12 +150,12 @@ function isCalipso() { */ function runScript(scriptLauncher) { - if(!(fs.existsSync || nodepath.existsSync)(path + '/scripts/'+ scriptLauncher.name)) { + if (!(fs.existsSync || nodepath.existsSync)(path + '/scripts/' + scriptLauncher.name)) { scriptLauncher.name = 'help'; scriptLauncher.params = []; } - var script = require(path + '/scripts/'+ scriptLauncher.name); + var script = require(path + '/scripts/' + scriptLauncher.name); logo.print(); script.execute(scriptLauncher.params, path); @@ -170,9 +168,9 @@ function runScript(scriptLauncher) { function runTests(appLauncher) { // var test = appLauncher.name ? appLauncher.name : 'all'; - exec('make', { timeout: 60000, cwd:path }, function (error, stdout, stderr) { - console.log(stdout); - console.log(stderr); + exec('make', { timeout:60000, cwd:path }, function (error, stdout, stderr) { + console.log(stdout); + console.log(stderr); }); } @@ -199,14 +197,17 @@ function runServer(port) { var out = app.listen(port, function () { console.log("Calipso version: ".green + app.about.version); console.log("Calipso configured for: ".green + (global.process.env.NODE_ENV || 'development') + " environment.".green); - if (app.address) + if (app.address) { console.log("Calipso server listening on port: ".green + app.address().port); - else + } + else { console.log("Calipso server listening on port: ".green + port); + } }); process.nextTick(function () { - if (out && out.address && out.address().port !== port) + if (out && out.address && out.address().port !== port) { console.log("Calipso server listening on port: ".red + out.address().port); + } }); } else { console.log("\r\nCalipso terminated ...\r\n".grey); @@ -226,32 +227,35 @@ function createApplicationAt(path) { var self = this; var items = [ - {dest: path + '/bin', source: calipsoPath + '/bin/*.sh'}, - {dest: path + '/conf', source: calipsoPath + '/conf/*'}, - {dest: path + '/i18n', source: calipsoPath + '/i18n/*'}, - {dest: path + '/lib', source: calipsoPath + '/lib/*'}, - {dest: path + '/modules', source: calipsoPath + '/modules/*'}, - {dest: path + '/support', source: calipsoPath + '/support/*'}, - {dest: path + '/test', source: calipsoPath + '/test/*'}, - {dest: path + '/themes', source: calipsoPath + '/themes/*'}, - {dest: path + '/utils', source: calipsoPath + '/utils/*'}, - {dest: path + '/scripts', source: calipsoPath + '/scripts/*'}, - {dest: path + '/node_modules', source: calipsoPath + '/node_modules/*'}, - {dest: path + '/logs'}, - {dest: path + '/pids'}, - {dest: path + '/media'}, - {dest: path + '/tmp'}, - {dest: path, source: calipsoPath + '/app-cluster.js'}, - {dest: path, source: calipsoPath + '/app.js'}, - {dest: path, source: calipsoPath + '/package.json'}, - {dest: path, source: calipsoPath + '/logo.js'} + {dest:path + '/bin', source:calipsoPath + '/bin/*.sh'}, + {dest:path + '/conf', source:calipsoPath + '/conf/*'}, + {dest:path + '/i18n', source:calipsoPath + '/i18n/*'}, + {dest:path + '/lib', source:calipsoPath + '/lib/*'}, + {dest:path + '/modules', source:calipsoPath + '/modules/*'}, + {dest:path + '/support', source:calipsoPath + '/support/*'}, + {dest:path + '/test', source:calipsoPath + '/test/*'}, + {dest:path + '/themes', source:calipsoPath + '/themes/*'}, + {dest:path + '/utils', source:calipsoPath + '/utils/*'}, + {dest:path + '/scripts', source:calipsoPath + '/scripts/*'}, + {dest:path + '/node_modules', source:calipsoPath + '/node_modules/*'}, + {dest:path + '/logs'}, + {dest:path + '/pids'}, + {dest:path + '/media'}, + {dest:path + '/tmp'}, + {dest:path, source:calipsoPath + '/app-cluster.js'}, + {dest:path, source:calipsoPath + '/app.js'}, + {dest:path, source:calipsoPath + '/package.json'}, + {dest:path, source:calipsoPath + '/logo.js'} ]; function digest(err) { - if (err) { console.log(err); return; } + if (err) { + console.log(err); + return; + } var item = items.splice(0, 1)[0]; if (!item) { - write(path + '/.calipso','Created @ ' + new Date()); + write(path + '/.calipso', 'Created @ ' + new Date()); console.log(''); console.log('Application created at: '.green + path.white.bold); // CC : Disabled to test default NPM installation process @@ -261,11 +265,17 @@ function createApplicationAt(path) { } mkdir(item.dest, function (err) { console.log('mkdir '.green + item.dest); - if (err) { console.log(err); return; } + if (err) { + console.log(err); + return; + } if (item.source) { copy(item.source, item.dest, function (err) { console.log('copied '.green + item.source); - if (err) { console.log(err); return; } + if (err) { + console.log(err); + return; + } digest(); }); } else { @@ -273,6 +283,7 @@ function createApplicationAt(path) { } }); } + digest(); } @@ -284,32 +295,33 @@ function runInstall(path) { console.log(stdout); console.log(stderr); } + if (os.type().match(/Windows.*/)) { - exec('npm install mongodb --mongodb:native', { maxBuffer: 200*1024 }, done); + exec('npm install mongodb --mongodb:native', { maxBuffer:200 * 1024 }, done); } else { - exec('./bin/siteInstall.sh', { timeout: 60000, cwd:path }, done); + exec('./bin/siteInstall.sh', { timeout:60000, cwd:path }, done); } } /** * Create a site */ -function createApplication(path,siteName) { +function createApplication(path, siteName) { var site; - if(siteName.toString().match(/^\//)) { + if (siteName.toString().match(/^\//)) { // site is a full path site = siteName.toString(); } else { site = path + "/" + siteName; } - mkdir(site,function() { - emptyDirectory(site, function(empty){ + mkdir(site, function () { + emptyDirectory(site, function (empty) { if (empty) { createApplicationAt(site); } else { - confirm('This will over-write the existing site, continue? '.red.bold, function(ok){ + confirm('This will over-write the existing site, continue? '.red.bold, function (ok) { if (ok) { process.stdin.destroy(); createApplicationAt(site); @@ -329,8 +341,10 @@ function createApplication(path,siteName) { * @param {Function} fn */ function emptyDirectory(path, fn) { - fs.readdir(path, function(err, files){ - if (err && 'ENOENT' != err.code) throw err; + fs.readdir(path, function (err, files) { + if (err && 'ENOENT' != err.code) { + throw err; + } fn(!files || !files.length); }); } @@ -355,7 +369,7 @@ function write(path, str) { */ function confirm(msg, fn) { - prompt(msg, function(val){ + prompt(msg, function (val) { fn(/^ *y(es)?/i.test(val)); }); } @@ -377,7 +391,7 @@ function prompt(msg, fn) { // stdin process.stdin.setEncoding('ascii'); - process.stdin.once('data', function(data){ + process.stdin.once('data',function (data) { fn(data); }).resume(); } @@ -393,17 +407,23 @@ function prompt(msg, fn) { function mkdir(path, fn) { if (os.type().match(/Windows.*/)) { - if ((fs.existsSync || nodepath.existsSync)(path)) + if ((fs.existsSync || nodepath.existsSync)(path)) { fn && fn(); - else - exec('mkdir "' + path + '"', { maxBuffer: 200*1024 }, function(err){ - if (err) throw err; + } + else { + exec('mkdir "' + path + '"', { maxBuffer:200 * 1024 }, function (err) { + if (err) { + throw err; + } console.log(' create: '.blue + path.white); fn && fn(); }); + } } else { - exec('mkdir -p ' + path, function(err){ - if (err) throw err; + exec('mkdir -p ' + path, function (err) { + if (err) { + throw err; + } console.log(' create: '.blue + path.white); fn && fn(); }); @@ -422,14 +442,18 @@ function copy(from, to, fn) { if (os.type().match(/Windows.*/)) { from = from.replace(/\//g, '\\').replace(/\\\\/g, '\\'); to = to.replace(/\//g, '\\').replace(/\\\\/g, '\\'); - exec('xcopy /E /I /Q /Y "' + from + '" "' + to + '"', { maxBuffer: 200*1024 }, function(err){ - if (err) throw err; + exec('xcopy /E /I /Q /Y "' + from + '" "' + to + '"', { maxBuffer:200 * 1024 }, function (err) { + if (err) { + throw err; + } console.log(' Copied: '.blue + to.white); fn && fn(); }); } else { - exec('cp -R ' + from + ' ' + to, function(err){ - if (err) throw err; + exec('cp -R ' + from + ' ' + to, function (err) { + if (err) { + throw err; + } console.log(' Copied: '.blue + to.white); fn && fn(); }); diff --git a/lib/calipso.js b/lib/calipso.js index be2979b5d..c0b194984 100644 --- a/lib/calipso.js +++ b/lib/calipso.js @@ -13,42 +13,39 @@ * */ var rootpath = process.cwd() + '/', - path = require('path'), - fs = require('fs'), - events = require('events'); + path = require('path'), + fs = require('fs'), + events = require('events'); // Core object var calipso = module.exports = { - // Router and initialisation - routingFn: routingFn, - init: init, + // Router and initialisation + routingFn:routingFn, + init:init, - // Configuration exposed - reloadConfig: reloadConfig, + // Configuration exposed + reloadConfig:reloadConfig, - // Core objects - themes, data, modules - theme: {}, - data: {}, - modules: {} + // Core objects - themes, data, modules + theme:{}, + data:{}, + modules:{} }; // Load libraries in the core folder loadCore(calipso); -function loadCore(calipso) -{ +function loadCore(calipso) { - fs.readdirSync(__dirname + '/core').forEach(function (library) - { - var isLibrary = library.split(".").length > 0 && library.split(".")[1] === 'js', - libName = library.split(".")[0].toLowerCase(); - if (isLibrary) - { - calipso[libName] = require(__dirname + '/core/' + library); - } - }); + fs.readdirSync(__dirname + '/core').forEach(function (library) { + var isLibrary = library.split(".").length > 0 && library.split(".")[1] === 'js', + libName = library.split(".")[0].toLowerCase(); + if (isLibrary) { + calipso[libName] = require(__dirname + '/core/' + library); + } + }); } module.exports.loaded = true; @@ -57,33 +54,31 @@ module.exports.loaded = true; * Calipso initialisation */ -function init(app, initCallback) -{ +function init(app, initCallback) { - calipso.app = app; + calipso.app = app; - // Load the calipso package.json into app.about - calipso.module.loadAbout(app, rootpath, 'package.json'); + // Load the calipso package.json into app.about + calipso.module.loadAbout(app, rootpath, 'package.json'); - // config is the actual instance of loaded config, configuration is the library. - calipso.config = app.config; + // config is the actual instance of loaded config, configuration is the library. + calipso.config = app.config; - // Store the callback function for later - calipso.initCallback = function () - { - initCallback(); - }; + // Store the callback function for later + calipso.initCallback = function () { + initCallback(); + }; - // Configure the cache - calipso.cacheService = calipso.cache.Cache({ - ttl: calipso.config.get('performance:cache:ttl') - }); + // Configure the cache + calipso.cacheService = calipso.cache.Cache({ + ttl:calipso.config.get('performance:cache:ttl') + }); - // Create our calipso event emitter - calipso.e = new calipso.event.CalipsoEventEmitter({maxListeners: calipso.config.get('server:events:maxListeners')}); + // Create our calipso event emitter + calipso.e = new calipso.event.CalipsoEventEmitter({maxListeners:calipso.config.get('server:events:maxListeners')}); - // Load configuration - initialiseCalipso(); + // Load configuration + initialiseCalipso(); } @@ -96,46 +91,45 @@ function init(app, initCallback) * Expects Calipso to be initialised. */ -function routingFn() -{ - - // Return the function that manages the routing - // Ok being non-synchro - return function (req, res, next) - { - - // Default menus and blocks for each request - // More of these can be added in modules, these are jsut the defaults - res.menu = { - admin: new calipso.menu('admin', 'weight', 'root', { - cls: 'admin' - }), - adminToolbar: new calipso.menu('adminToolbar', 'weight', 'root', { - cls: 'admin-toolbar toolbar' - }), - // TODO - Configurable! - userToolbar: new calipso.menu('userToolbar', 'weight', 'root', { - cls: 'user-toolbar toolbar' - }), - primary: new calipso.menu('primary', 'name', 'root', { - cls: 'primary' - }), - secondary: new calipso.menu('secondary', 'name', 'root', { - cls: 'secondary' - }) - }; - - // Initialise our clientJS library linked to this request - var Client = require('./client/Client'); - res.client = new Client(); - - // Initialise helpers - first pass - calipso.helpers.getDynamicHelpers(req, res, calipso); - - // Route the modules - calipso.module.eventRouteModules(req, res, next); - - }; +function routingFn() { + + // Return the function that manages the routing + // Ok being non-synchro + return function (req, res, next) { + + // Default menus and blocks for each request + // More of these can be added in modules, these are jsut the defaults + res.menu = { + admin:new calipso.menu('admin', 'weight', 'root', { + cls:'admin' + }), + adminToolbar:new calipso.menu('adminToolbar', 'weight', 'root', { + cls:'admin-toolbar toolbar' + }), + // TODO - Configurable! + userToolbar:new calipso.menu('userToolbar', 'weight', 'root', { + cls:'user-toolbar toolbar' + }), + primary:new calipso.menu('primary', 'name', 'root', { + cls:'primary' + }), + secondary:new calipso.menu('secondary', 'name', 'root', { + cls:'secondary' + }) + }; + + + // Initialise our clientJS library linked to this request + var Client = require('./client/Client'); + res.client = new Client(); + + // Initialise helpers - first pass + calipso.helpers.getDynamicHelpers(req, res, calipso); + + // Route the modules + calipso.module.eventRouteModules(req, res, next); + + }; } @@ -150,53 +144,46 @@ function routingFn() * */ -function initialiseCalipso(reloadConfig) -{ +function initialiseCalipso(reloadConfig) { - // Check if we need to reload the config from disk (e.g. from cluster mode) - if (reloadConfig) - { - calipso.config.load(); - } + // Check if we need to reload the config from disk (e.g. from cluster mode) + if (reloadConfig) { + calipso.config.load(); + } - // Clear Event listeners - calipso.e.init(); + // Clear Event listeners + calipso.e.init(); - // Configure the logging - calipso.logging.configureLogging(); + // Configure the logging + calipso.logging.configureLogging(); - // Check / Connect Mongo - calipso.storage.mongoConnect(calipso.config.get('database:uri'), false, function (err, connected) - { + // Check / Connect Mongo + calipso.storage.mongoConnect(calipso.config.get('database:uri'), false, function (err, connected) { - if (err) - { - console.log("There was an error connecting to the database: " + err.message); - process.exit(); - } + if (err) { + console.log("There was an error connecting to the database: " + err.message); + process.exit(); + } - // Load all the themes - loadThemes(function () - { + // Load all the themes + loadThemes(function () { - // Initialise the modules and theming engine - configureTheme(function () - { + // Initialise the modules and theming engine + configureTheme(function () { - // Load all the modules - calipso.module.loadModules(function () - { + // Load all the modules + calipso.module.loadModules(function () { - // Initialise, callback via calipso.initCallback - calipso.module.initModules(); + // Initialise, callback via calipso.initCallback + calipso.module.initModules(); - }); + }); - }); + }); - }); + }); - }); + }); } @@ -204,19 +191,16 @@ function initialiseCalipso(reloadConfig) * Called both via a hook.io event as * well as via the server that initiated it. */ -function reloadConfig(event, data, next) -{ - - // Create a callback - calipso.initCallback = function (err) - { - // If called via event emitter rather than hook - if (typeof next === "function") - { - next(err); - } - }; - return initialiseCalipso(true); +function reloadConfig(event, data, next) { + + // Create a callback + calipso.initCallback = function (err) { + // If called via event emitter rather than hook + if (typeof next === "function") { + next(err); + } + }; + return initialiseCalipso(true); } @@ -224,57 +208,49 @@ function reloadConfig(event, data, next) * Load the available themes into the calipso.themes object */ -function loadThemes(next) -{ - - var themeBasePath = calipso.config.get('server:themePath'), - themePath, legacyTheme, themes; - - // Load the available themes - calipso.availableThemes = calipso.availableThemes || {}; - - calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, themeBasePath)).forEach(function (folder) - { - - if (folder != "README" && folder[0] != '.') - { - - themes = calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, themeBasePath, folder)); - - // First scan for legacy themes - legacyTheme = false; - themes.forEach(function (theme) - { - if (theme === "theme.json") - { - legacyTheme = true; - console.log("Themes are now stored in sub-folders under the themes folder, please move: " + folder + " (e.g. to custom/" + folder + ").\r\n"); - } - }); - - // Process - if (!legacyTheme) - { - themes.forEach(function (theme) - { - - if (theme != "README" && theme[0] != '.') - { - themePath = calipso.lib.path.join(rootpath, themeBasePath, folder, theme); - // Create the theme object - calipso.availableThemes[theme] = { - name: theme, - path: themePath - }; - // Load the about info from package.json - calipso.module.loadAbout(calipso.availableThemes[theme], themePath, 'theme.json'); - } - }); - } - } - }); - - next(); +function loadThemes(next) { + + var themeBasePath = calipso.config.get('server:themePath'), + themePath, legacyTheme, themes; + + // Load the available themes + calipso.availableThemes = calipso.availableThemes || {}; + + calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, themeBasePath)).forEach(function (folder) { + + if (folder != "README" && folder[0] != '.') { + + themes = calipso.lib.fs.readdirSync(calipso.lib.path.join(rootpath, themeBasePath, folder)); + + // First scan for legacy themes + legacyTheme = false; + themes.forEach(function (theme) { + if (theme === "theme.json") { + legacyTheme = true; + console.log("Themes are now stored in sub-folders under the themes folder, please move: " + folder + " (e.g. to custom/" + folder + ").\r\n"); + } + }); + + // Process + if (!legacyTheme) { + themes.forEach(function (theme) { + + if (theme != "README" && theme[0] != '.') { + themePath = calipso.lib.path.join(rootpath, themeBasePath, folder, theme); + // Create the theme object + calipso.availableThemes[theme] = { + name:theme, + path:themePath + }; + // Load the about info from package.json + calipso.module.loadAbout(calipso.availableThemes[theme], themePath, 'theme.json'); + } + }); + } + } + }); + + next(); } @@ -282,97 +258,76 @@ function loadThemes(next) * Configure a theme using the theme library. */ -function configureTheme(next, overrideTheme) -{ - - var defaultTheme = calipso.config.get("theme:default"); - var themeName = overrideTheme ? overrideTheme : calipso.config.get('theme:front'); - var themeConfig = calipso.availableThemes[themeName]; // Reference to theme.json - if (themeConfig) - { - - // Themes is the library - calipso.themes.Theme(themeConfig, function (err, loadedTheme) - { - - // Current theme is always in calipso.theme - calipso.theme = loadedTheme; - - if (err) - { - calipso.error(err.message); - } - - if (!calipso.theme) - { - - if (loadedTheme.name === defaultTheme) - { - calipso.error('There has been a failure loading the default theme, calipso cannot start until this is fixed, terminating.'); - process.exit(); - return; - } - else - { - calipso.error('The `' + themeName + '` theme failed to load, attempting to use the default theme: `' + defaultTheme + '`'); - configureTheme(next, defaultTheme); - return; - } - - } - else - { - - // Search for middleware that already has themeStatic tag - var foundMiddleware = false, - mw; - calipso.app.stack.forEach(function (middleware, key) - { - if (middleware.handle.tag === 'theme.stylus') - { - foundMiddleware = true; - if ((fs.existsSync || path.existsSync)(themeConfig.path + '/stylus')) - { - mw = calipso.app.mwHelpers.stylusMiddleware(themeConfig.path); - } - else - { - mw = {tag: 'theme.stylus'}; - } - calipso.app.stack[key].handle = mw; - } - - if (middleware.handle.tag === 'theme.static') - { - foundMiddleware = true; - mw = calipso.app.mwHelpers.staticMiddleware(themeConfig.path); - mw.tag = 'theme.static'; - calipso.app.stack[key].handle = mw; - } - - }); - - next(); - - } - - }); - - } - else - { - - if (themeName === defaultTheme) - { - console.error("Unable to locate the theme: " + themeName + ", terminating."); - process.exit(); - } - else - { - calipso.error('The `' + themeName + '` theme is missing, trying the defaul theme: `' + defaultTheme + '`'); - configureTheme(next, defaultTheme); - } - - } +function configureTheme(next, overrideTheme) { + + var defaultTheme = calipso.config.get("theme:default"); + var themeName = overrideTheme ? overrideTheme : calipso.config.get('theme:front'); + var themeConfig = calipso.availableThemes[themeName]; // Reference to theme.json + if (themeConfig) { + + // Themes is the library + calipso.themes.Theme(themeConfig, function (err, loadedTheme) { + + // Current theme is always in calipso.theme + calipso.theme = loadedTheme; + + if (err) { + calipso.error(err.message); + } + + if (!calipso.theme) { + + if (loadedTheme.name === defaultTheme) { + calipso.error('There has been a failure loading the default theme, calipso cannot start until this is fixed, terminating.'); + process.exit(); + return; + } else { + calipso.error('The `' + themeName + '` theme failed to load, attempting to use the default theme: `' + defaultTheme + '`'); + configureTheme(next, defaultTheme); + return; + } + + } else { + + // Search for middleware that already has themeStatic tag + var foundMiddleware = false, + mw; + calipso.app.stack.forEach(function (middleware, key) { + if (middleware.handle.tag === 'theme.stylus') { + foundMiddleware = true; + if ((fs.existsSync || path.existsSync)(themeConfig.path + '/stylus')) { + mw = calipso.app.mwHelpers.stylusMiddleware(themeConfig.path); + } else { + mw = {tag:'theme.stylus'}; + } + calipso.app.stack[key].handle = mw; + } + + if (middleware.handle.tag === 'theme.static') { + foundMiddleware = true; + mw = calipso.app.mwHelpers.staticMiddleware(themeConfig.path); + mw.tag = 'theme.static'; + calipso.app.stack[key].handle = mw; + } + + }); + + next(); + + } + + }); + + } else { + + if (themeName === defaultTheme) { + console.error("Unable to locate the theme: " + themeName + ", terminating."); + process.exit(); + } else { + calipso.error('The `' + themeName + '` theme is missing, trying the defaul theme: `' + defaultTheme + '`'); + configureTheme(next, defaultTheme); + } + + } } diff --git a/lib/cli/Download.js b/lib/cli/Download.js index 44c1c74ed..262b981fb 100644 --- a/lib/cli/Download.js +++ b/lib/cli/Download.js @@ -14,27 +14,25 @@ */ var sys; var zip; -try -{ - sys = require('util'); - zip = require('zipfile') +try { + sys = require('util'); + zip = require('zipfile') } -catch (e) -{ - sys = require('sys'); +catch (e) { + sys = require('sys'); } var rootpath = process.cwd() + '/', - path = require('path') + path = require('path') calipso = require(path.join(rootpath, 'lib/calipso')), - api = require(path.join(rootpath, 'lib/cli/RepoApi')), - moduleCli = require(path.join(rootpath, 'lib/cli/Modules')), - themeCli = require(path.join(rootpath, 'lib/cli/Themes')), - exec = require('child_process').exec, - colors = require('colors'), - util = require('util'), - zip, - fs = require('fs'), - rimraf = require('rimraf') + api = require(path.join(rootpath, 'lib/cli/RepoApi')), + moduleCli = require(path.join(rootpath, 'lib/cli/Modules')), + themeCli = require(path.join(rootpath, 'lib/cli/Themes')), + exec = require('child_process').exec, + colors = require('colors'), + util = require('util'), + zip, + fs = require('fs'), + rimraf = require('rimraf') semver = require('semver'); /** @@ -43,45 +41,37 @@ semver = require('semver'); * To enable both web and cli modes to listen to what it is doing ... ? * TODO - */ -function Downloader() -{ +function Downloader() { - var self = this; + var self = this; } /** * Download */ -function download(type, fromUrl, toPath, cli, next) -{ - - // Create a common file process function - var localNext = function (err, path) - { - if (err) - { - next(err); - } - else - { - processDownload(type, path, next); - } - } - - if (fromUrl.match(/^http.*/)) - { - return downloadUrl(fromUrl, toPath, cli, localNext); - } - - if (fromUrl.match(/^(.*)\/(.*)$/)) - { - return downloadGithub(fromUrl, toPath, cli, localNext); - } - - // Otherwise assume repo - append type - fromUrl = type + "/" + fromUrl; - return downloadRepo(type, fromUrl, toPath, cli, localNext); +function download(type, fromUrl, toPath, cli, next) { + + // Create a common file process function + var localNext = function (err, path) { + if (err) { + next(err); + } else { + processDownload(type, path, next); + } + } + + if (fromUrl.match(/^http.*/)) { + return downloadUrl(fromUrl, toPath, cli, localNext); + } + + if (fromUrl.match(/^(.*)\/(.*)$/)) { + return downloadGithub(fromUrl, toPath, cli, localNext); + } + + // Otherwise assume repo - append type + fromUrl = type + "/" + fromUrl; + return downloadRepo(type, fromUrl, toPath, cli, localNext); } @@ -89,36 +79,28 @@ function download(type, fromUrl, toPath, cli, next) * Download a module from repo * This is called by install */ -function downloadRepo(type, fromUrl, toPath, cli, next) -{ - - // Split module / version - var repoName = fromUrl; - - if (repoName) - { - - var version = repoName.split('@')[1] || ""; - var repoName = repoName.split('@')[0]; - var tmpName = repoName.replace("/", "-"); - - constructRepoUrl(repoName, version, function (err, url) - { - if (!err && url) - { - return downloadGithub(url, toPath, cli, next); - } - else - { - next(err); - } - }); - - } - else - { - next(new Error('You need to provide a calipso repository project name - e.g. ElasticSearch')); - } +function downloadRepo(type, fromUrl, toPath, cli, next) { + + // Split module / version + var repoName = fromUrl; + + if (repoName) { + + var version = repoName.split('@')[1] || ""; + var repoName = repoName.split('@')[0]; + var tmpName = repoName.replace("/", "-"); + + constructRepoUrl(repoName, version, function (err, url) { + if (!err && url) { + return downloadGithub(url, toPath, cli, next); + } else { + next(err); + } + }); + + } else { + next(new Error('You need to provide a calipso repository project name - e.g. ElasticSearch')); + } } @@ -126,41 +108,33 @@ function downloadRepo(type, fromUrl, toPath, cli, next) * Download a module from github * This is called by install */ -function downloadGithub(fromUrl, toPath, cli, next) -{ - - // Split module / version - var githubName = fromUrl; - - if (githubName) - { - - var tag = githubName.split('@')[1] || ""; - var githubName = githubName.split('@')[0]; - var tmpName = githubName.replace("/", "-"); - - if (githubName.split("/").length !== 2) - { - next(new Error('You need to provide a github project name - e.g. cliftonc/calipso-elastic')); - return; - } - - var url = constructGithubUrl(githubName, tag); - - if (url) - { - downloadFile(url, tmpName, toPath, next); - } - else - { - next(new Error('You need to provide a github project name - e.g. cliftonc/calipso-elastic')); - } - - } - else - { - next(new Error('You need to provide a github project name - e.g. cliftonc/calipso-elastic')); - } +function downloadGithub(fromUrl, toPath, cli, next) { + + // Split module / version + var githubName = fromUrl; + + if (githubName) { + + var tag = githubName.split('@')[1] || ""; + var githubName = githubName.split('@')[0]; + var tmpName = githubName.replace("/", "-"); + + if (githubName.split("/").length !== 2) { + next(new Error('You need to provide a github project name - e.g. cliftonc/calipso-elastic')); + return; + } + + var url = constructGithubUrl(githubName, tag); + + if (url) { + downloadFile(url, tmpName, toPath, next); + } else { + next(new Error('You need to provide a github project name - e.g. cliftonc/calipso-elastic')); + } + + } else { + next(new Error('You need to provide a github project name - e.g. cliftonc/calipso-elastic')); + } } @@ -168,243 +142,193 @@ function downloadGithub(fromUrl, toPath, cli, next) * Download a module from a url * This is called by install */ -function downloadUrl(fromUrl, toPath, cli, next) -{ - - // Split module / version - var url = fromUrl, path = require('path'); - - if (url) - { - - var u = require('url'), fs = require('fs'); - var parts = u.parse(url); - var tmpName = path.basename(parts.pathname); - - if (tmpName && tmpName.match(/.zip$/)) - { - downloadFile(url, tmpName, toPath, next); - } - else - { - next(new Error('You need to provide a valid url to your module zip file, e.g. http://cliftoncunningham.co.uk/module.zip')); - } - } - else - { - next(new Error('You need to provide a valid url to your module zip file, e.g. http://cliftoncunningham.co.uk/module.zip')); - } +function downloadUrl(fromUrl, toPath, cli, next) { + + // Split module / version + var url = fromUrl, path = require('path'); + + if (url) { + + var u = require('url'), fs = require('fs'); + var parts = u.parse(url); + var tmpName = path.basename(parts.pathname); + + if (tmpName && tmpName.match(/.zip$/)) { + downloadFile(url, tmpName, toPath, next); + } else { + next(new Error('You need to provide a valid url to your module zip file, e.g. http://cliftoncunningham.co.uk/module.zip')); + } + } else { + next(new Error('You need to provide a valid url to your module zip file, e.g. http://cliftoncunningham.co.uk/module.zip')); + } } -function downloadFile(url, fileName, toPath, next) -{ +function downloadFile(url, fileName, toPath, next) { - var u = require('url'), fs = require('fs'), path = require('path'); - var parts = u.parse(url); + var u = require('url'), fs = require('fs'), path = require('path'); + var parts = u.parse(url); - // Ensure we have our download folder - var tmpFolder = toPath; + // Ensure we have our download folder + var tmpFolder = toPath; - if (!(fs.existsSync || path.existsSync)(tmpFolder)) - { - fs.mkdirSync(tmpFolder, 0755); - } + if (!(fs.existsSync || path.existsSync)(tmpFolder)) { + fs.mkdirSync(tmpFolder, 0755); + } - if (parts.protocol === 'https:') - { - client = require('https'); - } - else - { - client = require('http'); - if (!parts.port) - { - parts.port = 80; - } - } + if (parts.protocol === 'https:') { + client = require('https'); + } else { + client = require('http'); + if (!parts.port) { + parts.port = 80; + } + } - console.log("\r\nResolving file location, and downloading ...".cyan); + console.log("\r\nResolving file location, and downloading ...".cyan); - client.get({ host: parts.hostname, port: parts.port, path: parts.pathname },function (res) - { + client.get({ host:parts.hostname, port:parts.port, path:parts.pathname },function (res) { - if (res.statusCode === 302) - { - console.log("Redirecting to ".grey + res.headers.location.grey + " ...".grey); - downloadFile(res.headers.location, fileName, toPath, next); - return; - } + if (res.statusCode === 302) { + console.log("Redirecting to ".grey + res.headers.location.grey + " ...".grey); + downloadFile(res.headers.location, fileName, toPath, next); + return; + } - if (res.statusCode === 200) - { + if (res.statusCode === 200) { - var tmpFile = tmpFolder + fileName + '.zip'; - var fd = fs.openSync(tmpFile, 'w'); - var size = 0; - var totalSize = parseInt(res.headers['content-length']); - var progress = 0; + var tmpFile = tmpFolder + fileName + '.zip'; + var fd = fs.openSync(tmpFile, 'w'); + var size = 0; + var totalSize = parseInt(res.headers['content-length']); + var progress = 0; - res.on('data', function (chunk) - { - size += chunk.length; - progress = showProgress(size, totalSize, progress); - fs.writeSync(fd, chunk, 0, chunk.length, null); - }); + res.on('data', function (chunk) { + size += chunk.length; + progress = showProgress(size, totalSize, progress); + fs.writeSync(fd, chunk, 0, chunk.length, null); + }); - res.on('end', function () - { - process.stdout.write("\n\n"); - fs.closeSync(fd); - next(null, tmpFile); - }); + res.on('end', function () { + process.stdout.write("\n\n"); + fs.closeSync(fd); + next(null, tmpFile); + }); - } - else - { + } else { - next(new Error("Unable to download file, status was " + res.statusCode)); + next(new Error("Unable to download file, status was " + res.statusCode)); - } + } - }).on('error', function (err) - { + }).on('error', function (err) { - next(err); + next(err); - }); + }); } -function showProgress(size, totalSize, progress) -{ - - var newProgress = Math.floor((size / totalSize) * 20); - if (newProgress > progress) - { - for (var i = progress + 1; - i <= newProgress; - i++) - { - switch (i) - { - case 1: - process.stdout.write("[".red + "0%".green); - break; - case 5: - process.stdout.write("25%".green); - break; - case 10: - process.stdout.write("50%".green); - break; - case 15: - process.stdout.write("75%".green); - break; - case 20: - process.stdout.write("100%".green + "]".red); - break; - default: - process.stdout.write(".".blue); - } - } - progress = newProgress; - } - return progress; +function showProgress(size, totalSize, progress) { + + var newProgress = Math.floor((size / totalSize) * 20); + if (newProgress > progress) { + for (var i = progress + 1; i <= newProgress; i++) { + switch (i) { + case 1: + process.stdout.write("[".red + "0%".green); + break; + case 5: + process.stdout.write("25%".green); + break; + case 10: + process.stdout.write("50%".green); + break; + case 15: + process.stdout.write("75%".green); + break; + case 20: + process.stdout.write("100%".green + "]".red); + break; + default: + process.stdout.write(".".blue); + } + } + progress = newProgress; + } + return progress; } /** * Create a github url */ -function constructGithubUrl(userProject, tag) -{ - - var url = ""; - if (tag) - { - url = "https://github.com/" + userProject + "/zipball/" + tag; - } - else - { - url = "https://github.com/" + userProject + "/zipball/master"; - } - return url; +function constructGithubUrl(userProject, tag) { + + var url = ""; + if (tag) { + url = "https://github.com/" + userProject + "/zipball/" + tag; + } else { + url = "https://github.com/" + userProject + "/zipball/master"; + } + return url; } /** * Create a url based on the repository details */ -function constructRepoUrl(repoName, version, next) -{ - - // Create our API wrapper - var repo = new api(); - - // Variables for lookup - var type = repoName.split("/")[0]; - var name = repoName.split("/")[1]; - var version = version || "master"; - - repo.get({type: type, name: name, version: version}, function (err, r) - { - if (err || !r) - { - next(err); - } - else - { - if (r.length === 1) - { - var versions = r[0].versions; - // Find the version specified - var versionMatched = false; - versions.forEach(function (v) - { - if (v.version === version) - { - next(null, v.url); - versionMatched = true; - console.log("Resolved ".cyan.bold + name.green.bold + "@".white + v.version.green.bold + " to github repo ".cyan.bold + v.url + (v.version === "master" ? "" : "@" + v.version)); - return; - } - }); - if (!versionMatched) - { - next(new Error("Unable to locate the version specified.")); - } - } - else - { - if (r.length === 0) - { - if (type === "module") - { - console.log("\r\nNo entries found, searching the repository for a module along the same lines ...".white.bold); - moduleCli.findModule(["", name], true, function (err, data) - { - // Throw a blank error - next(new Error("Please try again using one of the module names listed above, or perhaps this is the inspiration for you to build one? :)")); - }); - } - else - { - console.log("\r\nNo entries found, searching the repository for a theme along the same lines ...".white.bold); - themeCli.findTheme(["", name], true, function (err, data) - { - // Throw a blank error - next(new Error("Please try again using one of the theme names listed above, or perhaps this is the inspiration for you to build one? :)")); - }); - } - - } - else - { - next(new Error("There was an error locating that module, " + r.length.toString().red.bold + " entries returned".red)); - } - } - } - }); +function constructRepoUrl(repoName, version, next) { + + // Create our API wrapper + var repo = new api(); + + // Variables for lookup + var type = repoName.split("/")[0]; + var name = repoName.split("/")[1]; + var version = version || "master"; + + repo.get({type:type, name:name, version:version}, function (err, r) { + if (err || !r) { + next(err); + } else { + if (r.length === 1) { + var versions = r[0].versions; + // Find the version specified + var versionMatched = false; + versions.forEach(function (v) { + if (v.version === version) { + next(null, v.url); + versionMatched = true; + console.log("Resolved ".cyan.bold + name.green.bold + "@".white + v.version.green.bold + " to github repo ".cyan.bold + v.url + (v.version === "master" ? "" : "@" + v.version)); + return; + } + }); + if (!versionMatched) { + next(new Error("Unable to locate the version specified.")); + } + } else { + if (r.length === 0) { + if (type === "module") { + console.log("\r\nNo entries found, searching the repository for a module along the same lines ...".white.bold); + moduleCli.findModule(["", name], true, function (err, data) { + // Throw a blank error + next(new Error("Please try again using one of the module names listed above, or perhaps this is the inspiration for you to build one? :)")); + }); + } else { + console.log("\r\nNo entries found, searching the repository for a theme along the same lines ...".white.bold); + themeCli.findTheme(["", name], true, function (err, data) { + // Throw a blank error + next(new Error("Please try again using one of the theme names listed above, or perhaps this is the inspiration for you to build one? :)")); + }); + } + + } else { + next(new Error("There was an error locating that module, " + r.length.toString().red.bold + " entries returned".red)); + } + } + } + }); } @@ -412,33 +336,27 @@ function constructRepoUrl(repoName, version, next) * Process a downloaded module, place into modules folder */ -function processDownload(type, file, next) -{ - - // Checks - var isValid; - - // #1 - Is it a zip? - isValid = file.match(/.zip$/); - - // #2 - unzip it, check contents - if (isValid) - { - unzipDownload(type, file, function (err, tmpName, tmpFolder) - { - // Intercept to enable a cleanup - if (err) - { - fs.unlinkSync(file); - err.message = err.message + "The downloaded file has been deleted."; - } - next(err, tmpName, tmpFolder); - }); - } - else - { - next(new Error("The file downloaded must be a valid zip archive.")); - } +function processDownload(type, file, next) { + + // Checks + var isValid; + + // #1 - Is it a zip? + isValid = file.match(/.zip$/); + + // #2 - unzip it, check contents + if (isValid) { + unzipDownload(type, file, function (err, tmpName, tmpFolder) { + // Intercept to enable a cleanup + if (err) { + fs.unlinkSync(file); + err.message = err.message + "The downloaded file has been deleted."; + } + next(err, tmpName, tmpFolder); + }); + } else { + next(new Error("The file downloaded must be a valid zip archive.")); + } } @@ -446,202 +364,160 @@ function processDownload(type, file, next) * Process a downloaded module, place into modules folder */ -function unzipDownload(type, file, callback) -{ - - var zf, - baseFolder, - tmpFolder, - tmpName; - - try - { - zf = new zip.ZipFile(file) - } - catch (ex) - { - return callback(ex); - } - - zf.names.forEach(function (name) - { - - // First result is the basefolder - if (!baseFolder) - { - baseFolder = name; // Store - } - - // Now, lets find the package.json - if (type === 'module' && name === (baseFolder + "package.json")) - { - var buffer = zf.readFileSync(name); - var packageJson = JSON.parse(buffer); - tmpName = packageJson.name; - tmpFolder = path.join(path.dirname(file), tmpName + "/"); // Extraction will go here - } - - // Now, lets find the theme.json - if (type === 'theme' && name === (baseFolder + "theme.json")) - { - var buffer = zf.readFileSync(name); - var themeJson = JSON.parse(buffer); - tmpName = themeJson.name; - tmpFolder = path.join(path.dirname(file), tmpName + "/"); // Extraction will go here - } - - }); - - // Check that we have both a module name - if (tmpName) - { - - // Make sure we delete any existing tmp folder - if ((fs.existsSync || path.existsSync)(tmpFolder)) - { - rimraf.sync(tmpFolder); - } - - // First run through and create every directory synchronously - var folders = []; - zf.names.forEach(function (name) - { - folders.push(name.replace(baseFolder, "").split("/")); - }); - - folders.forEach(function (folderList) - { - var folder = tmpFolder; - folderList.forEach(function (currFolder) - { - var isDir = (!path.extname(currFolder) || currFolder[0] === '.'); - folder = path.join(folder, currFolder); - if (isDir) - { - dirExists(folder); - } - }); - }); - - // Now, lets extract all the files - var remaining = zf.names.length; - - zf.names.forEach(function (name) - { - - var dest = path.join( - tmpFolder, - name.replace(baseFolder, "") - ); - - // Skip directories, hiddens. - var isDir = (!path.extname(name) || name[0] === '.' || name[name.length] === "/"); - if (isDir) - { - remaining--; - if (!remaining) - { - return callback(null); - } - } - else - { - zf.readFile(name, function (err, buff) - { - if (err) - { - return callback(err); - } - fs.open(dest, 'w', 0644, function (err, fd) - { - if (err) - { - if (err.code !== "EISDIR") - { - // fs.unlinkSync(file); - return callback(err); - } - else - { - remaining--; - } - - } - else - { - fs.write(fd, buff, 0, buff.length, null, function (err) - { - if (err) - { - fs.unlinkSync(file); - return callback(err); - } - fs.close(fd, function (err) - { - if (err) - { - return callback(err); - } - remaining--; - if (!remaining) - { - fs.unlinkSync(file); - callback(null, tmpName, tmpFolder); - } - }); - }); - } - }); - }); - } - }); - - } - else - { - if (type === 'module') - { - next(new Error("The file does not appear to have a valid package.json that specifies the name.")); - } - else - { - next(new Error("The file does not appear to have a valid theme.json that specifies the name.")); - } - - } +function unzipDownload(type, file, callback) { + + var zf, + baseFolder, + tmpFolder, + tmpName; + + try { + zf = new zip.ZipFile(file) + } + catch (ex) { + return callback(ex); + } + + zf.names.forEach(function (name) { + + // First result is the basefolder + if (!baseFolder) { + baseFolder = name; // Store + } + + // Now, lets find the package.json + if (type === 'module' && name === (baseFolder + "package.json")) { + var buffer = zf.readFileSync(name); + var packageJson = JSON.parse(buffer); + tmpName = packageJson.name; + tmpFolder = path.join(path.dirname(file), tmpName + "/"); // Extraction will go here + } + + // Now, lets find the theme.json + if (type === 'theme' && name === (baseFolder + "theme.json")) { + var buffer = zf.readFileSync(name); + var themeJson = JSON.parse(buffer); + tmpName = themeJson.name; + tmpFolder = path.join(path.dirname(file), tmpName + "/"); // Extraction will go here + } + + }); + + // Check that we have both a module name + if (tmpName) { + + // Make sure we delete any existing tmp folder + if ((fs.existsSync || path.existsSync)(tmpFolder)) { + rimraf.sync(tmpFolder); + } + + // First run through and create every directory synchronously + var folders = []; + zf.names.forEach(function (name) { + folders.push(name.replace(baseFolder, "").split("/")); + }); + + folders.forEach(function (folderList) { + var folder = tmpFolder; + folderList.forEach(function (currFolder) { + var isDir = (!path.extname(currFolder) || currFolder[0] === '.'); + folder = path.join(folder, currFolder); + if (isDir) { + dirExists(folder); + } + }); + }); + + // Now, lets extract all the files + var remaining = zf.names.length; + + zf.names.forEach(function (name) { + + var dest = path.join( + tmpFolder, + name.replace(baseFolder, "") + ); + + // Skip directories, hiddens. + var isDir = (!path.extname(name) || name[0] === '.' || name[name.length] === "/"); + if (isDir) { + remaining--; + if (!remaining) { + return callback(null); + } + } else { + zf.readFile(name, function (err, buff) { + if (err) { + return callback(err); + } + fs.open(dest, 'w', 0644, function (err, fd) { + if (err) { + if (err.code !== "EISDIR") { + // fs.unlinkSync(file); + return callback(err); + } else { + remaining--; + } + + } else { + fs.write(fd, buff, 0, buff.length, null, function (err) { + if (err) { + fs.unlinkSync(file); + return callback(err); + } + fs.close(fd, function (err) { + if (err) { + return callback(err); + } + remaining--; + if (!remaining) { + fs.unlinkSync(file); + callback(null, tmpName, tmpFolder); + } + }); + }); + } + }); + }); + } + }); + + } else { + if (type === 'module') { + next(new Error("The file does not appear to have a valid package.json that specifies the name.")); + } else { + next(new Error("The file does not appear to have a valid theme.json that specifies the name.")); + } + + } } /** * */ -function dirExists(dest) -{ - - var fs = require('fs'); - - // Try to create the folder - try - { - fs.mkdirSync(dest, 0755) - } - catch (ex) - { - if (ex.code === 'EEXIST') - { - // Ignore - return true; - } - else - { - return false; - } - } - - return true; +function dirExists(dest) { + + var fs = require('fs'); + + // Try to create the folder + try { + fs.mkdirSync(dest, 0755) + } + catch (ex) { + if (ex.code === 'EEXIST') { + // Ignore + return true; + } else { + return false; + } + } + + return true; } + /** * Exports */ diff --git a/lib/cli/Modules.js b/lib/cli/Modules.js index c269d412e..8177f5b85 100644 --- a/lib/cli/Modules.js +++ b/lib/cli/Modules.js @@ -18,13 +18,13 @@ */ var sys; try { - sys = require('util'); + sys = require('util'); } catch (e) { - sys = require('sys'); + sys = require('sys'); } var rootpath = process.cwd() + '/', path = require('path') - calipso = require(path.join(rootpath, 'lib/calipso')), +calipso = require(path.join(rootpath, 'lib/calipso')), exec = require('child_process').exec, util = require('util'), colors = require('colors'), @@ -37,26 +37,26 @@ var rootpath = process.cwd() + '/', * Module router - takes params from CLI and passes to appropriate function * Controls return based on CLI mode or web mode */ -exports.moduleRouter = function(path,options,cli,next) { +exports.moduleRouter = function (path, options, cli, next) { - switch(options[0]) { + switch (options[0]) { case 'list': - listModules(options,cli,next); + listModules(options, cli, next); break; case 'check': - checkAll(options,cli,next); + checkAll(options, cli, next); break; case 'reinstall': - installModule(options,cli,next); + installModule(options, cli, next); break; case 'uninstall': - uninstallModule(options,cli,next); + uninstallModule(options, cli, next); break; case 'enable': - toggleModule(true,options,cli,next); + toggleModule(true, options, cli, next); break; case 'disable': - toggleModule(false,options,cli,next); + toggleModule(false, options, cli, next); break; case 'install': // Default is github downloadModule(options, cli, next); @@ -75,25 +75,25 @@ exports.moduleRouter = function(path,options,cli,next) { /** *Find module - */ + */ function findModule(options, cli, next) { var search = options[1]; // Second parameter is our query - var searchRegex = new RegExp(search,"ig"); - search = search.replace(/\*/g,""); + var searchRegex = new RegExp(search, "ig"); + search = search.replace(/\*/g, ""); var repo = new api(); - repo.find('module', options, function(err, data) { - if(data.length > 0) { + repo.find('module', options, function (err, data) { + if (data.length > 0) { console.log(""); var formattedText = ""; - data.forEach(function(module) { + data.forEach(function (module) { var versionString = ""; - module.versions.forEach(function(version) { - versionString += " - [" + version.version + "] : " + version.url + "\r\n" + module.versions.forEach(function (version) { + versionString += " - [" + version.version + "] : " + version.url + "\r\n" }); - var description = module.description.replace(searchRegex,search.yellow.bold); + var description = module.description.replace(searchRegex, search.yellow.bold); var author = " - [Author] ".cyan + module.author.cyan.bold; formattedText += module.name.white.bold + "\r\n"; formattedText += description + "\r\n"; @@ -110,7 +110,7 @@ function findModule(options, cli, next) { } // console.log is non-blocking, hence we need to delay or programme terminates // before the text is written out in a large output ... - setTimeout(next,200); + setTimeout(next, 200); }); } @@ -123,26 +123,26 @@ exports.findModule = findModule; function downloadModule(options, cli, next) { var toPath = calipso.app.path() + "/modules/downloaded/"; var fromUrl = options[1]; - download('module', fromUrl, toPath, cli, function(err,moduleName,path) { - if(err) { - next(err); - } else { - installViaNpm(moduleName,path,next); - } + download('module', fromUrl, toPath, cli, function (err, moduleName, path) { + if (err) { + next(err); + } else { + installViaNpm(moduleName, path, next); + } }); } /** * Show the list of currently installed modules - highlight those with issues / updates? */ -function listModules(options,cli,next) { +function listModules(options, cli, next) { // Command line - if(cli) { + if (cli) { var currentType = ""; - for(var moduleName in calipso.modules) { + for (var moduleName in calipso.modules) { var module = calipso.modules[moduleName]; - if(module.type != currentType) { + if (module.type != currentType) { console.log("\r\n" + module.type.green.bold); currentType = module.type; } @@ -151,7 +151,7 @@ function listModules(options,cli,next) { } // Else return it - next(null,calipso.modules); + next(null, calipso.modules); } @@ -162,7 +162,7 @@ function toggleModule(enabled, options, cli, next) { // Get the module name var moduleName = options[1]; - if(!moduleName) { + if (!moduleName) { next(new Error("You must specify a module name.")); } @@ -176,9 +176,9 @@ function toggleModule(enabled, options, cli, next) { var installed = installedModule ? true : false; // Assume that we want to re-install the dependencies via NPM - if(installed) { + if (installed) { - calipso.config.setSave(configKey + ':enabled', enabled, function(err) { + calipso.config.setSave(configKey + ':enabled', enabled, function (err) { console.log("Module " + moduleName.green.bold + " is now " + (enabled ? "enabled".green.bold : "disabled".red.bold) + "."); next(); }); @@ -196,11 +196,11 @@ function toggleModule(enabled, options, cli, next) { * Show the list of currently installed modules - highlight those with issues / updates? * options: ['install',,] */ -function installModule(options,cli,next) { +function installModule(options, cli, next) { // Get the module name var moduleName = options[1]; - if(!moduleName) { + if (!moduleName) { next(new Error("You must specify a module name (and optional @version).")); } @@ -213,20 +213,20 @@ function installModule(options,cli,next) { var installed = installedModule ? true : false; // Assume that we want to re-install the dependencies via NPM - if(installed) { + if (installed) { // If we have a version, check to see if this is an upgrade or downgrade - if(moduleVersion) { - if(semver.lt(moduleVersion,installedModule.about.version)) { + if (moduleVersion) { + if (semver.lt(moduleVersion, installedModule.about.version)) { // TODO } } var path = calipso.app.path + "/modules/" + installedModule.type + "/" + installedModule.name; - installViaNpm(moduleName,path,function(err) { - + installViaNpm(moduleName, path, function (err) { + // Now install via the exposed install function - if(installedModule.fn && installedModule.fn.install) { + if (installedModule.fn && installedModule.fn.install) { installedModule.fn.install(next); } else { next(); @@ -247,19 +247,19 @@ function installModule(options,cli,next) { /** * Install a module via npm */ -function installViaNpm(moduleName,path,next) { +function installViaNpm(moduleName, path, next) { console.log(path); console.log("Installing " + moduleName.green.bold + " via npm, output will show below (may be a small delay):"); - exec('npm install', { timeout: 60000, cwd:path }, function (error, stdout, stderr) { + exec('npm install', { timeout:60000, cwd:path }, function (error, stdout, stderr) { var err = ((error ? error.message : '') || stderr); - if(!err) { - exec('npm list', { timeout: 60000, cwd:path }, function (error, stdout, stderr) { + if (!err) { + exec('npm list', { timeout:60000, cwd:path }, function (error, stdout, stderr) { - console.log(""); - console.log(stdout + "\r\nModule " + moduleName.green.bold + " installed with all dependencies met."); - next(); + console.log(""); + console.log(stdout + "\r\nModule " + moduleName.green.bold + " installed with all dependencies met."); + next(); }); } else { @@ -273,11 +273,11 @@ function installViaNpm(moduleName,path,next) { * Uninstall a module * TODO This is quite brutal (rm -rf). */ -function uninstallModule(options,cli,next) { +function uninstallModule(options, cli, next) { // Get the module name var moduleName = options[1]; - if(!moduleName) { + if (!moduleName) { next(new Error("You must specify a module name (@version is ignored).")); return; } @@ -291,9 +291,9 @@ function uninstallModule(options,cli,next) { var installed = installedModule ? true : false; // Assume that we want to re-install the dependencies via NPM - if(installed) { + if (installed) { - if(installedModule.type === "core") { + if (installedModule.type === "core") { next(new Error("You should not delete core modules unless you really know what you are doing!")); return; } @@ -301,17 +301,17 @@ function uninstallModule(options,cli,next) { // This can't be messed with, as it is populated based on pre-existing path type/name. var path = __dirname + "/../modules/" + installedModule.type + "/" + installedModule.name; - confirm('This will remove the module completely from the site and cannot be undone, continue? '.red.bold, function(ok){ + confirm('This will remove the module completely from the site and cannot be undone, continue? '.red.bold, function (ok) { if (ok) { process.stdin.destroy(); console.log("Removing " + installedModule.name.green.bold + ", please wait ..."); - exec('rm -rf ' + path, { timeout: 5000, cwd:__dirname }, function (error, stdout, stderr) { + exec('rm -rf ' + path, { timeout:5000, cwd:__dirname }, function (error, stdout, stderr) { var err = ((error ? error.message : '') || stderr); - if(!err) { - console.log(stdout + "Module " + installedModule.name.green.bold + " uninstalled completely."); - next(); + if (!err) { + console.log(stdout + "Module " + installedModule.name.green.bold + " uninstalled completely."); + next(); } else { next(new Error(err)); } @@ -334,32 +334,32 @@ function uninstallModule(options,cli,next) { /** * Run through all installed modules (enabled or not), and install dependencies */ -function checkAll(options,cli,next) { +function checkAll(options, cli, next) { calipso.lib.step( function validateInstall() { - this(); + this(); }, function validateInstalledModules() { var group = this.group(); - for(var moduleName in calipso.modules) { + for (var moduleName in calipso.modules) { var options = []; options.push('install'); options.push(moduleName); options.push(true); - installModule(options,cli,group()); + installModule(options, cli, group()); } }, function done(err) { console.log(""); - if(err) { + if (err) { console.log("All modules processed, but there were errors,please check output above for status.".red.bold); } else { console.log("All modules processed with no apparent errors, please check output above to confirm.".green.bold); } next(err); } - ) + ) } @@ -371,7 +371,7 @@ function checkAll(options,cli,next) { */ function confirm(msg, fn) { - prompt(msg, function(val){ + prompt(msg, function (val) { fn(/^ *y(es)?/i.test(val)); }); } @@ -393,7 +393,7 @@ function prompt(msg, fn) { // stdin process.stdin.setEncoding('ascii'); - process.stdin.once('data', function(data){ + process.stdin.once('data',function (data) { fn(data); }).resume(); } diff --git a/lib/cli/RepoApi.js b/lib/cli/RepoApi.js index de4a3c490..8a427fed2 100644 --- a/lib/cli/RepoApi.js +++ b/lib/cli/RepoApi.js @@ -14,13 +14,13 @@ */ var sys; try { - sys = require('util'); + sys = require('util'); } catch (e) { - sys = require('sys'); + sys = require('sys'); } var rootpath = process.cwd() + '/', path = require('path') - calipso = require(path.join(rootpath, 'lib/calipso')), +calipso = require(path.join(rootpath, 'lib/calipso')), colors = require('colors'); @@ -37,7 +37,7 @@ function Api(url) { * type : module, theme, profile * name : name of module */ -Api.prototype.get = function(options, next) { +Api.prototype.get = function (options, next) { // Construct the url based on the options var url = this.url + path.join('get', @@ -60,7 +60,7 @@ Api.prototype.get = function(options, next) { * type : module, theme, profile * name : name of module */ -Api.prototype.find = function(type, options, next) { +Api.prototype.find = function (type, options, next) { // Construct the url based on the options var searchString = options[1]; @@ -81,37 +81,37 @@ function get(url, cb) { // Parse the url to determine request type var parts = require('url').parse(url); - if(parts.protocol === 'https:') { + if (parts.protocol === 'https:') { client = require('https'); } else { client = require('http'); - if(!parts.port) { + if (!parts.port) { parts.port = 80; } } // Make request - client.get({ host: parts.hostname, port: parts.port, path: parts.pathname }, function(res) { + client.get({ host:parts.hostname, port:parts.port, path:parts.pathname },function (res) { var data = ''; res.setEncoding('utf8'); - res.on('data', function(d) { + res.on('data', function (d) { data += d; }); - res.on('end', function() { + res.on('end', function () { try { var json = JSON.parse(data); - cb(null,json); - } catch(ex) { + cb(null, json); + } catch (ex) { cb(new Error("Unable to parse JSON response: " + ex.message)); } }); - }).on('error', function(err) { - cb(err,null); - }); + }).on('error', function (err) { + cb(err, null); + }); } /** diff --git a/lib/cli/Themes.js b/lib/cli/Themes.js index 973a8619a..dfd0bc453 100644 --- a/lib/cli/Themes.js +++ b/lib/cli/Themes.js @@ -18,13 +18,13 @@ */ var sys; try { - sys = require('util'); + sys = require('util'); } catch (e) { - sys = require('sys'); + sys = require('sys'); } var rootpath = process.cwd() + '/', path = require('path') - calipso = require(path.join(rootpath, 'lib/calipso')), +calipso = require(path.join(rootpath, 'lib/calipso')), exec = require('child_process').exec, colors = require('colors'), semver = require('semver'), @@ -34,14 +34,14 @@ var rootpath = process.cwd() + '/', * Theme router - takes params from CLI and passes to appropriate function * Controls return based on CLI mode or web mode */ -exports.themeRouter = function(path,options,cli,next) { +exports.themeRouter = function (path, options, cli, next) { - switch(options[0]) { + switch (options[0]) { case 'list': - listThemes(options,cli,next); + listThemes(options, cli, next); break; case 'uninstall': - uninstallTheme(options,cli,next); + uninstallTheme(options, cli, next); break; case 'find': findTheme(options, cli, next); @@ -61,36 +61,36 @@ exports.themeRouter = function(path,options,cli,next) { /** *Find module - */ + */ function findTheme(options, cli, next) { var search = options[1]; // Second parameter is our query - var searchRegex = new RegExp(search,"ig"); - search = search.replace(/\*/g,""); + var searchRegex = new RegExp(search, "ig"); + search = search.replace(/\*/g, ""); var repo = new api(); - repo.find('theme', options, function(err, data) { - console.log(""); - if(data.length > 0) { - data.forEach(function(module) { - var versionString = ""; - module.versions.forEach(function(version) { - versionString += " - [" + version.version + "] : " + version.url + "\r\n" - }); - var description = module.description.replace(searchRegex,search.yellow.bold); - var author = " - [Author] ".cyan + module.author.cyan.bold + "\r\n"; - console.log(module.name.white.bold + "\r\n" + description + "\r\n" + author + versionString.cyan); + repo.find('theme', options, function (err, data) { + console.log(""); + if (data.length > 0) { + data.forEach(function (module) { + var versionString = ""; + module.versions.forEach(function (version) { + versionString += " - [" + version.version + "] : " + version.url + "\r\n" }); - console.log(""); - console.log("To install a theme, use: ".white + "\r\n"); - console.log(" calipso themes download".cyan.bold + " ThemeName".green.bold + " [for latest version]".grey); - console.log(" calipso themes download".cyan.bold + " repo/project@version".green.bold + " [for specific version]".grey); - console.log(""); - } else { - console.log("No modules found that matched your search.".white + "\r\n"); - } - next(); + var description = module.description.replace(searchRegex, search.yellow.bold); + var author = " - [Author] ".cyan + module.author.cyan.bold + "\r\n"; + console.log(module.name.white.bold + "\r\n" + description + "\r\n" + author + versionString.cyan); + }); + console.log(""); + console.log("To install a theme, use: ".white + "\r\n"); + console.log(" calipso themes download".cyan.bold + " ThemeName".green.bold + " [for latest version]".grey); + console.log(" calipso themes download".cyan.bold + " repo/project@version".green.bold + " [for specific version]".grey); + console.log(""); + } else { + console.log("No modules found that matched your search.".white + "\r\n"); + } + next(); }); } @@ -102,27 +102,27 @@ exports.findTheme = findTheme; function downloadTheme(options, cli, next) { var toPath = calipso.app.path() + "/themes/downloaded/"; var fromUrl = options[1]; - download('theme', fromUrl, toPath, cli, function(err, themeName, path) { - if(err) { - next(err); - } else { - console.log("Theme ".green + themeName + " was installed successfully.".green); - next(); - } + download('theme', fromUrl, toPath, cli, function (err, themeName, path) { + if (err) { + next(err); + } else { + console.log("Theme ".green + themeName + " was installed successfully.".green); + next(); + } }); } /** * Show the list of currently installed modules - highlight those with issues / updates? */ -function listThemes(options,cli,next) { +function listThemes(options, cli, next) { // Command line - if(cli) { + if (cli) { console.log("\r\nInstalled Themes:".green.bold); - for(var themeName in calipso.availableThemes) { + for (var themeName in calipso.availableThemes) { var theme = calipso.availableThemes[themeName]; - console.log(" - " + theme.name.white.bold + " @ " + theme.about.version + " [".grey + theme.about.type.green + "]".grey); + console.log(" - " + theme.name.white.bold + " @ " + theme.about.version + " [".grey + theme.about.type.green + "]".grey); } console.log(""); } @@ -136,15 +136,15 @@ function listThemes(options,cli,next) { * Uninstall a theme * TODO This is quite brutal (rm -rf). */ -function uninstallTheme(options,cli,next) { +function uninstallTheme(options, cli, next) { // Get the module name var themeName = options[1]; - if(!themeName) { + if (!themeName) { next(new Error("You must specify a module name (@version is ignored).")); return; } - if(themeName === calipso.defaultTheme) { + if (themeName === calipso.defaultTheme) { next(new Error("You cannot delete the default theme.")); return; } @@ -154,22 +154,22 @@ function uninstallTheme(options,cli,next) { var installed = installedTheme ? true : false; // Assume that we want to re-install the dependencies via NPM - if(installed) { + if (installed) { // This can't be messed with, as it is populated based on pre-existing path type/name. var path = installedTheme.path; - confirm('This will remove the theme completely from the site and cannot be undone, continue? '.red.bold, function(ok){ + confirm('This will remove the theme completely from the site and cannot be undone, continue? '.red.bold, function (ok) { if (ok) { process.stdin.destroy(); console.log("Removing " + installedTheme.name.green.bold + ", please wait ..."); - exec('rm -rf ' + path, { timeout: 5000, cwd:__dirname }, function (error, stdout, stderr) { + exec('rm -rf ' + path, { timeout:5000, cwd:__dirname }, function (error, stdout, stderr) { var err = ((error ? error.message : '') || stderr); - if(!err) { - console.log(stdout + "Theme " + installedTheme.name.green.bold + " uninstalled completely."); - next(); + if (!err) { + console.log(stdout + "Theme " + installedTheme.name.green.bold + " uninstalled completely."); + next(); } else { next(new Error(err)); } @@ -198,7 +198,7 @@ function uninstallTheme(options,cli,next) { */ function confirm(msg, fn) { - prompt(msg, function(val){ + prompt(msg, function (val) { fn(/^ *y(es)?/i.test(val)); }); } @@ -220,7 +220,7 @@ function prompt(msg, fn) { // stdin process.stdin.setEncoding('ascii'); - process.stdin.once('data', function(data){ + process.stdin.once('data',function (data) { fn(data); }).resume(); } diff --git a/lib/client/Client.js b/lib/client/Client.js index a114a1ff5..5ed2b7204 100644 --- a/lib/client/Client.js +++ b/lib/client/Client.js @@ -17,22 +17,22 @@ var rootpath = process.cwd() + '/', */ var Client = module.exports = function Client(options) { - this.options = options || { - 'minified-script': 'media/calipso-main' - }; + this.options = options || { + 'minified-script':'media/calipso-main' + }; - this.scripts = []; - this.styles = []; + this.scripts = []; + this.styles = []; // Shortcuts to core, must be included somewhere (module or theme) to be rendered this.coreScripts = { - 'jquery': {key:'jquery', url:'jquery-1.8.2.min.js', weight: -100}, - 'calipso': {key:'calipso', url:'calipso.js', weight: -50} + 'jquery':{key:'jquery', url:'jquery-1.8.2.min.js', weight:-100}, + 'calipso':{key:'calipso', url:'calipso.js', weight:-50} } - }; +}; -Client.prototype.addScript = function(options) { +Client.prototype.addScript = function (options) { var self = this; @@ -42,13 +42,15 @@ Client.prototype.addScript = function(options) { options = this.coreScripts[options]; } else { options = { - name: options, - url: options, - weight: 0 + name:options, + url:options, + weight:0 }; } } - if (!options.name) options.name = options.url; + if (!options.name) { + options.name = options.url; + } // Add the script self._add('scripts', options.name, options); @@ -58,12 +60,12 @@ Client.prototype.addScript = function(options) { /** * Create simple list of all client JS */ -Client.prototype.listScripts = function(next) { +Client.prototype.listScripts = function (next) { // TODO - this should be updated to use LABjs by default (?) var self = this; var output = ""; - self.scripts.forEach(function(value) { + self.scripts.forEach(function (value) { output += '\r\n'; }); output += ""; @@ -71,19 +73,21 @@ Client.prototype.listScripts = function(next) { }; -Client.prototype.addStyle = function(options) { +Client.prototype.addStyle = function (options) { var self = this; // Convert our options over with flexible defaults if (typeof options === "string") { options = { - name: options, - url: options, - weight: 0 + name:options, + url:options, + weight:0 }; } - if (!options.name) options.name = options.url; + if (!options.name) { + options.name = options.url; + } // Add the script self._add('styles', options.name, options); @@ -93,13 +97,13 @@ Client.prototype.addStyle = function(options) { /** * Compile together all of the client side scripts */ -Client.prototype.listStyles = function(next) { +Client.prototype.listStyles = function (next) { // TODO - this should be updated to use LABjs by default (?) var self = this; var output = ""; - self.styles.forEach(function(value) { + self.styles.forEach(function (value) { output += '\r\n'; }); output += ""; @@ -111,13 +115,13 @@ Client.prototype.listStyles = function(next) { /** * Helper to add unique elements to an array */ -Client.prototype._add = function(arrName, name, options) { +Client.prototype._add = function (arrName, name, options) { var self = this; self[arrName] = self[arrName] || []; // Find first match - var found = calipso.lib._.find(self[arrName], function(value) { + var found = calipso.lib._.find(self[arrName], function (value) { return (value.name && value.name === name) ? true : false; }); @@ -130,7 +134,7 @@ Client.prototype._add = function(arrName, name, options) { } // Sort - TODO, this can probably be more efficient by placing the new item smarter - self[arrName].sort(function(a, b) { + self[arrName].sort(function (a, b) { return a.weight > b.weight; }); @@ -141,7 +145,7 @@ Client.prototype._add = function(arrName, name, options) { * Compile together all of the client side scripts * TODO - this is currently not used, needs to be worked on and thought through. * -Client.prototype.compile = function(next) { + Client.prototype.compile = function(next) { var self = this; @@ -207,4 +211,4 @@ Client.prototype.compile = function(next) { }) } -**/ + **/ diff --git a/lib/client/js/calipso.js b/lib/client/js/calipso.js index 99fc19da5..6a55e980e 100644 --- a/lib/client/js/calipso.js +++ b/lib/client/js/calipso.js @@ -5,11 +5,12 @@ * Javascript libraries. * */ -var Calipso = function(){}; +var Calipso = function () { +}; /** * Require a library */ -Calipso.prototype.require = function(jsFile) { +Calipso.prototype.require = function (jsFile) { } diff --git a/lib/client/js/jquery-1.7.2.min.js b/lib/client/js/jquery-1.7.2.min.js index 16ad06c5a..6d75307d5 100644 --- a/lib/client/js/jquery-1.7.2.min.js +++ b/lib/client/js/jquery-1.7.2.min.js @@ -1,4 +1,3295 @@ /*! jQuery v1.7.2 jquery.com | jquery.org/license */ -(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+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(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
"+""+"
",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
t
",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( -a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
","
"]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f -.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file +(function (a, b) { + function cy(a) { + return f.isWindow(a) ? a : a.nodeType === 9 ? a.defaultView || a.parentWindow : !1 + } + + function cu(a) { + if (!cj[a]) { + var b = c.body, d = f("<" + a + ">").appendTo(b), e = d.css("display"); + d.remove(); + if (e === "none" || e === "") { + ck || (ck = c.createElement("iframe"), ck.frameBorder = ck.width = ck.height = 0), b.appendChild(ck); + if (!cl || !ck.createElement) { + cl = (ck.contentWindow || ck.contentDocument).document, cl.write((f.support.boxModel ? "" : "") + ""), cl.close(); + } + d = cl.createElement(a), cl.body.appendChild(d), e = f.css(d, "display"), b.removeChild(ck) + } + cj[a] = e + } + return cj[a] + } + + function ct(a, b) { + var c = {}; + f.each(cp.concat.apply([], cp.slice(0, b)), function () { + c[this] = a + }); + return c + } + + function cs() { + cq = b + } + + function cr() { + setTimeout(cs, 0); + return cq = f.now() + } + + function ci() { + try { + return new a.ActiveXObject("Microsoft.XMLHTTP") + } catch (b) { + } + } + + function ch() { + try { + return new a.XMLHttpRequest + } catch (b) { + } + } + + function cb(a, c) { + a.dataFilter && (c = a.dataFilter(c, a.dataType)); + var d = a.dataTypes, e = {}, g, h, i = d.length, j, k = d[0], l, m, n, o, p; + for (g = 1; g < i; g++) { + if (g === 1) { + for (h in a.converters) { + typeof h == "string" && (e[h.toLowerCase()] = a.converters[h]); + } + } + l = k, k = d[g]; + if (k === "*") { + k = l; + } else if (l !== "*" && l !== k) { + m = l + " " + k, n = e[m] || e["* " + k]; + if (!n) { + p = b; + for (o in e) { + j = o.split(" "); + if (j[0] === l || j[0] === "*") { + p = e[j[1] + " " + k]; + if (p) { + o = e[o], o === !0 ? n = p : p === !0 && (n = o); + break + } + } + } + } + !n && !p && f.error("No conversion from " + m.replace(" ", " to ")), n !== !0 && (c = n ? n(c) : p(o(c))) + } + } + return c + } + + function ca(a, c, d) { + var e = a.contents, f = a.dataTypes, g = a.responseFields, h, i, j, k; + for (i in g) { + i in d && (c[g[i]] = d[i]); + } + while (f[0] === "*") { + f.shift(), h === b && (h = a.mimeType || c.getResponseHeader("content-type")); + } + if (h) { + for (i in e) { + if (e[i] && e[i].test(h)) { + f.unshift(i); + break + } + } + } + if (f[0]in d) { + j = f[0]; + } else { + for (i in d) { + if (!f[0] || a.converters[i + " " + f[0]]) { + j = i; + break + } + k || (k = i) + } + j = j || k + } + if (j) { + j !== f[0] && f.unshift(j); + return d[j] + } + } + + function b_(a, b, c, d) { + if (f.isArray(b)) { + f.each(b, function (b, e) { + c || bD.test(a) ? d(a, e) : b_(a + "[" + (typeof e == "object" ? b : "") + "]", e, c, d) + }); + } else if (!c && f.type(b) === "object") { + for (var e in b) { + b_(a + "[" + e + "]", b[e], c, d); + } + } else { + d(a, b) + } + } + + function b$(a, c) { + var d, e, g = f.ajaxSettings.flatOptions || {}; + for (d in c) { + c[d] !== b && ((g[d] ? a : e || (e = {}))[d] = c[d]); + } + e && f.extend(!0, a, e) + } + + function bZ(a, c, d, e, f, g) { + f = f || c.dataTypes[0], g = g || {}, g[f] = !0; + var h = a[f], i = 0, j = h ? h.length : 0, k = a === bS, l; + for (; i < j && (k || !l); i++) { + l = h[i](c, d, e), typeof l == "string" && (!k || g[l] ? l = b : (c.dataTypes.unshift(l), l = bZ(a, c, d, e, l, g))); + } + (k || !l) && !g["*"] && (l = bZ(a, c, d, e, "*", g)); + return l + } + + function bY(a) { + return function (b, c) { + typeof b != "string" && (c = b, b = "*"); + if (f.isFunction(c)) { + var d = b.toLowerCase().split(bO), e = 0, g = d.length, h, i, j; + for (; e < g; e++) { + h = d[e], j = /^\+/.test(h), j && (h = h.substr(1) || "*"), i = a[h] = a[h] || [], i[j ? "unshift" : "push"](c) + } + } + } + } + + function bB(a, b, c) { + var d = b === "width" ? a.offsetWidth : a.offsetHeight, e = b === "width" ? 1 : 0, g = 4; + if (d > 0) { + if (c !== "border") { + for (; e < g; e += 2) { + c || (d -= parseFloat(f.css(a, "padding" + bx[e])) || 0), c === "margin" ? d += parseFloat(f.css(a, c + bx[e])) || 0 : d -= parseFloat(f.css(a, "border" + bx[e] + "Width")) || 0; + } + } + return d + "px" + } + d = by(a, b); + if (d < 0 || d == null) { + d = a.style[b]; + } + if (bt.test(d)) { + return d; + } + d = parseFloat(d) || 0; + if (c) { + for (; e < g; e += 2) { + d += parseFloat(f.css(a, "padding" + bx[e])) || 0, c !== "padding" && (d += parseFloat(f.css(a, "border" + bx[e] + "Width")) || 0), c === "margin" && (d += parseFloat(f.css(a, c + bx[e])) || 0); + } + } + return d + "px" + } + + function bo(a) { + var b = c.createElement("div"); + bh.appendChild(b), b.innerHTML = a.outerHTML; + return b.firstChild + } + + function bn(a) { + var b = (a.nodeName || "").toLowerCase(); + b === "input" ? bm(a) : b !== "script" && typeof a.getElementsByTagName != "undefined" && f.grep(a.getElementsByTagName("input"), bm) + } + + function bm(a) { + if (a.type === "checkbox" || a.type === "radio") { + a.defaultChecked = a.checked + } + } + + function bl(a) { + return typeof a.getElementsByTagName != "undefined" ? a.getElementsByTagName("*") : typeof a.querySelectorAll != "undefined" ? a.querySelectorAll("*") : [] + } + + function bk(a, b) { + var c; + b.nodeType === 1 && (b.clearAttributes && b.clearAttributes(), b.mergeAttributes && b.mergeAttributes(a), c = b.nodeName.toLowerCase(), c === "object" ? b.outerHTML = a.outerHTML : c !== "input" || a.type !== "checkbox" && a.type !== "radio" ? c === "option" ? b.selected = a.defaultSelected : c === "input" || c === "textarea" ? b.defaultValue = a.defaultValue : c === "script" && b.text !== a.text && (b.text = a.text) : (a.checked && (b.defaultChecked = b.checked = a.checked), b.value !== a.value && (b.value = a.value)), b.removeAttribute(f.expando), b.removeAttribute("_submit_attached"), b.removeAttribute("_change_attached")) + } + + function bj(a, b) { + if (b.nodeType === 1 && !!f.hasData(a)) { + var c, d, e, g = f._data(a), h = f._data(b, g), i = g.events; + if (i) { + delete h.handle, h.events = {}; + for (c in i) { + for (d = 0, e = i[c].length; d < e; d++) { + f.event.add(b, c, i[c][d]) + } + } + } + h.data && (h.data = f.extend({}, h.data)) + } + } + + function bi(a, b) { + return f.nodeName(a, "table") ? a.getElementsByTagName("tbody")[0] || a.appendChild(a.ownerDocument.createElement("tbody")) : a + } + + function U(a) { + var b = V.split("|"), c = a.createDocumentFragment(); + if (c.createElement) { + while (b.length) { + c.createElement(b.pop()); + } + } + return c + } + + function T(a, b, c) { + b = b || 0; + if (f.isFunction(b)) { + return f.grep(a, function (a, d) { + var e = !!b.call(a, d, a); + return e === c + }); + } + if (b.nodeType) { + return f.grep(a, function (a, d) { + return a === b === c + }); + } + if (typeof b == "string") { + var d = f.grep(a, function (a) { + return a.nodeType === 1 + }); + if (O.test(b)) { + return f.filter(b, d, !c); + } + b = f.filter(b, d) + } + return f.grep(a, function (a, d) { + return f.inArray(a, b) >= 0 === c + }) + } + + function S(a) { + return!a || !a.parentNode || a.parentNode.nodeType === 11 + } + + function K() { + return!0 + } + + function J() { + return!1 + } + + function n(a, b, c) { + var d = b + "defer", e = b + "queue", g = b + "mark", h = f._data(a, d); + h && (c === "queue" || !f._data(a, e)) && (c === "mark" || !f._data(a, g)) && setTimeout(function () { + !f._data(a, e) && !f._data(a, g) && (f.removeData(a, d, !0), h.fire()) + }, 0) + } + + function m(a) { + for (var b in a) { + if (b === "data" && f.isEmptyObject(a[b])) { + continue; + } + if (b !== "toJSON") { + return!1 + } + } + return!0 + } + + function l(a, c, d) { + if (d === b && a.nodeType === 1) { + var e = "data-" + c.replace(k, "-$1").toLowerCase(); + d = a.getAttribute(e); + if (typeof d == "string") { + try { + d = d === "true" ? !0 : d === "false" ? !1 : d === "null" ? null : f.isNumeric(d) ? +d : j.test(d) ? f.parseJSON(d) : d + } catch (g) { + } + f.data(a, c, d) + } else { + d = b + } + } + return d + } + + function h(a) { + var b = g[a] = {}, c, d; + a = a.split(/\s+/); + for (c = 0, d = a.length; c < d; c++) { + b[a[c]] = !0; + } + return b + } + + var c = a.document, d = a.navigator, e = a.location, f = function () { + function J() { + if (!e.isReady) { + try { + c.documentElement.doScroll("left") + } catch (a) { + setTimeout(J, 1); + return + } + e.ready() + } + } + + var e = function (a, b) { + return new e.fn.init(a, b, h) + }, f = a.jQuery, g = a.$, h, i = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, j = /\S/, k = /^\s+/, l = /\s+$/, m = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, n = /^[\],:{}\s]*$/, o = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, p = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, q = /(?:^|:|,)(?:\s*\[)+/g, r = /(webkit)[ \/]([\w.]+)/, s = /(opera)(?:.*version)?[ \/]([\w.]+)/, t = /(msie) ([\w.]+)/, u = /(mozilla)(?:.*? rv:([\w.]+))?/, v = /-([a-z]|[0-9])/ig, w = /^-ms-/, x = function (a, b) { + return(b + "").toUpperCase() + }, y = d.userAgent, z, A, B, C = Object.prototype.toString, D = Object.prototype.hasOwnProperty, E = Array.prototype.push, F = Array.prototype.slice, G = String.prototype.trim, H = Array.prototype.indexOf, I = {}; + e.fn = e.prototype = {constructor:e, init:function (a, d, f) { + var g, h, j, k; + if (!a) { + return this; + } + if (a.nodeType) { + this.context = this[0] = a, this.length = 1; + return this + } + if (a === "body" && !d && c.body) { + this.context = c, this[0] = c.body, this.selector = a, this.length = 1; + return this + } + if (typeof a == "string") { + a.charAt(0) !== "<" || a.charAt(a.length - 1) !== ">" || a.length < 3 ? g = i.exec(a) : g = [null, a, null]; + if (g && (g[1] || !d)) { + if (g[1]) { + d = d instanceof e ? d[0] : d, k = d ? d.ownerDocument || d : c, j = m.exec(a), j ? e.isPlainObject(d) ? (a = [c.createElement(j[1])], e.fn.attr.call(a, d, !0)) : a = [k.createElement(j[1])] : (j = e.buildFragment([g[1]], [k]), a = (j.cacheable ? e.clone(j.fragment) : j.fragment).childNodes); + return e.merge(this, a) + } + h = c.getElementById(g[2]); + if (h && h.parentNode) { + if (h.id !== g[2]) { + return f.find(a); + } + this.length = 1, this[0] = h + } + this.context = c, this.selector = a; + return this + } + return!d || d.jquery ? (d || f).find(a) : this.constructor(d).find(a) + } + if (e.isFunction(a)) { + return f.ready(a); + } + a.selector !== b && (this.selector = a.selector, this.context = a.context); + return e.makeArray(a, this) + }, selector:"", jquery:"1.7.2", length:0, size:function () { + return this.length + }, toArray:function () { + return F.call(this, 0) + }, get:function (a) { + return a == null ? this.toArray() : a < 0 ? this[this.length + a] : this[a] + }, pushStack:function (a, b, c) { + var d = this.constructor(); + e.isArray(a) ? E.apply(d, a) : e.merge(d, a), d.prevObject = this, d.context = this.context, b === "find" ? d.selector = this.selector + (this.selector ? " " : "") + c : b && (d.selector = this.selector + "." + b + "(" + c + ")"); + return d + }, each:function (a, b) { + return e.each(this, a, b) + }, ready:function (a) { + e.bindReady(), A.add(a); + return this + }, eq:function (a) { + a = +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(F.apply(this, arguments), "slice", F.call(arguments).join(",")) + }, map:function (a) { + return this.pushStack(e.map(this, function (b, c) { + return a.call(b, c, b) + })) + }, end:function () { + return this.prevObject || this.constructor(null) + }, push:E, sort:[].sort, splice:[].splice}, e.fn.init.prototype = e.fn, e.extend = e.fn.extend = function () { + var a, c, d, f, g, h, i = arguments[0] || {}, j = 1, k = arguments.length, l = !1; + typeof i == "boolean" && (l = i, i = arguments[1] || {}, j = 2), typeof i != "object" && !e.isFunction(i) && (i = {}), k === j && (i = this, --j); + for (; j < k; j++) { + if ((a = arguments[j]) != null) { + for (c in a) { + d = i[c], f = a[c]; + if (i === f) { + continue; + } + l && f && (e.isPlainObject(f) || (g = e.isArray(f))) ? (g ? (g = !1, h = d && e.isArray(d) ? d : []) : h = d && e.isPlainObject(d) ? d : {}, i[c] = e.extend(l, h, f)) : f !== b && (i[c] = f) + } + } + } + return i + }, e.extend({noConflict:function (b) { + a.$ === e && (a.$ = g), b && a.jQuery === e && (a.jQuery = f); + return e + }, isReady:!1, readyWait:1, holdReady:function (a) { + a ? e.readyWait++ : e.ready(!0) + }, ready:function (a) { + if (a === !0 && !--e.readyWait || a !== !0 && !e.isReady) { + if (!c.body) { + return setTimeout(e.ready, 1); + } + e.isReady = !0; + if (a !== !0 && --e.readyWait > 0) { + return; + } + A.fireWith(c, [e]), e.fn.trigger && e(c).trigger("ready").off("ready") + } + }, bindReady:function () { + if (!A) { + A = e.Callbacks("once memory"); + if (c.readyState === "complete") { + return setTimeout(e.ready, 1); + } + if (c.addEventListener) { + c.addEventListener("DOMContentLoaded", B, !1), a.addEventListener("load", e.ready, !1); + } else if (c.attachEvent) { + c.attachEvent("onreadystatechange", B), a.attachEvent("onload", e.ready); + var b = !1; + try { + b = a.frameElement == null + } catch (d) { + } + c.documentElement.doScroll && b && J() + } + } + }, isFunction:function (a) { + return e.type(a) === "function" + }, isArray:Array.isArray || function (a) { + return e.type(a) === "array" + }, isWindow:function (a) { + return a != null && a == a.window + }, isNumeric:function (a) { + return!isNaN(parseFloat(a)) && isFinite(a) + }, type:function (a) { + return a == null ? String(a) : I[C.call(a)] || "object" + }, isPlainObject:function (a) { + if (!a || e.type(a) !== "object" || a.nodeType || e.isWindow(a)) { + return!1; + } + try { + if (a.constructor && !D.call(a, "constructor") && !D.call(a.constructor.prototype, "isPrototypeOf")) { + return!1 + } + } catch (c) { + return!1 + } + var d; + for (d in a) { + ; + } + return d === b || D.call(a, d) + }, isEmptyObject:function (a) { + for (var b in a) { + return!1; + } + return!0 + }, error:function (a) { + throw new Error(a) + }, parseJSON:function (b) { + if (typeof b != "string" || !b) { + return null; + } + b = e.trim(b); + if (a.JSON && a.JSON.parse) { + return a.JSON.parse(b); + } + if (n.test(b.replace(o, "@").replace(p, "]").replace(q, ""))) { + return(new Function("return " + b))(); + } + e.error("Invalid JSON: " + b) + }, parseXML:function (c) { + if (typeof c != "string" || !c) { + return null; + } + var d, f; + try { + a.DOMParser ? (f = new DOMParser, d = f.parseFromString(c, "text/xml")) : (d = new ActiveXObject("Microsoft.XMLDOM"), d.async = "false", d.loadXML(c)) + } catch (g) { + d = b + } + (!d || !d.documentElement || d.getElementsByTagName("parsererror").length) && e.error("Invalid XML: " + c); + return d + }, noop:function () { + }, globalEval:function (b) { + b && j.test(b) && (a.execScript || function (b) { + a.eval.call(a, b) + })(b) + }, camelCase:function (a) { + return a.replace(w, "ms-").replace(v, x) + }, nodeName:function (a, b) { + return a.nodeName && a.nodeName.toUpperCase() === b.toUpperCase() + }, each:function (a, c, d) { + var f, g = 0, h = a.length, i = h === b || e.isFunction(a); + if (d) { + if (i) { + for (f in a) { + if (c.apply(a[f], d) === !1) { + break + } + } + } else { + for (; g < h;) { + if (c.apply(a[g++], d) === !1) { + break + } + } + } + } else if (i) { + for (f in a) { + if (c.call(a[f], f, a[f]) === !1) { + break + } + } + } else { + for (; g < h;) { + if (c.call(a[g], g, a[g++]) === !1) { + break; + } + } + } + return a + }, trim:G ? function (a) { + return a == null ? "" : G.call(a) + } : function (a) { + return a == null ? "" : (a + "").replace(k, "").replace(l, "") + }, makeArray:function (a, b) { + var c = b || []; + if (a != null) { + var d = e.type(a); + a.length == null || d === "string" || d === "function" || d === "regexp" || e.isWindow(a) ? E.call(c, a) : e.merge(c, a) + } + return c + }, inArray:function (a, b, c) { + var d; + if (b) { + if (H) { + return H.call(b, a, c); + } + d = b.length, c = c ? c < 0 ? Math.max(0, d + c) : c : 0; + for (; c < d; c++) { + if (c in b && b[c] === a) { + return c + } + } + } + return-1 + }, merge:function (a, c) { + var d = a.length, e = 0; + if (typeof c.length == "number") { + for (var f = c.length; e < f; e++) { + a[d++] = c[e]; + } + } else { + while (c[e] !== b) { + a[d++] = c[e++]; + } + } + a.length = d; + return a + }, grep:function (a, b, c) { + var d = [], e; + c = !!c; + for (var f = 0, g = a.length; f < g; f++) { + e = !!b(a[f], f), c !== e && d.push(a[f]); + } + return d + }, map:function (a, c, d) { + var f, g, h = [], i = 0, j = a.length, k = a instanceof e || j !== b && typeof j == "number" && (j > 0 && a[0] && a[j - 1] || j === 0 || e.isArray(a)); + if (k) { + for (; i < j; i++) { + f = c(a[i], i, d), f != null && (h[h.length] = f); + } + } else { + for (g in a) { + f = c(a[g], g, d), f != null && (h[h.length] = f); + } + } + return h.concat.apply([], h) + }, guid:1, proxy:function (a, c) { + if (typeof c == "string") { + var d = a[c]; + c = a, a = d + } + if (!e.isFunction(a)) { + return b; + } + var f = F.call(arguments, 2), g = function () { + return a.apply(c, f.concat(F.call(arguments))) + }; + g.guid = a.guid = a.guid || g.guid || e.guid++; + return g + }, access:function (a, c, d, f, g, h, i) { + var j, k = d == null, l = 0, m = a.length; + if (d && typeof d == "object") { + for (l in d) { + e.access(a, c, l, d[l], 1, h, f); + } + g = 1 + } else if (f !== b) { + j = i === b && e.isFunction(f), k && (j ? (j = c, c = function (a, b, c) { + return j.call(e(a), c) + }) : (c.call(a, f), c = null)); + if (c) { + for (; l < m; l++) { + c(a[l], d, j ? f.call(a[l], l, c(a[l], d)) : f, i); + } + } + g = 1 + } + return g ? a : k ? c.call(a) : m ? c(a[0], d) : h + }, now:function () { + return(new Date).getTime() + }, uaMatch:function (a) { + a = a.toLowerCase(); + var b = r.exec(a) || s.exec(a) || t.exec(a) || a.indexOf("compatible") < 0 && u.exec(a) || []; + return{browser:b[1] || "", version:b[2] || "0"} + }, sub:function () { + function a(b, c) { + return new a.fn.init(b, c) + } + + e.extend(!0, a, this), a.superclass = this, a.fn = a.prototype = this(), a.fn.constructor = a, a.sub = this.sub, a.fn.init = function (d, f) { + f && f instanceof e && !(f instanceof a) && (f = a(f)); + return e.fn.init.call(this, d, f, b) + }, a.fn.init.prototype = a.fn; + var b = a(c); + return a + }, browser:{}}), e.each("Boolean Number String Function Array Date RegExp Object".split(" "), function (a, b) { + I["[object " + b + "]"] = b.toLowerCase() + }), z = e.uaMatch(y), z.browser && (e.browser[z.browser] = !0, e.browser.version = z.version), e.browser.webkit && (e.browser.safari = !0), j.test(" ") && (k = /^[\s\xA0]+/, l = /[\s\xA0]+$/), h = e(c), c.addEventListener ? B = function () { + c.removeEventListener("DOMContentLoaded", B, !1), e.ready() + } : c.attachEvent && (B = function () { + c.readyState === "complete" && (c.detachEvent("onreadystatechange", B), e.ready()) + }); + return e + }(), g = {}; + f.Callbacks = function (a) { + a = a ? g[a] || h(a) : {}; + var c = [], d = [], e, i, j, k, l, m, n = function (b) { + var d, e, g, h, i; + for (d = 0, e = b.length; d < e; d++) { + g = b[d], h = f.type(g), h === "array" ? n(g) : h === "function" && (!a.unique || !p.has(g)) && c.push(g) + } + }, o = function (b, f) { + f = f || [], e = !a.memory || [b, f], i = !0, j = !0, m = k || 0, k = 0, l = c.length; + for (; c && m < l; m++) { + if (c[m].apply(b, f) === !1 && a.stopOnFalse) { + e = !0; + break + } + } + j = !1, c && (a.once ? e === !0 ? p.disable() : c = [] : d && d.length && (e = d.shift(), p.fireWith(e[0], e[1]))) + }, p = {add:function () { + if (c) { + var a = c.length; + n(arguments), j ? l = c.length : e && e !== !0 && (k = a, o(e[0], e[1])) + } + return this + }, remove:function () { + if (c) { + var b = arguments, d = 0, e = b.length; + for (; d < e; d++) { + for (var f = 0; f < c.length; f++) { + if (b[d] === c[f]) { + j && f <= l && (l--, f <= m && m--), c.splice(f--, 1); + if (a.unique) { + break + } + } + } + } + } + return this + }, has:function (a) { + if (c) { + var b = 0, d = c.length; + for (; b < d; b++) { + if (a === c[b]) { + return!0 + } + } + } + return!1 + }, empty:function () { + c = []; + return this + }, disable:function () { + c = d = e = b; + return this + }, disabled:function () { + return!c + }, lock:function () { + d = b, (!e || e === !0) && p.disable(); + return this + }, locked:function () { + return!d + }, fireWith:function (b, c) { + d && (j ? a.once || d.push([b, c]) : (!a.once || !e) && o(b, c)); + return this + }, fire:function () { + p.fireWith(this, arguments); + return this + }, fired:function () { + return!!i + }}; + return p + }; + var i = [].slice; + f.extend({Deferred:function (a) { + var b = f.Callbacks("once memory"), c = f.Callbacks("once memory"), d = f.Callbacks("memory"), e = "pending", g = {resolve:b, reject:c, notify:d}, h = {done:b.add, fail:c.add, progress:d.add, state:function () { + return e + }, isResolved:b.fired, isRejected:c.fired, then:function (a, b, c) { + i.done(a).fail(b).progress(c); + return this + }, always:function () { + i.done.apply(i, arguments).fail.apply(i, arguments); + return this + }, pipe:function (a, b, c) { + return f.Deferred(function (d) { + f.each({done:[a, "resolve"], fail:[b, "reject"], progress:[c, "notify"]}, function (a, b) { + var c = b[0], e = b[1], g; + f.isFunction(c) ? i[a](function () { + g = c.apply(this, arguments), g && f.isFunction(g.promise) ? g.promise().then(d.resolve, d.reject, d.notify) : d[e + "With"](this === i ? d : this, [g]) + }) : i[a](d[e]) + }) + }).promise() + }, promise:function (a) { + if (a == null) { + a = h; + } else { + for (var b in h) { + a[b] = h[b]; + } + } + return a + }}, i = h.promise({}), j; + for (j in g) { + i[j] = g[j].fire, i[j + "With"] = g[j].fireWith; + } + i.done(function () { + e = "resolved" + }, c.disable, d.lock).fail(function () { + e = "rejected" + }, b.disable, d.lock), a && a.call(i, i); + return i + }, when:function (a) { + function m(a) { + return function (b) { + e[a] = arguments.length > 1 ? i.call(arguments, 0) : b, j.notifyWith(k, e) + } + } + + function l(a) { + return function (c) { + b[a] = arguments.length > 1 ? i.call(arguments, 0) : c, --g || j.resolveWith(j, b) + } + } + + var b = i.call(arguments, 0), c = 0, d = b.length, e = Array(d), g = d, h = d, j = d <= 1 && a && f.isFunction(a.promise) ? a : f.Deferred(), k = j.promise(); + if (d > 1) { + for (; c < d; c++) { + b[c] && b[c].promise && f.isFunction(b[c].promise) ? b[c].promise().then(l(c), j.reject, m(c)) : --g; + } + g || j.resolveWith(j, b) + } else { + j !== a && j.resolveWith(j, d ? [a] : []); + } + return k + }}), f.support = function () { + var b, d, e, g, h, i, j, k, l, m, n, o, p = c.createElement("div"), q = c.documentElement; + p.setAttribute("className", "t"), p.innerHTML = "
a", d = p.getElementsByTagName("*"), e = p.getElementsByTagName("a")[0]; + if (!d || !d.length || !e) { + return{}; + } + g = c.createElement("select"), h = g.appendChild(c.createElement("option")), i = p.getElementsByTagName("input")[0], b = {leadingWhitespace:p.firstChild.nodeType === 3, tbody:!p.getElementsByTagName("tbody").length, htmlSerialize:!!p.getElementsByTagName("link").length, style:/top/.test(e.getAttribute("style")), hrefNormalized:e.getAttribute("href") === "/a", opacity:/^0.55/.test(e.style.opacity), cssFloat:!!e.style.cssFloat, checkOn:i.value === "on", optSelected:h.selected, getSetAttribute:p.className !== "t", enctype:!!c.createElement("form").enctype, html5Clone:c.createElement("nav").cloneNode(!0).outerHTML !== "<:nav>", submitBubbles:!0, changeBubbles:!0, focusinBubbles:!1, deleteExpando:!0, noCloneEvent:!0, inlineBlockNeedsLayout:!1, shrinkWrapBlocks:!1, reliableMarginRight:!0, pixelMargin:!0}, f.boxModel = b.boxModel = c.compatMode === "CSS1Compat", i.checked = !0, b.noCloneChecked = i.cloneNode(!0).checked, g.disabled = !0, b.optDisabled = !h.disabled; + try { + delete p.test + } catch (r) { + b.deleteExpando = !1 + } + !p.addEventListener && p.attachEvent && p.fireEvent && (p.attachEvent("onclick", function () { + b.noCloneEvent = !1 + }), p.cloneNode(!0).fireEvent("onclick")), i = c.createElement("input"), i.value = "t", i.setAttribute("type", "radio"), b.radioValue = i.value === "t", i.setAttribute("checked", "checked"), i.setAttribute("name", "t"), p.appendChild(i), j = c.createDocumentFragment(), j.appendChild(p.lastChild), b.checkClone = j.cloneNode(!0).cloneNode(!0).lastChild.checked, b.appendChecked = i.checked, j.removeChild(i), j.appendChild(p); + if (p.attachEvent) { + for (n in{submit:1, change:1, focusin:1}) { + m = "on" + n, o = m in p, o || (p.setAttribute(m, "return;"), o = typeof p[m] == "function"), b[n + "Bubbles"] = o; + } + } + j.removeChild(p), j = g = h = p = i = null, f(function () { + var d, e, g, h, i, j, l, m, n, q, r, s, t, u = c.getElementsByTagName("body")[0]; + !u || (m = 1, t = "padding:0;margin:0;border:", r = "position:absolute;top:0;left:0;width:1px;height:1px;", s = t + "0;visibility:hidden;", n = "style='" + r + t + "5px solid #000;", q = "
" + "" + "
", d = c.createElement("div"), d.style.cssText = s + "width:0;height:0;position:static;top:0;margin-top:" + m + "px", u.insertBefore(d, u.firstChild), p = c.createElement("div"), d.appendChild(p), p.innerHTML = "
t
", k = p.getElementsByTagName("td"), o = k[0].offsetHeight === 0, k[0].style.display = "", k[1].style.display = "none", b.reliableHiddenOffsets = o && k[0].offsetHeight === 0, a.getComputedStyle && (p.innerHTML = "", l = c.createElement("div"), l.style.width = "0", l.style.marginRight = "0", p.style.width = "2px", p.appendChild(l), b.reliableMarginRight = (parseInt((a.getComputedStyle(l, null) || {marginRight:0}).marginRight, 10) || 0) === 0), typeof p.style.zoom != "undefined" && (p.innerHTML = "", p.style.width = p.style.padding = "1px", p.style.border = 0, p.style.overflow = "hidden", p.style.display = "inline", p.style.zoom = 1, b.inlineBlockNeedsLayout = p.offsetWidth === 3, p.style.display = "block", p.style.overflow = "visible", p.innerHTML = "
", b.shrinkWrapBlocks = p.offsetWidth !== 3), p.style.cssText = r + s, p.innerHTML = q, e = p.firstChild, g = e.firstChild, i = e.nextSibling.firstChild.firstChild, j = {doesNotAddBorder:g.offsetTop !== 5, doesAddBorderForTableAndCells:i.offsetTop === 5}, g.style.position = "fixed", g.style.top = "20px", j.fixedPosition = g.offsetTop === 20 || g.offsetTop === 15, g.style.position = g.style.top = "", e.style.overflow = "hidden", e.style.position = "relative", j.subtractsBorderForOverflowNotVisible = g.offsetTop === -5, j.doesNotIncludeMarginInBodyOffset = u.offsetTop !== m, a.getComputedStyle && (p.style.marginTop = "1%", b.pixelMargin = (a.getComputedStyle(p, null) || {marginTop:0}).marginTop !== "1%"), typeof d.style.zoom != "undefined" && (d.style.zoom = 1), u.removeChild(d), l = p = d = null, f.extend(b, j)) + }); + return b + }(); + var j = /^(?:\{.*\}|\[.*\])$/, k = /([A-Z])/g; + f.extend({cache:{}, uuid:0, expando:"jQuery" + (f.fn.jquery + Math.random()).replace(/\D/g, ""), noData:{embed:!0, object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", applet:!0}, hasData:function (a) { + a = a.nodeType ? f.cache[a[f.expando]] : a[f.expando]; + return!!a && !m(a) + }, data:function (a, c, d, e) { + if (!!f.acceptData(a)) { + var g, h, i, j = f.expando, k = typeof c == "string", l = a.nodeType, m = l ? f.cache : a, n = l ? a[j] : a[j] && j, o = c === "events"; + if ((!n || !m[n] || !o && !e && !m[n].data) && k && d === b) { + return; + } + n || (l ? a[j] = n = ++f.uuid : n = j), m[n] || (m[n] = {}, l || (m[n].toJSON = f.noop)); + if (typeof c == "object" || typeof c == "function") { + e ? m[n] = f.extend(m[n], c) : m[n].data = f.extend(m[n].data, c); + } + g = h = m[n], e || (h.data || (h.data = {}), h = h.data), d !== b && (h[f.camelCase(c)] = d); + if (o && !h[c]) { + return g.events; + } + k ? (i = h[c], i == null && (i = h[f.camelCase(c)])) : i = h; + return i + } + }, removeData:function (a, b, c) { + if (!!f.acceptData(a)) { + var d, e, g, h = f.expando, i = a.nodeType, j = i ? f.cache : a, k = i ? a[h] : h; + if (!j[k]) { + return; + } + if (b) { + d = c ? j[k] : j[k].data; + if (d) { + f.isArray(b) || (b in d ? b = [b] : (b = f.camelCase(b), b in d ? b = [b] : b = b.split(" "))); + for (e = 0, g = b.length; e < g; e++) { + delete d[b[e]]; + } + if (!(c ? m : f.isEmptyObject)(d)) { + return + } + } + } + if (!c) { + delete j[k].data; + if (!m(j[k])) { + return + } + } + f.support.deleteExpando || !j.setInterval ? delete j[k] : j[k] = null, i && (f.support.deleteExpando ? delete a[h] : a.removeAttribute ? a.removeAttribute(h) : a[h] = null) + } + }, _data:function (a, b, c) { + return f.data(a, b, c, !0) + }, acceptData:function (a) { + if (a.nodeName) { + var b = f.noData[a.nodeName.toLowerCase()]; + if (b) { + return b !== !0 && a.getAttribute("classid") === b + } + } + return!0 + }}), f.fn.extend({data:function (a, c) { + var d, e, g, h, i, j = this[0], k = 0, m = null; + if (a === b) { + if (this.length) { + m = f.data(j); + if (j.nodeType === 1 && !f._data(j, "parsedAttrs")) { + g = j.attributes; + for (i = g.length; k < i; k++) { + h = g[k].name, h.indexOf("data-") === 0 && (h = f.camelCase(h.substring(5)), l(j, h, m[h])); + } + f._data(j, "parsedAttrs", !0) + } + } + return m + } + if (typeof a == "object") { + return this.each(function () { + f.data(this, a) + }); + } + d = a.split(".", 2), d[1] = d[1] ? "." + d[1] : "", e = d[1] + "!"; + return f.access(this, function (c) { + if (c === b) { + m = this.triggerHandler("getData" + e, [d[0]]), m === b && j && (m = f.data(j, a), m = l(j, a, m)); + return m === b && d[1] ? this.data(d[0]) : m + } + d[1] = c, this.each(function () { + var b = f(this); + b.triggerHandler("setData" + e, d), f.data(this, a, c), b.triggerHandler("changeData" + e, d) + }) + }, null, c, arguments.length > 1, null, !1) + }, removeData:function (a) { + return this.each(function () { + f.removeData(this, a) + }) + }}), f.extend({_mark:function (a, b) { + a && (b = (b || "fx") + "mark", f._data(a, b, (f._data(a, b) || 0) + 1)) + }, _unmark:function (a, b, c) { + a !== !0 && (c = b, b = a, a = !1); + if (b) { + c = c || "fx"; + var d = c + "mark", e = a ? 0 : (f._data(b, d) || 1) - 1; + e ? f._data(b, d, e) : (f.removeData(b, d, !0), n(b, c, "mark")) + } + }, queue:function (a, b, c) { + var d; + if (a) { + b = (b || "fx") + "queue", d = f._data(a, b), c && (!d || f.isArray(c) ? d = f._data(a, b, f.makeArray(c)) : d.push(c)); + return d || [] + } + }, dequeue:function (a, b) { + b = b || "fx"; + var c = f.queue(a, b), d = c.shift(), e = {}; + d === "inprogress" && (d = c.shift()), d && (b === "fx" && c.unshift("inprogress"), f._data(a, b + ".run", e), d.call(a, function () { + f.dequeue(a, b) + }, e)), c.length || (f.removeData(a, b + "queue " + b + ".run", !0), n(a, b, "queue")) + }}), f.fn.extend({queue:function (a, c) { + var d = 2; + typeof a != "string" && (c = a, a = "fx", d--); + if (arguments.length < d) { + return f.queue(this[0], a); + } + return c === b ? this : this.each(function () { + var b = f.queue(this, a, c); + a === "fx" && b[0] !== "inprogress" && f.dequeue(this, a) + }) + }, dequeue:function (a) { + return this.each(function () { + f.dequeue(this, a) + }) + }, delay:function (a, b) { + a = f.fx ? f.fx.speeds[a] || a : a, b = b || "fx"; + return this.queue(b, function (b, c) { + var d = setTimeout(b, a); + c.stop = function () { + clearTimeout(d) + } + }) + }, clearQueue:function (a) { + return this.queue(a || "fx", []) + }, promise:function (a, c) { + function m() { + --h || d.resolveWith(e, [e]) + } + + typeof a != "string" && (c = a, a = b), a = a || "fx"; + var d = f.Deferred(), e = this, g = e.length, h = 1, i = a + "defer", j = a + "queue", k = a + "mark", l; + while (g--) { + if (l = f.data(e[g], i, b, !0) || (f.data(e[g], j, b, !0) || f.data(e[g], k, b, !0)) && f.data(e[g], i, f.Callbacks("once memory"), !0)) { + h++, l.add(m); + } + } + m(); + return d.promise(c) + }}); + var o = /[\n\t\r]/g, p = /\s+/, q = /\r/g, r = /^(?:button|input)$/i, s = /^(?:button|input|object|select|textarea)$/i, t = /^a(?:rea)?$/i, u = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, v = f.support.getSetAttribute, w, x, y; + f.fn.extend({attr:function (a, b) { + return f.access(this, f.attr, a, b, arguments.length > 1) + }, removeAttr:function (a) { + return this.each(function () { + f.removeAttr(this, a) + }) + }, prop:function (a, b) { + return f.access(this, f.prop, a, b, arguments.length > 1) + }, removeProp:function (a) { + a = f.propFix[a] || a; + return this.each(function () { + try { + this[a] = b, delete this[a] + } catch (c) { + } + }) + }, addClass:function (a) { + var b, c, d, e, g, h, i; + if (f.isFunction(a)) { + return this.each(function (b) { + f(this).addClass(a.call(this, b, this.className)) + }); + } + if (a && typeof a == "string") { + b = a.split(p); + for (c = 0, d = this.length; c < d; c++) { + e = this[c]; + if (e.nodeType === 1) { + if (!e.className && b.length === 1) { + e.className = a; + } else { + g = " " + e.className + " "; + for (h = 0, i = b.length; h < i; h++) { + ~g.indexOf(" " + b[h] + " ") || (g += b[h] + " "); + } + e.className = f.trim(g) + } + } + } + } + return this + }, removeClass:function (a) { + var c, d, e, g, h, i, j; + if (f.isFunction(a)) { + return this.each(function (b) { + f(this).removeClass(a.call(this, b, this.className)) + }); + } + if (a && typeof a == "string" || a === b) { + c = (a || "").split(p); + for (d = 0, e = this.length; d < e; d++) { + g = this[d]; + if (g.nodeType === 1 && g.className) { + if (a) { + h = (" " + g.className + " ").replace(o, " "); + for (i = 0, j = c.length; i < j; i++) { + h = h.replace(" " + c[i] + " ", " "); + } + g.className = f.trim(h) + } else { + g.className = "" + } + } + } + } + return this + }, toggleClass:function (a, b) { + var c = typeof a, d = typeof b == "boolean"; + if (f.isFunction(a)) { + return this.each(function (c) { + f(this).toggleClass(a.call(this, c, this.className, b), b) + }); + } + return this.each(function () { + if (c === "string") { + var e, g = 0, h = f(this), i = b, j = a.split(p); + while (e = j[g++]) { + i = d ? i : !h.hasClass(e), h[i ? "addClass" : "removeClass"](e) + } + } else if (c === "undefined" || c === "boolean") { + this.className && f._data(this, "__className__", this.className), this.className = this.className || a === !1 ? "" : f._data(this, "__className__") || "" + } + }) + }, hasClass:function (a) { + var b = " " + a + " ", c = 0, d = this.length; + for (; c < d; c++) { + if (this[c].nodeType === 1 && (" " + this[c].className + " ").replace(o, " ").indexOf(b) > -1) { + return!0; + } + } + return!1 + }, val:function (a) { + var c, d, e, g = this[0]; + { + if (!!arguments.length) { + e = f.isFunction(a); + return this.each(function (d) { + var g = f(this), h; + if (this.nodeType === 1) { + e ? h = a.call(this, d, g.val()) : h = a, h == null ? h = "" : typeof h == "number" ? h += "" : f.isArray(h) && (h = f.map(h, function (a) { + return a == null ? "" : a + "" + })), c = f.valHooks[this.type] || f.valHooks[this.nodeName.toLowerCase()]; + if (!c || !("set"in c) || c.set(this, h, "value") === b) { + this.value = h + } + } + }) + } + if (g) { + c = f.valHooks[g.type] || f.valHooks[g.nodeName.toLowerCase()]; + if (c && "get"in c && (d = c.get(g, "value")) !== b) { + return d; + } + d = g.value; + return typeof d == "string" ? d.replace(q, "") : d == null ? "" : d + } + } + }}), f.extend({valHooks:{option:{get:function (a) { + var b = a.attributes.value; + return!b || b.specified ? a.value : a.text + }}, select:{get:function (a) { + var b, c, d, e, g = a.selectedIndex, h = [], i = a.options, j = a.type === "select-one"; + if (g < 0) { + return null; + } + c = j ? g : 0, d = j ? g + 1 : i.length; + for (; c < d; c++) { + e = i[c]; + if (e.selected && (f.support.optDisabled ? !e.disabled : e.getAttribute("disabled") === null) && (!e.parentNode.disabled || !f.nodeName(e.parentNode, "optgroup"))) { + b = f(e).val(); + if (j) { + return b; + } + h.push(b) + } + } + if (j && !h.length && i.length) { + return f(i[g]).val(); + } + return h + }, set:function (a, b) { + var c = f.makeArray(b); + f(a).find("option").each(function () { + this.selected = f.inArray(f(this).val(), c) >= 0 + }), c.length || (a.selectedIndex = -1); + return c + }}}, attrFn:{val:!0, css:!0, html:!0, text:!0, data:!0, width:!0, height:!0, offset:!0}, attr:function (a, c, d, e) { + var g, h, i, j = a.nodeType; + if (!!a && j !== 3 && j !== 8 && j !== 2) { + if (e && c in f.attrFn) { + return f(a)[c](d); + } + if (typeof a.getAttribute == "undefined") { + return f.prop(a, c, d); + } + i = j !== 1 || !f.isXMLDoc(a), i && (c = c.toLowerCase(), h = f.attrHooks[c] || (u.test(c) ? x : w)); + if (d !== b) { + if (d === null) { + f.removeAttr(a, c); + return + } + if (h && "set"in h && i && (g = h.set(a, d, c)) !== b) { + return g; + } + a.setAttribute(c, "" + d); + return d + } + if (h && "get"in h && i && (g = h.get(a, c)) !== null) { + return g; + } + g = a.getAttribute(c); + return g === null ? b : g + } + }, removeAttr:function (a, b) { + var c, d, e, g, h, i = 0; + if (b && a.nodeType === 1) { + d = b.toLowerCase().split(p), g = d.length; + for (; i < g; i++) { + e = d[i], e && (c = f.propFix[e] || e, h = u.test(e), h || f.attr(a, e, ""), a.removeAttribute(v ? e : c), h && c in a && (a[c] = !1)) + } + } + }, attrHooks:{type:{set:function (a, b) { + if (r.test(a.nodeName) && a.parentNode) { + f.error("type property can't be changed"); + } else if (!f.support.radioValue && b === "radio" && f.nodeName(a, "input")) { + var c = a.value; + a.setAttribute("type", b), c && (a.value = c); + return b + } + }}, value:{get:function (a, b) { + if (w && f.nodeName(a, "button")) { + return w.get(a, b); + } + return b in a ? a.value : null + }, set:function (a, b, c) { + if (w && f.nodeName(a, "button")) { + return w.set(a, b, c); + } + a.value = b + }}}, propFix:{tabindex:"tabIndex", readonly:"readOnly", "for":"htmlFor", "class":"className", maxlength:"maxLength", cellspacing:"cellSpacing", cellpadding:"cellPadding", rowspan:"rowSpan", colspan:"colSpan", usemap:"useMap", frameborder:"frameBorder", contenteditable:"contentEditable"}, prop:function (a, c, d) { + var e, g, h, i = a.nodeType; + if (!!a && i !== 3 && i !== 8 && i !== 2) { + h = i !== 1 || !f.isXMLDoc(a), h && (c = f.propFix[c] || c, g = f.propHooks[c]); + return d !== b ? g && "set"in g && (e = g.set(a, d, c)) !== b ? e : a[c] = d : g && "get"in g && (e = g.get(a, c)) !== null ? e : a[c] + } + }, propHooks:{tabIndex:{get:function (a) { + var c = a.getAttributeNode("tabindex"); + return c && c.specified ? parseInt(c.value, 10) : s.test(a.nodeName) || t.test(a.nodeName) && a.href ? 0 : b + }}}}), f.attrHooks.tabindex = f.propHooks.tabIndex, x = {get:function (a, c) { + var d, e = f.prop(a, c); + return e === !0 || typeof e != "boolean" && (d = a.getAttributeNode(c)) && d.nodeValue !== !1 ? c.toLowerCase() : b + }, set:function (a, b, c) { + var d; + b === !1 ? f.removeAttr(a, c) : (d = f.propFix[c] || c, d in a && (a[d] = !0), a.setAttribute(c, c.toLowerCase())); + return c + }}, v || (y = {name:!0, id:!0, coords:!0}, w = f.valHooks.button = {get:function (a, c) { + var d; + d = a.getAttributeNode(c); + return d && (y[c] ? d.nodeValue !== "" : d.specified) ? d.nodeValue : b + }, set:function (a, b, d) { + var e = a.getAttributeNode(d); + e || (e = c.createAttribute(d), a.setAttributeNode(e)); + return e.nodeValue = b + "" + }}, f.attrHooks.tabindex.set = w.set, f.each(["width", "height"], function (a, b) { + f.attrHooks[b] = f.extend(f.attrHooks[b], {set:function (a, c) { + if (c === "") { + a.setAttribute(b, "auto"); + return c + } + }}) + }), f.attrHooks.contenteditable = {get:w.get, set:function (a, b, c) { + b === "" && (b = "false"), w.set(a, b, c) + }}), f.support.hrefNormalized || f.each(["href", "src", "width", "height"], function (a, c) { + f.attrHooks[c] = f.extend(f.attrHooks[c], {get:function (a) { + var d = a.getAttribute(c, 2); + return d === null ? b : d + }}) + }), f.support.style || (f.attrHooks.style = {get:function (a) { + return a.style.cssText.toLowerCase() || b + }, set:function (a, b) { + return a.style.cssText = "" + b + }}), f.support.optSelected || (f.propHooks.selected = f.extend(f.propHooks.selected, {get:function (a) { + var b = a.parentNode; + b && (b.selectedIndex, b.parentNode && b.parentNode.selectedIndex); + return null + }})), f.support.enctype || (f.propFix.enctype = "encoding"), f.support.checkOn || f.each(["radio", "checkbox"], function () { + f.valHooks[this] = {get:function (a) { + return a.getAttribute("value") === null ? "on" : a.value + }} + }), f.each(["radio", "checkbox"], function () { + f.valHooks[this] = f.extend(f.valHooks[this], {set:function (a, b) { + if (f.isArray(b)) { + return a.checked = f.inArray(f(a).val(), b) >= 0 + } + }}) + }); + var z = /^(?:textarea|input|select)$/i, A = /^([^\.]*)?(?:\.(.+))?$/, B = /(?:^|\s)hover(\.\S+)?\b/, C = /^key/, D = /^(?:mouse|contextmenu)|click/, E = /^(?:focusinfocus|focusoutblur)$/, F = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, G = function (a) { + var b = F.exec(a); + b && (b[1] = (b[1] || "").toLowerCase(), b[3] = b[3] && new RegExp("(?:^|\\s)" + b[3] + "(?:\\s|$)")); + return b + }, H = function (a, b) { + var c = a.attributes || {}; + return(!b[1] || a.nodeName.toLowerCase() === b[1]) && (!b[2] || (c.id || {}).value === b[2]) && (!b[3] || b[3].test((c["class"] || {}).value)) + }, I = function (a) { + return f.event.special.hover ? a : a.replace(B, "mouseenter$1 mouseleave$1") + }; + f.event = {add:function (a, c, d, e, g) { + var h, i, j, k, l, m, n, o, p, q, r, s; + if (!(a.nodeType === 3 || a.nodeType === 8 || !c || !d || !(h = f._data(a)))) { + d.handler && (p = d, d = p.handler, g = p.selector), d.guid || (d.guid = f.guid++), j = h.events, j || (h.events = j = {}), i = h.handle, i || (h.handle = i = function (a) { + return typeof f != "undefined" && (!a || f.event.triggered !== a.type) ? f.event.dispatch.apply(i.elem, arguments) : b + }, i.elem = a), c = f.trim(I(c)).split(" "); + for (k = 0; k < c.length; k++) { + l = A.exec(c[k]) || [], m = l[1], n = (l[2] || "").split(".").sort(), s = f.event.special[m] || {}, m = (g ? s.delegateType : s.bindType) || m, s = f.event.special[m] || {}, o = f.extend({type:m, origType:l[1], data:e, handler:d, guid:d.guid, selector:g, quick:g && G(g), namespace:n.join(".")}, p), r = j[m]; + if (!r) { + r = j[m] = [], r.delegateCount = 0; + if (!s.setup || s.setup.call(a, e, n, i) === !1) { + a.addEventListener ? a.addEventListener(m, i, !1) : a.attachEvent && a.attachEvent("on" + m, i) + } + } + s.add && (s.add.call(a, o), o.handler.guid || (o.handler.guid = d.guid)), g ? r.splice(r.delegateCount++, 0, o) : r.push(o), f.event.global[m] = !0 + } + a = null + } + }, global:{}, remove:function (a, b, c, d, e) { + var g = f.hasData(a) && f._data(a), h, i, j, k, l, m, n, o, p, q, r, s; + if (!!g && !!(o = g.events)) { + b = f.trim(I(b || "")).split(" "); + for (h = 0; h < b.length; h++) { + i = A.exec(b[h]) || [], j = k = i[1], l = i[2]; + if (!j) { + for (j in o) { + f.event.remove(a, j + b[h], c, d, !0); + } + continue + } + p = f.event.special[j] || {}, j = (d ? p.delegateType : p.bindType) || j, r = o[j] || [], m = r.length, l = l ? new RegExp("(^|\\.)" + l.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; + for (n = 0; n < r.length; n++) { + s = r[n], (e || k === s.origType) && (!c || c.guid === s.guid) && (!l || l.test(s.namespace)) && (!d || d === s.selector || d === "**" && s.selector) && (r.splice(n--, 1), s.selector && r.delegateCount--, p.remove && p.remove.call(a, s)); + } + r.length === 0 && m !== r.length && ((!p.teardown || p.teardown.call(a, l) === !1) && f.removeEvent(a, j, g.handle), delete o[j]) + } + f.isEmptyObject(o) && (q = g.handle, q && (q.elem = null), f.removeData(a, ["events", "handle"], !0)) + } + }, customEvent:{getData:!0, setData:!0, changeData:!0}, trigger:function (c, d, e, g) { + if (!e || e.nodeType !== 3 && e.nodeType !== 8) { + var h = c.type || c, i = [], j, k, l, m, n, o, p, q, r, s; + if (E.test(h + f.event.triggered)) { + return; + } + h.indexOf("!") >= 0 && (h = h.slice(0, -1), k = !0), h.indexOf(".") >= 0 && (i = h.split("."), h = i.shift(), i.sort()); + if ((!e || f.event.customEvent[h]) && !f.event.global[h]) { + return; + } + c = typeof c == "object" ? c[f.expando] ? c : new f.Event(h, c) : new f.Event(h), c.type = h, c.isTrigger = !0, c.exclusive = k, c.namespace = i.join("."), c.namespace_re = c.namespace ? new RegExp("(^|\\.)" + i.join("\\.(?:.*\\.)?") + "(\\.|$)") : null, o = h.indexOf(":") < 0 ? "on" + h : ""; + if (!e) { + j = f.cache; + for (l in j) { + j[l].events && j[l].events[h] && f.event.trigger(c, d, j[l].handle.elem, !0); + } + return + } + c.result = b, c.target || (c.target = e), d = d != null ? f.makeArray(d) : [], d.unshift(c), p = f.event.special[h] || {}; + if (p.trigger && p.trigger.apply(e, d) === !1) { + return; + } + r = [ + [e, p.bindType || h] + ]; + if (!g && !p.noBubble && !f.isWindow(e)) { + s = p.delegateType || h, m = E.test(s + h) ? e : e.parentNode, n = null; + for (; m; m = m.parentNode) { + r.push([m, s]), n = m; + } + n && n === e.ownerDocument && r.push([n.defaultView || n.parentWindow || a, s]) + } + for (l = 0; l < r.length && !c.isPropagationStopped(); l++) { + m = r[l][0], c.type = r[l][1], q = (f._data(m, "events") || {})[c.type] && f._data(m, "handle"), q && q.apply(m, d), q = o && m[o], q && f.acceptData(m) && q.apply(m, d) === !1 && c.preventDefault(); + } + c.type = h, !g && !c.isDefaultPrevented() && (!p._default || p._default.apply(e.ownerDocument, d) === !1) && (h !== "click" || !f.nodeName(e, "a")) && f.acceptData(e) && o && e[h] && (h !== "focus" && h !== "blur" || c.target.offsetWidth !== 0) && !f.isWindow(e) && (n = e[o], n && (e[o] = null), f.event.triggered = h, e[h](), f.event.triggered = b, n && (e[o] = n)); + return c.result + } + }, dispatch:function (c) { + c = f.event.fix(c || a.event); + var d = (f._data(this, "events") || {})[c.type] || [], e = d.delegateCount, g = [].slice.call(arguments, 0), h = !c.exclusive && !c.namespace, i = f.event.special[c.type] || {}, j = [], k, l, m, n, o, p, q, r, s, t, u; + g[0] = c, c.delegateTarget = this; + if (!i.preDispatch || i.preDispatch.call(this, c) !== !1) { + if (e && (!c.button || c.type !== "click")) { + n = f(this), n.context = this.ownerDocument || this; + for (m = c.target; m != this; m = m.parentNode || this) { + if (m.disabled !== !0) { + p = {}, r = [], n[0] = m; + for (k = 0; k < e; k++) { + s = d[k], t = s.selector, p[t] === b && (p[t] = s.quick ? H(m, s.quick) : n.is(t)), p[t] && r.push(s); + } + r.length && j.push({elem:m, matches:r}) + } + } + } + d.length > e && j.push({elem:this, matches:d.slice(e)}); + for (k = 0; k < j.length && !c.isPropagationStopped(); k++) { + q = j[k], c.currentTarget = q.elem; + for (l = 0; l < q.matches.length && !c.isImmediatePropagationStopped(); l++) { + s = q.matches[l]; + if (h || !c.namespace && !s.namespace || c.namespace_re && c.namespace_re.test(s.namespace)) { + c.data = s.data, c.handleObj = s, o = ((f.event.special[s.origType] || {}).handle || s.handler).apply(q.elem, g), o !== b && (c.result = o, o === !1 && (c.preventDefault(), c.stopPropagation())) + } + } + } + i.postDispatch && i.postDispatch.call(this, c); + return c.result + } + }, props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks:{}, keyHooks:{props:"char charCode key keyCode".split(" "), filter:function (a, b) { + a.which == null && (a.which = b.charCode != null ? b.charCode : b.keyCode); + return a + }}, mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter:function (a, d) { + var e, f, g, h = d.button, i = d.fromElement; + a.pageX == null && d.clientX != null && (e = a.target.ownerDocument || c, f = e.documentElement, g = e.body, a.pageX = d.clientX + (f && f.scrollLeft || g && g.scrollLeft || 0) - (f && f.clientLeft || g && g.clientLeft || 0), a.pageY = d.clientY + (f && f.scrollTop || g && g.scrollTop || 0) - (f && f.clientTop || g && g.clientTop || 0)), !a.relatedTarget && i && (a.relatedTarget = i === a.target ? d.toElement : i), !a.which && h !== b && (a.which = h & 1 ? 1 : h & 2 ? 3 : h & 4 ? 2 : 0); + return a + }}, fix:function (a) { + if (a[f.expando]) { + return a; + } + var d, e, g = a, h = f.event.fixHooks[a.type] || {}, i = h.props ? this.props.concat(h.props) : this.props; + a = f.Event(g); + for (d = i.length; d;) { + e = i[--d], a[e] = g[e]; + } + a.target || (a.target = g.srcElement || c), a.target.nodeType === 3 && (a.target = a.target.parentNode), a.metaKey === b && (a.metaKey = a.ctrlKey); + return h.filter ? h.filter(a, g) : a + }, special:{ready:{setup:f.bindReady}, load:{noBubble:!0}, focus:{delegateType:"focusin"}, blur:{delegateType:"focusout"}, beforeunload:{setup:function (a, b, c) { + f.isWindow(this) && (this.onbeforeunload = c) + }, teardown:function (a, b) { + this.onbeforeunload === b && (this.onbeforeunload = null) + }}}, simulate:function (a, b, c, d) { + var e = f.extend(new f.Event, c, {type:a, isSimulated:!0, originalEvent:{}}); + d ? f.event.trigger(e, null, b) : f.event.dispatch.call(b, e), e.isDefaultPrevented() && c.preventDefault() + }}, f.event.handle = f.event.dispatch, f.removeEvent = c.removeEventListener ? function (a, b, c) { + a.removeEventListener && a.removeEventListener(b, c, !1) + } : function (a, b, c) { + a.detachEvent && a.detachEvent("on" + b, c) + }, f.Event = function (a, b) { + if (!(this instanceof f.Event)) { + return new f.Event(a, b); + } + a && a.type ? (this.originalEvent = a, this.type = a.type, this.isDefaultPrevented = a.defaultPrevented || a.returnValue === !1 || a.getPreventDefault && a.getPreventDefault() ? K : J) : this.type = a, b && f.extend(this, b), this.timeStamp = a && a.timeStamp || f.now(), this[f.expando] = !0 + }, f.Event.prototype = {preventDefault:function () { + this.isDefaultPrevented = K; + var a = this.originalEvent; + !a || (a.preventDefault ? a.preventDefault() : a.returnValue = !1) + }, stopPropagation:function () { + this.isPropagationStopped = K; + var a = this.originalEvent; + !a || (a.stopPropagation && a.stopPropagation(), a.cancelBubble = !0) + }, stopImmediatePropagation:function () { + this.isImmediatePropagationStopped = K, this.stopPropagation() + }, isDefaultPrevented:J, isPropagationStopped:J, isImmediatePropagationStopped:J}, f.each({mouseenter:"mouseover", mouseleave:"mouseout"}, function (a, b) { + f.event.special[a] = {delegateType:b, bindType:b, handle:function (a) { + var c = this, d = a.relatedTarget, e = a.handleObj, g = e.selector, h; + if (!d || d !== c && !f.contains(c, d)) { + a.type = e.origType, h = e.handler.apply(this, arguments), a.type = b; + } + return h + }} + }), f.support.submitBubbles || (f.event.special.submit = {setup:function () { + if (f.nodeName(this, "form")) { + return!1; + } + f.event.add(this, "click._submit keypress._submit", function (a) { + var c = a.target, d = f.nodeName(c, "input") || f.nodeName(c, "button") ? c.form : b; + d && !d._submit_attached && (f.event.add(d, "submit._submit", function (a) { + a._submit_bubble = !0 + }), d._submit_attached = !0) + }) + }, postDispatch:function (a) { + a._submit_bubble && (delete a._submit_bubble, this.parentNode && !a.isTrigger && f.event.simulate("submit", this.parentNode, a, !0)) + }, teardown:function () { + if (f.nodeName(this, "form")) { + return!1; + } + f.event.remove(this, "._submit") + }}), f.support.changeBubbles || (f.event.special.change = {setup:function () { + if (z.test(this.nodeName)) { + if (this.type === "checkbox" || this.type === "radio") { + f.event.add(this, "propertychange._change", function (a) { + a.originalEvent.propertyName === "checked" && (this._just_changed = !0) + }), f.event.add(this, "click._change", function (a) { + this._just_changed && !a.isTrigger && (this._just_changed = !1, f.event.simulate("change", this, a, !0)) + }); + } + return!1 + } + f.event.add(this, "beforeactivate._change", function (a) { + var b = a.target; + z.test(b.nodeName) && !b._change_attached && (f.event.add(b, "change._change", function (a) { + this.parentNode && !a.isSimulated && !a.isTrigger && f.event.simulate("change", this.parentNode, a, !0) + }), b._change_attached = !0) + }) + }, handle:function (a) { + var b = a.target; + if (this !== b || a.isSimulated || a.isTrigger || b.type !== "radio" && b.type !== "checkbox") { + return a.handleObj.handler.apply(this, arguments) + } + }, teardown:function () { + f.event.remove(this, "._change"); + return z.test(this.nodeName) + }}), f.support.focusinBubbles || f.each({focus:"focusin", blur:"focusout"}, function (a, b) { + var d = 0, e = function (a) { + f.event.simulate(b, a.target, f.event.fix(a), !0) + }; + f.event.special[b] = {setup:function () { + d++ === 0 && c.addEventListener(a, e, !0) + }, teardown:function () { + --d === 0 && c.removeEventListener(a, e, !0) + }} + }), f.fn.extend({on:function (a, c, d, e, g) { + var h, i; + if (typeof a == "object") { + typeof c != "string" && (d = d || c, c = b); + for (i in a) { + this.on(i, c, d, a[i], g); + } + return this + } + d == null && e == null ? (e = c, d = c = b) : e == null && (typeof c == "string" ? (e = d, d = b) : (e = d, d = c, c = b)); + if (e === !1) { + e = J; + } else if (!e) { + return this; + } + g === 1 && (h = e, e = function (a) { + f().off(a); + return h.apply(this, arguments) + }, e.guid = h.guid || (h.guid = f.guid++)); + return this.each(function () { + f.event.add(this, a, e, d, c) + }) + }, one:function (a, b, c, d) { + return this.on(a, b, c, d, 1) + }, off:function (a, c, d) { + if (a && a.preventDefault && a.handleObj) { + var e = a.handleObj; + f(a.delegateTarget).off(e.namespace ? e.origType + "." + e.namespace : e.origType, e.selector, e.handler); + return this + } + if (typeof a == "object") { + for (var g in a) { + this.off(g, c, a[g]); + } + return this + } + if (c === !1 || typeof c == "function") { + d = c, c = b; + } + d === !1 && (d = J); + return this.each(function () { + f.event.remove(this, a, d, c) + }) + }, bind:function (a, b, c) { + return this.on(a, null, b, c) + }, unbind:function (a, b) { + return this.off(a, null, b) + }, live:function (a, b, c) { + f(this.context).on(a, this.selector, b, c); + return this + }, die:function (a, b) { + f(this.context).off(a, this.selector || "**", b); + return this + }, delegate:function (a, b, c, d) { + return this.on(b, a, c, d) + }, undelegate:function (a, b, c) { + return arguments.length == 1 ? this.off(a, "**") : this.off(b, a, c) + }, trigger:function (a, b) { + return this.each(function () { + f.event.trigger(a, b, this) + }) + }, triggerHandler:function (a, b) { + if (this[0]) { + return f.event.trigger(a, b, this[0], !0) + } + }, toggle:function (a) { + var b = arguments, c = a.guid || f.guid++, d = 0, e = function (c) { + var e = (f._data(this, "lastToggle" + a.guid) || 0) % d; + f._data(this, "lastToggle" + a.guid, e + 1), c.preventDefault(); + return b[e].apply(this, arguments) || !1 + }; + e.guid = c; + while (d < b.length) { + b[d++].guid = c; + } + return this.click(e) + }, hover:function (a, b) { + return this.mouseenter(a).mouseleave(b || a) + }}), f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "), function (a, b) { + f.fn[b] = function (a, c) { + c == null && (c = a, a = null); + return arguments.length > 0 ? this.on(b, null, a, c) : this.trigger(b) + }, f.attrFn && (f.attrFn[b] = !0), C.test(b) && (f.event.fixHooks[b] = f.event.keyHooks), D.test(b) && (f.event.fixHooks[b] = f.event.mouseHooks) + }), function () { + function x(a, b, c, e, f, g) { + for (var h = 0, i = e.length; h < i; h++) { + var j = e[h]; + if (j) { + var k = !1; + j = j[a]; + while (j) { + if (j[d] === c) { + k = e[j.sizset]; + break + } + if (j.nodeType === 1) { + g || (j[d] = c, j.sizset = h); + if (typeof b != "string") { + if (j === b) { + k = !0; + break + } + } else if (m.filter(b, [j]).length > 0) { + k = j; + break + } + } + j = j[a] + } + e[h] = k + } + } + } + + function w(a, b, c, e, f, g) { + for (var h = 0, i = e.length; h < i; h++) { + var j = e[h]; + if (j) { + var k = !1; + j = j[a]; + while (j) { + if (j[d] === c) { + k = e[j.sizset]; + break + } + j.nodeType === 1 && !g && (j[d] = c, j.sizset = h); + if (j.nodeName.toLowerCase() === b) { + k = j; + break + } + j = j[a] + } + e[h] = k + } + } + } + + var a = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, d = "sizcache" + (Math.random() + "").replace(".", ""), e = 0, g = Object.prototype.toString, h = !1, i = !0, j = /\\/g, k = /\r\n/g, l = /\W/; + [0, 0].sort(function () { + i = !1; + return 0 + }); + var m = function (b, d, e, f) { + e = e || [], d = d || c; + var h = d; + if (d.nodeType !== 1 && d.nodeType !== 9) { + return[]; + } + if (!b || typeof b != "string") { + return e; + } + var i, j, k, l, n, q, r, t, u = !0, v = m.isXML(d), w = [], x = b; + do { + a.exec(""), i = a.exec(x); + if (i) { + x = i[3], w.push(i[1]); + if (i[2]) { + l = i[3]; + break + } + } + } while (i); + if (w.length > 1 && p.exec(b)) { + if (w.length === 2 && o.relative[w[0]]) { + j = y(w[0] + w[1], d, f); + } else { + j = o.relative[w[0]] ? [d] : m(w.shift(), d); + while (w.length) { + b = w.shift(), o.relative[b] && (b += w.shift()), j = y(b, j, f) + } + } + } else { + !f && w.length > 1 && d.nodeType === 9 && !v && o.match.ID.test(w[0]) && !o.match.ID.test(w[w.length - 1]) && (n = m.find(w.shift(), d, v), d = n.expr ? m.filter(n.expr, n.set)[0] : n.set[0]); + if (d) { + n = f ? {expr:w.pop(), set:s(f)} : m.find(w.pop(), w.length === 1 && (w[0] === "~" || w[0] === "+") && d.parentNode ? d.parentNode : d, v), j = n.expr ? m.filter(n.expr, n.set) : n.set, w.length > 0 ? k = s(j) : u = !1; + while (w.length) { + q = w.pop(), r = q, o.relative[q] ? r = w.pop() : q = "", r == null && (r = d), o.relative[q](k, r, v) + } + } else { + k = w = [] + } + } + k || (k = j), k || m.error(q || b); + if (g.call(k) === "[object Array]") { + if (!u) { + e.push.apply(e, k); + } else if (d && d.nodeType === 1) { + for (t = 0; k[t] != null; t++) { + k[t] && (k[t] === !0 || k[t].nodeType === 1 && m.contains(d, k[t])) && e.push(j[t]); + } + } else { + for (t = 0; k[t] != null; t++) { + k[t] && k[t].nodeType === 1 && e.push(j[t]); + } + } + } else { + s(k, e); + } + l && (m(l, h, e, f), m.uniqueSort(e)); + return e + }; + m.uniqueSort = function (a) { + if (u) { + h = i, a.sort(u); + if (h) { + for (var b = 1; b < a.length; b++) { + a[b] === a[b - 1] && a.splice(b--, 1) + } + } + } + return a + }, m.matches = function (a, b) { + return m(a, null, null, b) + }, m.matchesSelector = function (a, b) { + return m(b, null, null, [a]).length > 0 + }, m.find = function (a, b, c) { + var d, e, f, g, h, i; + if (!a) { + return[]; + } + for (e = 0, f = o.order.length; e < f; e++) { + h = o.order[e]; + if (g = o.leftMatch[h].exec(a)) { + i = g[1], g.splice(1, 1); + if (i.substr(i.length - 1) !== "\\") { + g[1] = (g[1] || "").replace(j, ""), d = o.find[h](g, b, c); + if (d != null) { + a = a.replace(o.match[h], ""); + break + } + } + } + } + d || (d = typeof b.getElementsByTagName != "undefined" ? b.getElementsByTagName("*") : []); + return{set:d, expr:a} + }, m.filter = function (a, c, d, e) { + var f, g, h, i, j, k, l, n, p, q = a, r = [], s = c, t = c && c[0] && m.isXML(c[0]); + while (a && c.length) { + for (h in o.filter) { + if ((f = o.leftMatch[h].exec(a)) != null && f[2]) { + k = o.filter[h], l = f[1], g = !1, f.splice(1, 1); + if (l.substr(l.length - 1) === "\\") { + continue; + } + s === r && (r = []); + if (o.preFilter[h]) { + f = o.preFilter[h](f, s, d, r, e, t); + if (!f) { + g = i = !0; + } else if (f === !0) { + continue + } + } + if (f) { + for (n = 0; (j = s[n]) != null; n++) { + j && (i = k(j, f, n, s), p = e ^ i, d && i != null ? p ? g = !0 : s[n] = !1 : p && (r.push(j), g = !0)); + } + } + if (i !== b) { + d || (s = r), a = a.replace(o.match[h], ""); + if (!g) { + return[]; + } + break + } + } + } + if (a === q) { + if (g == null) { + m.error(a); + } else { + break; + } + } + q = a + } + return s + }, m.error = function (a) { + throw new Error("Syntax error, unrecognized expression: " + a) + }; + var n = m.getText = function (a) { + var b, c, d = a.nodeType, e = ""; + if (d) { + if (d === 1 || d === 9 || d === 11) { + if (typeof a.textContent == "string") { + return a.textContent; + } + if (typeof a.innerText == "string") { + return a.innerText.replace(k, ""); + } + for (a = a.firstChild; a; a = a.nextSibling) { + e += n(a) + } + } else if (d === 3 || d === 4) { + return a.nodeValue + } + } else { + for (b = 0; c = a[b]; b++) { + c.nodeType !== 8 && (e += n(c)); + } + } + return e + }, o = m.selectors = {order:["ID", "NAME", "TAG"], match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/}, leftMatch:{}, attrMap:{"class":"className", "for":"htmlFor"}, attrHandle:{href:function (a) { + return a.getAttribute("href") + }, type:function (a) { + return a.getAttribute("type") + }}, relative:{"+":function (a, b) { + var c = typeof b == "string", d = c && !l.test(b), e = c && !d; + d && (b = b.toLowerCase()); + for (var f = 0, g = a.length, h; f < g; f++) { + if (h = a[f]) { + while ((h = h.previousSibling) && h.nodeType !== 1) { + ; + } + a[f] = e || h && h.nodeName.toLowerCase() === b ? h || !1 : h === b + } + } + e && m.filter(b, a, !0) + }, ">":function (a, b) { + var c, d = typeof b == "string", e = 0, f = a.length; + if (d && !l.test(b)) { + b = b.toLowerCase(); + for (; e < f; e++) { + c = a[e]; + if (c) { + var g = c.parentNode; + a[e] = g.nodeName.toLowerCase() === b ? g : !1 + } + } + } else { + for (; e < f; e++) { + c = a[e], c && (a[e] = d ? c.parentNode : c.parentNode === b); + } + d && m.filter(b, a, !0) + } + }, "":function (a, b, c) { + var d, f = e++, g = x; + typeof b == "string" && !l.test(b) && (b = b.toLowerCase(), d = b, g = w), g("parentNode", b, f, a, d, c) + }, "~":function (a, b, c) { + var d, f = e++, g = x; + typeof b == "string" && !l.test(b) && (b = b.toLowerCase(), d = b, g = w), g("previousSibling", b, f, a, d, c) + }}, find:{ID:function (a, b, c) { + if (typeof b.getElementById != "undefined" && !c) { + var d = b.getElementById(a[1]); + return d && d.parentNode ? [d] : [] + } + }, NAME:function (a, b) { + if (typeof b.getElementsByName != "undefined") { + var c = [], d = b.getElementsByName(a[1]); + for (var e = 0, f = d.length; e < f; e++) { + d[e].getAttribute("name") === a[1] && c.push(d[e]); + } + return c.length === 0 ? null : c + } + }, TAG:function (a, b) { + if (typeof b.getElementsByTagName != "undefined") { + return b.getElementsByTagName(a[1]) + } + }}, preFilter:{CLASS:function (a, b, c, d, e, f) { + a = " " + a[1].replace(j, "") + " "; + if (f) { + return a; + } + for (var g = 0, h; (h = b[g]) != null; g++) { + h && (e ^ (h.className && (" " + h.className + " ").replace(/[\t\n\r]/g, " ").indexOf(a) >= 0) ? c || d.push(h) : c && (b[g] = !1)); + } + return!1 + }, ID:function (a) { + return a[1].replace(j, "") + }, TAG:function (a, b) { + return a[1].replace(j, "").toLowerCase() + }, CHILD:function (a) { + if (a[1] === "nth") { + a[2] || m.error(a[0]), a[2] = a[2].replace(/^\+|\s*/g, ""); + var b = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2] === "even" && "2n" || a[2] === "odd" && "2n+1" || !/\D/.test(a[2]) && "0n+" + a[2] || a[2]); + a[2] = b[1] + (b[2] || 1) - 0, a[3] = b[3] - 0 + } else { + a[2] && m.error(a[0]); + } + a[0] = e++; + return a + }, ATTR:function (a, b, c, d, e, f) { + var g = a[1] = a[1].replace(j, ""); + !f && o.attrMap[g] && (a[1] = o.attrMap[g]), a[4] = (a[4] || a[5] || "").replace(j, ""), a[2] === "~=" && (a[4] = " " + a[4] + " "); + return a + }, PSEUDO:function (b, c, d, e, f) { + if (b[1] === "not") { + if ((a.exec(b[3]) || "").length > 1 || /^\w/.test(b[3])) { + b[3] = m(b[3], null, null, c); + } else { + var g = m.filter(b[3], c, d, !0 ^ f); + d || e.push.apply(e, g); + return!1 + } + } else if (o.match.POS.test(b[0]) || o.match.CHILD.test(b[0])) { + return!0; + } + return b + }, POS:function (a) { + a.unshift(!0); + return a + }}, filters:{enabled:function (a) { + return a.disabled === !1 && a.type !== "hidden" + }, disabled:function (a) { + return a.disabled === !0 + }, checked:function (a) { + return a.checked === !0 + }, selected:function (a) { + a.parentNode && a.parentNode.selectedIndex; + return a.selected === !0 + }, parent:function (a) { + return!!a.firstChild + }, empty:function (a) { + return!a.firstChild + }, has:function (a, b, c) { + return!!m(c[3], a).length + }, header:function (a) { + return/h\d/i.test(a.nodeName) + }, text:function (a) { + var b = a.getAttribute("type"), c = a.type; + return a.nodeName.toLowerCase() === "input" && "text" === c && (b === c || b === null) + }, radio:function (a) { + return a.nodeName.toLowerCase() === "input" && "radio" === a.type + }, checkbox:function (a) { + return a.nodeName.toLowerCase() === "input" && "checkbox" === a.type + }, file:function (a) { + return a.nodeName.toLowerCase() === "input" && "file" === a.type + }, password:function (a) { + return a.nodeName.toLowerCase() === "input" && "password" === a.type + }, submit:function (a) { + var b = a.nodeName.toLowerCase(); + return(b === "input" || b === "button") && "submit" === a.type + }, image:function (a) { + return a.nodeName.toLowerCase() === "input" && "image" === a.type + }, reset:function (a) { + var b = a.nodeName.toLowerCase(); + return(b === "input" || b === "button") && "reset" === a.type + }, button:function (a) { + var b = a.nodeName.toLowerCase(); + return b === "input" && "button" === a.type || b === "button" + }, input:function (a) { + return/input|select|textarea|button/i.test(a.nodeName) + }, focus:function (a) { + return a === a.ownerDocument.activeElement + }}, setFilters:{first:function (a, b) { + return b === 0 + }, last:function (a, b, c, d) { + return b === d.length - 1 + }, even:function (a, b) { + return b % 2 === 0 + }, odd:function (a, b) { + return b % 2 === 1 + }, lt:function (a, b, c) { + return b < c[3] - 0 + }, gt:function (a, b, c) { + return b > c[3] - 0 + }, nth:function (a, b, c) { + return c[3] - 0 === b + }, eq:function (a, b, c) { + return c[3] - 0 === b + }}, filter:{PSEUDO:function (a, b, c, d) { + var e = b[1], f = o.filters[e]; + if (f) { + return f(a, c, b, d); + } + if (e === "contains") { + return(a.textContent || a.innerText || n([a]) || "").indexOf(b[3]) >= 0; + } + if (e === "not") { + var g = b[3]; + for (var h = 0, i = g.length; h < i; h++) { + if (g[h] === a) { + return!1; + } + } + return!0 + } + m.error(e) + }, CHILD:function (a, b) { + var c, e, f, g, h, i, j, k = b[1], l = a; + switch (k) { + case"only": + case"first": + while (l = l.previousSibling) { + if (l.nodeType === 1) { + return!1; + } + } + if (k === "first") { + return!0; + } + l = a; + case"last": + while (l = l.nextSibling) { + if (l.nodeType === 1) { + return!1; + } + } + return!0; + case"nth": + c = b[2], e = b[3]; + if (c === 1 && e === 0) { + return!0; + } + f = b[0], g = a.parentNode; + if (g && (g[d] !== f || !a.nodeIndex)) { + i = 0; + for (l = g.firstChild; l; l = l.nextSibling) { + l.nodeType === 1 && (l.nodeIndex = ++i); + } + g[d] = f + } + j = a.nodeIndex - e; + return c === 0 ? j === 0 : j % c === 0 && j / c >= 0 + } + }, ID:function (a, b) { + return a.nodeType === 1 && a.getAttribute("id") === b + }, TAG:function (a, b) { + return b === "*" && a.nodeType === 1 || !!a.nodeName && a.nodeName.toLowerCase() === b + }, CLASS:function (a, b) { + return(" " + (a.className || a.getAttribute("class")) + " ").indexOf(b) > -1 + }, ATTR:function (a, b) { + var c = b[1], d = m.attr ? m.attr(a, c) : o.attrHandle[c] ? o.attrHandle[c](a) : a[c] != null ? a[c] : a.getAttribute(c), e = d + "", f = b[2], g = b[4]; + return d == null ? f === "!=" : !f && m.attr ? d != null : f === "=" ? e === g : f === "*=" ? e.indexOf(g) >= 0 : f === "~=" ? (" " + e + " ").indexOf(g) >= 0 : g ? f === "!=" ? e !== g : f === "^=" ? e.indexOf(g) === 0 : f === "$=" ? e.substr(e.length - g.length) === g : f === "|=" ? e === g || e.substr(0, g.length + 1) === g + "-" : !1 : e && d !== !1 + }, POS:function (a, b, c, d) { + var e = b[2], f = o.setFilters[e]; + if (f) { + return f(a, c, b, d) + } + }}}, p = o.match.POS, q = function (a, b) { + return"\\" + (b - 0 + 1) + }; + for (var r in o.match) { + o.match[r] = new RegExp(o.match[r].source + /(?![^\[]*\])(?![^\(]*\))/.source), o.leftMatch[r] = new RegExp(/(^(?:.|\r|\n)*?)/.source + o.match[r].source.replace(/\\(\d+)/g, q)); + } + o.match.globalPOS = p; + var s = function (a, b) { + a = Array.prototype.slice.call(a, 0); + if (b) { + b.push.apply(b, a); + return b + } + return a + }; + try { + Array.prototype.slice.call(c.documentElement.childNodes, 0)[0].nodeType + } catch (t) { + s = function (a, b) { + var c = 0, d = b || []; + if (g.call(a) === "[object Array]") { + Array.prototype.push.apply(d, a); + } else if (typeof a.length == "number") { + for (var e = a.length; c < e; c++) { + d.push(a[c]); + } + } else { + for (; a[c]; c++) { + d.push(a[c]); + } + } + return d + } + } + var u, v; + c.documentElement.compareDocumentPosition ? u = function (a, b) { + if (a === b) { + h = !0; + return 0 + } + if (!a.compareDocumentPosition || !b.compareDocumentPosition) { + return a.compareDocumentPosition ? -1 : 1; + } + return a.compareDocumentPosition(b) & 4 ? -1 : 1 + } : (u = function (a, b) { + if (a === b) { + h = !0; + return 0 + } + if (a.sourceIndex && b.sourceIndex) { + return a.sourceIndex - b.sourceIndex; + } + var c, d, e = [], f = [], g = a.parentNode, i = b.parentNode, j = g; + if (g === i) { + return v(a, b); + } + if (!g) { + return-1; + } + if (!i) { + return 1; + } + while (j) { + e.unshift(j), j = j.parentNode; + } + j = i; + while (j) { + f.unshift(j), j = j.parentNode; + } + c = e.length, d = f.length; + for (var k = 0; k < c && k < d; k++) { + if (e[k] !== f[k]) { + return v(e[k], f[k]); + } + } + return k === c ? v(a, f[k], -1) : v(e[k], b, 1) + }, v = function (a, b, c) { + if (a === b) { + return c; + } + var d = a.nextSibling; + while (d) { + if (d === b) { + return-1; + } + d = d.nextSibling + } + return 1 + }), function () { + var a = c.createElement("div"), d = "script" + (new Date).getTime(), e = c.documentElement; + a.innerHTML = "", e.insertBefore(a, e.firstChild), c.getElementById(d) && (o.find.ID = function (a, c, d) { + if (typeof c.getElementById != "undefined" && !d) { + var e = c.getElementById(a[1]); + return e ? e.id === a[1] || typeof e.getAttributeNode != "undefined" && e.getAttributeNode("id").nodeValue === a[1] ? [e] : b : [] + } + }, o.filter.ID = function (a, b) { + var c = typeof a.getAttributeNode != "undefined" && a.getAttributeNode("id"); + return a.nodeType === 1 && c && c.nodeValue === b + }), e.removeChild(a), e = a = null + }(), function () { + var a = c.createElement("div"); + a.appendChild(c.createComment("")), a.getElementsByTagName("*").length > 0 && (o.find.TAG = function (a, b) { + var c = b.getElementsByTagName(a[1]); + if (a[1] === "*") { + var d = []; + for (var e = 0; c[e]; e++) { + c[e].nodeType === 1 && d.push(c[e]); + } + c = d + } + return c + }), a.innerHTML = "", a.firstChild && typeof a.firstChild.getAttribute != "undefined" && a.firstChild.getAttribute("href") !== "#" && (o.attrHandle.href = function (a) { + return a.getAttribute("href", 2) + }), a = null + }(), c.querySelectorAll && function () { + var a = m, b = c.createElement("div"), d = "__sizzle__"; + b.innerHTML = "

"; + if (!b.querySelectorAll || b.querySelectorAll(".TEST").length !== 0) { + m = function (b, e, f, g) { + e = e || c; + if (!g && !m.isXML(e)) { + var h = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b); + if (h && (e.nodeType === 1 || e.nodeType === 9)) { + if (h[1]) { + return s(e.getElementsByTagName(b), f); + } + if (h[2] && o.find.CLASS && e.getElementsByClassName) { + return s(e.getElementsByClassName(h[2]), f) + } + } + if (e.nodeType === 9) { + if (b === "body" && e.body) { + return s([e.body], f); + } + if (h && h[3]) { + var i = e.getElementById(h[3]); + if (!i || !i.parentNode) { + return s([], f); + } + if (i.id === h[3]) { + return s([i], f) + } + } + try { + return s(e.querySelectorAll(b), f) + } catch (j) { + } + } else if (e.nodeType === 1 && e.nodeName.toLowerCase() !== "object") { + var k = e, l = e.getAttribute("id"), n = l || d, p = e.parentNode, q = /^\s*[+~]/.test(b); + l ? n = n.replace(/'/g, "\\$&") : e.setAttribute("id", n), q && p && (e = e.parentNode); + try { + if (!q || p) { + return s(e.querySelectorAll("[id='" + n + "'] " + b), f) + } + } catch (r) { + } finally { + l || k.removeAttribute("id") + } + } + } + return a(b, e, f, g) + }; + for (var e in a) { + m[e] = a[e]; + } + b = null + } + }(), function () { + var a = c.documentElement, b = a.matchesSelector || a.mozMatchesSelector || a.webkitMatchesSelector || a.msMatchesSelector; + if (b) { + var d = !b.call(c.createElement("div"), "div"), e = !1; + try { + b.call(c.documentElement, "[test!='']:sizzle") + } catch (f) { + e = !0 + } + m.matchesSelector = function (a, c) { + c = c.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); + if (!m.isXML(a)) { + try { + if (e || !o.match.PSEUDO.test(c) && !/!=/.test(c)) { + var f = b.call(a, c); + if (f || !d || a.document && a.document.nodeType !== 11) { + return f + } + } + } catch (g) { + } + } + return m(c, null, null, [a]).length > 0 + } + } + }(), function () { + var a = c.createElement("div"); + a.innerHTML = "
"; + if (!!a.getElementsByClassName && a.getElementsByClassName("e").length !== 0) { + a.lastChild.className = "e"; + if (a.getElementsByClassName("e").length === 1) { + return; + } + o.order.splice(1, 0, "CLASS"), o.find.CLASS = function (a, b, c) { + if (typeof b.getElementsByClassName != "undefined" && !c) { + return b.getElementsByClassName(a[1]) + } + }, a = null + } + }(), c.documentElement.contains ? m.contains = function (a, b) { + return a !== b && (a.contains ? a.contains(b) : !0) + } : c.documentElement.compareDocumentPosition ? m.contains = function (a, b) { + return!!(a.compareDocumentPosition(b) & 16) + } : m.contains = function () { + return!1 + }, m.isXML = function (a) { + var b = (a ? a.ownerDocument || a : 0).documentElement; + return b ? b.nodeName !== "HTML" : !1 + }; + var y = function (a, b, c) { + var d, e = [], f = "", g = b.nodeType ? [b] : b; + while (d = o.match.PSEUDO.exec(a)) { + f += d[0], a = a.replace(o.match.PSEUDO, ""); + } + a = o.relative[a] ? a + "*" : a; + for (var h = 0, i = g.length; h < i; h++) { + m(a, g[h], e, c); + } + return m.filter(f, e) + }; + m.attr = f.attr, m.selectors.attrMap = {}, f.find = m, f.expr = m.selectors, f.expr[":"] = f.expr.filters, f.unique = m.uniqueSort, f.text = m.getText, f.isXMLDoc = m.isXML, f.contains = m.contains + }(); + var L = /Until$/, M = /^(?:parents|prevUntil|prevAll)/, N = /,/, O = /^.[^:#\[\.,]*$/, P = Array.prototype.slice, Q = f.expr.match.globalPOS, R = {children:!0, contents:!0, next:!0, prev:!0}; + f.fn.extend({find:function (a) { + var b = this, c, d; + if (typeof a != "string") { + return f(a).filter(function () { + for (c = 0, d = b.length; c < d; c++) { + if (f.contains(b[c], this)) { + return!0 + } + } + }); + } + var e = this.pushStack("", "find", a), g, h, i; + for (c = 0, d = this.length; c < d; c++) { + g = e.length, f.find(a, this[c], e); + if (c > 0) { + for (h = g; h < e.length; h++) { + for (i = 0; i < g; i++) { + if (e[i] === e[h]) { + e.splice(h--, 1); + break + } + } + } + } + } + return e + }, has:function (a) { + var b = f(a); + return this.filter(function () { + for (var a = 0, c = b.length; a < c; a++) { + if (f.contains(this, b[a])) { + return!0 + } + } + }) + }, not:function (a) { + return this.pushStack(T(this, a, !1), "not", a) + }, filter:function (a) { + return this.pushStack(T(this, a, !0), "filter", a) + }, is:function (a) { + return!!a && (typeof a == "string" ? Q.test(a) ? f(a, this.context).index(this[0]) >= 0 : f.filter(a, this).length > 0 : this.filter(a).length > 0) + }, closest:function (a, b) { + var c = [], d, e, g = this[0]; + if (f.isArray(a)) { + var h = 1; + while (g && g.ownerDocument && g !== b) { + for (d = 0; d < a.length; d++) { + f(g).is(a[d]) && c.push({selector:a[d], elem:g, level:h}); + } + g = g.parentNode, h++ + } + return c + } + var i = Q.test(a) || typeof a != "string" ? f(a, b || this.context) : 0; + for (d = 0, e = this.length; d < e; d++) { + g = this[d]; + while (g) { + if (i ? i.index(g) > -1 : f.find.matchesSelector(g, a)) { + c.push(g); + break + } + g = g.parentNode; + if (!g || !g.ownerDocument || g === b || g.nodeType === 11) { + break + } + } + } + c = c.length > 1 ? f.unique(c) : c; + return this.pushStack(c, "closest", a) + }, index:function (a) { + if (!a) { + return this[0] && this[0].parentNode ? this.prevAll().length : -1; + } + if (typeof a == "string") { + return f.inArray(this[0], f(a)); + } + return f.inArray(a.jquery ? a[0] : a, this) + }, add:function (a, b) { + var c = typeof a == "string" ? f(a, b) : f.makeArray(a && a.nodeType ? [a] : a), d = f.merge(this.get(), c); + return this.pushStack(S(c[0]) || S(d[0]) ? d : f.unique(d)) + }, andSelf:function () { + return this.add(this.prevObject) + }}), f.each({parent:function (a) { + var b = a.parentNode; + return b && b.nodeType !== 11 ? b : null + }, parents:function (a) { + return f.dir(a, "parentNode") + }, parentsUntil:function (a, b, c) { + return f.dir(a, "parentNode", c) + }, next:function (a) { + return f.nth(a, 2, "nextSibling") + }, prev:function (a) { + return f.nth(a, 2, "previousSibling") + }, nextAll:function (a) { + return f.dir(a, "nextSibling") + }, prevAll:function (a) { + return f.dir(a, "previousSibling") + }, nextUntil:function (a, b, c) { + return f.dir(a, "nextSibling", c) + }, prevUntil:function (a, b, c) { + return f.dir(a, "previousSibling", c) + }, siblings:function (a) { + return f.sibling((a.parentNode || {}).firstChild, a) + }, children:function (a) { + return f.sibling(a.firstChild) + }, contents:function (a) { + return f.nodeName(a, "iframe") ? a.contentDocument || a.contentWindow.document : f.makeArray(a.childNodes) + }}, function (a, b) { + f.fn[a] = function (c, d) { + var e = f.map(this, b, c); + L.test(a) || (d = c), d && typeof d == "string" && (e = f.filter(d, e)), e = this.length > 1 && !R[a] ? f.unique(e) : e, (this.length > 1 || N.test(d)) && M.test(a) && (e = e.reverse()); + return this.pushStack(e, a, P.call(arguments).join(",")) + } + }), f.extend({filter:function (a, b, c) { + c && (a = ":not(" + a + ")"); + return b.length === 1 ? f.find.matchesSelector(b[0], a) ? [b[0]] : [] : f.find.matches(a, b) + }, dir:function (a, c, d) { + var e = [], g = a[c]; + while (g && g.nodeType !== 9 && (d === b || g.nodeType !== 1 || !f(g).is(d))) { + g.nodeType === 1 && e.push(g), g = g[c]; + } + return e + }, nth:function (a, b, c, d) { + b = b || 1; + var e = 0; + for (; a; a = a[c]) { + if (a.nodeType === 1 && ++e === b) { + break; + } + } + return a + }, sibling:function (a, b) { + var c = []; + for (; a; a = a.nextSibling) { + a.nodeType === 1 && a !== b && c.push(a); + } + return c + }}); + var V = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", W = / jQuery\d+="(?:\d+|null)"/g, X = /^\s+/, Y = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, Z = /<([\w:]+)/, $ = /]", "i"), bd = /checked\s*(?:[^=]|=\s*.checked.)/i, be = /\/(java|ecma)script/i, bf = /^\s*", ""], legend:[1, "
", "
"], thead:[1, "", "
"], tr:[2, "", "
"], td:[3, "", "
"], col:[2, "", "
"], area:[1, "", ""], _default:[0, "", ""]}, bh = U(c); + bg.optgroup = bg.option, bg.tbody = bg.tfoot = bg.colgroup = bg.caption = bg.thead, bg.th = bg.td, f.support.htmlSerialize || (bg._default = [1, "div
", "
"]), f.fn.extend({text:function (a) { + return f.access(this, function (a) { + return a === b ? f.text(this) : this.empty().append((this[0] && this[0].ownerDocument || c).createTextNode(a)) + }, null, a, arguments.length) + }, wrapAll:function (a) { + if (f.isFunction(a)) { + return this.each(function (b) { + f(this).wrapAll(a.call(this, b)) + }); + } + if (this[0]) { + var b = f(a, this[0].ownerDocument).eq(0).clone(!0); + this[0].parentNode && b.insertBefore(this[0]), b.map(function () { + var a = this; + while (a.firstChild && a.firstChild.nodeType === 1) { + a = a.firstChild; + } + return a + }).append(this) + } + return this + }, wrapInner:function (a) { + if (f.isFunction(a)) { + return this.each(function (b) { + f(this).wrapInner(a.call(this, b)) + }); + } + return this.each(function () { + var b = f(this), c = b.contents(); + c.length ? c.wrapAll(a) : b.append(a) + }) + }, wrap:function (a) { + var b = f.isFunction(a); + return this.each(function (c) { + f(this).wrapAll(b ? a.call(this, c) : a) + }) + }, unwrap:function () { + return this.parent().each(function () { + f.nodeName(this, "body") || f(this).replaceWith(this.childNodes) + }).end() + }, append:function () { + return this.domManip(arguments, !0, function (a) { + this.nodeType === 1 && this.appendChild(a) + }) + }, prepend:function () { + return this.domManip(arguments, !0, function (a) { + this.nodeType === 1 && this.insertBefore(a, this.firstChild) + }) + }, before:function () { + if (this[0] && this[0].parentNode) { + return this.domManip(arguments, !1, function (a) { + this.parentNode.insertBefore(a, this) + }); + } + if (arguments.length) { + var a = f + .clean(arguments); + a.push.apply(a, this.toArray()); + return this.pushStack(a, "before", arguments) + } + }, after:function () { + if (this[0] && this[0].parentNode) { + return this.domManip(arguments, !1, function (a) { + this.parentNode.insertBefore(a, this.nextSibling) + }); + } + if (arguments.length) { + var a = this.pushStack(this, "after", arguments); + a.push.apply(a, f.clean(arguments)); + return a + } + }, remove:function (a, b) { + for (var c = 0, d; (d = this[c]) != null; c++) { + if (!a || f.filter(a, [d]).length) { + !b && d.nodeType === 1 && (f.cleanData(d.getElementsByTagName("*")), f.cleanData([d])), d.parentNode && d.parentNode.removeChild(d); + } + } + return this + }, empty:function () { + for (var a = 0, b; (b = this[a]) != null; a++) { + b.nodeType === 1 && f.cleanData(b.getElementsByTagName("*")); + while (b.firstChild) { + b.removeChild(b.firstChild) + } + } + return this + }, clone:function (a, b) { + a = a == null ? !1 : a, b = b == null ? a : b; + return this.map(function () { + return f.clone(this, a, b) + }) + }, html:function (a) { + return f.access(this, function (a) { + var c = this[0] || {}, d = 0, e = this.length; + if (a === b) { + return c.nodeType === 1 ? c.innerHTML.replace(W, "") : null; + } + if (typeof a == "string" && !ba.test(a) && (f.support.leadingWhitespace || !X.test(a)) && !bg[(Z.exec(a) || ["", ""])[1].toLowerCase()]) { + a = a.replace(Y, "<$1>"); + try { + for (; d < e; d++) { + c = this[d] || {}, c.nodeType === 1 && (f.cleanData(c.getElementsByTagName("*")), c.innerHTML = a); + } + c = 0 + } catch (g) { + } + } + c && this.empty().append(a) + }, null, a, arguments.length) + }, replaceWith:function (a) { + if (this[0] && this[0].parentNode) { + if (f.isFunction(a)) { + return this.each(function (b) { + var c = f(this), d = c.html(); + c.replaceWith(a.call(this, b, d)) + }); + } + typeof a != "string" && (a = f(a).detach()); + return this.each(function () { + var b = this.nextSibling, c = this.parentNode; + f(this).remove(), b ? f(b).before(a) : f(c).append(a) + }) + } + return this.length ? this.pushStack(f(f.isFunction(a) ? a() : a), "replaceWith", a) : this + }, detach:function (a) { + return this.remove(a, !0) + }, domManip:function (a, c, d) { + var e, g, h, i, j = a[0], k = []; + if (!f.support.checkClone && arguments.length === 3 && typeof j == "string" && bd.test(j)) { + return this.each(function () { + f(this).domManip(a, c, d, !0) + }); + } + if (f.isFunction(j)) { + return this.each(function (e) { + var g = f(this); + a[0] = j.call(this, e, c ? g.html() : b), g.domManip(a, c, d) + }); + } + if (this[0]) { + i = j && j.parentNode, f.support.parentNode && i && i.nodeType === 11 && i.childNodes.length === this.length ? e = {fragment:i} : e = f.buildFragment(a, this, k), h = e.fragment, h.childNodes.length === 1 ? g = h = h.firstChild : g = h.firstChild; + if (g) { + c = c && f.nodeName(g, "tr"); + for (var l = 0, m = this.length, n = m - 1; l < m; l++) { + d.call(c ? bi(this[l], g) : this[l], e.cacheable || m > 1 && l < n ? f.clone(h, !0, !0) : h) + } + } + k.length && f.each(k, function (a, b) { + b.src ? f.ajax({type:"GET", global:!1, url:b.src, async:!1, dataType:"script"}) : f.globalEval((b.text || b.textContent || b.innerHTML || "").replace(bf, "/*$0*/")), b.parentNode && b.parentNode.removeChild(b) + }) + } + return this + }}), f.buildFragment = function (a, b, d) { + var e, g, h, i, j = a[0]; + b && b[0] && (i = b[0].ownerDocument || b[0]), i.createDocumentFragment || (i = c), a.length === 1 && typeof j == "string" && j.length < 512 && i === c && j.charAt(0) === "<" && !bb.test(j) && (f.support.checkClone || !bd.test(j)) && (f.support.html5Clone || !bc.test(j)) && (g = !0, h = f.fragments[j], h && h !== 1 && (e = h)), e || (e = i.createDocumentFragment(), f.clean(a, i, e, d)), g && (f.fragments[j] = h ? e : 1); + return{fragment:e, cacheable:g} + }, f.fragments = {}, f.each({appendTo:"append", prependTo:"prepend", insertBefore:"before", insertAfter:"after", replaceAll:"replaceWith"}, function (a, b) { + f.fn[a] = function (c) { + var d = [], e = f(c), g = this.length === 1 && this[0].parentNode; + if (g && g.nodeType === 11 && g.childNodes.length === 1 && e.length === 1) { + e[b](this[0]); + return this + } + for (var h = 0, i = e.length; h < i; h++) { + var j = (h > 0 ? this.clone(!0) : this).get(); + f(e[h])[b](j), d = d.concat(j) + } + return this.pushStack(d, a, e.selector) + } + }), f.extend({clone:function (a, b, c) { + var d, e, g, h = f.support.html5Clone || f.isXMLDoc(a) || !bc.test("<" + a.nodeName + ">") ? a.cloneNode(!0) : bo(a); + if ((!f.support.noCloneEvent || !f.support.noCloneChecked) && (a.nodeType === 1 || a.nodeType === 11) && !f.isXMLDoc(a)) { + bk(a, h), d = bl(a), e = bl(h); + for (g = 0; d[g]; ++g) { + e[g] && bk(d[g], e[g]) + } + } + if (b) { + bj(a, h); + if (c) { + d = bl(a), e = bl(h); + for (g = 0; d[g]; ++g) { + bj(d[g], e[g]) + } + } + } + d = e = null; + return h + }, clean:function (a, b, d, e) { + var g, h, i, j = []; + b = b || c, typeof b.createElement == "undefined" && (b = b.ownerDocument || b[0] && b[0].ownerDocument || c); + for (var k = 0, l; (l = a[k]) != null; k++) { + typeof l == "number" && (l += ""); + if (!l) { + continue; + } + if (typeof l == "string") { + if (!_.test(l)) { + l = b.createTextNode(l); + } else { + l = l.replace(Y, "<$1>"); + var m = (Z.exec(l) || ["", ""])[1].toLowerCase(), n = bg[m] || bg._default, o = n[0], p = b.createElement("div"), q = bh.childNodes, r; + b === c ? bh.appendChild(p) : U(b).appendChild(p), p.innerHTML = n[1] + l + n[2]; + while (o--) { + p = p.lastChild; + } + if (!f.support.tbody) { + var s = $.test(l), t = m === "table" && !s ? p.firstChild && p.firstChild.childNodes : n[1] === "" && !s ? p.childNodes : []; + for (i = t.length - 1; i >= 0; --i) { + f.nodeName(t[i], "tbody") && !t[i].childNodes.length && t[i].parentNode.removeChild(t[i]) + } + } + !f.support.leadingWhitespace && X.test(l) && p.insertBefore(b.createTextNode(X.exec(l)[0]), p.firstChild), l = p.childNodes, p && (p.parentNode.removeChild(p), q.length > 0 && (r = q[q.length - 1], r && r.parentNode && r.parentNode.removeChild(r))) + } + } + var u; + if (!f.support.appendChecked) { + if (l[0] && typeof (u = l.length) == "number") { + for (i = 0; i < u; i++) { + bn(l[i]); + } + } else { + bn(l); + } + } + l.nodeType ? j.push(l) : j = f.merge(j, l) + } + if (d) { + g = function (a) { + return!a.type || be.test(a.type) + }; + for (k = 0; j[k]; k++) { + h = j[k]; + if (e && f.nodeName(h, "script") && (!h.type || be.test(h.type))) { + e.push(h.parentNode ? h.parentNode.removeChild(h) : h); + } else { + if (h.nodeType === 1) { + var v = f.grep(h.getElementsByTagName("script"), g); + j.splice.apply(j, [k + 1, 0].concat(v)) + } + d.appendChild(h) + } + } + } + return j + }, cleanData:function (a) { + var b, c, d = f.cache, e = f.event.special, g = f.support.deleteExpando; + for (var h = 0, i; (i = a[h]) != null; h++) { + if (i.nodeName && f.noData[i.nodeName.toLowerCase()]) { + continue; + } + c = i[f.expando]; + if (c) { + b = d[c]; + if (b && b.events) { + for (var j in b.events) { + e[j] ? f.event.remove(i, j) : f.removeEvent(i, j, b.handle); + } + b.handle && (b.handle.elem = null) + } + g ? delete i[f.expando] : i.removeAttribute && i.removeAttribute(f.expando), delete d[c] + } + } + }}); + var bp = /alpha\([^)]*\)/i, bq = /opacity=([^)]*)/, br = /([A-Z]|^ms)/g, bs = /^[\-+]?(?:\d*\.)?\d+$/i, bt = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i, bu = /^([\-+])=([\-+.\de]+)/, bv = /^margin/, bw = {position:"absolute", visibility:"hidden", display:"block"}, bx = ["Top", "Right", "Bottom", "Left"], by, bz, bA; + f.fn.css = function (a, c) { + return f.access(this, function (a, c, d) { + return d !== b ? f.style(a, c, d) : f.css(a, c) + }, a, c, arguments.length > 1) + }, f.extend({cssHooks:{opacity:{get:function (a, b) { + if (b) { + var c = by(a, "opacity"); + return c === "" ? "1" : c + } + return a.style.opacity + }}}, cssNumber:{fillOpacity:!0, fontWeight:!0, lineHeight:!0, opacity:!0, orphans:!0, widows:!0, zIndex:!0, zoom:!0}, cssProps:{"float":f.support.cssFloat ? "cssFloat" : "styleFloat"}, style:function (a, c, d, e) { + if (!!a && a.nodeType !== 3 && a.nodeType !== 8 && !!a.style) { + var g, h, i = f.camelCase(c), j = a.style, k = f.cssHooks[i]; + c = f.cssProps[i] || i; + if (d === b) { + if (k && "get"in k && (g = k.get(a, !1, e)) !== b) { + return g; + } + return j[c] + } + h = typeof d, h === "string" && (g = bu.exec(d)) && (d = +(g[1] + 1) * +g[2] + parseFloat(f.css(a, c)), h = "number"); + if (d == null || h === "number" && isNaN(d)) { + return; + } + h === "number" && !f.cssNumber[i] && (d += "px"); + if (!k || !("set"in k) || (d = k.set(a, d)) !== b) { + try { + j[c] = d + } catch (l) { + } + } + } + }, css:function (a, c, d) { + var e, g; + c = f.camelCase(c), g = f.cssHooks[c], c = f.cssProps[c] || c, c === "cssFloat" && (c = "float"); + if (g && "get"in g && (e = g.get(a, !0, d)) !== b) { + return e; + } + if (by) { + return by(a, c) + } + }, swap:function (a, b, c) { + var d = {}, e, f; + for (f in b) { + d[f] = a.style[f], a.style[f] = b[f]; + } + e = c.call(a); + for (f in b) { + a.style[f] = d[f]; + } + return e + }}), f.curCSS = f.css, c.defaultView && c.defaultView.getComputedStyle && (bz = function (a, b) { + var c, d, e, g, h = a.style; + b = b.replace(br, "-$1").toLowerCase(), (d = a.ownerDocument.defaultView) && (e = d.getComputedStyle(a, null)) && (c = e.getPropertyValue(b), c === "" && !f.contains(a.ownerDocument.documentElement, a) && (c = f.style(a, b))), !f.support.pixelMargin && e && bv.test(b) && bt.test(c) && (g = h.width, h.width = c, c = e.width, h.width = g); + return c + }), c.documentElement.currentStyle && (bA = function (a, b) { + var c, d, e, f = a.currentStyle && a.currentStyle[b], g = a.style; + f == null && g && (e = g[b]) && (f = e), bt.test(f) && (c = g.left, d = a.runtimeStyle && a.runtimeStyle.left, d && (a.runtimeStyle.left = a.currentStyle.left), g.left = b === "fontSize" ? "1em" : f, f = g.pixelLeft + "px", g.left = c, d && (a.runtimeStyle.left = d)); + return f === "" ? "auto" : f + }), by = bz || bA, f.each(["height", "width"], function (a, b) { + f.cssHooks[b] = {get:function (a, c, d) { + if (c) { + return a.offsetWidth !== 0 ? bB(a, b, d) : f.swap(a, bw, function () { + return bB(a, b, d) + }) + } + }, set:function (a, b) { + return bs.test(b) ? b + "px" : b + }} + }), f.support.opacity || (f.cssHooks.opacity = {get:function (a, b) { + return bq.test((b && a.currentStyle ? a.currentStyle.filter : a.style.filter) || "") ? parseFloat(RegExp.$1) / 100 + "" : b ? "1" : "" + }, set:function (a, b) { + var c = a.style, d = a.currentStyle, e = f.isNumeric(b) ? "alpha(opacity=" + b * 100 + ")" : "", g = d && d.filter || c.filter || ""; + c.zoom = 1; + if (b >= 1 && f.trim(g.replace(bp, "")) === "") { + c.removeAttribute("filter"); + if (d && !d.filter) { + return + } + } + c.filter = bp.test(g) ? g.replace(bp, e) : g + " " + e + }}), f(function () { + f.support.reliableMarginRight || (f.cssHooks.marginRight = {get:function (a, b) { + return f.swap(a, {display:"inline-block"}, function () { + return b ? by(a, "margin-right") : a.style.marginRight + }) + }}) + }), f.expr && f.expr.filters && (f.expr.filters.hidden = function (a) { + var b = a.offsetWidth, c = a.offsetHeight; + return b === 0 && c === 0 || !f.support.reliableHiddenOffsets && (a.style && a.style.display || f.css(a, "display")) === "none" + }, f.expr.filters.visible = function (a) { + return!f.expr.filters.hidden(a) + }), f.each({margin:"", padding:"", border:"Width"}, function (a, b) { + f.cssHooks[a + b] = {expand:function (c) { + var d, e = typeof c == "string" ? c.split(" ") : [c], f = {}; + for (d = 0; d < 4; d++) { + f[a + bx[d] + b] = e[d] || e[d - 2] || e[0]; + } + return f + }} + }); + var bC = /%20/g, bD = /\[\]$/, bE = /\r?\n/g, bF = /#.*$/, bG = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, bH = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, bI = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, bJ = /^(?:GET|HEAD)$/, bK = /^\/\//, bL = /\?/, bM = /)<[^<]*)*<\/script>/gi, bN = /^(?:select|textarea)/i, bO = /\s+/, bP = /([?&])_=[^&]*/, bQ = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, bR = f.fn.load, bS = {}, bT = {}, bU, bV, bW = ["*/"] + ["*"]; + try { + bU = e.href + } catch (bX) { + bU = c.createElement("a"), bU.href = "", bU = bU.href + } + bV = bQ.exec(bU.toLowerCase()) || [], f.fn.extend({load:function (a, c, d) { + if (typeof a != "string" && bR) { + return bR.apply(this, arguments); + } + if (!this.length) { + return this; + } + var e = a.indexOf(" "); + if (e >= 0) { + var g = a.slice(e, a.length); + a = a.slice(0, e) + } + var h = "GET"; + c && (f.isFunction(c) ? (d = c, c = b) : typeof c == "object" && (c = f.param(c, f.ajaxSettings.traditional), h = "POST")); + var i = this; + f.ajax({url:a, type:h, dataType:"html", data:c, complete:function (a, b, c) { + c = a.responseText, a.isResolved() && (a.done(function (a) { + c = a + }), i.html(g ? f("
").append(c.replace(bM, "")).find(g) : c)), d && i.each(d, [c, b, a]) + }}); + return this + }, serialize:function () { + return f.param(this.serializeArray()) + }, serializeArray:function () { + return this.map(function () { + return this.elements ? f.makeArray(this.elements) : this + }).filter(function () { + return this.name && !this.disabled && (this.checked || bN.test(this.nodeName) || bH.test(this.type)) + }).map(function (a, b) { + var c = f(this).val(); + return c == null ? null : f.isArray(c) ? f.map(c, function (a, c) { + return{name:b.name, value:a.replace(bE, "\r\n")} + }) : {name:b.name, value:c.replace(bE, "\r\n")} + }).get() + }}), f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), function (a, b) { + f.fn[b] = function (a) { + return this.on(b, a) + } + }), f.each(["get", "post"], function (a, c) { + f[c] = function (a, d, e, g) { + f.isFunction(d) && (g = g || e, e = d, d = b); + return f.ajax({type:c, url:a, data:d, success:e, dataType:g}) + } + }), f.extend({getScript:function (a, c) { + return f.get(a, b, c, "script") + }, getJSON:function (a, b, c) { + return f.get(a, b, c, "json") + }, ajaxSetup:function (a, b) { + b ? b$(a, f.ajaxSettings) : (b = a, a = f.ajaxSettings), b$(a, b); + return a + }, ajaxSettings:{url:bU, isLocal:bI.test(bV[1]), global:!0, type:"GET", contentType:"application/x-www-form-urlencoded; charset=UTF-8", processData:!0, async:!0, accepts:{xml:"application/xml, text/xml", html:"text/html", text:"text/plain", json:"application/json, text/javascript", "*":bW}, contents:{xml:/xml/, html:/html/, json:/json/}, responseFields:{xml:"responseXML", text:"responseText"}, converters:{"* text":a.String, "text html":!0, "text json":f.parseJSON, "text xml":f.parseXML}, flatOptions:{context:!0, url:!0}}, ajaxPrefilter:bY(bS), ajaxTransport:bY(bT), ajax:function (a, c) { + function w(a, c, l, m) { + if (s !== 2) { + s = 2, q && clearTimeout(q), p = b, n = m || "", v.readyState = a > 0 ? 4 : 0; + var o, r, u, w = c, x = l ? ca(d, v, l) : b, y, z; + if (a >= 200 && a < 300 || a === 304) { + if (d.ifModified) { + if (y = v.getResponseHeader("Last-Modified")) { + f.lastModified[k] = y; + } + if (z = v.getResponseHeader("Etag")) { + f.etag[k] = z + } + } + if (a === 304) { + w = "notmodified", o = !0; + } else { + try { + r = cb(d, x), w = "success", o = !0 + } catch (A) { + w = "parsererror", u = A + } + } + } else { + u = w; + if (!w || a) { + w = "error", a < 0 && (a = 0) + } + } + v.status = a, v.statusText = "" + (c || w), o ? h.resolveWith(e, [r, w, v]) : h.rejectWith(e, [v, w, u]), v.statusCode(j), j = b, t && g.trigger("ajax" + (o ? "Success" : "Error"), [v, d, o ? r : u]), i.fireWith(e, [v, w]), t && (g.trigger("ajaxComplete", [v, d]), --f.active || f.event.trigger("ajaxStop")) + } + } + + typeof a == "object" && (c = a, a = b), c = c || {}; + var d = f.ajaxSetup({}, c), e = d.context || d, g = e !== d && (e.nodeType || e instanceof f) ? f(e) : f.event, h = f.Deferred(), i = f.Callbacks("once memory"), j = d.statusCode || {}, k, l = {}, m = {}, n, o, p, q, r, s = 0, t, u, v = {readyState:0, setRequestHeader:function (a, b) { + if (!s) { + var c = a.toLowerCase(); + a = m[c] = m[c] || a, l[a] = b + } + return this + }, getAllResponseHeaders:function () { + return s === 2 ? n : null + }, getResponseHeader:function (a) { + var c; + if (s === 2) { + if (!o) { + o = {}; + while (c = bG.exec(n)) { + o[c[1].toLowerCase()] = c[2] + } + } + c = o[a.toLowerCase()] + } + return c === b ? null : c + }, overrideMimeType:function (a) { + s || (d.mimeType = a); + return this + }, abort:function (a) { + a = a || "abort", p && p.abort(a), w(0, a); + return this + }}; + h.promise(v), v.success = v.done, v.error = v.fail, v.complete = i.add, v.statusCode = function (a) { + if (a) { + var b; + if (s < 2) { + for (b in a) { + j[b] = [j[b], a[b]]; + } + } else { + b = a[v.status], v.then(b, b) + } + } + return this + }, d.url = ((a || d.url) + "").replace(bF, "").replace(bK, bV[1] + "//"), d.dataTypes = f.trim(d.dataType || "*").toLowerCase().split(bO), d.crossDomain == null && (r = bQ.exec(d.url.toLowerCase()), d.crossDomain = !(!r || r[1] == bV[1] && r[2] == bV[2] && (r[3] || (r[1] === "http:" ? 80 : 443)) == (bV[3] || (bV[1] === "http:" ? 80 : 443)))), d.data && d.processData && typeof d.data != "string" && (d.data = f.param(d.data, d.traditional)), bZ(bS, d, c, v); + if (s === 2) { + return!1; + } + t = d.global, d.type = d.type.toUpperCase(), d.hasContent = !bJ.test(d.type), t && f.active++ === 0 && f.event.trigger("ajaxStart"); + if (!d.hasContent) { + d.data && (d.url += (bL.test(d.url) ? "&" : "?") + d.data, delete d.data), k = d.url; + if (d.cache === !1) { + var x = f.now(), y = d.url.replace(bP, "$1_=" + x); + d.url = y + (y === d.url ? (bL.test(d.url) ? "&" : "?") + "_=" + x : "") + } + } + (d.data && d.hasContent && d.contentType !== !1 || c.contentType) && v.setRequestHeader("Content-Type", d.contentType), d.ifModified && (k = k || d.url, f.lastModified[k] && v.setRequestHeader("If-Modified-Since", f.lastModified[k]), f.etag[k] && v.setRequestHeader("If-None-Match", f.etag[k])), v.setRequestHeader("Accept", d.dataTypes[0] && d.accepts[d.dataTypes[0]] ? d.accepts[d.dataTypes[0]] + (d.dataTypes[0] !== "*" ? ", " + bW + "; q=0.01" : "") : d.accepts["*"]); + for (u in d.headers) { + v.setRequestHeader(u, d.headers[u]); + } + if (d.beforeSend && (d.beforeSend.call(e, v, d) === !1 || s === 2)) { + v.abort(); + return!1 + } + for (u in{success:1, error:1, complete:1}) { + v[u](d[u]); + } + p = bZ(bT, d, c, v); + if (!p) { + w(-1, "No Transport"); + } else { + v.readyState = 1, t && g.trigger("ajaxSend", [v, d]), d.async && d.timeout > 0 && (q = setTimeout(function () { + v.abort("timeout") + }, d.timeout)); + try { + s = 1, p.send(l, w) + } catch (z) { + if (s < 2) { + w(-1, z); + } else { + throw z + } + } + } + return v + }, param:function (a, c) { + var d = [], e = function (a, b) { + b = f.isFunction(b) ? b() : b, d[d.length] = encodeURIComponent(a) + "=" + encodeURIComponent(b) + }; + c === b && (c = f.ajaxSettings.traditional); + if (f.isArray(a) || a.jquery && !f.isPlainObject(a)) { + f.each(a, function () { + e(this.name, this.value) + }); + } else { + for (var g in a) { + b_(g, a[g], c, e); + } + } + return d.join("&").replace(bC, "+") + }}), f.extend({active:0, lastModified:{}, etag:{}}); + var cc = f.now(), cd = /(\=)\?(&|$)|\?\?/i; + f.ajaxSetup({jsonp:"callback", jsonpCallback:function () { + return f.expando + "_" + cc++ + }}), f.ajaxPrefilter("json jsonp", function (b, c, d) { + var e = typeof b.data == "string" && /^application\/x\-www\-form\-urlencoded/.test(b.contentType); + if (b.dataTypes[0] === "jsonp" || b.jsonp !== !1 && (cd.test(b.url) || e && cd.test(b.data))) { + var g, h = b.jsonpCallback = f.isFunction(b.jsonpCallback) ? b.jsonpCallback() : b.jsonpCallback, i = a[h], j = b.url, k = b.data, l = "$1" + h + "$2"; + b.jsonp !== !1 && (j = j.replace(cd, l), b.url === j && (e && (k = k.replace(cd, l)), b.data === k && (j += (/\?/.test(j) ? "&" : "?") + b.jsonp + "=" + h))), b.url = j, b.data = k, a[h] = function (a) { + g = [a] + }, d.always(function () { + a[h] = i, g && f.isFunction(i) && a[h](g[0]) + }), b.converters["script json"] = function () { + g || f.error(h + " was not called"); + return g[0] + }, b.dataTypes[0] = "json"; + return"script" + } + }), f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"}, contents:{script:/javascript|ecmascript/}, converters:{"text script":function (a) { + f.globalEval(a); + return a + }}}), f.ajaxPrefilter("script", function (a) { + a.cache === b && (a.cache = !1), a.crossDomain && (a.type = "GET", a.global = !1) + }), f.ajaxTransport("script", function (a) { + if (a.crossDomain) { + var d, e = c.head || c.getElementsByTagName("head")[0] || c.documentElement; + return{send:function (f, g) { + d = c.createElement("script"), d.async = "async", a.scriptCharset && (d.charset = a.scriptCharset), d.src = a.url, d.onload = d.onreadystatechange = function (a, c) { + if (c || !d.readyState || /loaded|complete/.test(d.readyState)) { + d.onload = d.onreadystatechange = null, e && d.parentNode && e.removeChild(d), d = b, c || g(200, "success") + } + }, e.insertBefore(d, e.firstChild) + }, abort:function () { + d && d.onload(0, 1) + }} + } + }); + var ce = a.ActiveXObject ? function () { + for (var a in cg) { + cg[a](0, 1) + } + } : !1, cf = 0, cg; + f.ajaxSettings.xhr = a.ActiveXObject ? function () { + return!this.isLocal && ch() || ci() + } : ch, function (a) { + f.extend(f.support, {ajax:!!a, cors:!!a && "withCredentials"in a}) + }(f.ajaxSettings.xhr()), f.support.ajax && f.ajaxTransport(function (c) { + if (!c.crossDomain || f.support.cors) { + var d; + return{send:function (e, g) { + var h = c.xhr(), i, j; + c.username ? h.open(c.type, c.url, c.async, c.username, c.password) : h.open(c.type, c.url, c.async); + if (c.xhrFields) { + for (j in c.xhrFields) { + h[j] = c.xhrFields[j]; + } + } + c.mimeType && h.overrideMimeType && h.overrideMimeType(c.mimeType), !c.crossDomain && !e["X-Requested-With"] && (e["X-Requested-With"] = "XMLHttpRequest"); + try { + for (j in e) { + h.setRequestHeader(j, e[j]) + } + } catch (k) { + } + h.send(c.hasContent && c.data || null), d = function (a, e) { + var j, k, l, m, n; + try { + if (d && (e || h.readyState === 4)) { + d = b, i && (h.onreadystatechange = f.noop, ce && delete cg[i]); + if (e) { + h.readyState !== 4 && h.abort(); + } else { + j = h.status, l = h.getAllResponseHeaders(), m = {}, n = h.responseXML, n && n.documentElement && (m.xml = n); + try { + m.text = h.responseText + } catch (a) { + } + try { + k = h.statusText + } catch (o) { + k = "" + } + !j && c.isLocal && !c.crossDomain ? j = m.text ? 200 : 404 : j === 1223 && (j = 204) + } + } + } catch (p) { + e || g(-1, p) + } + m && g(j, k, m, l) + }, !c.async || h.readyState === 4 ? d() : (i = ++cf, ce && (cg || (cg = {}, f(a).unload(ce)), cg[i] = d), h.onreadystatechange = d) + }, abort:function () { + d && d(0, 1) + }} + } + }); + var cj = {}, ck, cl, cm = /^(?:toggle|show|hide)$/, cn = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, co, cp = [ + ["height", "marginTop", "marginBottom", "paddingTop", "paddingBottom"], + ["width", "marginLeft", "marginRight", "paddingLeft", "paddingRight"], + ["opacity"] + ], cq; + f.fn.extend({show:function (a, b, c) { + var d, e; + if (a || a === 0) { + return this.animate(ct("show", 3), a, b, c); + } + for (var g = 0, h = this.length; g < h; g++) { + d = this[g], d.style && (e = d.style.display, !f._data(d, "olddisplay") && e === "none" && (e = d.style.display = ""), (e === "" && f.css(d, "display") === "none" || !f.contains(d.ownerDocument.documentElement, d)) && f._data(d, "olddisplay", cu(d.nodeName))); + } + for (g = 0; g < h; g++) { + d = this[g]; + if (d.style) { + e = d.style.display; + if (e === "" || e === "none") { + d.style.display = f._data(d, "olddisplay") || "" + } + } + } + return this + }, hide:function (a, b, c) { + if (a || a === 0) { + return this.animate(ct("hide", 3), a, b, c); + } + var d, e, g = 0, h = this.length; + for (; g < h; g++) { + d = this[g], d.style && (e = f.css(d, "display"), e !== "none" && !f._data(d, "olddisplay") && f._data(d, "olddisplay", e)); + } + for (g = 0; g < h; g++) { + this[g].style && (this[g].style.display = "none"); + } + return this + }, _toggle:f.fn.toggle, toggle:function (a, b, c) { + var d = typeof a == "boolean"; + f.isFunction(a) && f.isFunction(b) ? this._toggle.apply(this, arguments) : a == null || d ? this.each(function () { + var b = d ? a : f(this).is(":hidden"); + f(this)[b ? "show" : "hide"]() + }) : this.animate(ct("toggle", 3), a, b, c); + return this + }, fadeTo:function (a, b, c, d) { + return this.filter(":hidden").css("opacity", 0).show().end().animate({opacity:b}, a, c, d) + }, animate:function (a, b, c, d) { + function g() { + e.queue === !1 && f._mark(this); + var b = f.extend({}, e), c = this.nodeType === 1, d = c && f(this).is(":hidden"), g, h, i, j, k, l, m, n, o, p, q; + b.animatedProperties = {}; + for (i in a) { + g = f.camelCase(i), i !== g && (a[g] = a[i], delete a[i]); + if ((k = f.cssHooks[g]) && "expand"in k) { + l = k.expand(a[g]), delete a[g]; + for (i in l) { + i in a || (a[i] = l[i]) + } + } + } + for (g in a) { + h = a[g], f.isArray(h) ? (b.animatedProperties[g] = h[1], h = a[g] = h[0]) : b.animatedProperties[g] = b.specialEasing && b.specialEasing[g] || b.easing || "swing"; + if (h === "hide" && d || h === "show" && !d) { + return b.complete.call(this); + } + c && (g === "height" || g === "width") && (b.overflow = [this.style.overflow, this.style.overflowX, this.style.overflowY], f.css(this, "display") === "inline" && f.css(this, "float") === "none" && (!f.support.inlineBlockNeedsLayout || cu(this.nodeName) === "inline" ? this.style.display = "inline-block" : this.style.zoom = 1)) + } + b.overflow != null && (this.style.overflow = "hidden"); + for (i in a) { + j = new f.fx(this, b, i), h = a[i], cm.test(h) ? (q = f._data(this, "toggle" + i) || (h === "toggle" ? d ? "show" : "hide" : 0), q ? (f._data(this, "toggle" + i, q === "show" ? "hide" : "show"), j[q]()) : j[h]()) : (m = cn.exec(h), n = j.cur(), m ? (o = parseFloat(m[2]), p = m[3] || (f.cssNumber[i] ? "" : "px"), p !== "px" && (f.style(this, i, (o || 1) + p), n = (o || 1) / j.cur() * n, f.style(this, i, n + p)), m[1] && (o = (m[1] === "-=" ? -1 : 1) * o + n), j.custom(n, o, p)) : j.custom(n, h, "")); + } + return!0 + } + + var e = f.speed(b, c, d); + if (f.isEmptyObject(a)) { + return this.each(e.complete, [!1]); + } + a = f.extend({}, a); + return e.queue === !1 ? this.each(g) : this.queue(e.queue, g) + }, stop:function (a, c, d) { + typeof a != "string" && (d = c, c = a, a = b), c && a !== !1 && this.queue(a || "fx", []); + return this.each(function () { + function h(a, b, c) { + var e = b[c]; + f.removeData(a, c, !0), e.stop(d) + } + + var b, c = !1, e = f.timers, g = f._data(this); + d || f._unmark(!0, this); + if (a == null) { + for (b in g) { + g[b] && g[b].stop && b.indexOf(".run") === b.length - 4 && h(this, g, b); + } + } else { + g[b = a + ".run"] && g[b].stop && h(this, g, b); + } + for (b = e.length; b--;) { + e[b].elem === this && (a == null || e[b].queue === a) && (d ? e[b](!0) : e[b].saveState(), c = !0, e.splice(b, 1)); + } + (!d || !c) && f.dequeue(this, a) + }) + }}), f.each({slideDown:ct("show", 1), slideUp:ct("hide", 1), slideToggle:ct("toggle", 1), fadeIn:{opacity:"show"}, fadeOut:{opacity:"hide"}, fadeToggle:{opacity:"toggle"}}, function (a, b) { + f.fn[a] = function (a, c, d) { + return this.animate(b, a, c, d) + } + }), f.extend({speed:function (a, b, c) { + var d = a && typeof a == "object" ? f.extend({}, a) : {complete:c || !c && b || f.isFunction(a) && a, duration:a, easing:c && b || b && !f.isFunction(b) && b}; + d.duration = f.fx.off ? 0 : typeof d.duration == "number" ? d.duration : d.duration in f.fx.speeds ? f.fx.speeds[d.duration] : f.fx.speeds._default; + if (d.queue == null || d.queue === !0) { + d.queue = "fx"; + } + d.old = d.complete, d.complete = function (a) { + f.isFunction(d.old) && d.old.call(this), d.queue ? f.dequeue(this, d.queue) : a !== !1 && f._unmark(this) + }; + return d + }, easing:{linear:function (a) { + return a + }, swing:function (a) { + return-Math.cos(a * Math.PI) / 2 + .5 + }}, timers:[], fx:function (a, b, c) { + this.options = b, this.elem = a, this.prop = c, b.orig = b.orig || {} + }}), f.fx.prototype = {update:function () { + this.options.step && this.options.step.call(this.elem, this.now, this), (f.fx.step[this.prop] || f.fx.step._default)(this) + }, cur:function () { + if (this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null)) { + return this.elem[this.prop]; + } + var a, b = f.css(this.elem, this.prop); + return isNaN(a = parseFloat(b)) ? !b || b === "auto" ? 0 : b : a + }, custom:function (a, c, d) { + function h(a) { + return e.step(a) + } + + var e = this, g = f.fx; + this.startTime = cq || cr(), this.end = c, this.now = this.start = a, this.pos = this.state = 0, this.unit = d || this.unit || (f.cssNumber[this.prop] ? "" : "px"), h.queue = this.options.queue, h.elem = this.elem, h.saveState = function () { + f._data(e.elem, "fxshow" + e.prop) === b && (e.options.hide ? f._data(e.elem, "fxshow" + e.prop, e.start) : e.options.show && f._data(e.elem, "fxshow" + e.prop, e.end)) + }, h() && f.timers.push(h) && !co && (co = setInterval(g.tick, g.interval)) + }, show:function () { + var a = f._data(this.elem, "fxshow" + this.prop); + this.options.orig[this.prop] = a || f.style(this.elem, this.prop), this.options.show = !0, a !== b ? this.custom(this.cur(), a) : this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()), f(this.elem).show() + }, hide:function () { + this.options.orig[this.prop] = f._data(this.elem, "fxshow" + this.prop) || f.style(this.elem, this.prop), this.options.hide = !0, this.custom(this.cur(), 0) + }, step:function (a) { + var b, c, d, e = cq || cr(), g = !0, h = this.elem, i = this.options; + if (a || e >= i.duration + this.startTime) { + this.now = this.end, this.pos = this.state = 1, this.update(), i.animatedProperties[this.prop] = !0; + for (b in i.animatedProperties) { + i.animatedProperties[b] !== !0 && (g = !1); + } + if (g) { + i.overflow != null && !f.support.shrinkWrapBlocks && f.each(["", "X", "Y"], function (a, b) { + h.style["overflow" + b] = i.overflow[a] + }), i.hide && f(h).hide(); + if (i.hide || i.show) { + for (b in i.animatedProperties) { + f.style(h, b, i.orig[b]), f.removeData(h, "fxshow" + b, !0), f.removeData(h, "toggle" + b, !0); + } + } + d = i.complete, d && (i.complete = !1, d.call(h)) + } + return!1 + } + i.duration == Infinity ? this.now = e : (c = e - this.startTime, this.state = c / i.duration, this.pos = f.easing[i.animatedProperties[this.prop]](this.state, c, 0, 1, i.duration), this.now = this.start + (this.end - this.start) * this.pos), this.update(); + return!0 + }}, f.extend(f.fx, {tick:function () { + var a, b = f.timers, c = 0; + for (; c < b.length; c++) { + a = b[c], !a() && b[c] === a && b.splice(c--, 1); + } + b.length || f.fx.stop() + }, interval:13, stop:function () { + clearInterval(co), co = null + }, speeds:{slow:600, fast:200, _default:400}, step:{opacity:function (a) { + f.style(a.elem, "opacity", a.now) + }, _default:function (a) { + a.elem.style && a.elem.style[a.prop] != null ? a.elem.style[a.prop] = a.now + a.unit : a.elem[a.prop] = a.now + }}}), f.each(cp.concat.apply([], cp), function (a, b) { + b.indexOf("margin") && (f.fx.step[b] = function (a) { + f.style(a.elem, b, Math.max(0, a.now) + a.unit) + }) + }), f.expr && f.expr.filters && (f.expr.filters.animated = function (a) { + return f.grep(f.timers,function (b) { + return a === b.elem + }).length + }); + var cv, cw = /^t(?:able|d|h)$/i, cx = /^(?:body|html)$/i; + "getBoundingClientRect"in c.documentElement ? cv = function (a, b, c, d) { + try { + d = a.getBoundingClientRect() + } catch (e) { + } + if (!d || !f.contains(c, a)) { + return d ? {top:d.top, left:d.left} : {top:0, left:0}; + } + var g = b.body, h = cy(b), i = c.clientTop || g.clientTop || 0, j = c.clientLeft || g.clientLeft || 0, k = h.pageYOffset || f.support.boxModel && c.scrollTop || g.scrollTop, l = h.pageXOffset || f.support.boxModel && c.scrollLeft || g.scrollLeft, m = d.top + k - i, n = d.left + l - j; + return{top:m, left:n} + } : cv = function (a, b, c) { + var d, e = a.offsetParent, g = a, h = b.body, i = b.defaultView, j = i ? i.getComputedStyle(a, null) : a.currentStyle, k = a.offsetTop, l = a.offsetLeft; + while ((a = a.parentNode) && a !== h && a !== c) { + if (f.support.fixedPosition && j.position === "fixed") { + break; + } + d = i ? i.getComputedStyle(a, null) : a.currentStyle, k -= a.scrollTop, l -= a.scrollLeft, a === e && (k += a.offsetTop, l += a.offsetLeft, f.support.doesNotAddBorder && (!f.support.doesAddBorderForTableAndCells || !cw.test(a.nodeName)) && (k += parseFloat(d.borderTopWidth) || 0, l += parseFloat(d.borderLeftWidth) || 0), g = e, e = a.offsetParent), f.support.subtractsBorderForOverflowNotVisible && d.overflow !== "visible" && (k += parseFloat(d.borderTopWidth) || 0, l += parseFloat(d.borderLeftWidth) || 0), j = d + } + if (j.position === "relative" || j.position === "static") { + k += h.offsetTop, l += h.offsetLeft; + } + f.support.fixedPosition && j.position === "fixed" && (k += Math.max(c.scrollTop, h.scrollTop), l += Math.max(c.scrollLeft, h.scrollLeft)); + return{top:k, left:l} + }, f.fn.offset = function (a) { + if (arguments.length) { + return a === b ? this : this.each(function (b) { + f.offset.setOffset(this, a, b) + }); + } + var c = this[0], d = c && c.ownerDocument; + if (!d) { + return null; + } + if (c === d.body) { + return f.offset.bodyOffset(c); + } + return cv(c, d, d.documentElement) + }, f.offset = {bodyOffset:function (a) { + var b = a.offsetTop, c = a.offsetLeft; + f.support.doesNotIncludeMarginInBodyOffset && (b += parseFloat(f.css(a, "marginTop")) || 0, c += parseFloat(f.css(a, "marginLeft")) || 0); + return{top:b, left:c} + }, setOffset:function (a, b, c) { + var d = f.css(a, "position"); + d === "static" && (a.style.position = "relative"); + var e = f(a), g = e.offset(), h = f.css(a, "top"), i = f.css(a, "left"), j = (d === "absolute" || d === "fixed") && f.inArray("auto", [h, i]) > -1, k = {}, l = {}, m, n; + j ? (l = e.position(), m = l.top, n = l.left) : (m = parseFloat(h) || 0, n = parseFloat(i) || 0), f.isFunction(b) && (b = b.call(a, c, g)), b.top != null && (k.top = b.top - g.top + m), b.left != null && (k.left = b.left - g.left + n), "using"in b ? b.using.call(a, k) : e.css(k) + }}, f.fn.extend({position:function () { + if (!this[0]) { + return null; + } + var a = this[0], b = this.offsetParent(), c = this.offset(), d = cx.test(b[0].nodeName) ? {top:0, left:0} : b.offset(); + c.top -= parseFloat(f.css(a, "marginTop")) || 0, c.left -= parseFloat(f.css(a, "marginLeft")) || 0, d.top += parseFloat(f.css(b[0], "borderTopWidth")) || 0, d.left += parseFloat(f.css(b[0], "borderLeftWidth")) || 0; + return{top:c.top - d.top, left:c.left - d.left} + }, offsetParent:function () { + return this.map(function () { + var a = this.offsetParent || c.body; + while (a && !cx.test(a.nodeName) && f.css(a, "position") === "static") { + a = a.offsetParent; + } + return a + }) + }}), f.each({scrollLeft:"pageXOffset", scrollTop:"pageYOffset"}, function (a, c) { + var d = /Y/.test(c); + f.fn[a] = function (e) { + return f.access(this, function (a, e, g) { + var h = cy(a); + if (g === b) { + return h ? c in h ? h[c] : f.support.boxModel && h.document.documentElement[e] || h.document.body[e] : a[e]; + } + h ? h.scrollTo(d ? f(h).scrollLeft() : g, d ? g : f(h).scrollTop()) : a[e] = g + }, a, e, arguments.length, null) + } + }), f.each({Height:"height", Width:"width"}, function (a, c) { + var d = "client" + a, e = "scroll" + a, g = "offset" + a; + f.fn["inner" + a] = function () { + var a = this[0]; + return a ? a.style ? parseFloat(f.css(a, c, "padding")) : this[c]() : null + }, f.fn["outer" + a] = function (a) { + var b = this[0]; + return b ? b.style ? parseFloat(f.css(b, c, a ? "margin" : "border")) : this[c]() : null + }, f.fn[c] = function (a) { + return f.access(this, function (a, c, h) { + var i, j, k, l; + if (f.isWindow(a)) { + i = a.document, j = i.documentElement[d]; + return f.support.boxModel && j || i.body && i.body[d] || j + } + if (a.nodeType === 9) { + i = a.documentElement; + if (i[d] >= i[e]) { + return i[d]; + } + return Math.max(a.body[e], i[e], a.body[g], i[g]) + } + if (h === b) { + k = f.css(a, c), l = parseFloat(k); + return f.isNumeric(l) ? l : k + } + f(a).css(c, h) + }, c, a, arguments.length, null) + } + }), a.jQuery = a.$ = f, typeof define == "function" && define.amd && define.amd.jQuery && define("jquery", [], function () { + return f + }) +})(window); \ No newline at end of file diff --git a/lib/conf/default.json b/lib/conf/default.json index 19a03bb13..f20e817f7 100644 --- a/lib/conf/default.json +++ b/lib/conf/default.json @@ -1,79 +1,79 @@ { - "version": "0.3.0", - "installed": false, - "database": { - "uri": "mongodb://localhost/calipso" - }, - "authentication": { - "password": true, - "migrate2pbkdf2": false - }, - "server": { - "name": "Calipso", - "url": "http://localhost:3000", - "modulePath": "./modules", - "themePath": "./themes", - "cluster": { - "workers": 0, - "restartWorkers": true, - "maximumRestarts": 1 - }, - "events": { - "maxListeners": 500 - } - }, - "calipso": { - "repo": { - "url": "http://calip.so/repo/api", - "defaultVersion": "master" - } - }, - "performance": { - "cache": { - "enabled": false, - "ttl": 600 - }, - "watchFiles": true - }, - "session": { - "secret": "calipso" - }, - "theme": { - "default": "cleanslate", - "front": "cleanslate", - "admin": "cleanslate" - }, - "i18n": { - "language": "en", - "additive": true - }, - "logging": { - "console": { - "enabled": true, - "level": "info", - "timestamp": true, - "colorize": true - }, - "file": { - "enabled": false, - "level": "info", - "filepath": "logs/calipso.log", - "timestamp": true - } - }, - "libraries": { - "stylus": { - "enable": false, - "warn": true, - "compress": false - }, - "formidable": { - "keepExtensions": true - } - }, - "modules": { - "admin": { - "enabled": true - } - } + "version":"0.3.0", + "installed":false, + "database":{ + "uri":"mongodb://localhost/calipso" + }, + "authentication":{ + "password":true, + "migrate2pbkdf2":false + }, + "server":{ + "name":"Calipso", + "url":"http://localhost:3000", + "modulePath":"./modules", + "themePath":"./themes", + "cluster":{ + "workers":0, + "restartWorkers":true, + "maximumRestarts":1 + }, + "events":{ + "maxListeners":500 + } + }, + "calipso":{ + "repo":{ + "url":"http://calip.so/repo/api", + "defaultVersion":"master" + } + }, + "performance":{ + "cache":{ + "enabled":false, + "ttl":600 + }, + "watchFiles":true + }, + "session":{ + "secret":"calipso" + }, + "theme":{ + "default":"cleanslate", + "front":"cleanslate", + "admin":"cleanslate" + }, + "i18n":{ + "language":"en", + "additive":true + }, + "logging":{ + "console":{ + "enabled":true, + "level":"info", + "timestamp":true, + "colorize":true + }, + "file":{ + "enabled":false, + "level":"info", + "filepath":"logs/calipso.log", + "timestamp":true + } + }, + "libraries":{ + "stylus":{ + "enable":false, + "warn":true, + "compress":false + }, + "formidable":{ + "keepExtensions":true + } + }, + "modules":{ + "admin":{ + "enabled":true + } + } } \ No newline at end of file diff --git a/lib/core/Blocks.js b/lib/core/Blocks.js index 72d582373..b8df5fd67 100644 --- a/lib/core/Blocks.js +++ b/lib/core/Blocks.js @@ -33,7 +33,7 @@ function RenderedBlocks(cache) { /** * Set block content */ -RenderedBlocks.prototype.set = function(block, content, layout, params, next) { +RenderedBlocks.prototype.set = function (block, content, layout, params, next) { var cacheKey = calipso.cacheService.getCacheKey(['block', block], params); @@ -44,8 +44,8 @@ RenderedBlocks.prototype.set = function(block, content, layout, params, next) { if (this.contentCache[block]) { calipso.silly("Cache set for " + cacheKey); this.cache.set(cacheKey, { - content: content, - layout: layout + content:content, + layout:layout }, null, next); } else { next(); @@ -56,7 +56,7 @@ RenderedBlocks.prototype.set = function(block, content, layout, params, next) { /** * Get block content */ -RenderedBlocks.prototype.get = function(key, next) { +RenderedBlocks.prototype.get = function (key, next) { // Check to see if the key is a regex, for 0.4 and 0.5 nodej if (typeof key === 'object' || typeof key === "function") { @@ -78,12 +78,12 @@ RenderedBlocks.prototype.get = function(key, next) { /** * Get content from cache and load into block */ -RenderedBlocks.prototype.getCache = function(key, block, next) { +RenderedBlocks.prototype.getCache = function (key, block, next) { calipso.silly("Cache hit for block " + key); var self = this; - this.cache.get(key, function(err, cache) { + this.cache.get(key, function (err, cache) { self.content[block] = self.content[block] || []; self.content[block].push(cache.content); diff --git a/lib/core/Configuration.js b/lib/core/Configuration.js index 24f7ed8ee..f36efce7d 100644 --- a/lib/core/Configuration.js +++ b/lib/core/Configuration.js @@ -28,10 +28,14 @@ function Configuration(options) { } -Configuration.prototype.init = function(next) { - if (typeof next !== 'function') next = function(err) { - if (err) console.error(err.message) - }; +Configuration.prototype.init = function (next) { + if (typeof next !== 'function') { + next = function (err) { + if (err) { + console.error(err.message) + } + }; + } var Provider = require('nconf').Provider; this.nconf = new Provider(); this.load(next); @@ -41,7 +45,7 @@ Configuration.prototype.init = function(next) { * Check to see if configuration for this environment * doesn't exist, if so, it loads the default from default.json. */ -Configuration.prototype.check = function() { +Configuration.prototype.check = function () { if (!(fs.existsSync || path.existsSync)(this.file)) { try { @@ -52,9 +56,9 @@ Configuration.prototype.check = function() { } catch (ex) { return ex.message; } - return; + return null; } else { - return; + return null; } } @@ -62,7 +66,7 @@ Configuration.prototype.check = function() { /** * Load the configuration */ -Configuration.prototype.load = function(next) { +Configuration.prototype.load = function (next) { // Check if config exists for this environment or default it var checkConfig = this.check(); @@ -88,14 +92,14 @@ Configuration.prototype.load = function(next) { /** * Get config - wrapper */ -Configuration.prototype.get = function(key) { +Configuration.prototype.get = function (key) { return this.nconf.get(key); } /** * Get config for module - wrapper */ -Configuration.prototype.getModuleConfig = function(moduleName, key) { +Configuration.prototype.getModuleConfig = function (moduleName, key) { var moduleKey = 'modules:' + moduleName + ':config' + (key ? ':' + key : ''); return this.nconf.get(moduleKey); } @@ -104,7 +108,7 @@ Configuration.prototype.getModuleConfig = function(moduleName, key) { /** * Set config */ -Configuration.prototype.set = function(key, value) { +Configuration.prototype.set = function (key, value) { this.dirty = true; this.nconf.set(key, value); } @@ -112,7 +116,7 @@ Configuration.prototype.set = function(key, value) { /** * Set config for module - wrapper */ -Configuration.prototype.setModuleConfig = function(moduleName, key, value) { +Configuration.prototype.setModuleConfig = function (moduleName, key, value) { var moduleKey = 'modules:' + moduleName + ':config' + (key ? ':' + key : ''); this.dirty = true; this.nconf.set(moduleKey, value); @@ -121,15 +125,15 @@ Configuration.prototype.setModuleConfig = function(moduleName, key, value) { /** * Set default config for module - wrapper */ -Configuration.prototype.setDefaultModuleConfig = function(moduleName, config) { +Configuration.prototype.setDefaultModuleConfig = function (moduleName, config) { var moduleKey = 'modules:' + moduleName + ':config'; this.dirty = true; // Extract the defaults from the config - var defaultConfig = _.reduce(_.keys(config), function(memo, key) { + var defaultConfig = _.reduce(_.keys(config), function (memo, key) { memo[key] = config[key]. - default; + default; return memo; }, {}) @@ -140,7 +144,7 @@ Configuration.prototype.setDefaultModuleConfig = function(moduleName, config) { /** * Save config */ -Configuration.prototype.save = function(next) { +Configuration.prototype.save = function (next) { this.dirty = false; this.nconf.save(next); } @@ -149,7 +153,7 @@ Configuration.prototype.save = function(next) { * Set & save config */ -Configuration.prototype.setSave = function(key, value, next) { +Configuration.prototype.setSave = function (key, value, next) { this.set(key, value); this.dirty = false; this.save(next); diff --git a/lib/core/Date.js b/lib/core/Date.js index 5151c2e15..9b5449520 100644 --- a/lib/core/Date.js +++ b/lib/core/Date.js @@ -20,35 +20,35 @@ function CalipsoDate() { this.regional = []; // Available regional settings, indexed by language code this.regional[''] = { // Default regional settings - closeText: 'Done', + closeText:'Done', // Display text for close link - prevText: 'Prev', + prevText:'Prev', // Display text for previous month link - nextText: 'Next', + nextText:'Next', // Display text for next month link - currentText: 'Today', + currentText:'Today', // Display text for current month link - monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + monthNames:['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], // Names of months for drop-down and formatting - monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + monthNamesShort:['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], // For formatting - dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + dayNames:['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], // For formatting - dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + dayNamesShort:['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], // For formatting - dayNamesMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], + dayNamesMin:['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], // Column headings for days starting at Sunday - weekHeader: 'Wk', + weekHeader:'Wk', // Column header for week of the year - dateFormat: 'mm/dd/yy', + dateFormat:'mm/dd/yy', // See format options on parseDate - firstDay: 0, + firstDay:0, // The first day of the week, Sun = 0, Mon = 1, ... - isRTL: false, + isRTL:false, // True if right-to-left language, false if left-to-right - showMonthAfterYear: false, + showMonthAfterYear:false, // True if the year select precedes month, false for month then year - yearSuffix: '' // Additional text to append to the year in the month headers + yearSuffix:'' // Additional text to append to the year in the month headers }; this._defaults = this.regional['']; @@ -71,21 +71,25 @@ function CalipsoDate() { } /* Parse a string value into a date object. - See formatDate below for the possible formats. + See formatDate below for the possible formats. - @param format string - the expected format of the date - @param value string - the date in the above format - @param settings Object - attributes include: - shortYearCutoff number - the cutoff year for determining the century (optional) - dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) - dayNames string[7] - names of the days from Sunday (optional) - monthNamesShort string[12] - abbreviated names of the months (optional) - monthNames string[12] - names of the months (optional) - @return Date - the extracted date value or null if value is blank */ -CalipsoDate.prototype.parseDate = function(format, value, settings) { - if (format == null || value == null) throw 'Invalid arguments'; + @param format string - the expected format of the date + @param value string - the date in the above format + @param settings Object - attributes include: + shortYearCutoff number - the cutoff year for determining the century (optional) + dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + dayNames string[7] - names of the days from Sunday (optional) + monthNamesShort string[12] - abbreviated names of the months (optional) + monthNames string[12] - names of the months (optional) + @return Date - the extracted date value or null if value is blank */ +CalipsoDate.prototype.parseDate = function (format, value, settings) { + if (format == null || value == null) { + throw 'Invalid arguments'; + } value = (typeof value == 'object' ? value.toString() : value + ''); - if (value == '') return null; + if (value == '') { + return null; + } var shortYearCutoff = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff; shortYearCutoff = (typeof shortYearCutoff != 'string' ? shortYearCutoff : new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10)); var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort; @@ -98,195 +102,248 @@ CalipsoDate.prototype.parseDate = function(format, value, settings) { var doy = -1; var literal = false; // Check whether a format character is doubled - var lookAhead = function(match) { - var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); - if (matches) iFormat++; - return matches; - }; + var lookAhead = function (match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); + if (matches) { + iFormat++; + } + return matches; + }; // Extract a number from the string value - var getNumber = function(match) { - var isDoubled = lookAhead(match); - var size = (match == '@' ? 14 : (match == '!' ? 20 : (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2)))); - var digits = new RegExp('^\\d{1,' + size + '}'); - var num = value.substring(iValue).match(digits); - if (!num) throw 'Missing number at position ' + iValue; - iValue += num[0].length; - return parseInt(num[0], 10); - }; + var getNumber = function (match) { + var isDoubled = lookAhead(match); + var size = (match == '@' ? 14 : (match == '!' ? 20 : (match == 'y' && isDoubled ? 4 : (match == 'o' ? 3 : 2)))); + var digits = new RegExp('^\\d{1,' + size + '}'); + var num = value.substring(iValue).match(digits); + if (!num) { + throw 'Missing number at position ' + iValue; + } + iValue += num[0].length; + return parseInt(num[0], 10); + }; // Extract a name from the string value and convert to an index - var getName = function(match, shortNames, longNames) { - var names = $.map(lookAhead(match) ? longNames : shortNames, function(v, k) { - return [[k, v]]; - }).sort(function(a, b) { + var getName = function (match, shortNames, longNames) { + var names = $.map(lookAhead(match) ? longNames : shortNames,function (v, k) { + return [ + [k, v] + ]; + }).sort(function (a, b) { return -(a[1].length - b[1].length); }); - var index = -1; - $.each(names, function(i, pair) { - var name = pair[1]; - if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) { - index = pair[0]; - iValue += name.length; - return false; - } - }); - if (index != -1) return index + 1; - else throw 'Unknown name at position ' + iValue; - }; + var index = -1; + $.each(names, function (i, pair) { + var name = pair[1]; + if (value.substr(iValue, name.length).toLowerCase() == name.toLowerCase()) { + index = pair[0]; + iValue += name.length; + return false; + } + }); + if (index != -1) { + return index + 1; + } + else { + throw 'Unknown name at position ' + iValue; + } + }; // Confirm that a literal character matches the string value - var checkLiteral = function() { - if (value.charAt(iValue) != format.charAt(iFormat)) throw 'Unexpected literal at position ' + iValue; - iValue++; - }; + var checkLiteral = function () { + if (value.charAt(iValue) != format.charAt(iFormat)) { + throw 'Unexpected literal at position ' + iValue; + } + iValue++; + }; var iValue = 0; for (var iFormat = 0; iFormat < format.length; iFormat++) { - if (literal) if (format.charAt(iFormat) == "'" && !lookAhead("'")) literal = false; - else checkLiteral(); - else switch (format.charAt(iFormat)) { - case 'd': - day = getNumber('d'); - break; - case 'D': - getName('D', dayNamesShort, dayNames); - break; - case 'o': - doy = getNumber('o'); - break; - case 'm': - month = getNumber('m'); - break; - case 'M': - month = getName('M', monthNamesShort, monthNames); - break; - case 'y': - year = getNumber('y'); - break; - case '@': - var date = new Date(getNumber('@')); - year = date.getFullYear(); - month = date.getMonth() + 1; - day = date.getDate(); - break; - case '!': - var date = new Date((getNumber('!') - this._ticksTo1970) / 10000); - year = date.getFullYear(); - month = date.getMonth() + 1; - day = date.getDate(); - break; - case "'": - if (lookAhead("'")) checkLiteral(); - else literal = true; - break; - default: - checkLiteral(); + if (literal) { + if (format.charAt(iFormat) == "'" && !lookAhead("'")) { + literal = false; + } + else { + checkLiteral(); + } } + else { + switch (format.charAt(iFormat)) { + case 'd': + day = getNumber('d'); + break; + case 'D': + getName('D', dayNamesShort, dayNames); + break; + case 'o': + doy = getNumber('o'); + break; + case 'm': + month = getNumber('m'); + break; + case 'M': + month = getName('M', monthNamesShort, monthNames); + break; + case 'y': + year = getNumber('y'); + break; + case '@': + var date = new Date(getNumber('@')); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case '!': + var date = new Date((getNumber('!') - this._ticksTo1970) / 10000); + year = date.getFullYear(); + month = date.getMonth() + 1; + day = date.getDate(); + break; + case "'": + if (lookAhead("'")) { + checkLiteral(); + } + else { + literal = true; + } + break; + default: + checkLiteral(); + } + } + } + if (year == -1) { + year = new Date().getFullYear(); + } + else if (year < 100) { + year += new Date().getFullYear() - new Date().getFullYear() % 100 + (year <= shortYearCutoff ? 0 : -100); } - if (year == -1) year = new Date().getFullYear(); - else if (year < 100) year += new Date().getFullYear() - new Date().getFullYear() % 100 + (year <= shortYearCutoff ? 0 : -100); if (doy > -1) { month = 1; day = doy; do { var dim = this._getDaysInMonth(year, month - 1); - if (day <= dim) break; + if (day <= dim) { + break; + } month++; day -= dim; } while (true); } var date = this._daylightSavingAdjust(new Date(year, month - 1, day)); - if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) throw 'Invalid date'; // E.g. 31/02/00 + if (date.getFullYear() != year || date.getMonth() + 1 != month || date.getDate() != day) { + throw 'Invalid date'; + } // E.g. 31/02/00 return date; } /* - Format a date object into a string value. + Format a date object into a string value. - The format can be combinations of the following + The format can be combinations of the following - d - day of month (no leading zero) - dd - day of month (two digit) - o - day of year (no leading zeros) - oo - day of year (three digit) - D - day name short - DD - day name long - m - month of year (no leading zero) - mm - month of year (two digit) - M - month name short - MM - month name long - y - year (two digit) - yy - year (four digit) - @ - Unix timestamp (ms since 01/01/1970) - ! - Windows ticks (100ns since 01/01/0001) - '...' - literal text - '' - single quote + d - day of month (no leading zero) + dd - day of month (two digit) + o - day of year (no leading zeros) + oo - day of year (three digit) + D - day name short + DD - day name long + m - month of year (no leading zero) + mm - month of year (two digit) + M - month name short + MM - month name long + y - year (two digit) + yy - year (four digit) + @ - Unix timestamp (ms since 01/01/1970) + ! - Windows ticks (100ns since 01/01/0001) + '...' - literal text + '' - single quote - @param format string - the desired format of the date - @param date Date - the date value to format - @param settings Object - attributes include: - dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) - dayNames string[7] - names of the days from Sunday (optional) - monthNamesShort string[12] - abbreviated names of the months (optional) - monthNames string[12] - names of the months (optional) - @return string - the date in the above format */ + @param format string - the desired format of the date + @param date Date - the date value to format + @param settings Object - attributes include: + dayNamesShort string[7] - abbreviated names of the days from Sunday (optional) + dayNames string[7] - names of the days from Sunday (optional) + monthNamesShort string[12] - abbreviated names of the months (optional) + monthNames string[12] - names of the months (optional) + @return string - the date in the above format */ -CalipsoDate.prototype.formatDate = function(format, date, settings) { - if (!date) return ''; +CalipsoDate.prototype.formatDate = function (format, date, settings) { + if (!date) { + return ''; + } var dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort; var dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames; var monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort; var monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames; // Check whether a format character is doubled - var lookAhead = function(match) { - var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); - if (matches) iFormat++; - return matches; - }; + var lookAhead = function (match) { + var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) == match); + if (matches) { + iFormat++; + } + return matches; + }; // Format a number, with leading zero if necessary - var formatNumber = function(match, value, len) { - var num = '' + value; - if (lookAhead(match)) while (num.length < len) - num = '0' + num; - return num; - }; + var formatNumber = function (match, value, len) { + var num = '' + value; + if (lookAhead(match)) { + while (num.length < len) { + num = '0' + num; + } + } + return num; + }; // Format a name, short or long as requested - var formatName = function(match, value, shortNames, longNames) { - return (lookAhead(match) ? longNames[value] : shortNames[value]); - }; + var formatName = function (match, value, shortNames, longNames) { + return (lookAhead(match) ? longNames[value] : shortNames[value]); + }; var output = ''; var literal = false; - if (date) for (var iFormat = 0; iFormat < format.length; iFormat++) { - if (literal) if (format.charAt(iFormat) == "'" && !lookAhead("'")) literal = false; - else output += format.charAt(iFormat); - else switch (format.charAt(iFormat)) { - case 'd': - output += formatNumber('d', date.getDate(), 2); - break; - case 'D': - output += formatName('D', date.getDay(), dayNamesShort, dayNames); - break; - case 'o': - output += formatNumber('o', (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000, 3); - break; - case 'm': - output += formatNumber('m', date.getMonth() + 1, 2); - break; - case 'M': - output += formatName('M', date.getMonth(), monthNamesShort, monthNames); - break; - case 'y': - output += (lookAhead('y') ? date.getFullYear() : (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100); - break; - case '@': - output += date.getTime(); - break; - case '!': - output += date.getTime() * 10000 + this._ticksTo1970; - break; - case "'": - if (lookAhead("'")) output += "'"; - else literal = true; - break; - default: - output += format.charAt(iFormat); + if (date) { + for (var iFormat = 0; iFormat < format.length; iFormat++) { + if (literal) { + if (format.charAt(iFormat) == "'" && !lookAhead("'")) { + literal = false; + } + else { + output += format.charAt(iFormat); + } + } + else { + switch (format.charAt(iFormat)) { + case 'd': + output += formatNumber('d', date.getDate(), 2); + break; + case 'D': + output += formatName('D', date.getDay(), dayNamesShort, dayNames); + break; + case 'o': + output += formatNumber('o', (date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000, 3); + break; + case 'm': + output += formatNumber('m', date.getMonth() + 1, 2); + break; + case 'M': + output += formatName('M', date.getMonth(), monthNamesShort, monthNames); + break; + case 'y': + output += (lookAhead('y') ? date.getFullYear() : (date.getYear() % 100 < 10 ? '0' : '') + date.getYear() % 100); + break; + case '@': + output += date.getTime(); + break; + case '!': + output += date.getTime() * 10000 + this._ticksTo1970; + break; + case "'": + if (lookAhead("'")) { + output += "'"; + } + else { + literal = true; + } + break; + default: + output += format.charAt(iFormat); + } + } } } return output; diff --git a/lib/core/Event.js b/lib/core/Event.js index a9aaacb03..964f5240a 100644 --- a/lib/core/Event.js +++ b/lib/core/Event.js @@ -20,14 +20,14 @@ var rootpath = process.cwd() + '/', calipso = require(path.join('..', 'calipso')); exports = module.exports = { - CalipsoEventEmitter: CalipsoEventEmitter, - RequestEventListener: RequestEventListener, - addModuleEventListener: addModuleEventListener, + CalipsoEventEmitter:CalipsoEventEmitter, + RequestEventListener:RequestEventListener, + addModuleEventListener:addModuleEventListener, // Module & Routing Event constants - ROUTE_START: 'route_s', - ROUTE_FINISH: 'route_f', - INIT_START: 'init_s', - INIT_FINISH: 'init_f' + ROUTE_START:'route_s', + ROUTE_FINISH:'route_f', + INIT_START:'init_s', + INIT_FINISH:'init_f' }; @@ -52,7 +52,7 @@ function CalipsoEventEmitter(options) { this.events = {}; // Clear all existing listeners - this.init = function() { + this.init = function () { // Clear down the event emitters for (var event in self.events) { @@ -74,71 +74,71 @@ function CalipsoEventEmitter(options) { }; // Wrapper for event emitter, enable turn on / off - this.addEvent = function(event, options) { + this.addEvent = function (event, options) { options = calipso.lib._.extend({ - enabled: true + enabled:true }, options); this.events[event] = options; // Enable tracking of attached listeners for debugging purposes this.events[event].preListeners = { - '#': 0 + '#':0 }; this.events[event].postListeners = { - '#': 0 + '#':0 }; this.events[event].custom = {}; }; // Pre and post event prefixes var pre_prefix = 'PRE_', - post_prefix = 'POST_'; + post_prefix = 'POST_'; // Register a pre listener - this.pre = function(event, listener, fn) { + this.pre = function (event, listener, fn) { self.emitter.on(pre_prefix + event, fn); this.events[event].preListeners[listener] = this.events[event].preListeners[listener] || []; this.events[event].preListeners[listener].push({ - name: fn.name + name:fn.name }); this.events[event].preListeners['#'] += 1; }; // Register a post listener - this.post = function(event, listener, fn) { + this.post = function (event, listener, fn) { self.emitter.on(post_prefix + event, fn); this.events[event].postListeners[listener] = this.events[event].postListeners[listener] || []; this.events[event].postListeners[listener].push({ - name: fn.name + name:fn.name }); this.events[event].postListeners['#'] += 1; }; // Register a custom event listener - this.custom = function(event, key, listener, fn) { + this.custom = function (event, key, listener, fn) { self.emitter.on(event + '_' + key, fn); // Register under key this.events[event].custom[key] = this.events[event].custom[key] || { - customListeners: { - '#': 0 + customListeners:{ + '#':0 } }; // Register this.events[event].custom[key].customListeners[listener] = this.events[event].custom[key].customListeners[listener] || []; this.events[event].custom[key].customListeners[listener].push({ - name: fn.name + name:fn.name }); this.events[event].custom[key].customListeners['#'] += 1; }; // Emit a pre event - this.pre_emit = function(event, data, next) { + this.pre_emit = function (event, data, next) { var cb; @@ -146,7 +146,8 @@ function CalipsoEventEmitter(options) { if (typeof next === "function") { cb = createCallback(this.events[event].preListeners['#'], data, next); } else { - cb = function() {}; + cb = function () { + }; } if (this.events[event] && this.events[event].enabled) { @@ -156,7 +157,7 @@ function CalipsoEventEmitter(options) { }; // Emit a post event - this.post_emit = function(event, data, next) { + this.post_emit = function (event, data, next) { var cb; @@ -164,7 +165,8 @@ function CalipsoEventEmitter(options) { if (typeof next === "function") { cb = createCallback(this.events[event].postListeners['#'], data, next); } else { - cb = function() {}; + cb = function () { + }; } if (this.events[event] && this.events[event].enabled) { @@ -174,7 +176,7 @@ function CalipsoEventEmitter(options) { }; // Emit a custom event - this.custom_emit = function(event, key, data, next) { + this.custom_emit = function (event, key, data, next) { var cb; @@ -184,7 +186,8 @@ function CalipsoEventEmitter(options) { if (typeof next === "function") { cb = createCallback(this.events[event].custom[key].customListeners['#'], data, next); } else { - cb = function() {}; + cb = function () { + }; } self.emitter.emit(event + '_' + key, event + '_' + key, data, cb); @@ -200,10 +203,12 @@ function CalipsoEventEmitter(options) { function createCallback(total, data, callback) { var count = 0, - total = total, - outputStack = []; + total = total, + outputStack = []; - if (data) outputStack.push(data); + if (data) { + outputStack.push(data); + } // No listeners, so callback immediately if (total === 0) { @@ -211,11 +216,13 @@ function CalipsoEventEmitter(options) { return; } - return function(data) { + return function (data) { count += 1; - if (data) outputStack.push(data); + if (data) { + outputStack.push(data); + } // Merge the outputs from the stack if (count === total) { @@ -248,11 +255,11 @@ function ModuleInitEventEmitter(moduleName, options) { var maxListeners = self.options.maxListeners || 100; this.setMaxListeners(maxListeners); - this.init_start = function(options) { + this.init_start = function (options) { self.emit(exports.INIT_START, self.moduleName, options); }; - this.init_finish = function(options) { + this.init_finish = function (options) { self.emit(exports.INIT_FINISH, self.moduleName, options); }; @@ -271,14 +278,15 @@ function addModuleEventListener(module, options) { options = options || {}; var moduleEventEmitter = module.event = new ModuleInitEventEmitter(module.name, options), - notifyDependencyFn = options.notifyDependencyFn || function() {}; + notifyDependencyFn = options.notifyDependencyFn || function () { + }; // Link events - moduleEventEmitter.once(exports.INIT_START, function(moduleName, options) { + moduleEventEmitter.once(exports.INIT_START, function (moduleName, options) { // Do nothing }); - moduleEventEmitter.once(exports.INIT_FINISH, function(moduleName, options) { + moduleEventEmitter.once(exports.INIT_FINISH, function (moduleName, options) { // Check for dependent modules, init them notifyDependencyFn(moduleName, options); }); @@ -304,11 +312,11 @@ function ModuleRequestEventEmitter(moduleName, options) { var maxListeners = self.options.maxListeners || 100; this.setMaxListeners(maxListeners); - this.route_start = function(options) { + this.route_start = function (options) { self.emit(exports.ROUTE_START, self.moduleName, options); }; - this.route_finish = function(options) { + this.route_finish = function (options) { self.emit(exports.ROUTE_FINISH, self.moduleName, options); }; @@ -326,14 +334,16 @@ function RequestEventListener(options) { // Register a module, listen to its events var self = this, - notifyDependencyFn = options.notifyDependencyFn || function() {}, - registerDependenciesFn = options.registerDependenciesFn || function() {}; + notifyDependencyFn = options.notifyDependencyFn || function () { + }, + registerDependenciesFn = options.registerDependenciesFn || function () { + }; // Local hash of module event emitters, used to track routing status this.modules = {}; // Register a module - this.registerModule = function(req, res, moduleName, options) { + this.registerModule = function (req, res, moduleName, options) { // Register event emitter var moduleEventEmitter = self.modules[moduleName] = new ModuleRequestEventEmitter(moduleName, options); @@ -343,19 +353,19 @@ function RequestEventListener(options) { self.modules[moduleName].check = {}; // Hash of dependent modules to check if initialised // Curried function to notify dependent modules that we have finished - var notifyDependencies = function(moduleName) { - notifyDependencyFn(req, res, moduleName, self.modules); + var notifyDependencies = function (moduleName) { + notifyDependencyFn(req, res, moduleName, self.modules); }; registerDependenciesFn(self, moduleName); - + // Start - moduleEventEmitter.once(exports.ROUTE_START, function(moduleName, options) { + moduleEventEmitter.once(exports.ROUTE_START, function (moduleName, options) { self.modules[moduleName].start = new Date(); }); // Finish - moduleEventEmitter.once(exports.ROUTE_FINISH, function(moduleName, options) { + moduleEventEmitter.once(exports.ROUTE_FINISH, function (moduleName, options) { self.modules[moduleName].finish = new Date(); self.modules[moduleName].duration = self.modules[moduleName].finish - self.modules[moduleName].start; @@ -384,7 +394,7 @@ util.inherits(ModuleRequestEventEmitter, events.EventEmitter); function mergeArray(arr, first) { var output = {}; - arr.forEach(function(value, key) { + arr.forEach(function (value, key) { if (first) { output = merge(value, output); } else { diff --git a/lib/core/Form.js b/lib/core/Form.js index 7f63c7b9c..384c43a92 100644 --- a/lib/core/Form.js +++ b/lib/core/Form.js @@ -17,11 +17,12 @@ * */ + var rootpath = process.cwd() + '/', - path = require('path'), - calipso = require(path.join('..', 'calipso')), - qs = require('qs'), - merge = require('connect').utils.merge; + path = require('path'), + calipso = require(path.join('..', 'calipso')), + qs = require('qs'), + merge = require('connect').utils.merge; // Global variable (in this context) for translation function var t; @@ -30,19 +31,18 @@ var t; * The default calipso form object, with default configuration values. * Constructor */ -function Form() -{ +function Form() { - // TODO - tagStyle should also affect whether attributes can be minimised ('selected' vs. 'selected="selected"') + // TODO - tagStyle should also affect whether attributes can be minimised ('selected' vs. 'selected="selected"') - // tagStyle should be one of [html, xhtml, xml] - this.tagStyle = "html"; + // tagStyle should be one of [html, xhtml, xml] + this.tagStyle = "html"; - // adjust the way tags are closed based on the given tagStyle. - this.tagClose = this.tagStyle == "html" ? '>' : ' />'; + // adjust the way tags are closed based on the given tagStyle. + this.tagClose = this.tagStyle == "html" ? '>' : ' />'; - // cheap way of ensuring unique radio ids - this.radioCount = 0; + // cheap way of ensuring unique radio ids + this.radioCount = 0; } @@ -76,101 +76,102 @@ var me = Form.prototype; // if complete for every country, this will be a lot of data and should // probably be broken out to a separate file. me.countries = [ - "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", - "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", - "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", - "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina", - "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", - "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic", - "Chad", "Chile", "China (People's Republic of China)", "Colombia", "Comoros", - "Democratic Republic of the Congo", "Republic of the Congo", - "Costa Rica", "Côte d'Ivoire", "Croatia", "Cuba", "Cyprus", - "Czech Republic", "Denmark, the Kingdom of", "Djibouti", "Dominica", - "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador", - "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Fiji", - "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", - "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", - "Haiti", "Honduras", "Hungary", "Iceland", "India", "Indonesia", "Iran", - "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", - "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", - "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", - "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", - "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", - "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", - "Federated States of Micronesia", "Moldova", "Monaco", "Mongolia", - "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", - "Nepal", "Netherlands, the Kingdom of", "New Zealand", "Nicaragua", "Niger", - "Nigeria", "Norway", "Oman", "Pakistan", "Palau", "Palestinian territories", - "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", - "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", - "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino", - "São Tomé and Príncipe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", - "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", - "Somalia", "South Africa", "South Sudan", "Spain", "Sri Lanka", "Sudan", - "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", - "Taiwan (Republic of China)", "Tajikistan", "Tanzania", "Thailand", "Togo", - "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", - "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", - "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City", - "Venezuela", "Vietnam", "Western Sahara", "Yemen", "Zambia", "Zimbabwe" + "Afghanistan", "Albania", "Algeria", "Andorra", "Angola", + "Antigua and Barbuda", "Argentina", "Armenia", "Australia", "Austria", + "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", + "Belgium", "Belize", "Benin", "Bhutan", "Bolivia", "Bosnia and Herzegovina", + "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", + "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Republic", + "Chad", "Chile", "China (People's Republic of China)", "Colombia", "Comoros", + "Democratic Republic of the Congo", "Republic of the Congo", + "Costa Rica", "Côte d'Ivoire", "Croatia", "Cuba", "Cyprus", + "Czech Republic", "Denmark, the Kingdom of", "Djibouti", "Dominica", + "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador", + "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Fiji", + "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", + "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", + "Haiti", "Honduras", "Hungary", "Iceland", "India", "Indonesia", "Iran", + "Iraq", "Ireland", "Israel", "Italy", "Jamaica", "Japan", "Jordan", + "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", + "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", + "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", + "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", + "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", + "Federated States of Micronesia", "Moldova", "Monaco", "Mongolia", + "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", + "Nepal", "Netherlands, the Kingdom of", "New Zealand", "Nicaragua", "Niger", + "Nigeria", "Norway", "Oman", "Pakistan", "Palau", "Palestinian territories", + "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", + "Portugal", "Qatar", "Romania", "Russia", "Rwanda", "Saint Kitts and Nevis", + "Saint Lucia", "Saint Vincent and the Grenadines", "Samoa", "San Marino", + "São Tomé and Príncipe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", + "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", + "Somalia", "South Africa", "South Sudan", "Spain", "Sri Lanka", "Sudan", + "Suriname", "Swaziland", "Sweden", "Switzerland", "Syria", + "Taiwan (Republic of China)", "Tajikistan", "Tanzania", "Thailand", "Togo", + "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", + "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", + "United States", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City", + "Venezuela", "Vietnam", "Western Sahara", "Yemen", "Zambia", "Zimbabwe" ]; me.states = { - "United States": { - AL: "Alabama", - AK: "Alaska", - AZ: "Arizona", - AR: "Arkansas", - CA: "California", - CO: "Colorado", - CT: "Connecticut", - DE: "Delaware", - DC: "District Of Columbia", - FL: "Florida", - GA: "Georgia", - HI: "Hawaii", - ID: "Idaho", - IL: "Illinois", - IN: "Indiana", - IA: "Iowa", - KS: "Kansas", - KY: "Kentucky", - LA: "Louisiana", - ME: "Maine", - MD: "Maryland", - MA: "Massachusetts", - MI: "Michigan", - MN: "Minnesota", - MS: "Mississippi", - MO: "Missouri", - MT: "Montana", - NE: "Nebraska", - NV: "Nevada", - NH: "New Hampshire", - NJ: "New Jersey", - NM: "New Mexico", - NY: "New York", - NC: "North Carolina", - ND: "North Dakota", - OH: "Ohio", - OK: "Oklahoma", - OR: "Oregon", - PA: "Pennsylvania", - RI: "Rhode Island", - SC: "South Carolina", - SD: "South Dakota", - TN: "Tennessee", - TX: "Texas", - UT: "Utah", - VT: "Vermont", - VA: "Virginia", - WA: "Washington", - WV: "West Virginia", - WI: "Wisconsin", - WY: "Wyoming" - } + "United States":{ + AL:"Alabama", + AK:"Alaska", + AZ:"Arizona", + AR:"Arkansas", + CA:"California", + CO:"Colorado", + CT:"Connecticut", + DE:"Delaware", + DC:"District Of Columbia", + FL:"Florida", + GA:"Georgia", + HI:"Hawaii", + ID:"Idaho", + IL:"Illinois", + IN:"Indiana", + IA:"Iowa", + KS:"Kansas", + KY:"Kentucky", + LA:"Louisiana", + ME:"Maine", + MD:"Maryland", + MA:"Massachusetts", + MI:"Michigan", + MN:"Minnesota", + MS:"Mississippi", + MO:"Missouri", + MT:"Montana", + NE:"Nebraska", + NV:"Nevada", + NH:"New Hampshire", + NJ:"New Jersey", + NM:"New Mexico", + NY:"New York", + NC:"North Carolina", + ND:"North Dakota", + OH:"Ohio", + OK:"Oklahoma", + OR:"Oregon", + PA:"Pennsylvania", + RI:"Rhode Island", + SC:"South Carolina", + SD:"South Dakota", + TN:"Tennessee", + TX:"Texas", + UT:"Utah", + VT:"Vermont", + VA:"Virginia", + WA:"Washington", + WV:"West Virginia", + WI:"Wisconsin", + WY:"Wyoming" + } }; + /** * Functions for each tag type, these are now exposed directly on the object * so that they can be redefined within modules (e.g. in a module that provides @@ -195,66 +196,62 @@ me.states = { * **/ -me.defaultTagRenderer = function (field, value, bare) -{ - var isCheckable = field.type == 'radio' || field.type == 'checkbox'; - var checked = field.checked || (isCheckable && value && (field.value == value || value === true)); - - //console.log('... field: ', field, value); - var tagOutput = ""; - - if (field.type == 'checkbox' && !field.readonly && !field.disabled) - { - // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value - tagOutput += ''; - } - - tagOutput += ''; - } - - return bare ? tagOutput : me.decorateField(field, tagOutput); +me.defaultTagRenderer = function (field, value, bare) { + var isCheckable = field.type == 'radio' || field.type == 'checkbox'; + var checked = field.checked || (isCheckable && value && (field.value == value || value === true)); + + //console.log('... field: ', field, value); + var tagOutput = ""; + + if (field.type == 'checkbox' && !field.readonly && !field.disabled) { + // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value + tagOutput += ''; + } + + tagOutput += ''; + } + + return bare ? tagOutput : me.decorateField(field, tagOutput); }; -me.decorateField = function (field, tagHTML) -{ - calipso.silly('FORM: decorateField, field: ', field); - var isCheckable = !field.labelFirst && (field.type == "checkbox" || field.type == "radio"); - var labelHTML = field.label ? ( - '' - ) : ''; - - var wrapperId = field.name ? ( - field.name.replace(/\[/g, '_').replace(/\]/g, '') - + (field.type == 'radio' ? '-radio' + me.radioCount : '') - ) : ''; - - return field.label && field.label.length > 0 ? ( - '
' + - '
' + - // put checkboxes and radios ("checkables") before their labels, unless field.labelFirst is true - (isCheckable ? tagHTML + labelHTML : labelHTML + tagHTML) + - '
' + - (field.description ? '' + t(field.description) + '' : '') + - '
' - ) : tagHTML; +me.decorateField = function (field, tagHTML) { + calipso.silly('FORM: decorateField, field: ', field); + var isCheckable = !field.labelFirst && (field.type == "checkbox" || field.type == "radio"); + var labelHTML = field.label ? ( + '' + ) : ''; + + var wrapperId = field.name ? ( + field.name.replace(/\[/g, '_').replace(/\]/g, '') + + (field.type == 'radio' ? '-radio' + me.radioCount : '') + ) : ''; + + return field.label && field.label.length > 0 ? ( + '
' + + '
' + + // put checkboxes and radios ("checkables") before their labels, unless field.labelFirst is true + (isCheckable ? tagHTML + labelHTML : labelHTML + tagHTML) + + '
' + + (field.description ? '' + t(field.description) + '' : '') + + '
' + ) : tagHTML; }; // if there is no `canContain` or `cannotContain`, then the element is not a container. @@ -264,426 +261,391 @@ me.decorateField = function (field, tagHTML) // && (!y.canBeContainedBy || x in y.canBeContainedBy) me.elementTypes = { - 'page': { - cannotContain: ['page'], - render: function (el) - { - } - }, - - 'section': { - cannotContain: ['page'], - isTab: false, - render: function (el, values, isTabs) - { - return ( - '' + - (el.label ? '

' + t(el.label) + '

' : '') + - (el.description ? '

' + el.description + '

' : '') + - '
' + - me.render_fields(el, values) + - '
' + - '' - ); - } - }, - - // todo: allow for pre-rendered markup for the description, or other renderers (such as markdown) - 'fieldset': { - cannotContain: ['section', 'page'], - render: function (el, values) - { - if (!el.label) - { - el.label = el.legend; - } - return ( - '
' + - // is preferable, but legends are not fully stylable, so 'label' =

- (el.label ? '

' + t(el.label) + '

' : '') + - (el.description ? '

' + el.description + '

' : '') + - '
' + - me.render_fields(el, values) + - '
' + - '
' - ); - } - }, - - // special .. might also be used as a container (i.e., depending on what radio is active, elements 'under' it are active?) - // special - have to share ids .. are part of a set - TODO - allow for more than one radio group (already done?) - 'radios': { // it's a container because radios must belong to a 'set' .. also, sometimes a form uses radios kindof like tabs.... - canContain: ['option'], - render: function (field, values) - { - return me.elementTypes.fieldset.render(field, values); - } - }, - - // special .. might also be used as a container (i.e., depending on whether a checkbox is checked, elements 'under' it are active?) - 'checkboxes': { - canContain: ['option'], - render: function (field, values) - { - return me.elementTypes.fieldset.render(field, values); - } - }, - - 'select': { // it's a container because it contains options - canContain: ['options', 'optgroup'], - render: function (field, value) - { - - var tagOutput = ''; - - var options = typeof field.options === 'function' ? field.options() : field.options; - - if (field.optgroups) - { - field.optgroups.forEach(function (optgroup) - { - tagOutput += ''; - optgroup.options.forEach(function (option) - { - tagOutput += me.elementTypes.option.render(option, value, 'select'); - }); - tagOutput += ''; - }); - } - else - { - options.forEach(function (option) - { - tagOutput += me.elementTypes.option.render(option, value, 'select'); - }); - } - tagOutput += ''; - - return me.decorateField(field, tagOutput); - } - }, - - 'optgroup': { - canBeContainedBy: ['select'], - canContain: ['option'] - }, - - 'options': { - canBeContainedBy: ['select'], - canContain: ['option'] - }, - - // an "option" can be an ' - ); - } - else - { - return me.defaultTagRenderer(option, value); - } - } - }, - - // type: 'radio' should become type: option, and be in a {type: radios} - 'radio': { - render: me.defaultTagRenderer - }, - - // type: 'checkbox' should become type: option, and be in a {type: checkboxes} - 'checkbox': { - render: function (field, value, bare) - { - - // Quickly flip values to true/false if on/off - value = (value === "on" ? true : (value === "off" ? false : value)); - - // Now set the checked variable - var checked = (value ? true : (field.value ? true : (field.checked ? true : false))); - - var tagOutput = ""; - - if (!field.readonly && !field.disabled) - { - // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value - tagOutput += ''; - } - - tagOutput += ''; - } - return bare ? tagOutput : me.decorateField(field, tagOutput); - } - }, - - 'text': { - render: me.defaultTagRenderer - }, - - 'textarea': { - render: function (field, value) - { - return me.decorateField(field, '' - + value - + ''); - } - }, - - 'hidden': { - render: me.defaultTagRenderer - }, - - 'password': { // can be special, if there is a 'verify' - render: me.defaultTagRenderer - }, - - // might allow file to take a url - 'file': { - render: me.defaultTagRenderer - }, - - 'buttons': { - canContain: ['submit', 'image', 'reset', 'button', 'link'] - }, - - // buttons should only be able to be added to the 'action set' - // button can be [submit, reset, cancel (a link), button (generic), link (generic)] - // if form has pages, 'previous' and 'next' buttons should interpolate until the last page, 'submit' - 'button': { - render: me.defaultTagRenderer - }, - - 'submit': { - render: me.defaultTagRenderer - }, - - 'image': { - render: me.defaultTagRenderer - }, - - 'reset': { - render: me.defaultTagRenderer - }, - - // a link is not really a form control, but is provided here for convenience - // it also doesn't really make sense for it to have a value. - // a link should have an href and text, and optionally, cls ('class'), id - 'link': { - render: function (field, value) - { - var id = field.id || field.name; - var text = field.text || field.value; - return '' + text + ''; - } - }, - - 'readonlytext': { - render: function (field, value, bare) - { - var id = field.id || field.name; - var text = field.text || field.value; - var tagOutput = '
' + text + '
'; - return bare ? tagOutput : me.decorateField(field, tagOutput); - } - }, - - 'date': { - render: function (field, value, bare) - { - - if (!value) - { - value = new Date(); - } - - // TODO - use user's Locale - var monthNames = calipso.date.regional[''].monthNamesShort; - - var tagOutput = ''; - for (var monthNameCounter = 0; - monthNameCounter < 12; - monthNameCounter++) - { - tagOutput += ( - '' - ); - } - tagOutput += ''; - - tagOutput += ' '; - - tagOutput += '' + + (el.label ? '

' + t(el.label) + '

' : '') + + (el.description ? '

' + el.description + '

' : '') + + '
' + + me.render_fields(el, values) + + '
' + + '' + ); + } + }, + + // todo: allow for pre-rendered markup for the description, or other renderers (such as markdown) + 'fieldset':{ + cannotContain:['section', 'page'], + render:function (el, values) { + if (!el.label) { + el.label = el.legend; + } + return ( + '
' + + // is preferable, but legends are not fully stylable, so 'label' =

+ (el.label ? '

' + t(el.label) + '

' : '') + + (el.description ? '

' + el.description + '

' : '') + + '
' + + me.render_fields(el, values) + + '
' + + '
' + ); + } + }, + + // special .. might also be used as a container (i.e., depending on what radio is active, elements 'under' it are active?) + // special - have to share ids .. are part of a set - TODO - allow for more than one radio group (already done?) + 'radios':{ // it's a container because radios must belong to a 'set' .. also, sometimes a form uses radios kindof like tabs.... + canContain:['option'], + render:function (field, values) { + return me.elementTypes.fieldset.render(field, values); + } + }, + + // special .. might also be used as a container (i.e., depending on whether a checkbox is checked, elements 'under' it are active?) + 'checkboxes':{ + canContain:['option'], + render:function (field, values) { + return me.elementTypes.fieldset.render(field, values); + } + }, + + 'select':{ // it's a container because it contains options + canContain:['options', 'optgroup'], + render:function (field, value) { + + var tagOutput = ''; + + var options = typeof field.options === 'function' ? field.options() : field.options; + + if (field.optgroups) { + field.optgroups.forEach(function (optgroup) { + tagOutput += ''; + optgroup.options.forEach(function (option) { + tagOutput += me.elementTypes.option.render(option, value, 'select'); + }); + tagOutput += ''; + }); + } else { + options.forEach(function (option) { + tagOutput += me.elementTypes.option.render(option, value, 'select'); + }); + } + tagOutput += ''; + + return me.decorateField(field, tagOutput); + } + }, + + 'optgroup':{ + canBeContainedBy:['select'], + canContain:['option'] + }, + + 'options':{ + canBeContainedBy:['select'], + canContain:['option'] + }, + + // an "option" can be an ' + ); + } else { + return me.defaultTagRenderer(option, value); + } + } + }, + + // type: 'radio' should become type: option, and be in a {type: radios} + 'radio':{ + render:me.defaultTagRenderer + }, + + // type: 'checkbox' should become type: option, and be in a {type: checkboxes} + 'checkbox':{ + render:function (field, value, bare) { + + // Quickly flip values to true/false if on/off + value = (value === "on" ? true : (value === "off" ? false : value)); + + // Now set the checked variable + var checked = (value ? true : (field.value ? true : (field.checked ? true : false))); + + var tagOutput = ""; + + if (!field.readonly && !field.disabled) { + // Workaround for readonly/disabled fields (esp. checkboxes/radios) - add a hidden field with the value + tagOutput += ''; + } + + tagOutput += ''; + } + return bare ? tagOutput : me.decorateField(field, tagOutput); + } + }, + + 'text':{ + render:me.defaultTagRenderer + }, + + 'textarea':{ + render:function (field, value) { + return me.decorateField(field, '' + + value + + ''); + } + }, + + 'hidden':{ + render:me.defaultTagRenderer + }, + + 'password':{ // can be special, if there is a 'verify' + render:me.defaultTagRenderer + }, + + // might allow file to take a url + 'file':{ + render:me.defaultTagRenderer + }, + + 'buttons':{ + canContain:['submit', 'image', 'reset', 'button', 'link'] + }, + + // buttons should only be able to be added to the 'action set' + // button can be [submit, reset, cancel (a link), button (generic), link (generic)] + // if form has pages, 'previous' and 'next' buttons should interpolate until the last page, 'submit' + 'button':{ + render:me.defaultTagRenderer + }, + + 'submit':{ + render:me.defaultTagRenderer + }, + + 'image':{ + render:me.defaultTagRenderer + }, + + 'reset':{ + render:me.defaultTagRenderer + }, + + // a link is not really a form control, but is provided here for convenience + // it also doesn't really make sense for it to have a value. + // a link should have an href and text, and optionally, cls ('class'), id + 'link':{ + render:function (field, value) { + var id = field.id || field.name; + var text = field.text || field.value; + return '' + text + ''; + } + }, + + 'readonlytext':{ + render:function (field, value, bare) { + var id = field.id || field.name; + var text = field.text || field.value; + var tagOutput = '
' + text + '
'; + return bare ? tagOutput : me.decorateField(field, tagOutput); + } + }, + + 'date':{ + render:function (field, value, bare) { + + if (!value) { + value = new Date(); + } + + // TODO - use user's Locale + var monthNames = calipso.date.regional[''].monthNamesShort; + + var tagOutput = ''; + for (var monthNameCounter = 0; monthNameCounter < 12; monthNameCounter++) { + tagOutput += ( + '' + ); + } + tagOutput += ''; + + tagOutput += ' '; + + tagOutput += ' 0) - { - calipso.silly("Saving form " + form.id + " in session."); - req.session.forms = req.session.forms || {}; - req.session.forms[form.id] = form; - req.session.save(next); - } - else - { - next(); - } +function saveFormInSession(form, req, next) { + + // If we have a form id and a session, save it + if (form.id && calipso.lib._.keys(req.session).length > 0) { + calipso.silly("Saving form " + form.id + " in session."); + req.session.forms = req.session.forms || {}; + req.session.forms[form.id] = form; + req.session.save(next); + } else { + next(); + } } /** * Deal with form tabs in jQuery UI style if required. */ -me.formTabs = function (sections) -{ - - if (!sections) - { - return ''; - } - - var tabOutput = ''; +me.formTabs = function (sections) { + + if (!sections) { + return ''; + } + + var tabOutput = ''; }; + /** * Render the initial form tag * * @param form * @returns {String} */ -me.start_form = function (form) -{ - return ( - '
' + - '' + - '' + t(form.title) + '' + - '
' + - (form.tabs ? this.formTabs(form.sections) : '') + - '
' - ); +me.start_form = function (form) { + return ( + '' + + '' + + '' + t(form.title) + '' + + '
' + + (form.tabs ? this.formTabs(form.sections) : '') + + '
' + ); }; /** @@ -854,38 +803,35 @@ me.start_form = function (form) * @param form * @returns {String} */ -me.end_form = function (form) -{ - return '
'; +me.end_form = function (form) { + return '
'; }; + /** * Render the form sections, iterating through and then rendering * each of the fields within a section. */ -me.render_sections = function (form, values) -{ - - var self = this; - var sections = form.sections; - - if (!sections) - { - return ''; - } - - var sectionOutput = ''; - - sections.forEach(function (section) - { - sectionOutput += ( - '' + - '

' + t(section.label) + '

' + - self.render_fields(section, values) + - '' - ); - }); - return sectionOutput; +me.render_sections = function (form, values) { + + var self = this; + var sections = form.sections; + + if (!sections) { + return ''; + } + + var sectionOutput = ''; + + sections.forEach(function (section) { + sectionOutput += ( + '' + + '

' + t(section.label) + '

' + + self.render_fields(section, values) + + '' + ); + }); + return sectionOutput; }; @@ -894,71 +840,62 @@ me.render_sections = function (form, values) * @param buttons * @returns {String} */ -me.render_buttons = function (buttons) -{ +me.render_buttons = function (buttons) { - var self = this; - var buttonsOutput = '
'; + var self = this; + var buttonsOutput = '
'; - buttons.forEach(function (field) - { - buttonsOutput += self.elementTypes[field.tag || field.type].render(field); - }); + buttons.forEach(function (field) { + buttonsOutput += self.elementTypes[field.tag || field.type].render(field); + }); - buttonsOutput += '
'; + buttonsOutput += '
'; - return buttonsOutput; + return buttonsOutput; }; + /** * Render the fields on a form * @param fields * @returns {String} */ -me.render_fields = function (fieldContainer, values) -{ - - var fields = fieldContainer.fields || fieldContainer.children; - var self = this; - var fieldOutput = ''; - - if (!fields) - { - return ''; - } - - fields.forEach(function (field) - { - - var value = ''; - var fieldName = field.name; - - // If we have a field name, lookup the value - if (fieldName) - { - value = getValueForField(fieldName, values); - } - - // if the 'field' is really just a container, pass the values on down - // todo: consider adding a property 'isContainer' - if (field.type == 'section' || field.type == 'fieldset') - { - value = values; - } - - // field.tag was introduced to allow for
'); }; @@ -89,7 +89,7 @@ CalipsoTable.prototype.start_table = function(table) { * @param table * @returns {String} */ -CalipsoTable.prototype.end_table = function(table) { +CalipsoTable.prototype.end_table = function (table) { return '
'; }; @@ -99,16 +99,18 @@ CalipsoTable.prototype.end_table = function(table) { * @param table * @returns {String} */ -CalipsoTable.prototype.render_headers = function(table) { +CalipsoTable.prototype.render_headers = function (table) { // If there are no columns, return - if (table.columns.length === 0) throw new Error("You must define columns to render a table."); + if (table.columns.length === 0) { + throw new Error("You must define columns to render a table."); + } // Test var output = ""; // Iterate - table.columns.forEach(function(column, key) { + table.columns.forEach(function (column, key) { // set the class // Check to see if we are sorting by this column @@ -126,7 +128,7 @@ CalipsoTable.prototype.render_headers = function(table) { }; -/** +/** * Helper function to determine column header sort class */ @@ -149,12 +151,16 @@ function getHeaderClass(table, column) { /** * Convert a sortBy parameter into mongo sort queries */ -CalipsoTable.prototype.sortQuery = function(qry, sortBy) { +CalipsoTable.prototype.sortQuery = function (qry, sortBy) { - if (typeof sortBy === 'string') sortBy = [sortBy]; - if (!sortBy || sortBy.length === 0) return qry; + if (typeof sortBy === 'string') { + sortBy = [sortBy]; + } + if (!sortBy || sortBy.length === 0) { + return qry; + } - sortBy.forEach(function(sort) { + sortBy.forEach(function (sort) { var sortArr = sort.split(","); if (sortArr.length === 2) { var dir = sortArr[1] === 'asc' ? 1 : (sortArr[1] === 'desc' ? -1 : 0); @@ -169,14 +175,18 @@ CalipsoTable.prototype.sortQuery = function(qry, sortBy) { /** * Convert a sort by form param into a view sort object */ -CalipsoTable.prototype.parseSort = function(sortBy) { +CalipsoTable.prototype.parseSort = function (sortBy) { var options = {}; - if (typeof sortBy === 'string') sortBy = [sortBy]; - if (!sortBy || sortBy.length === 0) return options; + if (typeof sortBy === 'string') { + sortBy = [sortBy]; + } + if (!sortBy || sortBy.length === 0) { + return options; + } - sortBy.forEach(function(sort) { + sortBy.forEach(function (sort) { var sortArr = sort.split(","); if (sortArr.length === 2) { options[sortArr[0]] = sortArr[1]; @@ -192,19 +202,21 @@ CalipsoTable.prototype.parseSort = function(sortBy) { * @param table * @returns {String} */ -CalipsoTable.prototype.render_data = function(table, req) { +CalipsoTable.prototype.render_data = function (table, req) { // If there are no columns, return - if (table.columns.length === 0) throw new Error("You must define columns to render a table."); + if (table.columns.length === 0) { + throw new Error("You must define columns to render a table."); + } // Test var output = ""; // Iterate - table.data.forEach(function(row) { + table.data.forEach(function (row) { output += ""; // Iterate over the columns - table.columns.forEach(function(column) { + table.columns.forEach(function (column) { output += ""; if (column.name in row) { if (typeof column.fn === "function") { @@ -229,7 +241,7 @@ CalipsoTable.prototype.render_data = function(table, req) { * @param table * @returns {String} */ -CalipsoTable.prototype.render_pager = function(table, url) { +CalipsoTable.prototype.render_pager = function (table, url) { // Test var output = ""; diff --git a/lib/core/Themes.js b/lib/core/Themes.js index b26b8ca20..448529eef 100644 --- a/lib/core/Themes.js +++ b/lib/core/Themes.js @@ -34,7 +34,7 @@ var rootpath = process.cwd() + '/', /** * The theme object itself, instantiated within calipso */ -module.exports.Theme = function(theme, next) { +module.exports.Theme = function (theme, next) { // Defaults var themeName = theme.name; @@ -43,14 +43,14 @@ module.exports.Theme = function(theme, next) { /** * Load a theme */ - loadTheme(themeName, themePath, function(err, themeConfig) { + loadTheme(themeName, themePath, function (err, themeConfig) { if (err) { next(err); return; } - cacheTheme(themeConfig, themePath, function(err, themeCache) { + cacheTheme(themeConfig, themePath, function (err, themeCache) { if (err) { next(err); @@ -59,17 +59,17 @@ module.exports.Theme = function(theme, next) { // Load the theme configuration file. var theme = { - theme: themeName, - cache: themeCache, - config: themeConfig, - compileTemplate: function(data, templatePath, templateExtension) { + theme:themeName, + cache:themeCache, + config:themeConfig, + compileTemplate:function (data, templatePath, templateExtension) { // expose private function for module to use return compileTemplate(data, templatePath, templateExtension); }, // Render a module // Changed in 0.1.1 to be asynch - renderItem: function(req, res, template, block, options, next) { + renderItem:function (req, res, template, block, options, next) { var output = ""; @@ -100,7 +100,7 @@ module.exports.Theme = function(theme, next) { } }, - render: function(req, res, next) { + render:function (req, res, next) { var cache = this.cache, theme = this, layout = res.layout ? res.layout : "default", content, themeOptions, err; @@ -115,7 +115,7 @@ module.exports.Theme = function(theme, next) { } } - processTheme(req, res, layout, theme, function(err) { + processTheme(req, res, layout, theme, function (err) { // If something went wrong ... if (err) { @@ -138,7 +138,7 @@ module.exports.Theme = function(theme, next) { }); }, - getLayoutsArray: function() { + getLayoutsArray:function () { var theme = this; var layouts = []; @@ -196,8 +196,8 @@ function processSection(req, res, section, sectionPath, layoutConfig, theme, nex } // Override with a 403 (no permissions) page - if(section === "body" && res.statusCode === 403) { - if(!theme.cache.hasOwnProperty("403")) { + if (section === "body" && res.statusCode === 403) { + if (!theme.cache.hasOwnProperty("403")) { localNext(new Error("You must define a 403 template in the error folder e.g. error/403.html")); return; } @@ -222,13 +222,13 @@ function processSection(req, res, section, sectionPath, layoutConfig, theme, nex // Get the basic theme options themeOptions = createOptions(req, res, { - blockData: blockData + blockData:blockData }); // Add any custom functions if (typeof sectionCacheFn === "function") { - sectionCacheFn(req, themeOptions, function(err, fnOptions) { + sectionCacheFn(req, themeOptions, function (err, fnOptions) { if (err) { err.xMessage = "Issue executing the theme function for section " + section + ", check " + sectionPath.replace(".", "/") + ".js"; @@ -367,10 +367,10 @@ function processTheme(req, res, layout, theme, next) { function sectionCache(req, res, cacheKey, section, templateName, layoutConfig, theme, next) { - calipso.cacheService.check(cacheKey, function(err, isCached) { + calipso.cacheService.check(cacheKey, function (err, isCached) { if (isCached) { calipso.silly("Cache hit for " + cacheKey + ", section " + section); - calipso.cacheService.get(cacheKey, function(err, cache) { + calipso.cacheService.get(cacheKey, function (err, cache) { if (!err) { res.bufferedOutput[section] = cache.content; } @@ -378,11 +378,11 @@ function sectionCache(req, res, cacheKey, section, templateName, layoutConfig, t }); } else { calipso.silly("Cache miss for " + cacheKey + ", section " + section); - processSection(req, res, section, templateName, layoutConfig, theme, function(err) { + processSection(req, res, section, templateName, layoutConfig, theme, function (err) { if (!err) { var content = res.bufferedOutput[section]; calipso.cacheService.set(cacheKey, { - content: content + content:content }, null, next); } else { next(err); @@ -401,9 +401,9 @@ function loadTheme(theme, themePath, next) { var themeFile = calipso.lib.path.join(themePath, "theme.json"); - (fs.exists || path.exists)(themeFile, function(exists) { - if(exists) { - fs.readFile(themeFile, 'utf8', function(err, data) { + (fs.exists || path.exists)(themeFile, function (exists) { + if (exists) { + fs.readFile(themeFile, 'utf8', function (err, data) { if (!err) { var jsonData; try { @@ -443,8 +443,8 @@ function cacheTheme(themeConfig, themePath, next) { // Add the layout template templates.push({ - name: layout, - templatePath: calipso.lib.path.join("templates", layoutConfig.template) + name:layout, + templatePath:calipso.lib.path.join("templates", layoutConfig.template) }); @@ -453,8 +453,8 @@ function cacheTheme(themeConfig, themePath, next) { template = layoutConfig.sections[section].template; if (template) { templates.push({ - name: layout + "." + section, - templatePath: calipso.lib.path.join("templates", layout, template) + name:layout + "." + section, + templatePath:calipso.lib.path.join("templates", layout, template) }); } } @@ -471,23 +471,23 @@ function cacheTheme(themeConfig, themePath, next) { // Push error message templates templateFiles = calipso.lib.fs.readdirSync(calipso.lib.path.join(themePath, 'templates', 'error')); - errorCodeTemplates = calipso.lib._.select(templateFiles, function(filename) { + errorCodeTemplates = calipso.lib._.select(templateFiles, function (filename) { // Select files that start with 3 digits, indicating an error code return filename.match(/^\d{3}./); }); - calipso.lib._.each(errorCodeTemplates, function(filename) { + calipso.lib._.each(errorCodeTemplates, function (filename) { templates.push({ - name: filename.match(/^\d{3}/)[0], - templatePath: calipso.lib.path.join("templates", "error", filename) + name:filename.match(/^\d{3}/)[0], + templatePath:calipso.lib.path.join("templates", "error", filename) }); }); - var templateIterator = function(templateName, cb) { - loadTemplate(templateCache, templateName, themePath, cb); - }; + var templateIterator = function (templateName, cb) { + loadTemplate(templateCache, templateName, themePath, cb); + }; - calipso.lib.async.map(templates, templateIterator, function(err, result) { + calipso.lib.async.map(templates, templateIterator, function (err, result) { if (err) { // May not be a problem as missing templates default to default calipso.error("Error loading templates, msg: " + err.message + ", stack: " + err.stack); @@ -531,14 +531,16 @@ function loadModuleOverrideTemplate(templateCache, module, template, path) { function loadTemplate(templateCache, template, themePath, next) { // Reset / default - if (!templateCache[template.name]) templateCache[template.name] = {}; + if (!templateCache[template.name]) { + templateCache[template.name] = {}; + } // Template paths and functions var templatePath = calipso.lib.path.join(themePath, template.templatePath), templateExtension = template.templatePath.match(/([^\.]+)$/)[0], templateFnPath = calipso.lib.path.join(themePath, template.templatePath.replace("." + templateExtension, ".js")); - (fs.exists || path.exists)(templatePath,function(exists) { + (fs.exists || path.exists)(templatePath, function (exists) { if (exists) { @@ -555,10 +557,10 @@ function loadTemplate(templateCache, template, themePath, next) { try { fs.unwatchFile(templatePath); fs.watchFile(templatePath, { - persistent: true, - interval: 200 - }, function(curr, prev) { - loadTemplate(templateCache, template, themePath, function() { + persistent:true, + interval:200 + }, function (curr, prev) { + loadTemplate(templateCache, template, themePath, function () { calipso.silly("Template " + templatePath + " reloaded ..."); }); }); @@ -605,14 +607,17 @@ function loadTemplate(templateCache, template, themePath, next) { function compileTemplate(template, templatePath, templateExtension) { - var compiledTemplate = function() {}, + var compiledTemplate = function () { + }, templateEngine; var options = { - filename: templatePath + filename:templatePath }; // If we get html, replace with ejs - if (templateExtension === "html") templateExtension = "ejs"; + if (templateExtension === "html") { + templateExtension = "ejs"; + } // Load a template engine based on the extension try { diff --git a/lib/core/Utils.js b/lib/core/Utils.js index 87c16a318..bc8ba6b1e 100644 --- a/lib/core/Utils.js +++ b/lib/core/Utils.js @@ -11,7 +11,7 @@ module.exports = { * @param obj {object} The root object to search * @return {boolean} true if property exists, false otherwise */ - hasProperty: function(ns, obj) { + hasProperty:function (ns, obj) { if (!ns) { return obj; } @@ -38,7 +38,7 @@ module.exports = { * @param obj {object} The root object to search * @return {object} the object, either the namespaced obejct or the root object */ - getProperty: function(ns, obj) { + getProperty:function (ns, obj) { if (!ns) { return obj; } @@ -60,15 +60,17 @@ module.exports = { /** * Simple mongo object copier, used to do a shallow copy of objects */ - copyMongoObject: function(object, copy, schema) { + copyMongoObject:function (object, copy, schema) { var fields = _.keys(schema.paths); - _.each(fields, function(key) { - if (key !== '_id') copy.set(key, object.get(key)); + _.each(fields, function (key) { + if (key !== '_id') { + copy.set(key, object.get(key)); + } }); }, - escapeHtmlQuotes: function (string) { + escapeHtmlQuotes:function (string) { if (string && string.replace) { return string.replace(/\"/g, '"').replace(/\'/g, '''); } diff --git a/lib/core/cacheAdapters/memory.js b/lib/core/cacheAdapters/memory.js index fa9e193bb..e12706d8b 100644 --- a/lib/core/cacheAdapters/memory.js +++ b/lib/core/cacheAdapters/memory.js @@ -1,4 +1,3 @@ - /*! * Calipso - cache - Memory Store * Approach copied from Connect session store @@ -35,16 +34,16 @@ MemoryStore.prototype.__proto__ = Store.prototype; * @api public */ -MemoryStore.prototype.get = function(key, fn){ +MemoryStore.prototype.get = function (key, fn) { var self = this; //process.nextTick(function(){ - var cache = self.cache[key]; - if (cache) { - fn(null, cache.item); - } else { - fn(new Error('Cache miss: ' + key)); - } + var cache = self.cache[key]; + if (cache) { + fn(null, cache.item); + } else { + fn(new Error('Cache miss: ' + key)); + } //}); }; @@ -56,7 +55,7 @@ MemoryStore.prototype.get = function(key, fn){ * @api public */ -MemoryStore.prototype.check = function(key, fn){ +MemoryStore.prototype.check = function (key, fn) { var self = this; var cache = self.cache[key]; @@ -69,7 +68,7 @@ MemoryStore.prototype.check = function(key, fn){ self.destroy(key, fn); } } else { - fn(null,false); + fn(null, false); } }; @@ -85,13 +84,13 @@ MemoryStore.prototype.check = function(key, fn){ * @api public */ -MemoryStore.prototype.set = function(key, item, ttl, fn){ +MemoryStore.prototype.set = function (key, item, ttl, fn) { var self = this; //process.nextTick(function(){ - ttl = ttl || (self.options.ttl || 600); - var expires = Date.now() + ttl; - self.cache[key] = {item:item, expires:expires} - fn && fn(); + ttl = ttl || (self.options.ttl || 600); + var expires = Date.now() + ttl; + self.cache[key] = {item:item, expires:expires} + fn && fn(); //}); }; @@ -102,11 +101,11 @@ MemoryStore.prototype.set = function(key, item, ttl, fn){ * @api public */ -MemoryStore.prototype.destroy = function(key, fn){ +MemoryStore.prototype.destroy = function (key, fn) { var self = this; //process.nextTick(function(){ - delete self.cache[key]; - fn && fn(); + delete self.cache[key]; + fn && fn(); //}); }; @@ -117,7 +116,7 @@ MemoryStore.prototype.destroy = function(key, fn){ * @api public */ -MemoryStore.prototype.all = function(fn){ +MemoryStore.prototype.all = function (fn) { var arr = [] , keys = Object.keys(this.cache); for (var i = 0, len = keys.length; i < len; ++i) { @@ -133,7 +132,7 @@ MemoryStore.prototype.all = function(fn){ * @api public */ -MemoryStore.prototype.clear = function(fn){ +MemoryStore.prototype.clear = function (fn) { this.cache = {}; fn && fn(); }; @@ -145,6 +144,6 @@ MemoryStore.prototype.clear = function(fn){ * @api public */ -MemoryStore.prototype.length = function(fn){ +MemoryStore.prototype.length = function (fn) { fn(null, Object.keys(this.cache).length); }; diff --git a/lib/core/cacheAdapters/store.js b/lib/core/cacheAdapters/store.js index 0056af7e5..9eed465dc 100644 --- a/lib/core/cacheAdapters/store.js +++ b/lib/core/cacheAdapters/store.js @@ -1,4 +1,3 @@ - /*! * Calipso - cache - Store * Concepts taken from connect session @@ -18,38 +17,38 @@ var rootpath = process.cwd() + '/', * Store object - options: * prefix - a prefix to attach to all cache keys, defaults to calipso. */ -var Store = module.exports = function Store(options){ +var Store = module.exports = function Store(options) { this.options = options || {}; }; /** -* Generate a cache key - applies to all store types -*/ -Store.prototype.getCacheKey = function(keys, params) { + * Generate a cache key - applies to all store types + */ +Store.prototype.getCacheKey = function (keys, params) { - var prefix = this.options.prefix || "calipso"; + var prefix = this.options.prefix || "calipso"; - // Append the theme, allows for theme change - var cacheKey = prefix + "::" + calipso.theme.theme, paramCount = 0; + // Append the theme, allows for theme change + var cacheKey = prefix + "::" + calipso.theme.theme, paramCount = 0; - // Create the key from the keys - keys.forEach(function(value) { + // Create the key from the keys + keys.forEach(function (value) { cacheKey += "::" + value; - }) + }) - var qs = require("querystring"); + var qs = require("querystring"); - if(params) { - cacheKey += "::"; - calipso.lib._.each(params,function(param,key) { - if(param) { - cacheKey += (paramCount > 0 ? "::" : "") + (param ? (key + "=" + qs.escape(param)) : ""); - paramCount += 1; - } - }); - } + if (params) { + cacheKey += "::"; + calipso.lib._.each(params, function (param, key) { + if (param) { + cacheKey += (paramCount > 0 ? "::" : "") + (param ? (key + "=" + qs.escape(param)) : ""); + paramCount += 1; + } + }); + } - return cacheKey; + return cacheKey; } \ No newline at end of file diff --git a/logo.js b/logo.js index 121e83115..5156d734f 100644 --- a/logo.js +++ b/logo.js @@ -8,15 +8,14 @@ require('colors'); -exports.print = function () -{ - console.log(""); - console.log(" _ _ ".yellow.bold); - console.log(" ___ __ _| (_)_ __ ___ ___ ".yellow.bold); - console.log(" / __|/ _` | | | '_ \\/ __|/ _ \\ ".green.bold); - console.log("| (__| (_| | | | |_) \\__ \\ (_) | ".blue.bold); - console.log(" \\___|\\__,_|_|_| .__/|___/\\___/ ".magenta.bold); - console.log(" |_| ".magenta.bold); - console.log(""); -} +exports.print = function () { + console.log(""); + console.log(" _ _ ".yellow.bold); + console.log(" ___ __ _| (_)_ __ ___ ___ ".yellow.bold); + console.log(" / __|/ _` | | | '_ \\/ __|/ _ \\ ".green.bold); + console.log("| (__| (_| | | | |_) \\__ \\ (_) | ".blue.bold); + console.log(" \\___|\\__,_|_|_| .__/|___/\\___/ ".magenta.bold); + console.log(" |_| ".magenta.bold); + console.log(""); +}; diff --git a/modules/core/admin/admin.js b/modules/core/admin/admin.js index e3e1bfde9..0f7600d4b 100644 --- a/modules/core/admin/admin.js +++ b/modules/core/admin/admin.js @@ -2,131 +2,127 @@ * Core administration module */ var rootpath = process.cwd() + '/', - path = require('path'), - calipso = require(path.join(rootpath, 'lib/calipso')), + path = require('path'), + calipso = require(path.join(rootpath, 'lib/calipso')), - exports = module.exports = { - init: init, - route: route, - first: true // Admin must run before all else - }; + exports = module.exports = { + init:init, + route:route, + first:true // Admin must run before all else + }; /* * Router */ -function route(req, res, module, app, next) -{ - - // Config helpers - var corePermit = "admin:core:configuration", - modulePermit = "admin:module:configuration", - cachePermit = "admin:core:cache"; - - // Menu items - res.menu.admin.addMenuItem(req, {name: 'Administration', path: 'admin', url: '/admin', description: 'Calipso administration ...', permit: corePermit}); - res.menu.admin.addMenuItem(req, {name: 'Core', path: 'admin/core', url: '/admin', description: 'Manage core settings for Calipso ...', permit: corePermit}); - res.menu.admin.addMenuItem(req, {name: 'Configuration', path: 'admin/core/config', url: '/admin/core/config', description: 'Core configuration ...', permit: corePermit}); - res.menu.admin.addMenuItem(req, {name: 'View Languages', path: 'admin/core/languages', url: '/admin/core/languages', description: 'Languages ...', permit: corePermit}); - res.menu.admin.addMenuItem(req, {name: 'View Cache', path: 'admin/core/cache', url: '/admin/core/cache', description: 'Cache ...', permit: cachePermit}); - res.menu.admin.addMenuItem(req, {name: 'Clear Cache', path: 'admin/core/cache/clear', url: '/admin/core/cache/clear', description: 'Clear Cache ...', permit: cachePermit}); - res.menu.admin.addMenuItem(req, {name: 'Modules', path: 'admin/modules', url: '/admin', description: 'Manage module settings ...', permit: modulePermit}); - - // Routing and Route Handler - module.router.route(req, res, next); +function route(req, res, module, app, next) { + + // Config helpers + var corePermit = "admin:core:configuration", + modulePermit = "admin:module:configuration", + cachePermit = "admin:core:cache"; + + // Menu items + res.menu.admin.addMenuItem(req, {name:'Administration', path:'admin', url:'/admin', description:'Calipso administration ...', permit:corePermit}); + res.menu.admin.addMenuItem(req, {name:'Core', path:'admin/core', url:'/admin', description:'Manage core settings for Calipso ...', permit:corePermit}); + res.menu.admin.addMenuItem(req, {name:'Configuration', path:'admin/core/config', url:'/admin/core/config', description:'Core configuration ...', permit:corePermit}); + res.menu.admin.addMenuItem(req, {name:'View Languages', path:'admin/core/languages', url:'/admin/core/languages', description:'Languages ...', permit:corePermit}); + res.menu.admin.addMenuItem(req, {name:'View Cache', path:'admin/core/cache', url:'/admin/core/cache', description:'Cache ...', permit:cachePermit}); + res.menu.admin.addMenuItem(req, {name:'Clear Cache', path:'admin/core/cache/clear', url:'/admin/core/cache/clear', description:'Clear Cache ...', permit:cachePermit}); + res.menu.admin.addMenuItem(req, {name:'Modules', path:'admin/modules', url:'/admin', description:'Manage module settings ...', permit:modulePermit}); + + // Routing and Route Handler + module.router.route(req, res, next); } /* * Initialisation */ -function init(module, app, next) -{ - - // Initialise administration events - enabled for hook.io - calipso.e.addEvent('CONFIG_UPDATE', {enabled: true}); - - // Add listener to config_update - calipso.e.post('CONFIG_UPDATE', module.name, calipso.reloadConfig); - - calipso.permission.Helper.addPermission("admin:core:configuration", "Manage core configuration."); - calipso.permission.Helper.addPermission("admin:module:configuration", "Manage module configuration."); - calipso.permission.Helper.addPermission("admin:core:cache", "View and clear cache."); - - // Admin routes - calipso.lib.step( - - function defineRoutes() - { - - // Permissions - var corePermit = "admin:core:configuration", - modulePermit = "admin:module:configuration", - cachePermit = "admin:core:cache"; - - // Core Administration dashboard - module.router.addRoute('GET /admin', showAdmin, { - template: 'admin', - block: 'admin.show', - admin: true, - permit: corePermit - }, this.parallel()); - - // Core configuration - module.router.addRoute('GET /admin/core/config', coreConfig, { - block: 'admin.show', - admin: true, - permit: corePermit - }, this.parallel()); - - module.router.addRoute('POST /admin/core/config/save', saveAdmin, { - admin: true, - permit: corePermit - }, this.parallel()); - - module.router.addRoute('GET /admin/core/cache', showCache, { - admin: true, - template: 'cache', - block: 'admin.cache', - permit: cachePermit - }, this.parallel()); - - module.router.addRoute('GET /admin/core/cache/clear', clearCache, { - admin: true, - template: 'cache', - block: 'admin.cache', - permit: cachePermit - }, this.parallel()); - - module.router.addRoute('GET /admin/core/languages', showLanguages, { - admin: true, - template: 'languages', - block: 'admin.languages', - permit: corePermit - }, this.parallel()); - - module.router.addRoute('GET /admin/modules', modulesConfig, { - admin: true, - block: 'admin.show', - permit: modulePermit - }, this.parallel()); - - module.router.addRoute('POST /admin/modules/save', saveModulesConfig, { - admin: true, - permit: modulePermit - }, this.parallel()); - - // Default installation routers - only accessible in install mode - module.router.addRoute('GET /admin/install', install, null, this.parallel()); - module.router.addRoute('POST /admin/install', install, null, this.parallel()); - module.router.addRoute('POST /admin/installTest/mongo', installMongoTest, null, this.parallel()); - module.router.addRoute('POST /admin/installTest/user', installUserTest, null, this.parallel()); - - }, function done() - { - - next(); - - }); +function init(module, app, next) { + + // Initialise administration events - enabled for hook.io + calipso.e.addEvent('CONFIG_UPDATE', {enabled:true}); + + // Add listener to config_update + calipso.e.post('CONFIG_UPDATE', module.name, calipso.reloadConfig); + + calipso.permission.Helper.addPermission("admin:core:configuration", "Manage core configuration."); + calipso.permission.Helper.addPermission("admin:module:configuration", "Manage module configuration."); + calipso.permission.Helper.addPermission("admin:core:cache", "View and clear cache."); + + // Admin routes + calipso.lib.step( + + function defineRoutes() { + + // Permissions + var corePermit = "admin:core:configuration", + modulePermit = "admin:module:configuration", + cachePermit = "admin:core:cache"; + + // Core Administration dashboard + module.router.addRoute('GET /admin', showAdmin, { + template:'admin', + block:'admin.show', + admin:true, + permit:corePermit + }, this.parallel()); + + // Core configuration + module.router.addRoute('GET /admin/core/config', coreConfig, { + block:'admin.show', + admin:true, + permit:corePermit + }, this.parallel()); + + module.router.addRoute('POST /admin/core/config/save', saveAdmin, { + admin:true, + permit:corePermit + }, this.parallel()); + + module.router.addRoute('GET /admin/core/cache', showCache, { + admin:true, + template:'cache', + block:'admin.cache', + permit:cachePermit + }, this.parallel()); + + module.router.addRoute('GET /admin/core/cache/clear', clearCache, { + admin:true, + template:'cache', + block:'admin.cache', + permit:cachePermit + }, this.parallel()); + + module.router.addRoute('GET /admin/core/languages', showLanguages, { + admin:true, + template:'languages', + block:'admin.languages', + permit:corePermit + }, this.parallel()); + + module.router.addRoute('GET /admin/modules', modulesConfig, { + admin:true, + block:'admin.show', + permit:modulePermit + }, this.parallel()); + + module.router.addRoute('POST /admin/modules/save', saveModulesConfig, { + admin:true, + permit:modulePermit + }, this.parallel()); + + // Default installation routers - only accessible in install mode + module.router.addRoute('GET /admin/install', install, null, this.parallel()); + module.router.addRoute('POST /admin/install', install, null, this.parallel()); + module.router.addRoute('POST /admin/installTest/mongo', installMongoTest, null, this.parallel()); + module.router.addRoute('POST /admin/installTest/user', installUserTest, null, this.parallel()); + + }, function done() { + + next(); + + }); } @@ -134,73 +130,59 @@ function init(module, app, next) * Show languages stored in memory, * optionally enable translation of these by google translate. */ -function showLanguages(req, res, template, block, next) -{ - - // Check to see if we should google translate?! - // e.g. /admin/languages?translate=es - if (req.moduleParams.translate) - { - - var language = req.moduleParams.translate; - var languageCache = req.languageCache[language]; - - var gt = require('utils/googleTranslate'); - - if (languageCache) - { - calipso.lib.step( - function translateAll() - { - var group = this.group(); - for (var item in - languageCache) - { - gt.googleTranslate(item, language, group()); - } - }, - function allTranslated(err, translations) - { - - if (err) - { - req.flash('error', req.t('There was an error translating that language because {msg}', {msg: err.message})); - } - - if (!err && translations) - { - translations.forEach(function (translation) - { - req.languageCache[language][translation.string] = translation.translation; - }); - } - - calipso.theme.renderItem(req, res, template, block, { - languageCache: req.languageCache - }, next); - - } - ) - } - else - { - - req.flash('info', req.t('That language does not exist.')); - calipso.theme.renderItem(req, res, template, block, { - languageCache: req.languageCache - }, next); - - } - - } - else - { - - calipso.theme.renderItem(req, res, template, block, { - languageCache: req.languageCache - }, next); - - } +function showLanguages(req, res, template, block, next) { + + // Check to see if we should google translate?! + // e.g. /admin/languages?translate=es + if (req.moduleParams.translate) { + + var language = req.moduleParams.translate; + var languageCache = req.languageCache[language]; + + var gt = require('utils/googleTranslate'); + + if (languageCache) { + calipso.lib.step( + function translateAll() { + var group = this.group(); + for (var item in languageCache) { + gt.googleTranslate(item, language, group()); + } + }, + function allTranslated(err, translations) { + + if (err) { + req.flash('error', req.t('There was an error translating that language because {msg}', {msg:err.message})); + } + + if (!err && translations) { + translations.forEach(function (translation) { + req.languageCache[language][translation.string] = translation.translation; + }); + } + + calipso.theme.renderItem(req, res, template, block, { + languageCache:req.languageCache + }, next); + + } + ) + } else { + + req.flash('info', req.t('That language does not exist.')); + calipso.theme.renderItem(req, res, template, block, { + languageCache:req.languageCache + }, next); + + } + + } else { + + calipso.theme.renderItem(req, res, template, block, { + languageCache:req.languageCache + }, next); + + } } @@ -209,405 +191,353 @@ function showLanguages(req, res, template, block, next) * in the configuration, which is detected in the core routing function * in calipso.js and redirected here. */ -function install(req, res, template, block, next) -{ - - - // If not in install mode, do not install - if (calipso.config.get('installed')) - { - res.redirect("/"); - next(); - return; - } - - // Ensure we are using the install layout - res.layout = "install"; - - // The install process will work in steps - var installStep = req.moduleParams.installStep || "welcome"; - - // Process the input from the previous step - calipso.form.process(req, function (form) - { - - if (form) - { - - if (form.userStep) - { - // Store the user for later - calipso.data.adminUser = form.user; - } - else - { - // Update the configuration - updateConfiguration(form); - } - // Override install step - installStep = form.installStep - } - - // Process the installation - switch (installStep) - { - case "welcome": - installWelcome(req, res, localNext); - break; - case "mongodb": - installMongo(req, res, localNext); - break; - case "user": - installUser(req, res, localNext); - break; - case "modules": - installModules(req, res, localNext); - break; - case "finalise": - doInstallation(req, res, localNext); - break; - default: - localNext(new Error("A step was specified that is not defined in the install process: " + installStep)); - } - - }); - - function localNext(err) - { - if (err) - { - res.statusCode = 500; - res.errorMessage = err.message; - req.flash('error', req.t('Calipso has become stuck in install mode. The specific error returned was: ' + err.message)); - } - next(); - } +function install(req, res, template, block, next) { + + + // If not in install mode, do not install + if (calipso.config.get('installed')) { + res.redirect("/"); + next(); + return; + } + + // Ensure we are using the install layout + res.layout = "install"; + + // The install process will work in steps + var installStep = req.moduleParams.installStep || "welcome"; + + // Process the input from the previous step + calipso.form.process(req, function (form) { + + if (form) { + + if (form.userStep) { + // Store the user for later + calipso.data.adminUser = form.user; + } else { + // Update the configuration + updateConfiguration(form); + } + // Override install step + installStep = form.installStep + } + + // Process the installation + switch (installStep) { + case "welcome": + installWelcome(req, res, localNext); + break; + case "mongodb": + installMongo(req, res, localNext); + break; + case "user": + installUser(req, res, localNext); + break; + case "modules": + installModules(req, res, localNext); + break; + case "finalise": + doInstallation(req, res, localNext); + break; + default: + localNext(new Error("A step was specified that is not defined in the install process: " + installStep)); + } + + }); + + function localNext(err) { + if (err) { + res.statusCode = 500; + res.errorMessage = err.message; + req.flash('error', req.t('Calipso has become stuck in install mode. The specific error returned was: ' + err.message)); + } + next(); + } } /** * Installation welcome screen - called by install router, not a routing function. */ -function updateConfiguration(values) -{ - - // Update config for all the values, do not save now - for (value in - values) - { - if (value !== 'installStep' && value !== 'userStep' && value !== 'returnTo' && value !== 'submit') - { - calipso.config.set(value, values[value]); - } - } - return; +function updateConfiguration(values) { + + // Update config for all the values, do not save now + for (value in values) { + if (value !== 'installStep' && value !== 'userStep' && value !== 'returnTo' && value !== 'submit') { + calipso.config.set(value, values[value]); + } + } + return; } /** * Installation welcome screen - called by install router, not a routing function. */ -function installWelcome(req, res, next) -{ +function installWelcome(req, res, next) { - // Manually grab the template - var template = calipso.modules.admin.templates.install_welcome; - calipso.theme.renderItem(req, res, template, 'admin.install.welcome', {}, next); + // Manually grab the template + var template = calipso.modules.admin.templates.install_welcome; + calipso.theme.renderItem(req, res, template, 'admin.install.welcome', {}, next); } /** * Installation mongodb - called by install router, not a routing function. */ -function installMongo(req, res, next) -{ - - // Manually grab the template - var template = calipso.modules.admin.templates.install_mongo; - - // Create the form - var mongoForm = {id: 'install-mongo-form', title: '', type: 'form', method: 'POST', action: '/admin/install', - fields: [ - {label: 'MongoDB URI', name: 'database:uri', cls: 'database-uri', type: 'text', description: 'Enter the database URI, in the form: mongodb://servername:port/database'}, - {label: '', name: 'installStep', type: 'hidden'} - ], - buttons: []}; // Submitted via template - - var formValues = { - database: { - uri: calipso.config.get('database:uri') - }, - 'installStep': 'user' - } - - calipso.form.render(mongoForm, formValues, req, function (form) - { - calipso.theme.renderItem(req, res, template, 'admin.install.mongo', {form: form}, next); - }); +function installMongo(req, res, next) { + + // Manually grab the template + var template = calipso.modules.admin.templates.install_mongo; + + // Create the form + var mongoForm = {id:'install-mongo-form', title:'', type:'form', method:'POST', action:'/admin/install', + fields:[ + {label:'MongoDB URI', name:'database:uri', cls:'database-uri', type:'text', description:'Enter the database URI, in the form: mongodb://servername:port/database'}, + {label:'', name:'installStep', type:'hidden'} + ], + buttons:[]}; // Submitted via template + + var formValues = { + database:{ + uri:calipso.config.get('database:uri') + }, + 'installStep':'user' + } + + calipso.form.render(mongoForm, formValues, req, function (form) { + calipso.theme.renderItem(req, res, template, 'admin.install.mongo', {form:form}, next); + }); } /** * Function to enable ajax testing of the mongo configuration */ -function installMongoTest(req, res, template, block, next) -{ - - if (calipso.config.get('installed')) - { - res.format = "json"; - res.end(JSON.stringify({status: "Invalid Request"}), "UTF-8"); - } - - calipso.form.process(req, function (form) - { - - var dbUri = form.dbUri; - var output = {}; - - if (dbUri) - { - calipso.storage.mongoConnect(dbUri, true, function (err, connected) - { - if (!err) - { - output.status = "OK"; - } - else - { - output.status = "FAILED"; - output.message = "Failed to connect to MongoDB because: " + err.message; - } - res.format = "json"; - res.end(JSON.stringify(output), "UTF-8"); - }); - } - else - { - output.status = "FAILED"; - output.message = "You need to provide a valid database uri, in the format described."; - res.format = "json"; - res.end(JSON.stringify(output), "UTF-8"); - } - - }); +function installMongoTest(req, res, template, block, next) { + + if (calipso.config.get('installed')) { + res.format = "json"; + res.end(JSON.stringify({status:"Invalid Request"}), "UTF-8"); + } + + calipso.form.process(req, function (form) { + + var dbUri = form.dbUri; + var output = {}; + + if (dbUri) { + calipso.storage.mongoConnect(dbUri, true, function (err, connected) { + if (!err) { + output.status = "OK"; + } else { + output.status = "FAILED"; + output.message = "Failed to connect to MongoDB because: " + err.message; + } + res.format = "json"; + res.end(JSON.stringify(output), "UTF-8"); + }); + } else { + output.status = "FAILED"; + output.message = "You need to provide a valid database uri, in the format described."; + res.format = "json"; + res.end(JSON.stringify(output), "UTF-8"); + } + + }); } /** * Installation user - called by install router, not a routing function. */ -function installUser(req, res, next) -{ - - // Manually grab the template - var template = calipso.modules.admin.templates.install_user; - - // Create the form - // TODO - reference exported form from user module instead, this will be difficult to maintain - var userForm = { - id: 'install-user-form', title: '', type: 'form', method: 'POST', action: '/admin/install', - fields: [ - {label: 'Username', name: 'user[username]', cls: 'username', type: 'text'}, - {label: 'Full Name', name: 'user[fullname]', type: 'text'}, - {label: 'Email', name: 'user[email]', cls: 'email', type: 'text'}, - {label: 'Language', name: 'user[language]', type: 'select', options: req.languages}, - // TODO : Select based on available - {label: 'Password', name: 'user[password]', cls: 'password', type: 'password'}, - {label: 'Repeat Password', name: 'user[check_password]', cls: 'check_password', type: 'password'}, - {label: '', name: 'installStep', type: 'hidden'}, - {label: '', name: 'userStep', type: 'hidden'} - ], - buttons: [] - }; - - var formValues = { - user: (calipso.data.adminUser || {}), // Store here during install process - 'userStep': true, - 'installStep': 'modules' - } - - calipso.form.render(userForm, formValues, req, function (form) - { - calipso.theme.renderItem(req, res, template, 'admin.install.user', {form: form}, next); - }); +function installUser(req, res, next) { + + // Manually grab the template + var template = calipso.modules.admin.templates.install_user; + + // Create the form + // TODO - reference exported form from user module instead, this will be difficult to maintain + var userForm = { + id:'install-user-form', title:'', type:'form', method:'POST', action:'/admin/install', + fields:[ + {label:'Username', name:'user[username]', cls:'username', type:'text'}, + {label:'Full Name', name:'user[fullname]', type:'text'}, + {label:'Email', name:'user[email]', cls:'email', type:'text'}, + {label:'Language', name:'user[language]', type:'select', options:req.languages}, + // TODO : Select based on available + {label:'Password', name:'user[password]', cls:'password', type:'password'}, + {label:'Repeat Password', name:'user[check_password]', cls:'check_password', type:'password'}, + {label:'', name:'installStep', type:'hidden'}, + {label:'', name:'userStep', type:'hidden'} + ], + buttons:[] + }; + + var formValues = { + user:(calipso.data.adminUser || {}), // Store here during install process + 'userStep':true, + 'installStep':'modules' + } + + calipso.form.render(userForm, formValues, req, function (form) { + calipso.theme.renderItem(req, res, template, 'admin.install.user', {form:form}, next); + }); } /** * Function to enable ajax testing of the mongo configuration */ -function installUserTest(req, res, template, block, next) -{ - - if (calipso.config.get('installed')) - { - res.format = "json"; - res.end(JSON.stringify({status: "Invalid Request"}), "UTF-8"); - } - - calipso.form.process(req, function (form) - { - - // Check to see if new passwords match - var err; - - if (form.password != form.check_password) - { - err = new Error(req.t('Your passwords do not match.')); - } - - // Check to see if new passwords are blank - if (form.password === '') - { - err = new Error(req.t('Your password cannot be blank.')); - } - - if (form.username === '') - { - err = new Error(req.t('Your username cannot be blank.')); - } - - // Check to see if new passwords are blank - if (form.email === '') - { - err = new Error(req.t('Your email cannot be blank.')); - } - - var output = {}; - if (err) - { - output.status = "FAILED"; - output.message = "There was a problem because: " + err.message; - } - else - { - output.status = "OK"; - } - res.format = "json"; - res.end(JSON.stringify(output), "UTF-8"); - - }); +function installUserTest(req, res, template, block, next) { + + if (calipso.config.get('installed')) { + res.format = "json"; + res.end(JSON.stringify({status:"Invalid Request"}), "UTF-8"); + } + + calipso.form.process(req, function (form) { + + // Check to see if new passwords match + var err; + + if (form.password != form.check_password) { + err = new Error(req.t('Your passwords do not match.')); + } + + // Check to see if new passwords are blank + if (form.password === '') { + err = new Error(req.t('Your password cannot be blank.')); + } + + if (form.username === '') { + err = new Error(req.t('Your username cannot be blank.')); + } + + // Check to see if new passwords are blank + if (form.email === '') { + err = new Error(req.t('Your email cannot be blank.')); + } + + var output = {}; + if (err) { + output.status = "FAILED"; + output.message = "There was a problem because: " + err.message; + } else { + output.status = "OK"; + } + res.format = "json"; + res.end(JSON.stringify(output), "UTF-8"); + + }); } /** * Install Modules - called by install router, not a routing function. */ -function installModules(req, res, next) -{ - - // Manually grab the template - var template = calipso.modules.admin.templates.install_modules; - - // Create the form - var moduleForm = {id: 'install-modules-form', title: '', type: 'form', method: 'POST', action: '/admin/install', - fields: [ - {label: '', name: 'installStep', type: 'hidden'} - ], - buttons: []}; // Submitted via template - - //Add the modules - moduleForm.fields = createModuleFields(moduleForm.fields); - - // Defaults - var formValues = { - modules: { - admin: { - enabled: true - }, - content: { - enabled: true - }, - contentTypes: { - enabled: true - }, - user: { - enabled: true - }, - permissions: { - enabled: true - }, - taxonomy: { - enabled: true - }, - tagcloud: { - enabled: true - } - }, - installStep: 'finalise' - }; - - calipso.form.render(moduleForm, formValues, req, function (form) - { - calipso.theme.renderItem(req, res, template, 'admin.install.modules', {form: form}, next); - }); +function installModules(req, res, next) { + + // Manually grab the template + var template = calipso.modules.admin.templates.install_modules; + + // Create the form + var moduleForm = {id:'install-modules-form', title:'', type:'form', method:'POST', action:'/admin/install', + fields:[ + {label:'', name:'installStep', type:'hidden'} + ], + buttons:[]}; // Submitted via template + + //Add the modules + moduleForm.fields = createModuleFields(moduleForm.fields); + + // Defaults + var formValues = { + modules:{ + admin:{ + enabled:true + }, + content:{ + enabled:true + }, + contentTypes:{ + enabled:true + }, + user:{ + enabled:true + }, + permissions:{ + enabled:true + }, + taxonomy:{ + enabled:true + }, + tagcloud:{ + enabled:true + } + }, + installStep:'finalise' + }; + + calipso.form.render(moduleForm, formValues, req, function (form) { + calipso.theme.renderItem(req, res, template, 'admin.install.modules', {form:form}, next); + }); } -function doInstallation(req, res, next) -{ - - // NOTE: User is installed via the user module - - // Set the install flag to true, enable db connection - calipso.config.set('installed', true); - calipso.storage.mongoConnect(function (err) - { - - if (err) - { - return next(err); - } - - // Note - the admin user is created in the user module install process - calipso.lib.step( - function saveConfiguration() - { - // Save configuration to file - calipso.info("Saving configuration ... "); - calipso.config.save(this); - }, - function reloadConfiguration() - { - // This actually re-loads all of the modules - calipso.info("Reloading updated configuration ... "); - calipso.reloadConfig("ADMIN_INSTALL", calipso.config, this); - }, - function installModules() - { - - // TODO - this should just be part of enabling them the first time! - - var group = this.group(); - - // Get a list of all the modules to install - var modulesToInstall = []; - for (var module in - calipso.modules) - { - // Check to see if the module is currently enabled, if so install it - if (calipso.modules[module].enabled && calipso.modules[module].fn && typeof calipso.modules[module].fn.install === 'function') - { - modulesToInstall.push(module); - } - } - - modulesToInstall.forEach(function (module) - { - calipso.info("Installing module " + module); - calipso.modules[module].fn.install(group()); - }); - - }, - function done(err) - { - - res.redirect("/") - return next(err); - - } - ); - - }); +function doInstallation(req, res, next) { + + // NOTE: User is installed via the user module + + // Set the install flag to true, enable db connection + calipso.config.set('installed', true); + calipso.storage.mongoConnect(function (err) { + + if (err) { + return next(err); + } + + // Note - the admin user is created in the user module install process + calipso.lib.step( + function saveConfiguration() { + // Save configuration to file + calipso.info("Saving configuration ... "); + calipso.config.save(this); + }, + function reloadConfiguration() { + // This actually re-loads all of the modules + calipso.info("Reloading updated configuration ... "); + calipso.reloadConfig("ADMIN_INSTALL", calipso.config, this); + }, + function installModules() { + + // TODO - this should just be part of enabling them the first time! + + var group = this.group(); + + // Get a list of all the modules to install + var modulesToInstall = []; + for (var module in calipso.modules) { + // Check to see if the module is currently enabled, if so install it + if (calipso.modules[module].enabled && calipso.modules[module].fn && typeof calipso.modules[module].fn.install === 'function') { + modulesToInstall.push(module); + } + } + + modulesToInstall.forEach(function (module) { + calipso.info("Installing module " + module); + calipso.modules[module].fn.install(group()); + }); + + }, + function done(err) { + + res.redirect("/") + return next(err); + + } + ); + + }); } @@ -615,10 +545,9 @@ function doInstallation(req, res, next) * Show the current configuration * TODO Refactor this to a proper form */ -function showAdmin(req, res, template, block, next) -{ +function showAdmin(req, res, template, block, next) { - calipso.theme.renderItem(req, res, template, block, {}, next); + calipso.theme.renderItem(req, res, template, block, {}, next); } @@ -626,400 +555,402 @@ function showAdmin(req, res, template, block, next) * Show the current configuration * TODO Refactor this to a proper form */ -function coreConfig(req, res, template, block, next) -{ - - calipso.data.themes = []; - calipso.data.adminThemes = []; // TODO - for (var themeName in - calipso.availableThemes) - { - var theme = calipso.availableThemes[themeName]; - if (theme.about.type === "full" || theme.about.type === "frontend") - { - calipso.data.themes.push(themeName); - } - if (theme.about.type === "full" || theme.about.type === "admin") - { - calipso.data.adminThemes.push(themeName); - } - if (!theme.about.type) - { - console.error("Theme " + themeName + " not enabled due to missing type."); - } - } - - var adminForm = { - id: 'admin-form', - title: 'Administration', - type: 'form', - method: 'POST', - action: '/admin/core/config/save', - tabs: true, - sections: [ - { - id: 'form-section-core', - label: 'Site', - fields: [ - { - label: 'Site Name', - name: 'server:name', - type: 'text' - }, - { - label: 'Login Path', - name: 'server:loginPath', - type: 'text' - }, - { - label: 'Modules Location', - name: 'server:modulePath', - type: 'text' - }, - { - label: 'Themes Location', - name: 'server:themePath', - type: 'text' - }, - { - label: 'Server URL', - name: 'server:url', - type: 'text' - }, - { - label: 'Session Secret', - name: 'session:secret', - type: 'password' - }, - { - label: 'Session Max Age (seconds)', - name: 'session:maxAge', - type: 'text' - } - ] - }, - { - id: 'form-section-language', - label: 'Language', - fields: [ - { - label: 'Default Language', - name: 'i18n:language', - type: 'select', - options: req.languages - }, - { - label: 'Add Unknown Terms', - name: 'i18n:additive', - type: 'checkbox', - labelFirst: true - } - ] - }, - { - id: 'form-section-performance', - label: 'Performance & Clustering', - fields: [ - { - label: 'Performance', - legend: 'Performance', - type: 'fieldset', - fields: [ - { - label: 'Enable Cache', - name: 'performance:cache:enabled', - type: 'checkbox', - description: 'Experimental - will probably break things!', - labelFirst: true - }, - { - label: 'Default Cache TTL', - name: 'performance:cache:ttl', - type: 'text', - description: 'Default age (in seconds) for cache items.' - }, - { - label: 'Watch Template Files', - name: 'performance:watchFiles', - type: 'checkbox', - labelFirst: true - } - ] - }, - { - label: 'Clustering', - legend: 'Clustering', - type: 'fieldset', - fields: [ - { - label: 'Number Workers', - description: 'Number of workers to start, set to 0 to have Calipso default to number of available cpus.', - name: 'server:cluster:workers', - type: 'text' - }, - { - label: 'Restart Workers', - name: 'server:cluster:restartWorkers', - description: 'Automatically restart workers if they die.', - type: 'checkbox', - labelFirst: true - }, - { - label: 'Maximum Restarts', - name: 'server:cluster:maximumRestarts', - description: 'Number of failures before it will stop attempting to restart a worker.', - type: 'text' - } - ] - }, - { - label: 'Event Emitter', - legend: 'Event Emitter', - type: 'fieldset', - fields: [ - { - label: 'EventEmitter Max Listeners', - name: 'server:events:maxListeners', - type: 'text' - } - ] - } - ] - }, - { - id: 'form-section-authentication', - label: 'Authentication', - fields: [ - { label: 'Password Login and Registration (changes require a restart of calipso)', - legend: 'Password Login and Registration (changes require a restart of calipso)', - type: 'fieldset', - fields: [ - { - label: 'Enable password authentication and registration', - type: 'checkbox', - name: 'server:authentication:password', - description: 'Please make sure you have made an external user (google, facebook or twitter an admin account) so you don\'t lose access to your system.' - }, - { - label: 'Enable password migration to pbkdf2 hash', - type: 'checkbox', - name: 'server:authentication:migrate2pbkdf2', - description: 'As new people create password hashes they will be converted to pbkdf2 hashes.' - } - ] - }, - { label: 'Facebook Authentication (changes require a restart of calipso)', - legend: 'Set this information to enable Facebook Authentication (changes require a restart of calipso)', - type: 'fieldset', - fields: [ - { - label: 'AppId', - description: 'Set AppId and Secret to enable facebook authentication', - name: 'server:authentication:facebookAppId', - type: 'password' - }, - { - label: 'AppSecret', - description: 'AppSecret for this application to allow facebook authentication', - name: 'server:authentication:facebookAppSecret', - type: 'password' - } - ] - }, - { label: 'Google Authentication (changes require a restart of calipso)', - legend: 'Set this information to enable Google Authentication (changes require a restart of calipso)', - type: 'fieldset', - fields: [ - { - label: 'ClientId', - description: 'Set ClientId and ClientSecret to enable google authentication', - name: 'server:authentication:googleClientId', - type: 'password' - }, - { - label: 'ClientSecret', - description: 'ClientSecret for this application to allow google authentication', - name: 'server:authentication:googleClientSecret', - type: 'password' - }, - { - label: 'Google Callback', - description: 'Callback URL for google authentication', - type: 'readonlytext', - value: calipso.config.get('server:url') + '/auth/google/callback' - } - ] - }, - { label: 'Twitter Authentication (changes require a restart of calipso)', - legend: 'Set this information to enable Twitter Authentication (changes require a restart of calipso)', - type: 'fieldset', - fields: [ - { - label: 'Twitter ConsumerKey', - description: 'Set ConsumerKey and ConsumerSecret to allow twitter authentication', - name: 'server:authentication:twitterConsumerKey', - type: 'password' - }, - { - label: 'Twitter ConsumerSecret', - description: 'ConsumerSecret for this application to allow twitter authentication', - name: 'server:authentication:twitterConsumerSecret', - type: 'password' - }, - { - label: 'Twitter Callback', - description: 'Callback URL for twitter authentication', - type: 'readonlytext', - value: calipso.config.get('server:url') + '/auth/twitter/callback' - } - ] - } - ] - }, - { - id: 'form-section-theme', - label: 'Theme', - fields: [ - { - label: 'Frontend Theme', - name: 'theme:front', - type: 'select', - options: calipso.data.themes, - description: 'Theme used for all web pages excluding admin pages' - }, - { - label: 'Admin Theme', - name: 'theme:admin', - type: 'select', - options: calipso.data.adminThemes, - description: 'Administration theme [NOT YET IMPLEMENTED]' - }, - { - name: 'theme:default', - type: 'hidden' - }, - { - label: 'Stylus Middleware', - legend: 'Stylus Middleware', - type: 'fieldset', - fields: [ - { - label: 'Enable Stylus', - type: 'checkbox', - defaultValue: false, - name: 'libraries:stylus:enable' - }, - { - label: 'Show Warnings', - type: 'checkbox', - defaultValue: false, - name: 'libraries:stylus:warn' - }, - { - label: 'Compress CSS', - type: 'checkbox', - defaultValue: false, - name: 'libraries:stylus:compress' - } - ] - } - ] - }, - { - id: 'form-section-logging', - label: 'Logging', - fields: [ - { - label: 'Console Logging', - name: 'logging:console:enabled', - type: 'checkbox', - labelFirst: true, - description: 'Enable logging to the console.' - }, - { - label: 'Console Log Level', - name: 'logging:console:level', - type: 'select', - options: calipso.data.loglevels, - description: 'Log level that controls verbosity of display on the console.' - }, - { - label: 'Console Timestamp', - name: 'logging:console:timestamp', - type: 'checkbox', - labelFirst: true, - description: 'Prepend timestamps to console logs.' - }, - { - label: 'Console Colorize', - name: 'logging:console:colorize', - type: 'checkbox', - labelFirst: true, - description: 'Show colors on the console logs' - }, - { - label: 'File Logging', - name: 'logging:file:enabled', - type: 'checkbox', - labelFirst: true - }, - { - label: 'File Log Level', - name: 'logging:file:level', - type: 'select', - options: calipso.data.loglevels, - description: 'Log level that controls verbosity of display in the file logs.' - }, - { - label: 'File Log Path', - name: 'logging:file:filepath', - type: 'text', - description: 'Path to create the file logs.' - }, - { - label: 'File Log Timestamp', - name: 'logging:file:timestamp', - type: 'checkbox', - labelFirst: true, - description: 'Prepend timestamps to file logs.' - } - ] - }, - { - id: 'form-section-modules', - label: 'Modules', - fields: [] // populated in a loop just below - } - ], - fields: [ - { - label: '', - name: 'returnTo', - type: 'hidden' - } - ], - buttons: [ - { - name: 'submit', - type: 'submit', - value: 'Save Configuration' - }, - {name: 'cancel', type: 'button', href: '/admin', value: 'Cancel'} - ] - }; - - // Values can come straight off the config. - var values = calipso.config; - - var adminModuleFields = adminForm.sections[6].fields; - createModuleFields(adminModuleFields); - - res.layout = 'admin'; - - calipso.form.render(adminForm, values, req, function (form) - { - calipso.theme.renderItem(req, res, form, block, {}, next); - }); +function coreConfig(req, res, template, block, next) { + + calipso.data.themes = []; + calipso.data.adminThemes = []; // TODO + for (var themeName in calipso.availableThemes) { + var theme = calipso.availableThemes[themeName]; + if (theme.about.type === "full" || theme.about.type === "frontend") { + calipso.data.themes.push(themeName); + } + if (theme.about.type === "full" || theme.about.type === "admin") { + calipso.data.adminThemes.push(themeName); + } + if (!theme.about.type) { + console.error("Theme " + themeName + " not enabled due to missing type."); + } + } + + var adminForm = { + id:'admin-form', + title:'Administration', + type:'form', + method:'POST', + action:'/admin/core/config/save', + tabs:true, + sections:[ + { + id:'form-section-core', + label:'Site', + fields:[ + { + label:'Site Name', + name:'server:name', + type:'text' + }, + { + label:'Login Path', + name:'server:loginPath', + type:'text' + }, + { + label:'Modules Location', + name:'server:modulePath', + type:'text' + }, + { + label:'Themes Location', + name:'server:themePath', + type:'text' + }, + { + label:'Server URL', + name:'server:url', + type:'text' + }, + { + label:'Session Secret', + name:'session:secret', + type:'password' + }, + { + label:'Session Max Age (seconds)', + name:'session:maxAge', + type:'text' + } + ] + }, + { + id:'form-section-language', + label:'Language', + fields:[ + { + label:'Default Language', + name:'i18n:language', + type:'select', + options:req.languages + }, + { + label:'Add Unknown Terms', + name:'i18n:additive', + type:'checkbox', + labelFirst:true + } + ] + }, + { + id:'form-section-performance', + label:'Performance & Clustering', + fields:[ + { + label:'Performance', + legend:'Performance', + type:'fieldset', + fields:[ + { + label:'Enable Cache', + name:'performance:cache:enabled', + type:'checkbox', + description:'Experimental - will probably break things!', + labelFirst:true + }, + { + label:'Default Cache TTL', + name:'performance:cache:ttl', + type:'text', + description:'Default age (in seconds) for cache items.' + }, + { + label:'Watch Template Files', + name:'performance:watchFiles', + type:'checkbox', + labelFirst:true + } + ] + }, + { + label:'Clustering', + legend:'Clustering', + type:'fieldset', + fields:[ + { + label:'Number Workers', + description:'Number of workers to start, set to 0 to have Calipso default to number of available cpus.', + name:'server:cluster:workers', + type:'text' + }, + { + label:'Restart Workers', + name:'server:cluster:restartWorkers', + description:'Automatically restart workers if they die.', + type:'checkbox', + labelFirst:true + }, + { + label:'Maximum Restarts', + name:'server:cluster:maximumRestarts', + description:'Number of failures before it will stop attempting to restart a worker.', + type:'text' + } + ] + }, + { + label:'Event Emitter', + legend:'Event Emitter', + type:'fieldset', + fields:[ + { + label:'EventEmitter Max Listeners', + name:'server:events:maxListeners', + type:'text' + } + ] + } + ] + }, + { + id:'form-section-authentication', + label:'Authentication', + fields:[ + { + label:'Password Login and Registration (changes require a restart of calipso)', + legend:'Password Login and Registration (changes require a restart of calipso)', + type:'fieldset', + fields:[ + { + label:'Enable password authentication and registration', + type:'checkbox', + name:'server:authentication:password', + description:'Please make sure you have made an external user (google, facebook or twitter an admin account) so you don\'t lose access to your system.' + }, + { + label:'Enable password migration to pbkdf2 hash', + type:'checkbox', + name:'server:authentication:migrate2pbkdf2', + description:'As new people create password hashes they will be converted to pbkdf2 hashes.' + } + ] + }, + { + label:'Facebook Authentication (changes require a restart of calipso)', + legend:'Set this information to enable Facebook Authentication (changes require a restart of calipso)', + type:'fieldset', + fields:[ + { + label:'AppId', + description:'Set AppId and Secret to enable facebook authentication', + name:'server:authentication:facebookAppId', + type:'password' + }, + { + label:'AppSecret', + description:'AppSecret for this application to allow facebook authentication', + name:'server:authentication:facebookAppSecret', + type:'password' + } + ] + }, + { + label:'Google Authentication (changes require a restart of calipso)', + legend:'Set this information to enable Google Authentication (changes require a restart of calipso)', + type:'fieldset', + fields:[ + { + label:'ClientId', + description:'Set ClientId and ClientSecret to enable google authentication', + name:'server:authentication:googleClientId', + type:'password' + }, + { + label:'ClientSecret', + description:'ClientSecret for this application to allow google authentication', + name:'server:authentication:googleClientSecret', + type:'password' + }, + { + label:'Google Callback', + description:'Callback URL for google authentication', + type:'readonlytext', + value:calipso.config.get('server:url') + '/auth/google/callback' + } + ] + }, + { + label:'Twitter Authentication (changes require a restart of calipso)', + legend:'Set this information to enable Twitter Authentication (changes require a restart of calipso)', + type:'fieldset', + fields:[ + { + label:'Twitter ConsumerKey', + description:'Set ConsumerKey and ConsumerSecret to allow twitter authentication', + name:'server:authentication:twitterConsumerKey', + type:'password' + }, + { + label:'Twitter ConsumerSecret', + description:'ConsumerSecret for this application to allow twitter authentication', + name:'server:authentication:twitterConsumerSecret', + type:'password' + }, + { + label:'Twitter Callback', + description:'Callback URL for twitter authentication', + type:'readonlytext', + value:calipso.config.get('server:url') + '/auth/twitter/callback' + } + ] + } + ] + }, + { + id:'form-section-theme', + label:'Theme', + fields:[ + { + label:'Frontend Theme', + name:'theme:front', + type:'select', + options:calipso.data.themes, + description:'Theme used for all web pages excluding admin pages' + }, + { + label:'Admin Theme', + name:'theme:admin', + type:'select', + options:calipso.data.adminThemes, + description:'Administration theme [NOT YET IMPLEMENTED]' + }, + { + name:'theme:default', + type:'hidden' + }, + { + label:'Stylus Middleware', + legend:'Stylus Middleware', + type:'fieldset', + fields:[ + { + label:'Enable Stylus', + type:'checkbox', + defaultValue:false, + name:'libraries:stylus:enable' + }, + { + label:'Show Warnings', + type:'checkbox', + defaultValue:false, + name:'libraries:stylus:warn' + }, + { + label:'Compress CSS', + type:'checkbox', + defaultValue:false, + name:'libraries:stylus:compress' + } + ] + } + ] + }, + { + id:'form-section-logging', + label:'Logging', + fields:[ + { + label:'Console Logging', + name:'logging:console:enabled', + type:'checkbox', + labelFirst:true, + description:'Enable logging to the console.' + }, + { + label:'Console Log Level', + name:'logging:console:level', + type:'select', + options:calipso.data.loglevels, + description:'Log level that controls verbosity of display on the console.' + }, + { + label:'Console Timestamp', + name:'logging:console:timestamp', + type:'checkbox', + labelFirst:true, + description:'Prepend timestamps to console logs.' + }, + { + label:'Console Colorize', + name:'logging:console:colorize', + type:'checkbox', + labelFirst:true, + description:'Show colors on the console logs' + }, + { + label:'File Logging', + name:'logging:file:enabled', + type:'checkbox', + labelFirst:true + }, + { + label:'File Log Level', + name:'logging:file:level', + type:'select', + options:calipso.data.loglevels, + description:'Log level that controls verbosity of display in the file logs.' + }, + { + label:'File Log Path', + name:'logging:file:filepath', + type:'text', + description:'Path to create the file logs.' + }, + { + label:'File Log Timestamp', + name:'logging:file:timestamp', + type:'checkbox', + labelFirst:true, + description:'Prepend timestamps to file logs.' + } + ] + }, + { + id:'form-section-modules', + label:'Modules', + fields:[] // populated in a loop just below + } + ], + fields:[ + { + label:'', + name:'returnTo', + type:'hidden' + } + ], + buttons:[ + { + name:'submit', + type:'submit', + value:'Save Configuration' + }, + { + name:'cancel', + type:'button', + href:'/admin', + value:'Cancel' + } + ] + }; + + // Values can come straight off the config. + var values = calipso.config; + + var adminModuleFields = adminForm.sections[6].fields; + createModuleFields(adminModuleFields); + + res.layout = 'admin'; + + calipso.form.render(adminForm, values, req, function (form) { + calipso.theme.renderItem(req, res, form, block, {}, next); + }); } @@ -1027,339 +958,290 @@ function coreConfig(req, res, template, block, next) * Show the current configuration * TODO Refactor this to a proper form */ -function modulesConfig(req, res, template, block, next) -{ - - var moduleName = req.query.module || ''; - - if (!moduleName || !calipso.modules[moduleName]) - { - req.flash('error', 'You need to specify a valid module.'); - res.redirect('/admin'); - return next(); - } - - var configForm = { - id: 'module-config-form', - title: 'Configure: ' + moduleName, - type: 'form', - method: 'POST', - action: '/admin/modules/save', - tabs: false, - fields: [ - { - label: '', - value: moduleName, - name: 'moduleName', - type: 'hidden' - } - ], - buttons: [ - { - name: 'submit', - type: 'submit', - value: 'Save Configuration' - }, - {name: 'cancel', type: 'button', href: '/admin', value: 'Cancel'} - ] - }; - - // Values can come straight off the config. - var values = calipso.config.getModuleConfig(moduleName); - - // Fields come from the module - var config = calipso.modules[moduleName].fn.config; - - calipso.lib._.keys(config).forEach(function (key) - { - var field = {}; - field.label = config[key].label || key; - field.name = key; - - if (config[key].type) - { - - field.type = config[key].type; - - // select boxes - if (config[key].options) - { - field.options = config[key].options; - } - - } - else - { - // infer from value - if (typeof values[key] === 'boolean') - { - field.type = 'checkbox'; - field.labelFirst = true; - } - else - { - field.type = 'text'; - } - } - - field.description = config[key].description || ''; - configForm.fields.push(field); - }) - - res.layout = 'admin'; - - calipso.form.render(configForm, values, req, function (form) - { - calipso.theme.renderItem(req, res, form, block, {}, next); - }); +function modulesConfig(req, res, template, block, next) { + + var moduleName = req.query.module || ''; + + if (!moduleName || !calipso.modules[moduleName]) { + req.flash('error', 'You need to specify a valid module.'); + res.redirect('/admin'); + return next(); + } + + var configForm = { + id:'module-config-form', + title:'Configure: ' + moduleName, + type:'form', + method:'POST', + action:'/admin/modules/save', + tabs:false, + fields:[ + { + label:'', + value:moduleName, + name:'moduleName', + type:'hidden' + } + ], + buttons:[ + { + name:'submit', + type:'submit', + value:'Save Configuration' + }, + { + name:'cancel', + type:'button', + href:'/admin', + value:'Cancel' + } + ] + }; + + // Values can come straight off the config. + var values = calipso.config.getModuleConfig(moduleName); + + // Fields come from the module + var config = calipso.modules[moduleName].fn.config; + + calipso.lib._.keys(config).forEach(function (key) { + var field = {}; + field.label = config[key].label || key; + field.name = key; + + if (config[key].type) { + + field.type = config[key].type; + + // select boxes + if (config[key].options) { + field.options = config[key].options; + } + + } else { + // infer from value + if (typeof values[key] === 'boolean') { + field.type = 'checkbox'; + field.labelFirst = true; + } else { + field.type = 'text'; + } + } + + field.description = config[key].description || ''; + configForm.fields.push(field); + }) + + res.layout = 'admin'; + + calipso.form.render(configForm, values, req, function (form) { + calipso.theme.renderItem(req, res, form, block, {}, next); + }); } /** * Save module configuratino */ -function saveModulesConfig(req, res, template, block, next) -{ +function saveModulesConfig(req, res, template, block, next) { - calipso.form.process(req, function (moduleConfig) - { + calipso.form.process(req, function (moduleConfig) { - if (moduleConfig) - { + if (moduleConfig) { - var moduleName = moduleConfig.moduleName; + var moduleName = moduleConfig.moduleName; - // Clean the submitted object - delete moduleConfig.moduleName - delete moduleConfig.submit + // Clean the submitted object + delete moduleConfig.moduleName + delete moduleConfig.submit - calipso.config.setModuleConfig(moduleName, '', moduleConfig); + calipso.config.setModuleConfig(moduleName, '', moduleConfig); - calipso.e.pre_emit('CONFIG_UPDATE', {module: moduleName, config: moduleConfig}, function (config) - { + calipso.e.pre_emit('CONFIG_UPDATE', {module:moduleName, config:moduleConfig}, function (config) { - calipso.config.save(function (err) - { + calipso.config.save(function (err) { - if (err) - { + if (err) { - req.flash('error', req.t('Could not save the updated configuration, there was an error: ' + err.message)); - res.redirect('/admin/modules?module=' + moduleName); + req.flash('error', req.t('Could not save the updated configuration, there was an error: ' + err.message)); + res.redirect('/admin/modules?module=' + moduleName); - } - else - { + } else { - // Set the reload config flag for event handler to pick up - calipso.e.post_emit('CONFIG_UPDATE', {module: moduleName, config: moduleConfig}, function (config) - { + // Set the reload config flag for event handler to pick up + calipso.e.post_emit('CONFIG_UPDATE', {module:moduleName, config:moduleConfig}, function (config) { - req.flash('info', req.t('Changes to configuration saved.')); - res.redirect('/admin'); - next(); + req.flash('info', req.t('Changes to configuration saved.')); + res.redirect('/admin'); + next(); - }); + }); - } - }); + } + }); - }); + }); - } - else - { + } else { - req.flash('error', req.t('Could not process the updated module configuration.')); - res.redirect('/admin'); - next(); + req.flash('error', req.t('Could not process the updated module configuration.')); + res.redirect('/admin'); + next(); - } + } - }); + }); } /** * Create a form field for a module */ -function createModuleFields(formFields) -{ - - var readonlyModules = ["admin", "user", "content", "contentTypes", "permissions"]; // Modules that cant be disabled - var tempModuleFields = {}; - - // load up the tempModuleFields (according to module category) - for (var moduleName in - calipso.modules) - { - - var cM = {}; - var module = calipso.modules[moduleName]; - - if (module.about) - { - var moduleDisplayName = module.about.label ? module.about.label : module.about.name; - - cM.label = moduleDisplayName; - cM.name = 'modules:' + moduleName + ":enabled"; - // cM.checked = module.enabled; - cM.type = 'checkbox'; - if (calipso.lib._.indexOf(readonlyModules, moduleName) !== -1) - { - cM.readonly = true; - } - cM.description = module.about ? module.about.description : '' + moduleName + ' is missing its package.json file'; - - //adminModuleFields[moduleFieldMap[module.type]].fields.push(cM); - tempModuleFields[module.type] = tempModuleFields[module.type] || []; - tempModuleFields[module.type].push(cM); - - } - else - { - - calipso.error("Module: " + moduleName + " @ " + module.path + ", appears to be invalid, it will not be shown in the configuration form."); - - } - - } - - for (moduleType in - tempModuleFields) - { - var moduleTypeFields = tempModuleFields[moduleType]; - // "Site" modules fieldset will only show up if there are any to show. - if (moduleTypeFields.length) - { - formFields.push({ - type: 'fieldset', - name: moduleType + '_fieldset', // shouldn't need a name ... - legend: moduleType, - fields: moduleTypeFields - }); - } - } - ; - - // sort modules - function moduleSort(a, b) - { - return a.name < b.name ? -1 : 1; - } - - for (var i = 0; - i < formFields.length; - i++) - { - if (formFields[i].fields && formFields[i].fields.length) - { - formFields[i].fields.sort(moduleSort); - } - } - - return formFields; +function createModuleFields(formFields) { + + var readonlyModules = ["admin", "user", "content", "contentTypes", "permissions"]; // Modules that cant be disabled + var tempModuleFields = {}; + + // load up the tempModuleFields (according to module category) + for (var moduleName in calipso.modules) { + + var cM = {}; + var module = calipso.modules[moduleName]; + + if (module.about) { + var moduleDisplayName = module.about.label ? module.about.label : module.about.name; + + cM.label = moduleDisplayName; + cM.name = 'modules:' + moduleName + ":enabled"; + // cM.checked = module.enabled; + cM.type = 'checkbox'; + if (calipso.lib._.indexOf(readonlyModules, moduleName) !== -1) { + cM.readonly = true; + } + cM.description = module.about ? module.about.description : '' + moduleName + ' is missing its package.json file'; + + //adminModuleFields[moduleFieldMap[module.type]].fields.push(cM); + tempModuleFields[module.type] = tempModuleFields[module.type] || []; + tempModuleFields[module.type].push(cM); + + } else { + + calipso.error("Module: " + moduleName + " @ " + module.path + ", appears to be invalid, it will not be shown in the configuration form."); + + } + + } + + for (moduleType in tempModuleFields) { + var moduleTypeFields = tempModuleFields[moduleType]; + // "Site" modules fieldset will only show up if there are any to show. + if (moduleTypeFields.length) { + formFields.push({ + type:'fieldset', + name:moduleType + '_fieldset', // shouldn't need a name ... + legend:moduleType, + fields:moduleTypeFields + }); + } + } + + // sort modules + function moduleSort(a, b) { + return a.name < b.name ? -1 : 1; + } + + for (var i = 0; i < formFields.length; i++) { + if (formFields[i].fields && formFields[i].fields.length) { + formFields[i].fields.sort(moduleSort); + } + } + + return formFields; } /** * Display the reload administration block */ -function reloadAdmin(req, res, template, block, next) -{ +function reloadAdmin(req, res, template, block, next) { - calipso.theme.renderItem(req, res, template, block, {}, next); + calipso.theme.renderItem(req, res, template, block, {}, next); } /** * Save the modified configuration details on submission */ -function saveAdmin(req, res, template, block, next) -{ +function saveAdmin(req, res, template, block, next) { - calipso.form.process(req, function (config) - { + calipso.form.process(req, function (config) { - if (config) - { + if (config) { - calipso.e.pre_emit('CONFIG_UPDATE', config, function (config) - { + calipso.e.pre_emit('CONFIG_UPDATE', config, function (config) { - // Update the configuration - updateConfiguration(config); - // updateEnabledModules(config); + // Update the configuration + updateConfiguration(config); + // updateEnabledModules(config); - calipso.config.save(function (err) - { - if (err) - { + calipso.config.save(function (err) { + if (err) { - req.flash('error', req.t('Could not save the updated configuration, there was an error: ' + err.message)); - res.redirect('/admin/core/config'); + req.flash('error', req.t('Could not save the updated configuration, there was an error: ' + err.message)); + res.redirect('/admin/core/config'); - } - else - { + } else { - // Set the reload config flag for event handler to pick up - calipso.e.post_emit('CONFIG_UPDATE', config, function (config) - { + // Set the reload config flag for event handler to pick up + calipso.e.post_emit('CONFIG_UPDATE', config, function (config) { - req.flash('info', req.t('Changes to configuration saved.')); - res.redirect('/admin'); - next(); + req.flash('info', req.t('Changes to configuration saved.')); + res.redirect('/admin'); + next(); - }); + }); - } - }); + } + }); - }); + }); - } - else - { + } else { - req.flash('error', req.t('Could not process the updated configuration.')); - res.redirect('/admin/core/config'); - next(); + req.flash('error', req.t('Could not process the updated configuration.')); + res.redirect('/admin/core/config'); + next(); - } + } - }); + }); } /** * Process the core config and enable / disable modules */ -function updateEnabledModules(form) -{ - // todo ? +function updateEnabledModules(form) { + // todo ? } /** * Display the cache */ -function showCache(req, res, template, block, next) -{ +function showCache(req, res, template, block, next) { - calipso.theme.renderItem(req, res, template, block, { - cache: calipso.cache.cache - }, next); + calipso.theme.renderItem(req, res, template, block, { + cache:calipso.cache.cache + }, next); } /** * Display the cache */ -function clearCache(req, res, template, block, next) -{ - calipso.cache.clear(function () - { - calipso.theme.renderItem(req, res, template, block, { - cache: calipso.cache.cache - }, next); - }); +function clearCache(req, res, template, block, next) { + calipso.cache.clear(function () { + calipso.theme.renderItem(req, res, template, block, { + cache:calipso.cache.cache + }, next); + }); } diff --git a/modules/core/admin/package.json b/modules/core/admin/package.json index 3bcafaa23..d47c3d6fb 100644 --- a/modules/core/admin/package.json +++ b/modules/core/admin/package.json @@ -1,12 +1,12 @@ { - "name":"admin", - "label":"Administration", - "description":"Forms and functions to provide administration of configuration, installation and reloading of Calipso.", - "version":"0.2.0", - "homepage":"http://calip.so", - "repository":{ - "type":"git", - "url":"git://github.com/cliftonc/calipso.git" - }, - "author":"Clifton Cunningham (cliftoncunningham.co.uk)" + "name":"admin", + "label":"Administration", + "description":"Forms and functions to provide administration of configuration, installation and reloading of Calipso.", + "version":"0.2.0", + "homepage":"http://calip.so", + "repository":{ + "type":"git", + "url":"git://github.com/cliftonc/calipso.git" + }, + "author":"Clifton Cunningham (cliftoncunningham.co.uk)" } diff --git a/modules/core/admin/templates/cache.html b/modules/core/admin/templates/cache.html index 72ddffb43..1cd355b60 100644 --- a/modules/core/admin/templates/cache.html +++ b/modules/core/admin/templates/cache.html @@ -1,8 +1,8 @@ -
- <% - for(var item in cache) { - var cacheItem = cache[item]; - %> - <%= item %>, expires: <%= cacheItem.expires %>
- <% } %> +
+ <% + for(var item in cache) { + var cacheItem = cache[item]; + %> + <%= item %>, expires: <%= cacheItem.expires %>
+ <% } %>
\ No newline at end of file diff --git a/modules/core/admin/templates/install_modules.html b/modules/core/admin/templates/install_modules.html index 85db24098..78bf43eef 100644 --- a/modules/core/admin/templates/install_modules.html +++ b/modules/core/admin/templates/install_modules.html @@ -1,64 +1,59 @@ -