diff --git a/.gitignore b/.gitignore index fb7b73015..fc99d1227 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +docs/* .tmp* cookbook* site/docs/* diff --git a/.gitmodules b/.gitmodules index e24b5d3fb..de5d08310 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,6 @@ [submodule "can"] path = can url = git://github.com/bitovi/canjs.git -[submodule "canui"] - path = canui - url = git://github.com/bitovi/canui.git -[submodule "jquery"] - path = jquery - url = git://github.com/bitovi/jquerypp.git [submodule "steal"] path = steal url = git://github.com/bitovi/steal.git @@ -16,3 +10,9 @@ [submodule "documentjs"] path = documentjs url = git://github.com/bitovi/documentjs.git +[submodule "jquerypp"] + path = jquerypp + url = https://github.com/bitovi/jquerypp.git +[submodule "jmvc"] + path = jmvc + url = git://github.com/bitovi/jmvc-generators diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 000000000..54ec9d1dd --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,18 @@ +module.exports = function (grunt) { + + grunt.loadNpmTasks('grunt-contrib-connect'); + + grunt.initConfig({ + connect: { + server: { + options: { + port: 8000, + base: '.', + keepalive: true + } + } + } + }) + + grunt.registerTask("server", "connect:server") +}; \ No newline at end of file diff --git a/build/changelog.ejs b/build/changelog.ejs new file mode 100644 index 000000000..4205c23e0 --- /dev/null +++ b/build/changelog.ejs @@ -0,0 +1,4 @@ +__<%= version %>__ ( <%= date.toDateString().substring(4) %> ) +<% for(var i = 0; i < issues.length; i++) { %> +- change: [<%= issues[i].title %>](<%= issues[i].url %>)<% } %> + diff --git a/build/tasks/bannerize.js b/build/tasks/bannerize.js index d97720469..2eb1540df 100644 --- a/build/tasks/bannerize.js +++ b/build/tasks/bannerize.js @@ -1,23 +1,16 @@ var path = require('path'); -// A grunt task that strips multiline comments +// A grunt task that writes the banner to every file module.exports = function (grunt) { grunt.registerMultiTask('bannerize', 'Adds the banner to a set of files', function () { - var _ = grunt.utils._; var options = grunt.config.process(['bannerize', this.target]); - var banner = grunt.helper('banner'); - var defaults = _.extend({ exclude : [] }, grunt.config('strip')._options); - grunt.file.expandFiles(this.file.src).forEach(function (file) { - for(var i = 0; i < defaults.exclude.length; i++) { - if(defaults.exclude[i].test(file)) { - return; - } - } + var banner = this.data.banner; + + grunt.file.expand(this.data.files).forEach(function (file) { var outFile = options.out ? path.join(options.out, path.basename(file)) : file; - grunt.log.writeln('Adding banner to ' + file); - var code = grunt.helper('file_strip_banner', file, { block : true }); - grunt.file.write(outFile, banner + code); + grunt.log.writeln('Adding banner to ' + file); + grunt.file.write(outFile, banner + grunt.file.read(file)); }); }); } diff --git a/build/tasks/beautify.js b/build/tasks/beautify.js index de999530e..df48cf113 100644 --- a/build/tasks/beautify.js +++ b/build/tasks/beautify.js @@ -5,6 +5,7 @@ * Copyright (c) 2012 Camille Moncelier * Licensed under the MIT license. */ +var beautifier = require('js-beautify'); module.exports = function (grunt) { @@ -18,8 +19,6 @@ module.exports = function (grunt) { }; grunt.registerMultiTask('beautify', 'Javascript beautifier', function () { - var beautifier = require('node-beautify'); - var options = null; var tmp = grunt.config(['beautifier', this.target, 'options']); if (typeof tmp === 'object') { @@ -38,7 +37,13 @@ module.exports = function (grunt) { // Beautify specified files. var excludes = grunt.config(['beautifier', this.target, 'exclude']); - grunt.file.expandFiles(this.file.src).filter(function (file) { + + grunt.file.expand(this.filesSrc).filter(function (file) { + if(/\.min\./.test(file)) { + grunt.log.writeln('Not beautifying ' + file); + return false; + } + for (var i = 0; i < excludes.length; i++) { if (excludes[i].test(file)) { grunt.log.writeln('Not beautifying ' + file); @@ -48,7 +53,7 @@ module.exports = function (grunt) { return true; }).forEach(function (filepath) { grunt.log.writeln('Beautifying ' + filepath); - var result = beautifier.beautifyJs(grunt.file.read(filepath), options); + var result = beautifier(grunt.file.read(filepath), options); grunt.file.write(filepath, result); }); diff --git a/build/tasks/build.js b/build/tasks/build.js index 63537e8f3..69c13dbf7 100644 --- a/build/tasks/build.js +++ b/build/tasks/build.js @@ -5,35 +5,27 @@ module.exports = function( grunt ) { grunt.registerMultiTask('build', 'Runs build files.', function() { var done = this.async(); var target = this.target; - var files = Array.isArray(this.file.src) ? this.file.src : [this.file.src]; - // TODO grunt.file.expandFiles(this.file.src); - var series = files.map(function (file) { - return function(callback) { - var options = grunt.config.process(['build', target]); - var args = [file, options.out || 'dist/', options.version || 'edge']; - var libraries = Array.isArray(options.libraries) ? options.libraries : []; - args.push.apply(args, libraries); + var options = grunt.config.process(['build', target]); + var args = [this.data.src, this.data.out || 'dist/', this.data.version || 'edge']; + var libraries = Array.isArray(this.data.libraries) ? this.data.libraries : []; - grunt.verbose.writeflags(options, 'Options'); - grunt.log.writeln('Running ./js ' + args.join(' ')); + args.push.apply(args, libraries); - grunt.utils.exec({ - cmd : "./js", - args : args, - opts : { - cwd: jsDir - } - }, function(error, result, code) { - callback(error, result, code); - }); + grunt.verbose.writeflags(this.data, 'Options'); + grunt.log.writeln('Running ./js ' + args.join(' ')); - grunt.log.write("Building " + file + " with Steal...\n"); + grunt.util.spawn({ + cmd : "./js", + args : args, + opts : { + cwd: jsDir } - }); - grunt.utils.async.parallel(series, function(error, results) { + }, function(error, result, code) { grunt.log.writeln('Done building'); done(); - }) + }); + + grunt.log.write("Building " + this.data.src + " with Steal...\n"); }); }; diff --git a/build/tasks/changelog.js b/build/tasks/changelog.js new file mode 100644 index 000000000..6215b6f25 --- /dev/null +++ b/build/tasks/changelog.js @@ -0,0 +1,65 @@ +var https = require('https'), +querystring = require('querystring'), +ejs = require('ejs'); + +module.exports = function(grunt) { + + grunt.registerMultiTask('changelog', 'Updates changelog.md based on GitHub milestones', function() { + var done = this.async(), + + params = querystring.stringify({ + milestone: this.data.milestone, + state: 'closed', + per_page: 100 + }), + + path = '/repos/' + this.data.user + '/' + this.data.repo + '/issues?' + params, + + buffer = '', + self = this; + + var write = function() { + var issues = JSON.parse(buffer), + log = ''; + + if(grunt.file.exists('changelog.md')) { + log = grunt.file.read('changelog.md'); + }; + + ejs.renderFile(__dirname + '/../changelog.ejs', { + version: self.data.version, + date: new Date(Date.now()), + issues: issues + }, function(e, template) { + + if(e) { + done(e); + } + + grunt.file.write('changelog.md', template + log); + done(); + + }); + }, + + req = https.request({ + hostname: 'api.github.com', + path: path + }, function(res) { + + res.on('data', function(data) { + buffer += data; + }); + + res.on('end', write); + }); + + req.end(); + + req.on('error', function(e) { + done(e); + }); + + }); + +} \ No newline at end of file diff --git a/build/tasks/docco.js b/build/tasks/docco.js index 8b80d8d11..964a668c3 100644 --- a/build/tasks/docco.js +++ b/build/tasks/docco.js @@ -5,24 +5,18 @@ var docco = require('docco'); module.exports = function(grunt) { grunt.registerMultiTask('docco', 'Docco processor.', function() { - var _ = grunt.utils._; - var options = grunt.config.process(['docco', this.target]); - var defaults = _.extend({ - exclude : [/\.min\./] - }, grunt.config.process('docco')._options); - grunt.verbose.writeflags(options, 'Options'); + var _ = grunt.util._; var done = this.async(); - var src = grunt.file.expandFiles(this.file.src).filter(function(file) { - for(var i = 0; i < defaults.exclude.length; i++) { - if(defaults.exclude[i].test(file)) { - return false; - } - } - return true; + var options = this.options(); + var src = grunt.file.expand(this.data.files).filter(function(file) { + return !_.some(options.exclude, function(exclude) { + return exclude.test(file); + }); }); - docco.document(src, _.extend({}, defaults.docco, options.docco) || {}, function(err, result, code){ - grunt.log.writeln("Doccoed [" + src.join(", ") + "]; " + err ? err : "(No errors)" + "\n" + result + " " + code); + docco.document( _.extend({ args: src }, this.data.docco, options.docco), function(err, result, code){ + grunt.log.writeln("Doccoed [" + src.join(", ") + "]; " + + err ? err : "(No errors)" + "\n" + result + " " + code); done(); }); }); diff --git a/build/tasks/downloads.js b/build/tasks/downloads.js deleted file mode 100644 index e2c29abe4..000000000 --- a/build/tasks/downloads.js +++ /dev/null @@ -1,103 +0,0 @@ -var program = require("commander"); -var GitHubApi = require("github"); -var authenticated = false; -var fs = require('fs'); -var github = new GitHubApi({ - version : "3.0.0" -}); -var getCredentials = function (callback) { - if(authenticated) { - return callback(); - } - - program.prompt("Github Username: ", function (name) { - var username = name; - - program.password("Github Password: ", "*", function (pass) { - var password = pass; - process.stdin.pause(); - github.authenticate({ - type : "basic", - username : username, - password : password - }); - authenticated = true; - callback(); - }); - - }); -} - -module.exports = function (grunt) { - grunt.registerMultiTask('downloads', 'Uploads generated files as GitHub downloads.', function () { - if(this.target == '_options') { - return; - } - - var _ = grunt.utils._; - var done = this.async(); - var ops = grunt.config(['downloads', this.target]); - var defaults = _.extend({}, grunt.config(['downloads', '_options'])); - var sourceFile = this.file.src; - - var desc = grunt.template.process(ops.description); - var name = grunt.template.process(ops.filename); - - grunt.log.writeln('Deploying ' + desc); - fs.readFile(sourceFile, function (err, buf) { - if (err) { - return grunt.fail.fatal(err); - } - - var createDownload = function() { - github.httpSend({ - "user" : defaults.user, - "repo" : defaults.repository, - "name" : name, - "size" : buf.length, - "description" : desc, - "content_type" : ops.content_type || "text/javascript" - }, { - "url" : "/repos/:user/:repo/downloads", - "method" : "POST", - "params" : { - "$user" : null, - "$repo" : null, - "$name" : null, - "$size" : null, - "description" : null, - "$content_type" : null - } - }, function (err, socket) { - if(err) { - return grunt.fail.fatal(err); - } - var data = JSON.parse(socket.data); - - grunt.utils.s3.postToS3({ - key : data.path, - acl : data.acl, - success_action_status : "201", - Filename : data.name, - AWSAccessKeyId : data.accesskeyid, - policy64 : data.policy, - signature64 : data.signature, - contentType : data.mime_type, - data : buf, - bucket : "github" - }, function (e) { - if(e) { - return grunt.fail.fatal(e); - } - grunt.log.writeln('Successfully created GitHub download and uploaded to S3.'); - done(); - }) - }); - } - - grunt.log.writeln('Uploading to ' + defaults.user + '/' + defaults.repository + '/' + name); - getCredentials(createDownload); - - }); - }); -} \ No newline at end of file diff --git a/build/tasks/shell.js b/build/tasks/shell.js deleted file mode 100644 index 89d28831a..000000000 --- a/build/tasks/shell.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * grunt-shell - * 0.1.2 - 2012-06-28 - * github.com/sindresorhus/grunt-shell - * - * (c) Sindre Sorhus - * sindresorhus.com - * MIT License - */ -module.exports = function (grunt) { - 'use strict'; - - var _ = grunt.utils._; - var log = grunt.log; - - grunt.registerMultiTask('shell', 'Run shell commands', function () { - var exec = require('child_process').exec; - var done = this.async(); - var data = _.extend([], grunt.config.get('shell')._options, this.data); - var dataOut = data.stdout; - var dataErr = data.stderr; - - if (_.isFunction(data.callback)) { - data.callback.call(this); - return; - } - - var command = grunt.template.process(this.file.src); - log.writeln('Running ' + command); - exec(command, data.execOptions, function (err, stdout, stderr) { - if (stdout) { - if (_.isFunction(dataOut)) { - dataOut(stdout); - } else if (dataOut === true) { - log.write(stdout); - } - } - - if (err) { - if (_.isFunction(dataErr)) { - dataErr(stderr); - } else if (data.failOnError === true) { - grunt.fatal(err); - } else if (dataErr === true) { - log.error(err); - } - } - - done(); - }); - }); -}; \ No newline at end of file diff --git a/build/tasks/strip.js b/build/tasks/strip.js deleted file mode 100644 index 31558b192..000000000 --- a/build/tasks/strip.js +++ /dev/null @@ -1,36 +0,0 @@ -var path = require('path'); - -// A grunt task that strips multiline comments -module.exports = function (grunt) { - grunt.registerMultiTask('strip', 'Remove multiline comments from files', function () { - var _ = grunt.utils._; - var options = grunt.config.process(['strip', this.target]); - var defaults = _.extend({ - exclude : [/\.min\./] - }, grunt.config('strip')._options); - grunt.file.expandFiles(this.file.src).forEach(function (file) { - for(var i = 0; i < defaults.exclude.length; i++) { - if(defaults.exclude[i].test(file)) { - return; - } - } - var outFile = options.out ? path.join(options.out, path.basename(file)) : file; - grunt.log.writeln('Stripping ' + file + ' of all multiline and empty inline comments'); - - var code = grunt.file.read(file); - - // Remove multiline comments - code = code.replace(/\/\*([\s\S]*?)\*\//gim, "") - .replace(/\/\/(\s*)\n/gim, ""); - - // Remove double semicolons from steal pluginify - code = code.replace(/;[\s]*;/gim, ";"); - code = code.replace(/(\/\/.*)\n[\s]*;/gi, "$1"); - - // Only single new lines - code = code.replace(/(\n){3,}/gim, "\n\n"); - - grunt.file.write(outFile, code); - }); - }); -} diff --git a/build/tasks/testify.js b/build/tasks/testify.js new file mode 100644 index 000000000..6aa1190e1 --- /dev/null +++ b/build/tasks/testify.js @@ -0,0 +1,54 @@ +var ejs = require('ejs'); +var beautify = require('js-beautify'); + +module.exports = function(grunt) { + var _ = grunt.util._; + + grunt.registerMultiTask('testify', 'Generates test runners', function() { + var done = this.async(); + var data = this.data; + var template = grunt.file.read(this.data.template); + var transform = this.data.transform || {}; + var modules = this.data.builder.modules; + var configurations = this.data.builder.configurations; + + _.each(configurations, function(config, configurationName) { + var options = { + configuration: config, + modules: [], + tests: [], + root: data.root, + '_': _ + }; + + _.each(modules, function(definition, key) { + if(!definition.configurations || definition.configurations.indexOf(configurationName) !== -1) { + var name = key.substr(key.lastIndexOf('/') + 1); + var mod = transform.module ? transform.module(definition, key) : key; + var test = transform.test ? transform.test(definition, key) : (key + '/' + name + '_test.js'); + + mod && options.modules.push(mod); + test && options.tests.push(test); + } + }); + + _.extend(config.steal, { + root: data.root + }); + + if(transform && transform.options) { + _.extend(options, transform.options.call(config, configurationName)); + } + + var lib = '\n'+ + beautify.html(ejs.render(template, options), { + "wrap_line_length": 70 + }); + + grunt.log.writeln('Generating ' + data.out + configurationName + '.html'); + grunt.file.write(data.out + configurationName + '.html', lib); + }); + + done(); + }); +}; \ No newline at end of file diff --git a/build/tasks/updateversion.js b/build/tasks/updateversion.js new file mode 100644 index 000000000..5febe4698 --- /dev/null +++ b/build/tasks/updateversion.js @@ -0,0 +1,20 @@ +var path = require('path'); + +// A grunt task that writes the banner to every file +module.exports = function (grunt) { + grunt.registerMultiTask('updateversion', 'Replaces given symbol with current version', function () { + var options = grunt.config.process(['updateversion', this.target]); + var version = this.data.version; + var symbol = this.data.symbol; + + grunt.file.expand(this.data.files).forEach(function (file) { + var outFile = options.out ? path.join(options.out, path.basename(file)) : file; + var fileContents = grunt.file.read(file).replace(symbol, version); + + if(grunt.file.read(file).match(symbol)) { + grunt.log.writeln('Updating version in ' + file); + grunt.file.write(outFile, fileContents.replace(symbol, version)); + } + }); + }); +} diff --git a/build/tasks/utils.js b/build/tasks/utils.js deleted file mode 100644 index 4f014fd54..000000000 --- a/build/tasks/utils.js +++ /dev/null @@ -1,137 +0,0 @@ -var spawn = require("child_process").spawn; - -// { -// // The command to execute. It should be in the system path. -// cmd: commandToExecute, -// // An array of arguments to pass to the command. -// args: arrayOfArguments, -// // Additional options for the Node.js child_process spawn method. -// opts: nodeSpawnOptions -// } - -module.exports = function (grunt) { - grunt.utils.exec = function (options, callback) { - var build = grunt.utils.spawn(options, callback); - - build.stdout.on("data", function (buf) { - grunt.log.write("" + buf); - }); - - build.stderr.on("data", function (buf) { - grunt.log.write("" + buf); - }); - - build.on("exit", function (code) { - callback(null, code); - }); - - return build; - }; - - // Generated by CoffeeScript 1.3.1 - (function () { - var crypto, https, joinBuffers, postToS3, readText, signPolicy, url, _ref; - - url = require('url'); - - https = require('https'); - - crypto = require('crypto'); - - _ref = require('tafa-misc-util'), joinBuffers = _ref.joinBuffers, readText = _ref.readText; - - signPolicy = function (secretKey, policy) { - var data, hmac, json, key, policy64, signature64; - json = JSON.stringify(policy); - policy64 = new Buffer(json).toString('base64'); - data = new Buffer(policy64, 'utf-8'); - key = new Buffer(secretKey, 'utf-8'); - hmac = crypto.createHmac('sha1', key); - hmac.update(data); - signature64 = hmac.digest('base64'); - return { - signature64 : signature64, - policy64 : policy64 - }; - }; - - postToS3 = function (_arg, callback) { - var AWSAccessKeyId, Filename, acl, addParam, arr, boundary, bucket, buf, ca, contentType, customUrl, data, host, hostname, key, options, policy64, port, protocol, req, req_body, signature64, success_action_status, _ref1; - AWSAccessKeyId = _arg.AWSAccessKeyId, policy64 = _arg.policy64, signature64 = _arg.signature64, bucket = _arg.bucket, key = _arg.key, data = _arg.data, boundary = _arg.boundary, customUrl = _arg.customUrl, ca = _arg.ca, acl = _arg.acl, success_action_status = _arg.success_action_status, Filename = _arg.Filename, contentType = _arg.contentType; - if (callback == null) { - callback = (function () { - }); - } - if (customUrl) { - _ref1 = url.parse(customUrl), protocol = _ref1.protocol, hostname = _ref1.hostname, port = _ref1.port; - if (protocol !== "https:") { - return callback(new Error("customUrl must be https://")); - } - host = hostname; - port || (port = 443); - } else { - host = "" + bucket + ".s3.amazonaws.com"; - port = 443; - } - boundary || (boundary = '----------R46EARkAg4SAXSjufGsb6m'); - buf = function (x) { - return new Buffer(x); - }; - arr = []; - addParam = function (k, v) { - arr.push(buf('--' + boundary + '\r\n')); - arr.push(buf('Content-Disposition: form-data; name="' + k + '"\r\n\r\n')); - return arr.push(buf(v), buf('\r\n')); - }; - addParam('key', key); - addParam('acl', acl); - addParam('success_action_status', success_action_status); - addParam('Filename', Filename); - addParam('AWSAccessKeyId', AWSAccessKeyId); - addParam('Policy', policy64); - addParam('Signature', signature64); - addParam('Content-Type', contentType); - arr.push(buf('--' + boundary + '\r\n')); - arr.push(buf('Content-Disposition: form-data; name="file"; filename="data"\r\n')); - arr.push(buf("Content-Length: " + data.length + "\r\n")); - arr.push(buf('Content-Transfer-Encoding: binary\r\n\r\n')); - arr.push(data, buf('\r\n')); - arr.push(buf('--' + boundary + '--')); - req_body = joinBuffers(arr); - options = { - host : host, - port : port, - path : '/', - method : 'POST', - headers : { - 'Host' : "" + bucket + ".s3.amazonaws.com", - 'Content-Type' : 'multipart/form-data; boundary=' + boundary, - 'Content-Length' : req_body.length - } - }; - if (ca) { - options.ca = ca; - } - req = https.request(options, function (res) { - var _ref2; - if ((200 <= (_ref2 = res.statusCode) && _ref2 < 300)) { - return callback(null); - } else { - return readText(res, function (text) { - return callback({ - responseCode : res.statusCode, - responseText : text - }); - }); - } - }); - return req.end(req_body); - }; - - grunt.utils.s3 = { - postToS3 : postToS3, - signPolicy : signPolicy - }; - - }).call(this); -} \ No newline at end of file diff --git a/can b/can index f16f5f0b6..1a46211f4 160000 --- a/can +++ b/can @@ -1 +1 @@ -Subproject commit f16f5f0b6d53107340cceb1f3f044c409533e225 +Subproject commit 1a46211f419ad39e10be11220ce723be1485112a diff --git a/canui b/canui deleted file mode 160000 index 538977ddc..000000000 --- a/canui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 538977ddc99e5d2ac2c87286c05da1c87451ead5 diff --git a/documentjs b/documentjs index 3d9ca968e..99e1215df 160000 --- a/documentjs +++ b/documentjs @@ -1 +1 @@ -Subproject commit 3d9ca968edb5d1d779686ca0323f9b9a54e56cf5 +Subproject commit 99e1215df6cab9ebc3adf3d8801fd6339bf1bdc2 diff --git a/funcunit b/funcunit index 9da5a5617..1b0194d98 160000 --- a/funcunit +++ b/funcunit @@ -1 +1 @@ -Subproject commit 9da5a561758202083163e6a7747ac9ce07d3e412 +Subproject commit 1b0194d984f93be68c8d5dc8aba05196f1844ec2 diff --git a/grunt.js b/grunt.js deleted file mode 100644 index 9d5cb6995..000000000 --- a/grunt.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function (grunt) { - -}; \ No newline at end of file diff --git a/jmvc b/jmvc new file mode 160000 index 000000000..fffedf3c9 --- /dev/null +++ b/jmvc @@ -0,0 +1 @@ +Subproject commit fffedf3c9503cc89154156bf7aac37f415e20edd diff --git a/jmvc/generate/app b/jmvc/generate/app deleted file mode 100644 index 0f6238f24..000000000 --- a/jmvc/generate/app +++ /dev/null @@ -1,22 +0,0 @@ -// _args = ['cookbook']; load('steal/generate/app') - -if (!_args[0]) { - print("Usage: steal/js steal/generate/app path"); - quit(); -} - -load('steal/rhino/rhino.js'); - -steal('steal/generate','steal/generate/system.js', function(steal) { - var moduleId = _args[0]; - var md = steal.generate.convert(moduleId), - data = steal.extend({ - path: moduleId, - application_name: md._alias, - current_path: steal.File.cwdURL(), - path_to_steal : steal.File(moduleId).pathToRoot() - }, steal.system); - - steal.generate("jmvc/generate/templates/app", moduleId, data); -}); - diff --git a/jmvc/generate/coffee/controller b/jmvc/generate/coffee/controller deleted file mode 100644 index a547f8a21..000000000 --- a/jmvc/generate/coffee/controller +++ /dev/null @@ -1,35 +0,0 @@ -if (_args.length < 1) { - print("USAGE : steal/js steal/generate/coffee/controller Company.Widget") - print("EX : steal/js steal/generate/coffee/controller Company.WidgetName"); - print(" > company/widget_name/widget_name.coffee") - print(); - quit(); -} - -load('steal/rhino/rhino.js'); - -steal( '//steal/generate/generate', - '//steal/generate/system', -function(steal){ - var upper = function(parts){ - for(var i =0; i < parts.length; i++){ - parts[i] = parts[i].charAt(0).toUpperCase()+parts[i].substr(1) - } - return parts - } - - if(_args[0].charAt(0) !== _args[0].charAt(0).toUpperCase()){ - var caps = upper( _args[0].split(/_|-/) ).join(''), - name = upper(caps.split("/")).join('.'); - - print(" Creating "+name); - _args[0] = name; - } - - var md = steal.generate.convert(_args[0]), - path = _args[0].toLowerCase().replace('.',"/"); - md.path_to_steal = new steal.File(path).pathToRoot() - steal.generate("jquery/generate/coffee/templates/controller",md.path+"/"+md.underscore,md) - -}); - diff --git a/jmvc/generate/coffee/templates/controller/(underscore).coffee.ejs b/jmvc/generate/coffee/templates/controller/(underscore).coffee.ejs deleted file mode 100644 index 61c267b76..000000000 --- a/jmvc/generate/coffee/templates/controller/(underscore).coffee.ejs +++ /dev/null @@ -1,11 +0,0 @@ -steal "jquery/controller", -($) -> - - $.Controller "<%= name %>", - { - defaults : {}, - }, - { - init : -> - @element.html "Hello World!" - } diff --git a/jmvc/generate/coffee/templates/controller/(underscore).html.ejs b/jmvc/generate/coffee/templates/controller/(underscore).html.ejs deleted file mode 100644 index 7ad7f07cc..000000000 --- a/jmvc/generate/coffee/templates/controller/(underscore).html.ejs +++ /dev/null @@ -1,24 +0,0 @@ - - - - <%= name %> - - - -

<%= name %> Demo

-
- - - - \ No newline at end of file diff --git a/jmvc/generate/coffee/templates/controller/(underscore)_test.coffee.ejs b/jmvc/generate/coffee/templates/controller/(underscore)_test.coffee.ejs deleted file mode 100644 index 7bf21374d..000000000 --- a/jmvc/generate/coffee/templates/controller/(underscore)_test.coffee.ejs +++ /dev/null @@ -1,7 +0,0 @@ -steal "funcunit", - -> - module "<%= fullName %>", setup: -> - S.open "//<%= path %>/<%=underscore%>/<%=underscore%>.html" - - test "Text Test", -> - equals S("h1").text(), "<%= fullName %> Demo", "demo text" \ No newline at end of file diff --git a/jmvc/generate/coffee/templates/controller/funcunit.html.ejs b/jmvc/generate/coffee/templates/controller/funcunit.html.ejs deleted file mode 100644 index 282c9c9a0..000000000 --- a/jmvc/generate/coffee/templates/controller/funcunit.html.ejs +++ /dev/null @@ -1,16 +0,0 @@ - - - - - <%= name %> FuncUnit Test - - - - -

<%= name %> Test Suite

-

-
-

-
    - - \ No newline at end of file diff --git a/jmvc/generate/control b/jmvc/generate/control deleted file mode 100644 index 8dca56e2e..000000000 --- a/jmvc/generate/control +++ /dev/null @@ -1,22 +0,0 @@ -if (_args.length < 1) { - print("USAGE : steal/js steal/generate/controller Company.Widget") - print("EX : steal/js steal/generate/controller Company.WidgetName"); - print(" > company/widget_name/widget_name.js") - print(); - quit(); -} - -load('steal/rhino/rhino.js'); - -steal( 'steal/generate', - 'steal/generate/system.js', -function(steal){ - - - var md = steal.generate.convert(_args[0]); - - md.path_to_steal = new steal.File(md.module).pathToRoot() - steal.generate("jmvc/generate/templates/control",md.module,md) - -}); - diff --git a/jmvc/generate/model b/jmvc/generate/model deleted file mode 100644 index f21097f7d..000000000 --- a/jmvc/generate/model +++ /dev/null @@ -1,46 +0,0 @@ -if (_args.length < 1) { - print("USAGE : js jmvc/generate/model cookbook/models/recipe") - print(); - quit(); -} - -load('steal/rhino/rhino.js'); - -steal( 'steal/generate', - 'steal/generate/system.js', - 'steal/generate/inflector.js', -function(steal){ - var md = steal.generate.convert(_args[0]); - - var folder = md.path.replace(/\/\w+$/, ""); - if(!folder){ - print("! Error: Models need to be part of an app"); - quit(); - } - if(!steal.File(folder).exists()){ - print("! Error: folder "+folder+" does not exist!"); - quit(); - } - - md.path_to_steal = new steal.File(md.path).pathToRoot(); - md.appPath = md.path.replace(/\/models$/,""); - - //check pluralization of last part - if(steal.Inflector.singularize(md.underscore) !== md.underscore){ - print("! Warning: Model names should be singular. I don't think "+md.underscore+" is singular!") - } - - - // generate the files - steal.generate("jmvc/generate/templates/model", md.appPath, md); - - // add to models/fixtures/fixtures.js - steal.generate.insertSteal(md.appPath + '/models/fixtures/fixtures.js', - md.appPath + '/models/fixtures/' + md.fullName + '.js'); - - // var text = readFile("jmvc/generate/templates/partials/fixture.js.ejs"); - // var fixturetext = new steal.EJS({ - // text: text - // }).render(md); - // steal.generate.insertCode(md.appPath+"/models/fixtures/fixtures.js", fixturetext); -}); \ No newline at end of file diff --git a/jmvc/generate/page b/jmvc/generate/page deleted file mode 100644 index 8d5f3f811..000000000 --- a/jmvc/generate/page +++ /dev/null @@ -1,21 +0,0 @@ -if (_args.length < 2) { - print("Creates an html page that loads one of your applications.\n") - print("USAGE: js steal/generate/test app_name page_location\n") - print(); - quit(); -} - -load('steal/rhino/rhino.js'); - -steal('steal/generate','steal/generate/system.js',function(steal){ - var path = _args[0].toLowerCase().replace('.',"/") - var data = steal.extend({ - path: path, - application_name: path.match(/[^\/]*$/)[0], - current_path: steal.File.cwdURL(), - path_to_steal : new steal.File(path).pathToRoot() - }, steal.system) - - var to = path+"/"+_args[1]; - steal.generate.render("jmvc/generate/templates/page.ejs", to, data) -}); \ No newline at end of file diff --git a/jmvc/generate/plugin b/jmvc/generate/plugin deleted file mode 100644 index c860ba83c..000000000 --- a/jmvc/generate/plugin +++ /dev/null @@ -1,17 +0,0 @@ -// _args = ['thing']; load('steal/generate/app') - -if (!_args[0]) { - print("Usage: steal/js steal/generate/plugin path"); - quit(); -} -load('steal/rhino/rhino.js'); - -steal('steal/generate',function(steal){ - - var md = steal.generate.convert(_args[0]); - - md.path_to_steal = new steal.File(md.module).pathToRoot(); - steal.generate("jmvc/generate/templates/plugin",md.module,md); - -})(); - diff --git a/jmvc/generate/scaffold b/jmvc/generate/scaffold deleted file mode 100644 index d865c1cc6..000000000 --- a/jmvc/generate/scaffold +++ /dev/null @@ -1,109 +0,0 @@ -if (_args.length < 1) { - print("USAGE : steal/js steal/generate/scaffold FullName Type") - print("TYPES : JsonRest\n") - print("EX : js steal/generate/scaffold Cashnet.Models.Customer"); - print(" > cashnet/models/customer.js ....") - print(); - quit(); -} - -load('steal/rhino/rhino.js'); - -steal( 'steal/generate', - 'steal/generate/system.js', - 'jmvc/generate/templates/model.js', -function(steal){ - var generate = steal.generate; - //check capitalization - generate.model(_args[0]); - - var parts = _args[0].split("/"), - last = parts[parts.length-1]; - - parts.forEach(function(part){ - if( part !== generate.downcase(part) ){ - print("! Warning: "+part+" should probably be lowercased. JavaScriptMVC likes capital namespaces and class names.") - } - }); - - // check folders - var folder = parts.slice(0, parts.length-1).join("/"); - - - if(!folder){ - print("! Error: Scaffolding needs to be part of an app"); - quit(); - } - if(!steal.File(folder).exists()){ - print("! Error: folder "+folder+" does not exist!"); - quit(); - } - //check pluralization of last part - if(steal.Inflector.singularize(last) !== last){ - print("! Warning: Model names should be singular. I don't think "+part+ - " is singular!") - } - - var md = steal.generate.convert(_args[0]); - - md.type = _args[1] - - // generate /scaffold files - steal.generate("jmvc/generate/templates/scaffold",md.appPath+"/"+md.underscore,md); - - try{ - - steal.generate.insertSteal( - md.appPath+"/"+ md.appName+"_test.js", - md.appPath+"/"+md.underscore+"/create/create_test.js"); - - steal.generate.insertSteal( - md.appPath+"/"+ md.appName+"_test.js", - md.appPath+"/"+md.underscore+"/list/list_test.js") - - steal.generate.insertSteal( - md.appPath+"/"+md.appPath+".js", - md.appPath+"/"+md.underscore+"/create", {newline: true, name: md.Alias+"Create"}) - steal.generate.insertSteal( - md.appPath+"/"+md.appPath+".js", - md.appPath+"/"+md.underscore+"/list", {newline: true, name: md.Alias+"List"}) - } catch(e) { - //quit(); - } - - var text = readFile("jmvc/generate/templates/scaffoldHookup.ejs"); - var hookup = new steal.EJS({ - text: text - }).render(md); - steal.generate.insertCode(md.appPath+"/"+md.appName+".js", hookup ); - - // insert the HTML into index - var text = readFile("jmvc/generate/templates/scaffoldHTML.ejs"); - var hookupHTML = new steal.EJS({ - text: text - }).render(md); - - var appFile = readFile( md.appPath+"/index.html" ); - - if( appFile.indexOf(text) == -1 ){ - var scriptIndex = appFile.indexOf(".less', - './models/fixtures/fixtures.js', -function(){ - -}) \ No newline at end of file diff --git a/jmvc/generate/templates/app/(application_name).less.ejs b/jmvc/generate/templates/app/(application_name).less.ejs deleted file mode 100644 index e742a3976..000000000 --- a/jmvc/generate/templates/app/(application_name).less.ejs +++ /dev/null @@ -1,30 +0,0 @@ -body { - font-family:Lucida Sans,Lucida Grande,Arial,sans-serif; - margin:0; - line-height:22px; - width:960px; - margin:0 auto; -} -h1 { - padding:30px 0 10px; - margin-top:0; - line-height:30px; -} - -ul{ - padding:0 0 0 15px; - list-style : none; -} - -a { - color:#ae3d26; - text-decoration:none; - &:hover { - text-decoration:underline; - } -} - -hr { - border:none; - border-top:1px dotted #000; -} \ No newline at end of file diff --git a/jmvc/generate/templates/app/(application_name).md.ejs b/jmvc/generate/templates/app/(application_name).md.ejs deleted file mode 100644 index a2a8b77ef..000000000 --- a/jmvc/generate/templates/app/(application_name).md.ejs +++ /dev/null @@ -1,21 +0,0 @@ -@page index <%= application_name %> - -# <%= application_name %> - -This is a placeholder for the homepage of your documentation. - -## Testing - -Open [//<%= application_name %>/test.html] - -## Building - -Run: - - > ./js <%= application_name %>/scripts/build.js - -## Documentation - -Run: - - > ./js <%= application_name %>/scripts/docs.js \ No newline at end of file diff --git a/jmvc/generate/templates/app/(application_name)_test.js.ejs b/jmvc/generate/templates/app/(application_name)_test.js.ejs deleted file mode 100644 index 746451635..000000000 --- a/jmvc/generate/templates/app/(application_name)_test.js.ejs +++ /dev/null @@ -1,16 +0,0 @@ -steal( - 'funcunit', - function (S) { - - // this tests the assembly - module("<%= application_name %>", { - setup : function () { - S.open("//<%= path %>/index.html"); - } - }); - - test("welcome test", function () { - equals(S("h1").text(), "Welcome to JavaScriptMVC!", "welcome text"); - }); - -}); diff --git a/jmvc/generate/templates/app/index.html.ejs b/jmvc/generate/templates/app/index.html.ejs deleted file mode 100644 index 6245a99ed..000000000 --- a/jmvc/generate/templates/app/index.html.ejs +++ /dev/null @@ -1,28 +0,0 @@ - - - - <%= application_name %> - - -

    Welcome to JavaScriptMVC!

    - -
    - Here are some links to get you started: - -
    - Join the community: - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/app/models/.ignore b/jmvc/generate/templates/app/models/.ignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/jmvc/generate/templates/app/models/fixtures/fixtures.js.ejs b/jmvc/generate/templates/app/models/fixtures/fixtures.js.ejs deleted file mode 100644 index 4e9c6d1e1..000000000 --- a/jmvc/generate/templates/app/models/fixtures/fixtures.js.ejs +++ /dev/null @@ -1,3 +0,0 @@ -// map fixtures for this application -steal("can/util/fixture", function(fixture) { -}); \ No newline at end of file diff --git a/jmvc/generate/templates/app/scripts/build.html.ejs b/jmvc/generate/templates/app/scripts/build.html.ejs deleted file mode 100644 index 35b326e75..000000000 --- a/jmvc/generate/templates/app/scripts/build.html.ejs +++ /dev/null @@ -1,21 +0,0 @@ - - - - <%= application_name %> Build Page - - -

    <%= application_name %> Build Page

    -

    This is a dummy page that loads your app so steal can - get all the files. -

    -

    If you built your app - to depend on HTML in the page before DOMContent loaded or - onload, you can add the HTML here, or you can change the - build.js script to point to a better html file. -

    - - - \ No newline at end of file diff --git a/jmvc/generate/templates/app/scripts/build.js.ejs b/jmvc/generate/templates/app/scripts/build.js.ejs deleted file mode 100644 index d76ebac21..000000000 --- a/jmvc/generate/templates/app/scripts/build.js.ejs +++ /dev/null @@ -1,6 +0,0 @@ -//js <%= path %>/scripts/build.js - -load("steal/rhino/rhino.js"); -steal('steal/build',function(){ - steal.build('<%= path %>/scripts/build.html',{to: '<%= path %>'}); -}); diff --git a/jmvc/generate/templates/app/scripts/clean.js.ejs b/jmvc/generate/templates/app/scripts/clean.js.ejs deleted file mode 100644 index 909c5fc37..000000000 --- a/jmvc/generate/templates/app/scripts/clean.js.ejs +++ /dev/null @@ -1,17 +0,0 @@ -//steal/js <%= path %>/scripts/compress.js - -load("steal/rhino/rhino.js"); -steal('steal/clean',function(){ - steal.clean('<%= path %>/<%= application_name %>.html',{ - indent_size: 1, - indent_char: '\t', - jslint : false, - ignore: /jquery\/jquery.js/, - predefined: { - steal: true, - jQuery: true, - $ : true, - window : true - } - }); -}); diff --git a/jmvc/generate/templates/app/scripts/crawl.js.ejs b/jmvc/generate/templates/app/scripts/crawl.js.ejs deleted file mode 100644 index 7290b5ebe..000000000 --- a/jmvc/generate/templates/app/scripts/crawl.js.ejs +++ /dev/null @@ -1,7 +0,0 @@ -// load('<%= path %>/scripts/crawl.js') - -load('steal/rhino/rhino.js') - -steal('steal/html/crawl', function(){ - steal.html.crawl("<%= path %>/<%= application_name %>.html","<%= path %>/out") -}); diff --git a/jmvc/generate/templates/app/scripts/docs.js.ejs b/jmvc/generate/templates/app/scripts/docs.js.ejs deleted file mode 100644 index 5809d9ad0..000000000 --- a/jmvc/generate/templates/app/scripts/docs.js.ejs +++ /dev/null @@ -1,8 +0,0 @@ -//js <%= path %>/scripts/doc.js - -load('steal/rhino/rhino.js'); -steal("documentjs", function(DocumentJS){ - DocumentJS('<%= path %>/index.html', { - markdown : ['<%= application_name %>', 'steal', 'jquery', 'can', 'funcunit'] - }); -}); \ No newline at end of file diff --git a/jmvc/generate/templates/app/test.html.ejs b/jmvc/generate/templates/app/test.html.ejs deleted file mode 100644 index 54f4bcf72..000000000 --- a/jmvc/generate/templates/app/test.html.ejs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/control/(underscore).html.ejs b/jmvc/generate/templates/control/(underscore).html.ejs deleted file mode 100644 index 18e40640e..000000000 --- a/jmvc/generate/templates/control/(underscore).html.ejs +++ /dev/null @@ -1,16 +0,0 @@ - - - - <%= Alias %> - - -

    <%= Alias %> Demo

    -
    - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/control/(underscore).js.ejs b/jmvc/generate/templates/control/(underscore).js.ejs deleted file mode 100644 index fc31ad4ed..000000000 --- a/jmvc/generate/templates/control/(underscore).js.ejs +++ /dev/null @@ -1,19 +0,0 @@ -steal('can','./init.ejs', function(can, initView){ - /** - * @class <%= module %> - * @alias <%= Alias %> - */ - return can.Control( - /** @Static */ - { - defaults : {} - }, - /** @Prototype */ - { - init : function(){ - this.element.html(initView({ - message: "Hello World from <%= Alias %>" - })); - } - }); -}); \ No newline at end of file diff --git a/jmvc/generate/templates/control/(underscore)_test.js.ejs b/jmvc/generate/templates/control/(underscore)_test.js.ejs deleted file mode 100644 index 3a5973e73..000000000 --- a/jmvc/generate/templates/control/(underscore)_test.js.ejs +++ /dev/null @@ -1,18 +0,0 @@ -steal('<%= module %>','funcunit', function( <%=Alias%>, S ) { - - module("<%= module %>", { - setup: function(){ - S.open( window ); - $("#qunit-test-area").html("
    ") - }, - teardown: function(){ - $("#qunit-test-area").empty(); - } - }); - - test("updates the element's html", function(){ - new <%= Alias %>('#<%=underscore%>'); - ok( $('#<%=underscore%>').html() , "updated html" ); - }); - -}); \ No newline at end of file diff --git a/jmvc/generate/templates/control/init.ejs.ejs b/jmvc/generate/templates/control/init.ejs.ejs deleted file mode 100644 index 5ad93e451..000000000 --- a/jmvc/generate/templates/control/init.ejs.ejs +++ /dev/null @@ -1 +0,0 @@ -<%%= this.message %> \ No newline at end of file diff --git a/jmvc/generate/templates/control/test.html.ejs b/jmvc/generate/templates/control/test.html.ejs deleted file mode 100644 index df9bfd866..000000000 --- a/jmvc/generate/templates/control/test.html.ejs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/fixturemake.ejs b/jmvc/generate/templates/fixturemake.ejs deleted file mode 100644 index 23ec33f9b..000000000 --- a/jmvc/generate/templates/fixturemake.ejs +++ /dev/null @@ -1,17 +0,0 @@ - - var store = fixture.store(5, function(i){ - return { - name: "<%= alias %> "+i, - description: "<%= alias %> " + i - } - }); - - fixture({ - 'GET /<%= pluralAlias %>' : store.findAll, - 'GET /<%= pluralAlias %>/{id}' : store.findOne, - 'POST /<%= pluralAlias %>' : store.create, - 'PUT /<%= pluralAlias %>/{id}' : store.update, - 'DELETE /<%= pluralAlias %>/{id}' : store.destroy - }); - - return store; \ No newline at end of file diff --git a/jmvc/generate/templates/model.js b/jmvc/generate/templates/model.js deleted file mode 100644 index d35d674e4..000000000 --- a/jmvc/generate/templates/model.js +++ /dev/null @@ -1,76 +0,0 @@ -steal( 'steal/generate', - 'steal/generate/system.js', - 'steal/generate/inflector.js', - function(steal){ - - - - var upper = function(parts){ - for(var i =0; i < parts.length; i++){ - parts[i] = parts[i].charAt(0).toUpperCase()+parts[i].substr(1) - } - return parts - } - /** - * Creates a model at the location provided - */ - steal.generate.model = function(arg){ - - // make sure we have a module id - if(arg.indexOf(".") > -1){ - print("JMVC's generators use module ids. Please remove any periods (.)."); - quit(); - } - var md = steal.generate.convert(arg); - - path = arg; - - var folder = md.parentModule; - if(!folder){ - print("! Error: Models need to be part of an app"); - quit(); - } - if(!steal.File(folder).exists()){ - print("! Error: folder "+folder+" does not exist!"); - quit(); - } - - md.path_to_steal = new steal.File(path).pathToRoot(); - - //check pluralization of last part - if(md.pluralAlias === md.alias){ - print("! Warning: Model names should be singular. I don't think "+md._alias+" is singular!") - } - - // generate the files - steal.generate("jmvc/generate/templates/model", md.appPath, md) - - try{ - // steal this model in models.js - // steal.generate.insertSteal(md.appPath+"/models/models.js", "./"+md.underscore+".js"); - - // steal this model's unit test in qunit.js - steal.generate.insertSteal(md.appPath+"/"+md.appName+"_test.js", "./models/"+md._alias+"_test.js"); - } catch (e) { - if(e.type && e.type == "DUPLICATE"){ - print("! Error: This model was already created!") - quit(); - } - } - - var text = readFile("jmvc/generate/templates/fixturemake.ejs"); - var fixturetext = new steal.EJS({ - text: text - }).render(md); - - - if(readFile(md.appPath+"/models/fixtures/fixtures.js").indexOf(fixturetext) == -1){ - steal.generate.insertCode(md.appPath+"/models/fixtures/fixtures.js", fixturetext); - } - - - } - - - -}); \ No newline at end of file diff --git a/jmvc/generate/templates/model/models/(underscore).js.ejs b/jmvc/generate/templates/model/models/(underscore).js.ejs deleted file mode 100644 index af1930632..000000000 --- a/jmvc/generate/templates/model/models/(underscore).js.ejs +++ /dev/null @@ -1,21 +0,0 @@ -steal('can', function (can) { - /** - * @class <%= module %> - * @alias <%= Alias %> - * @parent index - * @inherits can.Model - * - * Wraps backend <%= underscore %> services. - */ - return can.Model( - /* @static */ - { - findAll : "GET /<%= pluralAlias %>", - findOne : "GET /<%= pluralAlias %>/{id}", - create : "POST /<%= pluralAlias %>", - update : "PUT /<%= pluralAlias %>/{id}", - destroy : "DELETE /<%= pluralAlias %>/{id}" - }, - /* @Prototype */ - {}); -}); \ No newline at end of file diff --git a/jmvc/generate/templates/model/models/(underscore)_test.js.ejs b/jmvc/generate/templates/model/models/(underscore)_test.js.ejs deleted file mode 100644 index 8afcb11a7..000000000 --- a/jmvc/generate/templates/model/models/(underscore)_test.js.ejs +++ /dev/null @@ -1,53 +0,0 @@ -steal( "./<%= underscore %>.js", - "funcunit/qunit", - "<%= appPath %>/models/fixtures", - function( <%= Alias %> ){ - - module("<%= module %>"); - - test("findAll", function(){ - expect(4); - stop(); - <%= Alias %>.findAll({}, function(<%= pluralAlias %>){ - ok(<%= pluralAlias %>, "findAll provides an object") - ok(<%= pluralAlias %>.length, "findAll provides something array-like") - ok(<%= pluralAlias %>[0].name, "findAll provides an object with a name") - ok(<%= pluralAlias %>[0].description, "findAll provides an object with a description") - start(); - }); - }); - - test("create", function(){ - expect(3) - stop(); - new <%= Alias %>({name: "dry cleaning", description: "take to street corner"}).save(function(<%= underscore %>) { - ok(<%= underscore %>, "save provides an object"); - ok(<%= underscore %>.id, "save provides and object with an id"); - equals(<%= underscore %>.name,"dry cleaning", "save provides an objec with a name") - <%= underscore %>.destroy() - start(); - }); - }); - - test("update" , function(){ - expect(2); - stop(); - new <%= Alias %>({name: "cook dinner", description: "chicken"}).save(function(<%= underscore %>) { - equals(<%= underscore %>.description,"chicken", "save creates with description"); - <%= underscore %>.attr({description: "steak"}).save(function(<%= underscore %>){ - equals(<%= underscore %>.description,"steak", "save udpates with description"); - <%= underscore %>.destroy(); - start(); - }); - }); - }); - - test("destroy", function(){ - expect(1); - stop(); - new <%= Alias %>({name: "mow grass", description: "use riding mower"}).destroy(function(<%= underscore %>) { - ok( true ,"Destroy called" ) - start(); - }); - }); -}); \ No newline at end of file diff --git a/jmvc/generate/templates/page.ejs b/jmvc/generate/templates/page.ejs deleted file mode 100644 index 660365136..000000000 --- a/jmvc/generate/templates/page.ejs +++ /dev/null @@ -1,22 +0,0 @@ - - - - <%= application_name %> - - - -

    Welcome to JavaScriptMVC!

    - - - - diff --git a/jmvc/generate/templates/plugin/(underscore).html.ejs b/jmvc/generate/templates/plugin/(underscore).html.ejs deleted file mode 100644 index a3178e4b6..000000000 --- a/jmvc/generate/templates/plugin/(underscore).html.ejs +++ /dev/null @@ -1,16 +0,0 @@ - - - - <%= alias %> - - -

    <%= alias %> Demo

    -

    This is a dummy page to show off your plugin

    - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/plugin/(underscore).js.ejs b/jmvc/generate/templates/plugin/(underscore).js.ejs deleted file mode 100644 index a4ab7a149..000000000 --- a/jmvc/generate/templates/plugin/(underscore).js.ejs +++ /dev/null @@ -1,3 +0,0 @@ -steal('can',function(can) { - return {}; -}); \ No newline at end of file diff --git a/jmvc/generate/templates/plugin/(underscore)_test.js.ejs b/jmvc/generate/templates/plugin/(underscore)_test.js.ejs deleted file mode 100644 index 452475dfc..000000000 --- a/jmvc/generate/templates/plugin/(underscore)_test.js.ejs +++ /dev/null @@ -1,10 +0,0 @@ -steal('./<%= underscore %>','funcunit/qunit',function(<%= alias %>){ - - module("<%= module %>"); - - test("testing works", function(){ - ok(true,"an assert is run"); - }); - - -}); \ No newline at end of file diff --git a/jmvc/generate/templates/plugin/test.html.ejs b/jmvc/generate/templates/plugin/test.html.ejs deleted file mode 100644 index df9bfd866..000000000 --- a/jmvc/generate/templates/plugin/test.html.ejs +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/create/create.html.ejs b/jmvc/generate/templates/scaffold/create/create.html.ejs deleted file mode 100644 index 29e1be7d6..000000000 --- a/jmvc/generate/templates/scaffold/create/create.html.ejs +++ /dev/null @@ -1,32 +0,0 @@ - - - - <%= appPath %>/<%=underscore%>/create - - - -

    <%= appPath %>/<%=underscore%>/create Demo

    -
    -
    - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/create/create.js.ejs b/jmvc/generate/templates/scaffold/create/create.js.ejs deleted file mode 100644 index 565906e0f..000000000 --- a/jmvc/generate/templates/scaffold/create/create.js.ejs +++ /dev/null @@ -1,26 +0,0 @@ -steal('can', '<%= appPath %>/models/<%= underscore %>.js', './init.ejs', 'jquery/dom/form_params', - function (can, <%= Alias %>, initEJS) { - - /** - * @class <%= appPath %>/<%= underscore %>/create - * @alias <%=Alias%>Create - * @parent index - * @inherits jQuery.Controller - * Creates <%= plural %> - */ - return can.Control( - /** @Prototype */ - { - init: function () { - this.element.html(initEJS()); - }, - submit: function (el, ev) { - ev.preventDefault(); - el.find('[type=submit]').val('Creating...') - new <%= Alias %>(el.formParams()).save(function() { - el.find('[type=submit]').val('Create'); - el[0].reset() - }); - } - }); -}); \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/create/create_test.js.ejs b/jmvc/generate/templates/scaffold/create/create_test.js.ejs deleted file mode 100644 index 52ca4e5c2..000000000 --- a/jmvc/generate/templates/scaffold/create/create_test.js.ejs +++ /dev/null @@ -1,44 +0,0 @@ -steal('funcunit', - './create.js', - '<%= appPath %>/models/<%= underscore %>.js', - '<%= appPath %>/models/fixtures', - function (S, <%=Alias%>Create, <%=Alias%>, <%= underscore %>Store ) { - - module("<%= appPath %>/<%=underscore%>/create", { - setup: function(){ - $("#qunit-test-area").append("
    "); - new <%=Alias%>Create("#create"); - }, - teardown: function(){ - $("#qunit-test-area").empty(); - <%= underscore %>Store.reset(); - } - }); - - test("create <%= plural %>", function () { - stop(); - - <%=Alias%>.bind("created",function(ev, <%= alias %>){ - ok(true, "Ice Water added"); - equals(<%= alias %>.name, "Ice Water", "name set correctly"); - equals(<%= alias %>.description, - "Pour water in a glass. Add ice cubes.", - "description set correctly"); - start(); - Recipe.unbind("created",arguments.callee); - }) - - S("[name=name]").type("Ice Water"); - S("[name=description]").type("Pour water in a glass. Add ice cubes."); - - S("[type=submit]").click(); - - S("[type=submit]").val("Creating...","button text changed while created"); - S("[type=submit]").val("Create", function(){ - ok(true, "button text changed back after create"); - equals(S("[name=name]").val(), "", "form reset"); - equals(S("[name=description]").val(), "", "form reset"); - }); - }); - -}); \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/create/init.ejs.ejs b/jmvc/generate/templates/scaffold/create/init.ejs.ejs deleted file mode 100644 index a4944723e..000000000 --- a/jmvc/generate/templates/scaffold/create/init.ejs.ejs +++ /dev/null @@ -1,9 +0,0 @@ -

    New <%= underscore %>

    -

    -
    - -

    -


    - -

    -

    \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/create/test.html.ejs b/jmvc/generate/templates/scaffold/create/test.html.ejs deleted file mode 100644 index a75cde2c2..000000000 --- a/jmvc/generate/templates/scaffold/create/test.html.ejs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/list/init.ejs.ejs b/jmvc/generate/templates/scaffold/list/init.ejs.ejs deleted file mode 100644 index dbcafe1de..000000000 --- a/jmvc/generate/templates/scaffold/list/init.ejs.ejs +++ /dev/null @@ -1,6 +0,0 @@ -<%% this.each(function(current) { %> -
  1. el.data('<%= underscore %>', current) %>> -

    <%%= current.attr('name') %> X

    -

    <%%= current.attr('description') %>

    -
  2. -<%% }) %> \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/list/list.html.ejs b/jmvc/generate/templates/scaffold/list/list.html.ejs deleted file mode 100644 index 390b201d7..000000000 --- a/jmvc/generate/templates/scaffold/list/list.html.ejs +++ /dev/null @@ -1,34 +0,0 @@ - - - - <%= appPath %>/<%= underscore %>/list Demo - - - -

    <%= appPath %>/<%= underscore %>/list Demo

    - - Create <%= Alias %> - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/list/list.js.ejs b/jmvc/generate/templates/scaffold/list/list.js.ejs deleted file mode 100644 index 732840f60..000000000 --- a/jmvc/generate/templates/scaffold/list/list.js.ejs +++ /dev/null @@ -1,33 +0,0 @@ -steal('can','./init.ejs', '<%= appPath %>/models/<%= underscore %>.js', -function (can, initEJS, <%=Alias%>) { - /** - * @class <%= appPath %>/<%= underscore %>/list - * @alias <%=Alias%>List - * @parent index - * @inherits can.Control - * Lists <%= plural %> and lets you destroy them. - */ - return can.Control( - /** @Static */ - { - defaults : { - <%=Alias%>: <%=Alias%> - } - }, - /** @Prototype */ - { - init: function () { - this.list = new <%=Alias%>.List(); - this.element.html(initEJS(this.list)); - this.list.replace(<%=Alias%>.findAll()); - }, - '.destroy click': function (el) { - if (confirm("Are you sure you want to destroy?")) { - el.closest('.<%= underscore %>').data('<%= underscore %>').destroy(); - } - }, - "{<%=Alias%>} created": function (Model, ev, instance) { - this.list.push(instance); - } - }); -}); \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/list/list_test.js.ejs b/jmvc/generate/templates/scaffold/list/list_test.js.ejs deleted file mode 100644 index 996ebd9b2..000000000 --- a/jmvc/generate/templates/scaffold/list/list_test.js.ejs +++ /dev/null @@ -1,63 +0,0 @@ -steal( - 'funcunit', - './list.js', - '<%= appPath %>/models/<%= underscore %>.js', - '<%= appPath %>/models/fixtures', - function(S, <%=Alias%>List, <%=Alias%>, <%= underscore %>Store ){ - - module("<%= appPath %>/<%=underscore%>/list", { - setup: function(){ - $("#qunit-test-area").append("
    "); - this.list = new <%=Alias%>List("#<%=plural%>"); - }, - teardown: function(){ - $("#qunit-test-area").empty(); - <%= underscore %>Store.reset(); - } - }); - - test("lists all <%= plural %>", function(){ - stop(); - - // retrieve <%= plural %> - <%=Alias%>.findAll({}, function(<%= plural %>){ - // make sure they are listed in the page - - S(".<%= underscore %>").size(<%= plural %>.length,function(){ - ok(true, "All <%= plural %> listed"); - - start(); - }) - }) - }); - - test("lists created <%= plural %>", function(){ - - new <%=Alias%>({ - name: "Grilled Cheese", - description: "grill cheese in bread" - }).save(); - - S('h3:contains(Grilled Cheese X)').exists("Lists created <%= underscore %>"); - }) - - - test("delete <%= plural %>", function(){ - new <%=Alias%>({ - name: "Ice Water", - description: "mix ice and water" - }).save(); - - // wait until grilled cheese has been added - S('h3:contains(Ice Water X)').exists(); - - S.confirm(true); - S('h3:last a').click(); - - - S('h3:contains(Ice Water)').missing("Grilled Cheese Removed"); - - }); - - -}); \ No newline at end of file diff --git a/jmvc/generate/templates/scaffold/list/test.html.ejs b/jmvc/generate/templates/scaffold/list/test.html.ejs deleted file mode 100644 index e36256f7d..000000000 --- a/jmvc/generate/templates/scaffold/list/test.html.ejs +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/jmvc/generate/templates/scaffoldHTML.ejs b/jmvc/generate/templates/scaffoldHTML.ejs deleted file mode 100644 index 5b291accc..000000000 --- a/jmvc/generate/templates/scaffoldHTML.ejs +++ /dev/null @@ -1,4 +0,0 @@ -

    <%= Plural %>

    - -
    - \ No newline at end of file diff --git a/jmvc/generate/templates/scaffoldHookup.ejs b/jmvc/generate/templates/scaffoldHookup.ejs deleted file mode 100644 index 5afb059e1..000000000 --- a/jmvc/generate/templates/scaffoldHookup.ejs +++ /dev/null @@ -1,2 +0,0 @@ - new <%= Alias %>List('#<%= plural %>'); - new <%= Alias %>Create('#create'); \ No newline at end of file diff --git a/jmvc/generate/templates/scaffoldTest.ejs b/jmvc/generate/templates/scaffoldTest.ejs deleted file mode 100644 index eecd35467..000000000 --- a/jmvc/generate/templates/scaffoldTest.ejs +++ /dev/null @@ -1,11 +0,0 @@ - - test("creating a <%= plural %> adds it to the list ", function () { - - S("[name=name]").type("Ice Water"); - S("[name=description]").type("Pour water in a glass. Add ice cubes."); - - S("[type=submit]").click(); - - S("h3:contains(Ice Water)").exists(); - S("p:contains(Pour water in a glass. Add ice cubes.)").exists() - }); \ No newline at end of file diff --git a/jmvc/generate/test/app_plugin_model_controller.js b/jmvc/generate/test/app_plugin_model_controller.js deleted file mode 100644 index 9352a0722..000000000 --- a/jmvc/generate/test/app_plugin_model_controller.js +++ /dev/null @@ -1,60 +0,0 @@ -load('steal/rhino/rhino.js') -load('steal/rhino/test.js'); - -(function(rhinoSteal){ - _S = steal.test; - - - _S.module("jquery/generate") - STEALPRINT = false; - - _S.test("app" , function(t){ - _args = ['cnu']; - load('jquery/generate/app'); - _S.clear(); - _S.open('cnu/cnu.html') - t.ok(typeof steal !== 'undefined', "steal is fine") - _S.clear(); - }) - - _S.test("app 2 levels deep" , function(t){ - _args = ['cnu/widget']; - load('jquery/generate/plugin'); - _S.clear(); - _S.open('cnu/widget/widget.html') - t.ok(typeof steal !== 'undefined', "steal is fine") - _S.clear(); - }) - - /** - * Tests generating a very basic controller and model - */ - - _S.test("controller, model, and page" , function(t){ - _args = ['Cnu.Todos']; - load('jquery/generate/controller'); - _S.clear(); - - _args = ['Cnu.Models.Todo']; - load('jquery/generate/model'); - _S.clear(); - cnuContent = readFile('cnu/cnu.js') - +"\n.then('./models/todo.js')" - +"\n.then('./todos/todos.js')"; - load('steal/rhino/rhino.js') - new steal.File('cnu/cnu.js').save( cnuContent ); - - - _args = ['cnu','cnugen.html']; - load('jquery/generate/page'); - _S.clear(); - - _S.open('cnu/cnugen.html'); - - t.ok(typeof Cnu.Todos !== 'undefined',"load Cnu.Controllers.Todos") - t.ok(typeof Cnu.Models.Todo !== 'undefined', "load Cnu.Models.Todo") - - rhinoSteal.File("cnu").removeDir(); - }) - -})(steal); diff --git a/jmvc/generate/test/run.js b/jmvc/generate/test/run.js deleted file mode 100644 index 1efe5c72a..000000000 --- a/jmvc/generate/test/run.js +++ /dev/null @@ -1,3 +0,0 @@ -load("jquery/generate/test/app_plugin_model_controller.js"); - -load("jquery/generate/test/scaffold.js"); diff --git a/jmvc/generate/test/scaffold.js b/jmvc/generate/test/scaffold.js deleted file mode 100644 index ecfb9552f..000000000 --- a/jmvc/generate/test/scaffold.js +++ /dev/null @@ -1,87 +0,0 @@ - - -load('steal/rhino/rhino.js'); -load('steal/test/test.js'); - -steal('steal/test', function(s){ - - s.test.module("jquery/generate/scaffold") - - STEALPRINT = false; - - s.test.test("make app and scaffold", function(t){ - _args = ['cookbook']; - load('jquery/generate/app'); - _args = ['Cookbook.Models.Recipe']; - load('jquery/generate/scaffold'); - - - load('steal/rhino/rhino.js'); - var cookbookContent = readFile('cookbook/cookbook.js') - +"\n.then('./models/recipe.js')" - +"\n.then('./controllers/recipe_controller.js')"; - new steal.File('cookbook/cookbook.js').save( cookbookContent ); - - var qunitContent = readFile('cookbook/test/qunit/qunit.js'). - replace("cookbook_test", "recipe_test"); - new steal.File('cookbook/test/qunit/qunit.js').save( qunitContent ); - - var funcunitContent = readFile('cookbook/test/funcunit/funcunit.js'). - replace("cookbook_test", "recipe_controller_test"); - new steal.File('cookbook/test/funcunit/funcunit.js').save( funcunitContent ); - - t.clear(); - print('trying to open ...') - t.open('cookbook/cookbook.html', false) - t.ok(Cookbook.Controllers.Recipe, "Recipe Controller") - t.ok(Cookbook.Models.Recipe, "Recipe Controller") - t.clear(); - }); - - //now see if unit and functional run - -// s.test.test("scaffold unit tests", function(t){ -// -// load('steal/rhino/rhino.js'); -// load('funcunit/loader.js'); -// FuncUnit.load('cookbook/qunit.html'); -// }); -// -// s.test.test("scaffold functional tests", function(t){ -// load('steal/rhino/rhino.js'); -// load('funcunit/loader.js'); -// FuncUnit.load('cookbook/funcunit.html'); -// -// }); -// -// s.test.test("documentjs", function(t){ -// t.clear(); -// load('steal/rhino/rhino.js'); -// _args = ['cookbook/cookbook.html'] -// load("documentjs/documentjs.js"); -// DocumentJS('cookbook/cookbook.html'); -// }); - - s.test.test("compress", function(t){ - t.clear(); - load("cookbook/scripts/build.js") - - var cookbookPage = readFile('cookbook/cookbook.html'). - replace("steal.js?cookbook,development", "steal.js?cookbook,production"); - new steal.File('cookbook/cookbook.html').save( cookbookPage ); - - t.clear(); - t.open('cookbook/cookbook.html', false) - t.ok(Cookbook.Controllers.Recipe, "Recipe Controller") - t.ok(Cookbook.Models.Recipe, "Recipe Controller") - t.clear(); - }); - - - //print("-- cleanup --"); -// s.File("cookbook").removeDir(); - -}) - - - diff --git a/jquery b/jquery deleted file mode 160000 index 071ddcc18..000000000 --- a/jquery +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 071ddcc18df8b66e83545a101fe45a8d8aecf498 diff --git a/jquerypp b/jquerypp new file mode 160000 index 000000000..ad61dbb27 --- /dev/null +++ b/jquerypp @@ -0,0 +1 @@ +Subproject commit ad61dbb276cdc735f5a745132898deb17108059c diff --git a/package.json b/package.json index 6068345d5..c09924fdb 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,34 @@ { - "name" : "JavaScriptMVC", - "description" : "A full stack JavaScript application framework containing CanJS, StealJS, FuncUnit, jQuery++, and DocumentJS.", - "version" : "3.3.0pre", - "author" : { - "name" : "Bitovi", - "email" : "contact@bitovi.com", - "web" : "http://bitovi.com/" + "name": "JavaScriptMVC", + "description": "A full stack JavaScript application framework containing CanJS, StealJS, FuncUnit, jQuery++, and DocumentJS.", + "version": "3.3.0pre", + "author": { + "name": "Bitovi", + "email": "contact@bitovi.com", + "web": "http://bitovi.com/" }, - "devDependencies" : { - "grunt" : "~0.3.11", - "grunt-closure-tools" : "~0.6.2", - "http-server" : "0.5.1", - "docco" : ">= 0.1.x", - "node-beautify" : "*", - "github" : ">= 0.1.7", - "commander" : "*", - "tafa-misc-util" : "*" + "devDependencies": { + "grunt": "0.4.0", + "grunt-closure-tools": "0.7.7", + "http-server": "0.5.1", + "docco": "0.6.2", + "js-beautify": "1.2.0", + "github": ">= 0.1.7", + "commander": "*", + "tafa-misc-util": "*", + "lodash" : "0.1.0", + "grunt-contrib-connect": "0.1.2", + "ejs": "0.8.3" }, - "homepage" : "http://javascriptmvc.com/", - "repository" : { - "type" : "git", - "url" : "git@github.com:jupiterjs/javascriptmvc.git" + "homepage": "http://javascriptmvc.com/", + "repository": { + "type": "git", + "url": "git@github.com:jupiterjs/javascriptmvc.git" }, - "licenses" : [ + "licenses": [ { - "type" : "MIT", - "url" : "http://opensource.org/licenses/mit-license.php" + "type": "MIT", + "url": "http://opensource.org/licenses/mit-license.php" } ] } diff --git a/readme.md b/readme.md index d140d32db..b996024ea 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -@page index JavaScriptMVC +@page javascriptmvc JavaScriptMVC
    @@ -62,7 +62,6 @@ command-line and browser-based utilities enabling you to: - [steal load] JS, CSS, LESS, and CoffeeScript files and build them into a single production file. - [steal.generate generate] an application file/folder structure, complete with test, build and documentation scripts. - - [steal.get install] 3rd party dependencies. - [steal.clean clean and JSLint] your code. - make your Ajax app [steal.html crawlable]. - log [steal.dev messages] in development that get removed in production builds. @@ -71,7 +70,7 @@ command-line and browser-based utilities enabling you to: ## FuncUnit -[funcunit FuncUnit] is a web application testing framework that provides automated unit and +[FuncUnit FuncUnit] is a web application testing framework that provides automated unit and functional testing. Tests are written and debugged in the browser with FuncUnit's short, terse, jQuery-like API. The same tests can be instantly automated, run by Envjs or Selenium. diff --git a/scripts/getjmvc b/scripts/getjmvc index d8ab4630c..a1026fdab 100755 --- a/scripts/getjmvc +++ b/scripts/getjmvc @@ -61,7 +61,7 @@ if [ -f ${INSTALLPATH}steal/steal.js ] then cd ${INSTALLPATH}steal git pull origin master - cd ../jquery + cd ../jquerypp git pull origin master cd ../documentjs git pull origin master @@ -81,18 +81,23 @@ else fi git submodule add $FULLSRC/steal.git ${INSTALLPATH}steal -git submodule add $FULLSRC/canjs.git ${INSTALLPATH}canjs +git submodule add $FULLSRC/canjs.git ${INSTALLPATH}can git submodule add $FULLSRC/jquerypp.git ${INSTALLPATH}jquerypp git submodule add $FULLSRC/documentjs.git ${INSTALLPATH}documentjs git submodule add $FULLSRC/funcunit.git ${INSTALLPATH}funcunit +git submodule add $FULLSRC/jmvc-generators.git ${INSTALLPATH}jmvc git submodule init git submodule update cd ${INSTALLPATH}steal git checkout $BRANCH -cd ../jquery +cd ../jmvc +git checkout $BRANCH +cd ../can +git checkout $BRANCH +cd ../jquerypp git checkout $BRANCH cd ../documentjs -git checkout master +git checkout $BRANCH cd ../funcunit git checkout $BRANCH git submodule init diff --git a/site/docs.js b/site/docs.js index 00a697a63..5d6c00f52 100644 --- a/site/docs.js +++ b/site/docs.js @@ -3,8 +3,7 @@ steal('can', 'documentjs', 'jquery/build/lib.js') -steal('steal/generate', - 'steal/get', +steal('steal','steal/generate', 'steal/build', 'steal/build/pluginify', 'steal/coffee', @@ -30,4 +29,6 @@ steal('can/construct', 'can/view/modifiers', 'can/view/mustache') +steal('can/util/func.js') + // .then('canui') diff --git a/site/pages/developingwithgit.md b/site/pages/developingwithgit.md index 89ff3f632..a83334b3a 100644 --- a/site/pages/developingwithgit.md +++ b/site/pages/developingwithgit.md @@ -18,7 +18,7 @@ If you don't, you might find the following resources helpful: ## Git-ing JavaScriptMVC -JavaScriptMVC is comprised of 6 sub projects: +JavaScriptMVC is comprised of 7 sub projects: - [http://github.com/bitovi/steal] - [http://github.com/bitovi/canjs] @@ -26,6 +26,7 @@ JavaScriptMVC is comprised of 6 sub projects: - [http://github.com/bitovi/documentjs] - [http://github.com/bitovi/funcunit] - [http://github.com/bitovi/syn] + - [https://github.com/bitovi/jmvc-generators] We're going to fork each of these projects and add them as submodules to your master git project. @@ -38,11 +39,9 @@ the fork button (in the upper right of the page). @image site/images/fork.png -
    PRO TIP: - If you're working for a company, you should create company forks and give - employees access to the company forks. This will keep everyone using the - same version. -
    +> TIP: If you're working for a company, you should create company forks and give +employees access to the company forks. This will keep everyone using the +same version. #### Installing with a script @@ -57,7 +56,8 @@ chmod 755 getjmvc Use this script to install JMVC from github or your own fork. If its already installed, it will get latest for all the submodules. Assumes your project uses git. -##### Options: +##### Options + - -u username (default is bitovi) - -b branch (default is master) - -s source url (default is https://github.com) @@ -116,6 +116,7 @@ git submodule add git@github.com:_YOU_/canjs.git public/canjs git submodule add git@github.com:_YOU_/jquerypp.git public/jquerypp git submodule add git@github.com:_YOU_/documentjs.git public/documentjs git submodule add git@github.com:_YOU_/funcunit.git public/funcunit +git submodule add git@github.com:_YOU_/jmvc-generators.git public/jmvc @codeend _Note_: Learn a little more about submodules [here](http://johnleach.co.uk/words/archives/2008/10/12/323/git-submodules-in-n-easy-steps Submodules). diff --git a/site/scripts/doc.js b/site/scripts/doc.js index 5ab37aa0b..99c7ff31c 100644 --- a/site/scripts/doc.js +++ b/site/scripts/doc.js @@ -2,7 +2,10 @@ load('steal/rhino/rhino.js'); steal("documentjs", function(DocumentJS){ DocumentJS('site/scripts/doc.html',{ - markdown : [ 'readme.md', 'site', 'tutorials', 'steal', 'jquery', 'can', 'funcunit' ], - out : 'site/docs' + markdown : [ 'readme.md', 'site', 'tutorials', 'steal', 'jquerypp', 'can', 'funcunit', 'jmvc'], + out : 'docs', + parent: 'javascriptmvc', + "static": "site/static", + "templates": "site/templates" }); }); diff --git a/site/static/styles/config.less b/site/static/styles/config.less new file mode 100644 index 000000000..a38249601 --- /dev/null +++ b/site/static/styles/config.less @@ -0,0 +1,13 @@ +@logo: "../img/javascriptmvc-logo.svg"; +@logoFooter: "../img/javascriptmvc-logo-grey.svg"; +@logoWidth: 260px; +@pageBackground: url(../img/bkg-dots.png) repeat #fff; + +@colorHeader: #444444; +@colorLinks: #1f54c6; +@colorCode: #f9f7df; +@colorNav: #066248; +@colorSignature: #066248; +@colorParamsReturns: #ece7e7; +@fontColorParamsReturns: #000000; +@colorTags: #999999; \ No newline at end of file diff --git a/site/templates/layout.mustache b/site/templates/layout.mustache new file mode 100644 index 000000000..11e1d933a --- /dev/null +++ b/site/templates/layout.mustache @@ -0,0 +1,106 @@ + + + + + + + + + + + + JavaScriptMVC {{#if title}}- {{title}} {{else}} {{#if name}}- {{name}}{{/if}}{{/if}} + + + + + + + + +
    + +
    + + {{{content}}} + +
    + +
    + +

    {{staticLocation}}

    + + + + diff --git a/steal b/steal index 5ccd8ed0f..b0a9b68c5 160000 --- a/steal +++ b/steal @@ -1 +1 @@ -Subproject commit 5ccd8ed0fbb8c66a834b355d75e3f27ee7d0a443 +Subproject commit b0a9b68c521435ec5451565432b81f772b3d3ae8 diff --git a/stealconfig.js b/stealconfig.js index 73c00c553..bf932b0fe 100644 --- a/stealconfig.js +++ b/stealconfig.js @@ -2,10 +2,12 @@ steal.config({ map: { "*": { "jquery/jquery.js" : "jquery", - "can/util/util.js": "can/util/jquery/jquery.js" + "can/util/util.js": "can/util/jquery/jquery.js", + "jquery/": "jquerypp/" } }, paths: { + "jquery/": "jquerypp/", "jquery": "can/lib/jquery.1.9.1.js", "mootools/mootools.js" : "can/lib/mootools-core-1.4.5.js", "dojo/dojo.js" : "can/util/dojo/dojo-1.8.1.js", diff --git a/tutorials/ajaxy/ajaxy.html b/tutorials/ajaxy/ajaxy.html index 140b432a7..7edb1b3a0 100644 --- a/tutorials/ajaxy/ajaxy.html +++ b/tutorials/ajaxy/ajaxy.html @@ -19,35 +19,34 @@ +steal('jquery', + 'can/construct/proxy', + 'can/control', + 'can/route', + 'steal/html', + function($, can){ + +var Ajaxy = can.Control({ + "{route} change" : function(route, ev){ + this.updateContent(route.page) + }, + updateContent : function(hash){ + // postpone reading the html + steal.html.wait(); + + $.get("fixtures/" + hash + ".html", {}, this.proxy('replaceContent'), "text") + }, + replaceContent : function(html){ + this.element.html(html); + // indicate the html is ready to be crawled + steal.html.ready(); + } +}) + +new Ajaxy('#content', { + route: can.route(":page", { page: "videos" }) }); +}); + \ No newline at end of file diff --git a/tutorials/ajaxy/ajaxy.md b/tutorials/ajaxy/ajaxy.md index 92cd372df..2a07d5614 100644 --- a/tutorials/ajaxy/ajaxy.md +++ b/tutorials/ajaxy/ajaxy.md @@ -16,7 +16,7 @@ with the ajaxy/scripts/crawl.js script. The crawl script generates html pages that Google can use as a representation of the content of an Ajax application. Read Google's documentation on its -[https://developers.google.com/webmasters/ajax-crawling/docs/getting-started Ajax crawling API] +[Ajax crawling API](https://developers.google.com/webmasters/ajax-crawling/docs/getting-started) before continuing this tutorial. ## Setup @@ -24,7 +24,7 @@ of the content of an Ajax application. Read Google's documentation on its [installing Download and install] the latest version of JavaScriptMVC. After installing JavaScriptMVC, open a command line to -the [steal.static.root steal.config().root] folder (where you unzipped +the [steal.config.root steal.config.root] folder (where you unzipped JavaScriptMVC). @@ -171,7 +171,7 @@ before the Ajax request. And when the page is ready, Ajaxy calls: ## Getting Google To Crawl Your Site If you haven't already, read up on -Google's [https://developers.google.com/webmasters/ajax-crawling/docs/getting-started Ajax crawling API]. +Google's [Ajax crawling API.](https://developers.google.com/webmasters/ajax-crawling/docs/getting-started) When google wants to crawl your site, it will send a request to your page with \_escaped\_fragment=. @@ -189,10 +189,12 @@ To turn on Phantom: 1. Install it using the install instructions [funcunit.phantomjs here] 1. Open scripts/crawl.js and change the second parameter of steal.html.crawl to an options object with a browser option, like this: - steal('steal/html', function(){ - steal.html.crawl("ajaxy/ajaxy.html", - { - out: 'ajaxy/out', - browser: 'phantomjs' - }) - }) \ No newline at end of file +@codestart +steal('steal/html', function(){ + steal.html.crawl("ajaxy/ajaxy.html", + { + out: 'ajaxy/out', + browser: 'phantomjs' + }) +}) +@codeend \ No newline at end of file diff --git a/tutorials/done.md b/tutorials/done.md index 8ea3dc631..720effd94 100644 --- a/tutorials/done.md +++ b/tutorials/done.md @@ -3,9 +3,13 @@ JavaScriptMVC 3.3 introduces a lot of new features to build large and responsive applications. As such, there are a few changes from 3.2 and this guide walks through the API differences between the versions. +## CanJS + +[canjs Can] has replaced the previous MVC internals of JavaScript MVC, but provides backwards compatability to jQuery MX. jQuery MX $.Class, $.Model, and $.Controller will not be supported in future versions, and we urge you to switch to using can. + ## Steal -[stealjs | Steal] in 3.3 has support for AMD modules. Steal will still load resources as 3.2, however will also now follow the pattern for dependencies represented by a string id. +[stealjs Steal] in 3.3 has support for AMD modules. Steal will still load resources as 3.2, however will also now follow the pattern for dependencies represented by a string id. So, as a simple example: diff --git a/tutorials/examples.md b/tutorials/examples.md index af2557cec..5880a9cb4 100644 --- a/tutorials/examples.md +++ b/tutorials/examples.md @@ -1,10 +1,10 @@ @page examples Examples -@parent index 6 +@parent javascriptmvc 6 @description Example application to learn from. Here is the list of the example JavaScriptMVC applications. They cover specific JavaScriptMVC features and are ordered by complexity. If you need an introduction to the JavaScriptMVC framework it is best to read them in order. -## [todo Todo] - [open application ⇗](todo/todo.html) +## [todo TodoMVC JavaScriptMVC] - [open application ⇗](tutorials/examples/todo.html) In this guide, we're going to be installing and walking through the simplest [JavaScriptMVC](http://javascriptmvc.com/) application imaginable — a TODO list manager. @@ -14,16 +14,6 @@ This article covers: - Separation of application logic from user interface - Using model lists to manage collection of models -## [playermx PlayerMX] - [open application ⇗](player/player.html) - -This article walks through a simple video player application utilizing Popcorn.js. - -This article covers: - -- Installing and running the Application -- Templated Events with [jQuery.Controller $.Controller] -- How PlayerMX is built - ## [srchr Srchr] - [open application ⇗](srchr/srchr.html) Srchr searches multiple services (like Flickr, Upcoming, and Twitter) and saves the results between page requests. diff --git a/tutorials/examples/contacts.md b/tutorials/examples/contacts.md new file mode 100644 index 000000000..4f3fdf876 --- /dev/null +++ b/tutorials/examples/contacts.md @@ -0,0 +1,152 @@ +@page contacts Contacts +@parent examples 3 + +In this article we will walk through installing and the ins-and-outs of Contacts. Contacts is a lightweight application that allows users to add and organize their friends' contact information. + +This tutorial describes: + +- Installing and running the application +- The application's structure and organization +- Dividing the application into modular widgets +- Tieing the widgets together + +@image ../tutorials/images/contacts_preview.png + +Let's get started! + +## Setup + +The application source is hosted by [GitHub](https://github.com/bitovi/contacts). You can download the application on github using the following commands: + + $ git clone https://github.com/bitovi/contacts + $ cd contacts + $ git submodule update --init + +To run the application, open _contacts.html_ with your browser. We will be using [can.fixture fixtures] to simulate the AJAX requests so running it from a server isn’t necessary. + +This will run the application in development mode. If you want to build and run the application in production, in the command line run: + + $ ./js contacts/scripts/build.js + +then change the script tag in `contacts.html` to be in production mode: + + + +Additionally, the app can be found on [Github Pages](http://bitovi.github.io/contacts/) if you do not want to set it up. + +## Folder Structure + +The application resides in the `contacts` folder. Steal, CanJS, and CanUI folders sit perpendicular to this for reuse in other projects. The directory structure should mirror below. + + [top-level] + /can + /steal + /funcunit + /contacts + /form + /scripts + /test + /models + /fixtures + /views + /less + funcunit.html + qunit.html + contacts.js + ... + contacts.html + stealconfig.js + +The contacts folder contains: + +- `models` AJAX end-point definitions and helpers +- `views` EJS/Mustache can.view templates +- `fixtures` simulated AJAX response +- `less` LESS stylesheet such as [Boostrap](http://twitter.github.io/bootstrap/) 3.0 and `contacts.less` +- `contacts/form` child components of contacts + +Along with runners and scripts for building and tests. + +## Division of Modules + +The secret to building large applications is NEVER build large applications. Understanding how to divide and isolate modules in the application is the first step towards maintainable architecture. + +The goal for dividing your application should be to create modules that are isolated. + +Isolated modules are: + +- Limited to one specific purpose, for example showing a list of data +- Rarely reference other modules and never their parents +- Have a simple generic API making them easy to swap out + +Isolated modules are easily testable because they have a small, well defined scope. Each piece can be worked on in parallel because the code is divided. Reuse is easier because the modules are not coupled to each other. + +### Dividing Contacts + +The contacts app has 3 lists that filter the grid of contacts. You can create additional categories and contacts by clicking the 'new' icon. + +This application can be divided up into a few widgets: + +* List - accepts a generic data source and layout, renders and updates the list. +* Grid - accepts a generic data source, renders a grid. +* Form - create a new instance from a data source. + +Heres a visual representation of how this app is broken up into modules. + +@image ../tutorials/images/contacts_design.png + +## Tying it all together + +`contacts/contacts.js` will be where the application starts: loading each module, initializing them, and gluing them together. + +In the `init` method, we initalize all the base objects and inject the base view. + + init: function(){ + // initalize the lists and objects + this.categoryList = new Models.Category.List; + this.locationList = new Models.Location.List; + this.companyList = new Models.Company.List; + this.contactsList = new Models.Contact.List; + this.edited = new Observe; + this.total = can.compute(0); + this.isLoading = can.compute(function(loading){ + loading ? loadingCounter++ : loadingCounter--; + return loading > 0; + }); + + // Draw the view, setup helpers and partials + this.element.html(initView({ ... }); + + // Initalize each Form category + can.each(['location', 'category', + 'company', 'contact'], function(formType){ + new Form(this.element.find('#' + formType), { + edited : this.edited, + model : Models[can.capitalize(formType)], + list : this[formType + 'List'] + }); + }.bind(this)); + + this.setupScroll(); + this.loadFilters(); + this.loadContacts(); + } + +From this point on, the application uses live-binding to update the lists/views based on the filter/offset. + + {{#contacts}} + {{>contact}} + {{/contacts}} + +As the `contacts` list changes, the view automatically updates to reflect the new list. + +## Wrapup + +In this article, we explored: + +- Installing and running the application +- The application's structure and organization +- Dividing the application into modular widgets +- Tieing the widgets together + +If you're interested in other examples, check out the other application examples. \ No newline at end of file diff --git a/tutorials/examples/contacts/contacts.md b/tutorials/examples/contacts/contacts.md deleted file mode 100644 index 0037a8679..000000000 --- a/tutorials/examples/contacts/contacts.md +++ /dev/null @@ -1,53 +0,0 @@ -@page contacts Contacts -@parent examples 1 -@hide - -This is a tutorial application that introduces and explains step-by-step how to architect a JavascriptMVC 3.0 style application using a contacts application. Contacts is a lightweight application that allows users to add and organize their friends' contact information. - -This tutorial describes: - -* Installing and running the application -* The application's structure and organization -* How the application's widgets were designed -* The anatomy of the application's widgets -* How we glued the application's widgets together using event-oriented-architecture - -Using techniques we will cover in this tutorial, you will learn how to build an application that is modular, testable, and scaleable. - -@image tutorials/images/contacts_preview.jpg - -## Setup - -The application source is hosted by [GitHub](https://github.com/jupiterjs/contacts). You can either download the application using [steal.get GetJS] - - ./js steal/getjs contacts - -or clone the code using the following commands: - - $ git clone https://github.com/jupiterjs/contacts - $ cd contacts - $ git submodule update --init - -Once you get the application from GitHub you should have structure similar to below. - - [top-level] - /jquery - /steal - /funcunit - /scripts - /contacts - /scripts - /test - /models - /fixtures - /views - funcunit.html - qunit.html - contacts.css - contacts.js - ... - contacts.html - -To run the application, open _contacts.html_ with your browser. We will be using [jQuery.fixture fixtures] to simulate the AJAX requests so running it from a server isn’t necessary. - -Once the application is displayed in your browser, continue to [contacts.dc Divide and Conquer] to learn how we split up the application. \ No newline at end of file diff --git a/tutorials/examples/contacts/dc.md b/tutorials/examples/contacts/dc.md deleted file mode 100644 index 66a0878c5..000000000 --- a/tutorials/examples/contacts/dc.md +++ /dev/null @@ -1,83 +0,0 @@ -@page contacts.dc Divide and Conquer -@parent contacts 0 - -The secret to building large applications is never build large applications. Understanding how to divide your application is the first step towards maintainable architecture. This section covers why and how to divide up your app and uses contacts as an example. - -## Isolation - -The goal for dividing your application should be to create modules that are isolated. - -Isolated modules are dumb, lonely, and replaceable. Dumb because they are limited to one specific purpose, for example showing a list of data. Lonely because they rarely reference other modules. Replaceable because they have a simple API, making them easy to swap out. - -Isolated modules are easily testable because they have a small, well defined scope. Each piece can be worked on in parallel because the code is divided. Reuse is easier because the modules are not coupled to each other. - -## Dividing Contacts - -The contacts app has 3 lists that filter the grid of contacts. You can create additional categories and contacts by clicking the 'new' icon. - -The application can be divided up into a few widgets: - -* List - accepts a generic data source and layout, renders and updates the list. -* Grid - accepts a generic data source, renders a grid. -* Sort - sorts the grid items. -* Create - renders a form to create a new instance from a data source. - -Heres a visual representation of how this app is broken up into modules. - -@image tutorials/images/contacts_widgets.jpg - -## Folder Structure - -The code for these widgets is divided into three top level folders: MXUI, Jupiter, and Contacts. - -### MXUI - -[https://github.com/jupiterjs/mxui MXUI] (jQueryMX User Interface) is a JavaScriptMVC widget library. The Grid and List widgets from MXUI are used in Contacts. - -### Jupiter - -Each isolated module lives in its own directory. Those directories are then grouped in a namespace, for example _Jupiter_ or the name of your company. These modules are intended to be reused across applications. - -The Jupiter folder contains: - -* create - renders a form to create a new instance from a data source. -* style - attaches jQuery UI styles to elements. -* scrollable_grid - creates a infinitely scrollable grid using the MXUI grid. - -### Contacts - -Finally, we need a widget that loads the modules and ties them together into a application. In this application, the contacts folder contains this 'application widget'. - -This widget will be where the application starts, loading each module, initializing them, and gluing them together. - - $.Controller("Contacts.Controller", { - init: function(){ - - this.params = new Mxui.Data(); - - $("#category .list_wrapper").mxui_data_list({ - model : Contacts.Models.Category, - show : "//contacts/views/categoryList", - create: "//contacts/views/categoryCreate" - }); - - $("#location .list_wrapper").mxui_data_list({ - model : Contacts.Models.Location, - show : "//contacts/views/categoryList", - create: "//contacts/views/categoryCreate" - }); - -The contacts widget listens for events using event delegation and communicates with cousin widgets. - - "#category .list_wrapper activate": function(el, ev, item){ - this.params.attr("categoryId", item.id); - }, - "#category .list_wrapper deactivate": function(el, ev, item){ - this.params.attr("categoryId", null); - } - -In the [contacts.glue Gluing Modules Together] section, we will touch more on the pattern used to facilitate this cross-widget communication. - -Additionally in the contacts folder, you will find the building blocks of any JavascriptMVC application: models for communication with the server, fixtures for simulating AJAX responses from the server, and functional tests for testing the application. - -In the next section, we will get into more detail about [contacts.designing how to design each module]. \ No newline at end of file diff --git a/tutorials/examples/contacts/designing.md b/tutorials/examples/contacts/designing.md deleted file mode 100644 index 1711361ac..000000000 --- a/tutorials/examples/contacts/designing.md +++ /dev/null @@ -1,84 +0,0 @@ -@page contacts.designing Designing Modules -@parent contacts 1 - -Widgets should be dumb to whats going on around them. They should be self contained and provide an easy to use API for developers. The first step in designing a widget is to define the input and outputs. - -## Defining Inputs and Outputs - -Inputs are anything a widget consumes from the outside world. This can include parameters passed (optional or required) or DOM events expected. For example, a grid widget consumes parameters like page offset, initial sort, and column names. - -Outputs are anything a widget visibly produces for the outside world. This can include DOM created, events triggered, callback functions executed, data changed, etc. For example, a grid widget might produce "pageChanged" or "columnSorted" events. - -Let's examine the inputs and outputs of the _MXUI.Data.Grid_ widget. - -@image tutorials/images/inputs_outputs.jpg - -## Anatomy of the List Module - -One of the reusable modules in the Contacts app is List. The List is a simple widget that takes a model, gets and lists data, and listens for updates to the items to update and/or destroy rows. - - $("#category .list_wrapper").mxui_data_list({ - model : Contacts.Models.Category, - show : "//contacts/views/categoryList", - create: "//contacts/views/categoryCreate" - }) - -In this app, the category, location, and company sections are all filters for the contacts grid. Upon clicking one of these rows, the grid is filtered with the selection. These lists are all managed using _MXUI.Data.List_. We'll walk through how this widget works to illustrate creating reusable, self contained modules. - -### Getting the List Data - -When initialized, you pass the List a [jQuery.model model] class. JavaScriptMVC models have a standard API to perform CRUD operations. Every model implements a [jQuery.Model.static.findAll findAll] method, which we can leverage to fetch data. - - this.options.model.findAll(this.options.params, this.callback('list')) - -If you look at the models in _contacts/models_, you'll notice findAll is missing. By default, models will request data using REST standards, as described in [jquery.model.encapsulate Service Encapsulation]. Every model has a findAll method, implemented in the based [jquery.model jQuery.Model] class. - -### Drawing the List - -One of List's parameters is a 'list' template. After the model has completed its _findAll_, we will take its results and draw the UI. - - this.element.html(this.options.list,{ - items : this.options.sort ? items.sort(this.options.sort) : items, - options: this.options - }); - -### Capturing Updates - -In a live application, data changes. The List should be aware of these changes and update its UI accordingly. List listens for updates for its given model and refreshes UI with the latest data. - - "{model} updated" : function(model, ev, item){ - var el = item.elements(this.element).html(this.options.show, item); - if(this.options.updated){ - this.options.updated(this.element, el, item) - } - }, - -In the [contacts.glue Gluing Modules Together] section, we will discuss using this type of observer architecture. - -### Responding to Clicks - -As items are clicked in the list, a "selected" visual state is applied to the item. - - ".item {activateEvent}" : function(el, ev){ - if(el.hasClass("activated")){ - this._deactivate(el) - } else { - var old = this.find(".activated"); - this._deactivate(old); - this._activate(el); - } - }, - _deactivate: function(el){ - el.removeClass("activated"); - el.trigger("deactivate", el.model()); - }, - _activate: function(el){ - el.addClass("activated"); - el.trigger("activate", el.model()); - } - -In the above code, we trigger an _activate_ event. This event is one of List's outputs that we listen to using [jquery.controller.listening event delegation]. As opposed to applications listening for "click" events, we use activate to provide a level of abstraction. This allows the widget to provide other ways to activate a row, such as keyboard navigation or clicking. We could even expand this widget to use touch events for mobile devices, and applications wouldn't have to change. For more information on this you can read the [mvc.controller Controller] documentation. - -The mission of this widget was simple: create a list of data that updates itself as the items change. You and I have probably written this a dozen times in our applications. This List widget provides a reusable API because it has generic and well defined inputs and outputs. - -Next up, we will discuss how to [contacts.glue glue] our isolated modules into a full application. \ No newline at end of file diff --git a/tutorials/examples/contacts/glue.md b/tutorials/examples/contacts/glue.md deleted file mode 100644 index a6480e896..000000000 --- a/tutorials/examples/contacts/glue.md +++ /dev/null @@ -1,108 +0,0 @@ -@page contacts.glue Gluing Modules Together -@parent contacts 2 - -Now that we have learned how to create dumb, lonely, replaceable widgets, we need to glue them together into a smart app. - -## Changes in State - -Events are significant changes in state, and EOA is a pattern that detects and consumes these changes in state. EOA is an elegant way to tie decoupled modules together. - -### Cons of Callback Architecture - -Traditionally widgets accept callbacks. - - $('div').modal({ - closeCallback: windowClosed, - showCallback: windowOpened - }); - -This type of architecture in not desirable for many reasons: - -* The widget should be as dumb as possible. When you pass a callback your widget becomes coupled to its parent widget because it becomes aware of the context in which its used. - -* When you pass callbacks, only one widget can provide that callback. It creates a 1-to-1 architecture, which limits the scalability of the application. - -* Callbacks clutter the API and make it difficult for new developers to learn. - -### Event Oriented Architecture - -Rather than using callbacks, the contacts application uses EOA. One good example is how the grid is filtered from a user clicking an item in a list. - -As items are clicked in the list, it triggers an "activate" event, including the selected row’s model instance in the event. - - el.trigger("activate", el.model()); - -_Pro Tip: Because we bound [jquery.fn.model instances of models using EJS] as shown below, in our controller code we can call .model() on the element that was bound to retrieve the model as shown above._ - - <% for(var i =0 ; i < items.length;i ++){ %> -
  3. > - -The contacts widget then observes this state change using event delegation and updates the grid parameters. - - "#category .list_wrapper activate": function(el, ev, item){ - this.params.attr("categoryId", item.id); - } - -The contacts widget listens for events high in the DOM, so it can capture any events triggered by child widgets. - -@image tutorials/images/eoa_diagram1.jpg - - -Grid parameters are a JavaScriptMVC Model instance, used to define common data attributes like limit, offset, and count. The JavascriptMVC Model implements an observer pattern. As changes are made to attributes, widgets can listen for the _updated_ event and react accordingly. - -In the sample code above we call [jquery.model.prototype.attr attr] on this model instance to update the parameters. The grid is listening for changes in the attributes. Above, when activate is triggered, _categoryId_ is changed. The grid observes this change and executes an AJAX request to get data for the current parameter set. - - "{params} updated.attr" : function(params, ev, attr, val){ - if(attr !== 'count'){ - //want to throttle for rapid updates - clearTimeout(this.newRequestTimer,100) - this.newRequestTimer = setTimeout(this.callback('newRequest', attr, val)) - } - }, - - newRequest : function(attr, val){ - var clear = true; - if(!this.options.newPageClears && attr == "offset"){ // if offset changes and we have newPageClears false - clear = false; - } - this.options.model.findAll(this.options.params.attrs(), this.callback('list', clear)) - } - - -@image tutorials/images/eoa_diagram2.jpg - -## Changes in Data - -[jquery.model.events Model events] are events that are fired when CRUD operations (create/update/delete) occur on a model instance. Widgets can bind to these events to automatically update when data changes. - -### Cons of Custom Events - -A common widget communication pattern is triggering custom events when actions are completed. For example, when a user clicks "next page" in a pagination widget, the paginator could trigger "nextPage" and the grid could listen and reload with new data. This is also not desirable because: - -* The widget should be as dumb as possible. If a widget is listening to custom events from child widgets, you are coupling it to the parent widget. We don't want the grid widget depending on a specific "nextPage" event from the paginator, making the paginator not easily replaceable. - -* In some cases, the same state needs to be represented across multiple widgets. The grid, paginator, and possibly other widgets need to know about the current pagination data. Rather than complicate the design by adding unnecessary layers, it makes more sense to maintain this state once (in the model), and let widgets listen to changes in that state. - -### Listening to Model Events - -The List widget provides a good example of binding directly to model updates. - - "{model} updated" : function(model, ev, item){ - var el = item.elements(this.element).html(this.options.show, item); - if(this.options.updated){ - this.options.updated(this.element, el, item) - } - } - -As model changes are made, such as an update to a contact's name, we listen for these changes in the List and update the UI accordingly using [jquery.model.events model events]. - -## Wrap-up - -In this article, we explored: - -* The layers of the contacts application -* How to structure your application into modules, optimizing for widget reuse -* Designing widgets that are modular, testable, and have easy to use APIs -* How to glue your widgets together using event oriented architecture - -If you're interested in other examples, check out the other [examples application examples]. \ No newline at end of file diff --git a/tutorials/examples/playermx.md b/tutorials/examples/playermx.md deleted file mode 100644 index d1e21bac9..000000000 --- a/tutorials/examples/playermx.md +++ /dev/null @@ -1,243 +0,0 @@ -@page playermx PlayerMX -@parent examples 1 - -This article walks through a simple video player application utilizing Popcorn.js. We'll cover: - - - Setting up the application - - Templated events with [can.Control] - - How PlayerMX is built - -## Setup - -There's two options to install PlayerMX: getjs or using git. - -### Download with getjs - -After downloading and [installing installing JavaScriptMVC], run the following command: - -`./js steal/getjs player` - -_Note: When running the getjs command, be sure to navigate to your [rootfolder JavaScriptMVC root folder]._ - -### Github - -From a directory of your choice: - -_Clone files to your local drive_ -`$ git clone https://github.com/jupiterjs/playermx.git` - -_Navigate to application directory and initialize submodules_ -`$ cd playermx -$ git submodule update --init` - -### Running - -Open player/player.html in your browser and see the application run: - -@image tutorials/images/playermx.png - -_Note: Safari and Chrome currently support the mp4 codec, however Firefox requires the h.264 Flash player to process mp4's._ - -You can see our version running here: [http://javascriptmvc.com/player/player.html](http://javascriptmvc.com/player/player.html) - -Repo: [https://github.com/jupiterjs/playermx](https://github.com/jupiterjs/playermx) - -## PopcornJS Overview - -Repo: [https://github.com/cadecairos/popcorn-js](https://github.com/cadecairos/popcorn-js) - -> _Popcorn.js is an event framework for HTML5 media. Popcorn.js utilizes the native HTMLMediaElement properties, -methods and events, normalizes them into an easy to learn API and provides a plugin system for community contributed interactions._ - -> _[Source: http://popcornjs.org/documentation](http://popcornjs.org/documentation)_ - -Popcorn.js wraps up our mp4 file into a `video` object which we can pass to our widgets. -Our widgets can then play and pause the video based on user interaction. The Popcorn.js API we'll be using is: - -* Events: - * play - triggered when video plays - * pause - triggered when a video is paused - * timeupdate - triggered continuously during video playback -* Properties: - * paused - boolean, true if the video is paused -* Methods: - * play() - begins playing video, triggers "play" event - * pause() - pauses video, triggers "pause" event - * currentTime() - gets the current playback position in milliseconds - * duration() - gets length of the video in milliseconds - -## Templated Events Overview - -PlayerMX introduces the concept of binding events to JavaScript objects other than DOM elements. -This application uses the event syntax: `{myObject} click`, where `myObject` is the object and `click` is the event we're listening to. -This is what we call templated events. - -Templated events create a simple way to bind events without concern for cleanup. -For example, binding an event to a DOM element with $.bind(), will be removed when you call $.remove() on that element. -However, if you want to listen to events on a model, templated events handle the unbinding for you. -In essence, memory concerns are reduced with templated events. - -Specifically with PlayerMX, our widgets listen to events produced by our Popcorn video element. -If that element is removed from our page, by using templated events, we don't need to worry about cleaning up bound methods. - -## PlayerMX Architecture - -Once you've downloaded the application, you'll notice 3 folders within your `playermx` directory. -A few notes on the directory structure of this application: - -* The `can` folder refers to [CanJS](https://github.com/jupiterjs/canjs). -This is the CanJS core and what we'll be building our application on. -* The `jquery` folder refers to [jQuery++](https://github.com/jupiterjs/jquerypp). -This adds useful events like [resize jQuery.event.resize] and [jQuery.event.drag drag] which will be used in the player. -* The [steal](https://github.com/jupiterjs/steal) folder is our dependency management system. -This is how we include other resources such as scripts, stylesheets, templates or other JavaScriptMVC resources and apps altogether. -* The `player` folder is where we'll place our focus as this is the main directory for our application. - -The application is broken up into one main application page, `player.html`, with a corresponding script, -`player.js` and two corresponding widgets. - -@image tutorials/images/playermx_overview.png - -### player.js - -`Player.js` is our main application script. `Steal` loads the widgets we want, and then we initialize them. -This loosely couples our widgets from our application. The variable `video` is our reference to the Popcorn.js wrapped object. -`play` and `player_position` accept this object as a parameter. - - steal('./play.js', - './position.js', - - function() { - var video = Popcorn("#trailer"); - - new Play('#play', { video: video }); - new PlayerPosition('#position', { video: video }); - }); - -The first line of `player.js` is our call to `steal`. This is going to load all our dependencies. -In this case, we load `play.js` and `position.js`, then execute a function. - -### play.js - -@image tutorials/images/playermx_play.png - - - steal('player/popcorn', - 'can/control', - - function() { - -The purpose of this widget is to control the video playback. Listening to the `play` and `pause` events on the Popcorn.js object, -we'll add a CSS class designating playback state. - -Our widget will be created using `$.Controller`. By naming our controller "play", we have now have a `jQuery.fn.play()` method. -`init` is the constructor method for our controller. Any passed parameters are accessible via `options` on the controller. - - can.Control('Play', { - init : function(){ - if( this.options.video.video.paused ) { - this.element.text("play") - } else { - this.element.text("stop") - } - }, - -Within player.js, we've passed a video object to our controller. -Using templated events, we can listen to the events directly on this object. -Templated events allow listening to events on any object, not just DOM events. -In the code below, "{video}" refers to our object and "play" is the event we'll listen for. - - "{video} play" : function() { - this.element.text("stop").addClass('stop') - }, - - "{video} pause" : function() { - this.element.text("play").removeClass('stop') - }, - -We'll listen to clicks within the parent element and call the play/pause methods, depending on current state. -The separation of the click handler and the play/pause handlers is for extensibility. -We may have multiple widgets that control the playback of our video and each widget should be able to respond accordingly. - - click : function() { - if( this.options.video.video.paused ) { - this.options.video.play() - } else { - this.options.video.pause() - } - } - }); - }); - -### position.js - -@image tutorials/images/playermx_position.png - - - steal('player/popcorn', - 'can/control', - 'jquery/dom/dimensions', - 'jquery/event/resize', - 'jquery/event/drag/limit', - -The PlayerPosition widget shows a progression bar for our video. This should not only display playback position, but be draggable as well -`this` in the following context refers to our widget instance. Our progress indicator will be accessible via the `moving` property. -A simple div will suffice for this example and we'll set some basic css properties such as position and dimensions. - - function() { - can.Control('PlayerPosition', { - init : function(){ - this.moving = $("
    ").css({ - position: 'absolute', - left: "0px" - }) - - this.element.css("position","relative") - .append(this.moving); - - this.moving.outerWidth( this.element.height() ); - this.moving.outerHeight( this.element.height() ); - - }, - -The widget listens to the Popcorn.js `timeupdate` event on our video model to recalculate our indicator position. -We've separated the event listener from the resize method to allow for any other widgets that may be listening to `timeupdate`. - - "{video} timeupdate" : function(video){ - this.resize() - }, - resize : function(){ - var video = this.options.video, - percent = video.currentTime() / video.duration(), - width = this.element.width() - this.moving.outerWidth(); - - this.moving.css("left", percent*width+"px") - }, - -_Note: `draginit` and `dragend` are a couple of events provided by [jQuery++ jquerypp]. [jQuery.Drag jQuery.Drag]_ - -The drag events are scoped to the indicator div element. -We can now call the Popcorn.js `play` and `pause` methods, trusting other widgets to respond as needed. In this app, these events will get picked up by our 'Play' widget to start/stop the playback. - - "div draginit" : function(el, ev, drag){ - this.options.video.pause() - drag.limit(this.element) - }, - "div dragend" : function(el, ev, drag){ - var video = this.options.video, - width = this.element.width() - this.moving.outerWidth() - percent = parseInt(this.moving.css("left"), 10) / width; - - video.currentTime( percent * video.duration() ); - video.play() - } - }); - }); - -## Conclusion - -The secret to building large applications is to never build large applications. - -Applications can quickly become overwhelming, complex and difficult to maintain. -The takeaway from the PlayerMX architecture is to create isolated, dumb widgets that can be tied together with events. -This article is an example of how to loosely couple your widgets, use templated events and integrate with an API (Popcorn.js). diff --git a/tutorials/examples/srchr.md b/tutorials/examples/srchr.md index fecf848fb..815fc37e3 100644 --- a/tutorials/examples/srchr.md +++ b/tutorials/examples/srchr.md @@ -1,394 +1,46 @@ @page srchr Srchr @parent examples 0 -Srchr searches several data sources for content and displays it to the user. See it in action [here](http://javascriptmvc.com/srchr/srchr.html). This article covers: +Srchr searches several data sources for content and +displays it to the user. See it in +action [here](http://javascriptmvc.com/srchr/srchr.html). This article +covers how to install Srchr. To understand how Srchr works and many +of the core concepts behind JavaScriptMVC, please watch: -- Installing Srchr -- The ideas behind JavaScriptMVC (JMVC) -- How JMVC enables code separation -- Srchr's architecture + - [Part 1 - MVC architecture and the observer pattern](http://www.youtube.com/watch?v=NZi5Ru4KVug) + - [Part 2 - Development process](http://www.youtube.com/watch?v=yFxDY5SQQp4) -This article will talk about the architecture of the Srchr application, and how event oriented architecture can help you build loosely coupled, scalable applications. You will also learn how to assemble small pieces of functionality into the full blown application. ## Installing Srchr -You can install Srchr by using steal's [steal.get getjs] or via the [git repository](https://github.com/jupiterjs/srchr). -### 1. Installing with getjs +Install the Srchr app by cloning the git repo: -The simplest way to install Srchr is to use the built in getjs script: - - $ ./js steal/getjs srchr - -It will download the whole application complete with dependencies. - -### 2. Installing via git - -You can also install the Srchr app by cloning the git repo: - - $ git clone https://github.com/jupiterjs/srchr - $ cd srchr - $ git submodule update --init + > git clone git://github.com/bitovi/srchr srchr + > cd srchr + > git submodule update --init --recursive Once you get the application you should have a structure similar to below /srchr [top-level directory] - /jquery + /can + /documentjs /steal /funcunit - /scripts /srchr + /history + /models /scripts + /search + /search_result + /tabs + /templates /test - /models - /fixtures - /views - funcunit.html - qunit.html - srchr.css + test.html + srchr.less srchr.js srchr.html ... -Srchr is now ready to be used. To run the Srchr application simply open _srchr/srchr.html_ in your browser. We will be using [jQuery.fixture fixtures] to simulate AJAX requests so running it in a server isn't necessary ([googlefilesystem unless you're using Chrome]) - -## How Srchr was built - -Srchr was built the 'JavaScriptMVC' way. It has a folder/file structure where: - -* Code is logically separated and tested -* Code is easily assembled into higher-order functionality. -* Higher order functionality is tested. - -## How to organize and separate code - -Every JavaScript application implements widgets to show and manipulate data. In JavaScriptMVC every one of these widgets is built as an isolated part that can be reused and tested. These widgets communicate to each other with events, which results in a loosely coupled application. - -Srchr is broken into logically separated components: - -* Disabler - Listens for search messages and disables tab buttons. -* History - A cookie saved list of items. -* Search - Creates a search message when a search happens. -* Search Result - Seaches for results and displays them. -* Tabs - A basic tabs widget. - - -This is the event diagram of Srchr: - -@image tutorials/images/diagram.png - - - -This is the widget organization in Srchr: - -@image tutorials/images/app_organization.png - - - -By creating dumb, isolated widgets with a loose coupling to the rest of the application, we can easily organize code. Every widget is living in it's own directory complete with tests and resources. This allows us to test each of the widgets separately and reuse the code across projects. - -### Srchr's workflow - -Srchr's workflow can be outlined like this: - -1. User selects (via checkboxes) services that should be searched -2. User enters search term and clicks the search button -3. On tab activation a search is performed and results are shown (since first enabled tab is active by default, these results will be shown immediately) -4. Search params are saved to the history (via cookie) - -### Search behavior - -When the user performs a search, these are the actions that get triggered: - -1. Disabler widget listens to the search event and disables or enables tabs based on the checkboxes' states -2. Search results widget shows results for the first enabled tab -3. History widget saves the search params to the cookie - -This is the event architecture of the search: - -@image tutorials/images/diagram_search.png - - -### Tab behavior - -When the user clicks another tab it will trigger the activate event on that tab, which results in the following actions: - -1. Disabler widget listens to the activate event and checks if the tab is disabled. If it is, it will call preventDefault on the activate -2. Tabs widget listens to the activate event, and if it wasn't prevented, it will trigger the show event on the corresponding tab panel -3. Search results listens to the show event on the tab panel, and when triggered performs a search and shows results to the user - -This is the event architecture of the tabs: - -@image tutorials/images/diagram_tabs.png - - -Let's take a detailed look at the tab behavior: - -#### 1. Disabler widget - -Disabler widget has two event handlers. The first one listens to the activate event on list items: - - "{activateSelector} activate": function( el, ev ) { - if ( el.hasClass('disabled') ) { - ev.preventDefault(); - } - } - -It looks if the element has the disabled class and if it does it will preventDefault on the activate event. - -Other event handler listens to the search event. This event gets triggered by the search widget - - "{Srchr.Models.Search} search": function( el, ev, data ) { - var types = {}, - first = true; - - // Fill the list of types to check against. - $.each(data.types, function( index, type ) { - - // Model types come in as Srchr.Model.typeName, so just get the last part - types[type.split('.').pop()] = true; - }); - - this.element.find(this.options.activateSelector).each(function(){ - var el = $(this); - - // If the Model type we are iterating through is in the list, enable it. - // Otherwise, disable it. - if ( types[el.text()] ) { - el.removeClass("disabled"); - if ( first ) { - el.trigger('activate'); - first = false; - } - } else { - el.addClass("disabled"); - } - }); - } - - -The event handler checks which services are selected and adds or removes the "disabled" class from tab elements. It will also activate the first active tab. - -#### 2. Tabs widget - -Tabs widget has a simple task: listen to the "click" event on the tab, and trigger the "activate" event: - - "li click": function( el, ev ) { - ev.preventDefault(); - el.trigger("activate"); - } - -It will also handle the "activate" event if it is not prevented by the disabler widget: - - activate: function( el ) { - this.tab(this.find('.active').removeClass('active')).hide(); - this.tab(el.addClass('active')).show().trigger("show"); - } - -#### 3. Search results widget - -Search results widget listens to the show event to load search results and show them in the tab panel: - - /** - * Show the search results. - */ - "show": function(){ - this.getResults(); - }, - - /** - * Get the appropriate search results that this Search Results container is supposed to show. - */ - getResults: function(){ - // If we have a search... - if (this.currentSearch){ - - // and our search is new ... - if(this.searched != this.currentSearch){ - // put placeholder text in the panel... - this.element.html("Searching for "+this.currentSearch+""); - // and set a callback to render the results. - this.options.modelType.findAll({query: this.currentSearch}, this.callback('renderResults')); - this.searched = this.currentSearch; - } - - }else{ - // Tell the user to make a valid query - this.element.html("Enter a search term!"); - } - - } - - -## Assembling an application - -After the widgets are developed and tested in isolation, they need to be assembled into an application. Here is a quick overview of the techniques used to assemble the widgets in to the Srchr app. - -Srchr is a small application, so most of the assembling is done in the srchr.js file. When you generate a JavaScriptMVC application: - - ./js jquery/generate/app srchr - -it will create the directory and file structure that look like this: - -@image tutorials/images/app_scaffold.png - - -srchr/srchr.js file is your main JavaScript file that should load all higher order widgets and bootstrap the application. If you open the srchr.js file you will see something similar to this: - - // Load all of the plugin dependencies - steal('bunch/of/dependencies).then('srchr/srchr.less', function($){ - .... - }); - -Steal will first load all of the dependencies, and then run the function which is usually defined as a last argument. This function is where the application bootstrapping logic lives. It's responsibilities usually are: - -* render application skeleton on the page -* find elements and initialize higher order widgets - -Srchr is a small application so all bootstrapping is done in the srchr.js file. When you have a bigger application that handles multiple resources you should create resource based higher order widgets that take care of bootstrapping and assembling that part of application. You can read more about this [here](http://edge.javascriptmvc.com/jmvc/docs.html#!organizing). - -## Testing your application - -JavaScriptMVC comes with two testing frameworks built in. QUnit is used to unit test your models and FuncUnit functionally tests your whole application. - -If you open srchr/qunit.html in your browser, it will run unit tests. For functional testing open srchr/funcunit.html. Read more about [FuncUnit](http://javascriptmvc.com/docs.html#&who=FuncUnit) and [QUnit](http://docs.jquery.com/Qunit). - -In this article we already covered the separation of code, but another aspect of code separation is test isolation. In every module folder you will have a test file that should test only that widget. The usual module folder structure looks like this: - - funcunit.html - funcunit/ - tabs_test.js - funcunit.js - tabs.js - tabs.html - -Every module has a demo page that helps you develop and test that module in isolation. In the case of the tabs widget this file is called tabs.html. If you open that file you will see this code: - -

    Srchr.Tabs

    -

    A very basic tabs widget that creates 'activate' events.

    -

    Demo

    -

    Click the different tabs.

    - - -
    one
    -
    two
    -
    three
    - - - - - - -As you can see this page bootstraps the tabs widget. This is the minimum needed for the tabs widget to run. You should always use the demo page to develop your widgets because you can also use that page to test that widget. In the case of the tabs widget that code is in tabs/funcunit/tabs_test.js: - - module("srchr/tabs",{ - setup : function(){ - S.open('//srchr/tabs/tabs.html') - } - }); - - - test("Proper hiding and showing", function() { - S("li:eq(1)").click(); - S("div:eq(1)").visible(function() { - equals(S("div:eq(0)").css('display'), 'none', "Old tab contents are hidden"); - ok(!S("li:eq(0)").hasClass('active'), 'Old tab is not set to active'); - equals(S("div:eq(1)").css('display'), 'block', "New tab contents are visible"); - ok(S("li:eq(1)").hasClass('active'), 'New tab is set to active'); - }); - }); - - test("Clicking twice doesn't break anything", function() { - S("li:eq(2)").click(); - S("li:eq(2)").click(); - - S("div:eq(2)").visible(function() { - equals(S("div:eq(2)").css('display'), 'block', "New tab contents are visible"); - ok(S("li:eq(2)").hasClass('active'), 'New tab is set to active'); - }); - }); - -What this code does is: - -- Opens the tabs' widget demo page (tabs.html). That sets up the tabs widget -- Simulates the user interaction necessary to test the widget. - -The first test ("Proper hiding and showing") does the following: - -- Find the second list element and click on it -- Wait until the second div is visible -- Check if the first div is hidden -- Ensure that first list item doesn't have an "active" class -- Check if the second div is shown -- Ensure that second list item has an "active" class - -FuncUnit allows you to write high fidelity tests, that resemble user's interaction as close as possible. - -This is the pattern you should use when you develop your applications. Every widget should have it's own set of tests. You should also have application level tests that test how the widgets work together. You can find this file in test/funcunit/srchr_test.js. This file tests all of the higher level srchr's functionality. - -Test example from the application level test file: - - test('Search shows results in selected service', function(){ - - S('input[value=Srchr\\.Models\\.Twitter]').click(); - S('#query').click().type('Dogs\r'); - - // wait until there are 2 results - S("#twitter li").exists( function(){ - - ok(true, "We see results in twitter"); - // make sure we see dogs in the history - - var r = /Dogs\st/; - - ok(r.test(S("#history .search .text").text()), "we see dogs correctly"); - - // make sure flickr and everyone else is diabled - ok(S('#resultsTab li:eq(1)').hasClass('disabled'), "Flickr is disabled."); - ok(S('#resultsTab li:eq(2)').hasClass('disabled'), "Upcoming is disabled."); - }); - - }) - -This code tests multiple widgets at once: - -- (Search widget) It finds the input (checkbox) with the value "Srchr.Models.Twitter" and clicks it -- (Search widget) It enters "Dogs" into the search input and submits the search -- (Search result widget) Waits until results are visible -- (History widget) Checks if "Dogs" is added to the history -- (Tabs widget) Checks if Flickr and Upcoming tabs are disabled - -You can find all of the srchr tests here: - -- Disabler widget: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/disabler/funcunit/disabler_test.js), [test page](http://javascriptmvc.com/srchr/disabler/funcunit.html) -- History widget: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/history/funcunit/history_test.js), [test page](http://javascriptmvc.com/srchr/history/funcunit.html) -- Search widget: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/search/funcunit/search_test.js), [test page](http://javascriptmvc.com/srchr/search/funcunit.html) -- Search result widget: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/search_result/funcunit/search_result_test.js), [test page](http://javascriptmvc.com/srchr/search_result/funcunit.html) -- Search tabs widget: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/search_tabs/funcunit/search_tabs_test.js), [test page](http://javascriptmvc.com/srchr/search_tabs/funcunit.html) -- Tabs widget: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/tabs/funcunit/tabs_test.js), [test page](http://javascriptmvc.com/srchr/tabs/funcunit.html) -- Application tests: [test code](https://github.com/jupiterjs/srchr/blob/master/srchr/test/funcunit/srchr_test.js), [test page](http://javascriptmvc.com/srchr/funcunit.html) - - - -## Building a production version - -To build scalable and maintainable JavaScript applications, you should keep your code separated and isolated, but when you want to deploy you want as few files as possible, and you want your scripts minified. JMVC uses Steal to build a minified production version of your code. You can build your app by running the build.js script available in every JMVC project. You can build Srchr by running: - - $ ./js srchr/scripts/build.js - -This will compile and minify all of your JS code into the production.js and all of your CSS code in the production.css file. - -## Conclusion - -Successful completion of frontend applications depends on many things, but one of the most important is architecture. In this article we've seen how to use event oriented architecture to build loosely coupled and isolated modules that are assembled into an application. - -For instance, we could take out the disabler widget and rest of the app would continue working without problems. That is the power we need to build applications that are testable and maintainable. You can read more about application organization in [this article](http://edge.javascriptmvc.com/docs.html#!organizing). +Srchr is now ready to be used. To run the Srchr application simply open _srchr/index.html_ in your browser. diff --git a/tutorials/examples/todo.html b/tutorials/examples/todo.html new file mode 100644 index 000000000..0a75a959e --- /dev/null +++ b/tutorials/examples/todo.html @@ -0,0 +1,25 @@ + + + + + + CanJS • TodoMVC + + +
    +
    +
    +

    Double-click to edit a todo

    +

    Written by Bitovi

    +

    Part of TodoMVC

    +
    + + + + \ No newline at end of file diff --git a/tutorials/examples/todo.md b/tutorials/examples/todo.md index 0d04652ea..b84ee1a1d 100644 --- a/tutorials/examples/todo.md +++ b/tutorials/examples/todo.md @@ -3,22 +3,26 @@ ## Introduction -In this article we will be learning the basics of [JavaScriptMVC](http://javascriptmvc.com/) and the [Model-View-Controller pattern](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) by installing and walking through a simple To-do list manager. The separation of the application's core logic from its user interface behavior is the hallmark of MVC. By working through this exercise you will understand how JavaScriptMVC's particular flavor of this pattern enables you to create more flexible and maintainable browser-based applications. +In this article we will be learning the basics of [JavaScriptMVC](http://javascriptmvc.com/) and +the [Model-View-Controller pattern](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) by installing +and walking through a simple Todo list manager. The separation of the +application's core logic from its user interface behavior is the hallmark of MVC. By +working through this exercise you will understand how JavaScriptMVC's particular flavor of this pattern +enables you to create more flexible and maintainable browser-based applications. Let's get started! ## Setup -First, clone the application from our [repository](http://github.com/jupiterjs/todo) at GitHub, and initialize all the necessary submodules. The following commands will get you up and running: +First, clone the application from our [repository](http://github.com/bitovi/todomvc-javascriptmvc) at GitHub, +and initialize all the necessary submodules. The following commands will get you up and running: - $ git clone https://github.com/jupiterjs/todo - $ git checkout jmvc_3.3 - $ cd todo + $ git clone http://github.com/bitovi/todomvc-javascriptmvc + $ cd todomvc-javascriptmvc $ git submodule update --init -This bundle now contains everything you need to run the application locally. Since there is no server-side dependency, you can now open the `todo/todo.html` file in your browser and see it in action. - -@image tutorials/images/todos.png +Open `todomvc-javascriptmvc/todo/index.html` in your browser. You might have to host it +under a static server. ## Structure @@ -27,7 +31,7 @@ Now let's take a look at the anatomy of our application: /todo [top-level, the GitHub repository] /can /funcunit - /jquery + /jquerypp /steal /todo /scripts @@ -36,7 +40,7 @@ Now let's take a look at the anatomy of our application: qunit.html todo.css todo.ejs - todo.html + index.html todo.js Breaking it down: @@ -52,14 +56,14 @@ MVC is a well-established architectural pattern in software engineering. Without The diagram below shows how we've broken our application out into model, view, and control layers: -@image tutorials/images/todo_arch.png +@image ../tutorials/images/todo_arch.png ### Dependencies If you look at `todo.js` the first thing you'll notice is that all the code is wrapped in a call to the `steal` function: - steal('can/model', 'can/control', 'can/view/ejs', 'jquery/lang/json') - .then('./todo.css') + steal('todo/models/todo.js', 'todo/controls/todos', + './todo.less') In fact, this is true of every JavaScript file in a JavaScriptMVC application: we use `steal` to state our dependencies up-front, which tells the framework what libraries, plugins, stylesheets, etc. we need to load before we can begin. Typically, the final argument to `steal` will be a callback function, which will be executed when all the other dependencies (and _their_ dependencies, and so on...) have been loaded and executed as well. No more worrying whether you forgot any ` ## Reload and verify diff --git a/tutorials/getstarted/creating.md b/tutorials/getstarted/creating.md index 748bb7219..c628021f5 100644 --- a/tutorials/getstarted/creating.md +++ b/tutorials/getstarted/creating.md @@ -1,10 +1,12 @@ @page cookbook.creating Creating Cookbook @parent getstarted 0 +@body + We're going to create a basic cookbook application that lets us create, and delete recipes. It will look like: -@image tutorials/getstarted/Cookbook.png +@image ../tutorials/getstarted/Cookbook.png
    We'll use JavaScriptMVC's [steal.generate generator scripts] to @@ -56,10 +58,10 @@ page followed by `?cookbook` like: src='../path/to/steal/steal.js?cookbook'> -If you open [//cookbook/index.html], you'll see a +If you open //cookbook/index.html, you'll see a JavaScriptMVC welcome screen. -@image tutorials/getstarted/Welcome.png +@image ../tutorials/getstarted/Welcome.png
    Open `cookbook/index.html` and you will find: @@ -153,15 +155,33 @@ and `cookbook/recipe/list` and then tries to add these widgets to the However, `#recipes` and `#create` elements did not exist! Fortunately, the generator also added their HTML to `index.html` so that it includes: - + +

    Recipes

    +
      +
      + +Remove all other generated parts of the `index.html` page so it just looks like: + + + + + cookbook + + +

      Recipes

      +
        +
        + + + ## Run Cookbook That's it. You've created a simple Cookbook -application. Open cookbook/cookbook.html in a browser. +application. Open cookbook/index.html in a browser. -@image tutorials/getstarted/Cookbook.png +@image ../tutorials/getstarted/Cookbook.png

        @@ -262,7 +282,7 @@ where fixtures come in. ### The Recipe Fixture -[can.fixture Fixtures] intercept Ajax requests and +[can.fixture Fixtures] intercept AJAX requests and simulate the response. They enable you to start work on the front end without a ready server. @@ -411,7 +431,7 @@ the inner html of the `controls` element: <% }) %> In the template, `this` is `this.list`. `this.list` is initially empty so -the inner html of `this.element` is empty. The [can.Observe.List::replace list.replace](items) +the inner html of `this.element` is empty. The [can.Observe.List.prototype.replace replace] method replaces the contents of the list with `items`. If `items` is a [can.deferred deferred], it replaces the contents of the list with the resolved value of the deferred. diff --git a/tutorials/getstarted/documenting.md b/tutorials/getstarted/documenting.md index c4380e3ff..66e9cce15 100644 --- a/tutorials/getstarted/documenting.md +++ b/tutorials/getstarted/documenting.md @@ -1,6 +1,8 @@ @page cookbook.documenting Documenting Cookbook @parent getstarted 3 +@body + Documentation is a critical step in creating maintainable code. It's often burdensome on developers and becomes neglected. JavaScriptMVC's integrates [DocumentJS] to make @@ -15,9 +17,9 @@ Create the docs by running: ## Viewing Documentation -Open __cookbook/docs.html__ and you'll find something like: +Open __cookbook/docs/index.html__ and you'll find something like: -@image tutorials/getstarted/Docs.png +@image ../tutorials/getstarted/Docs.png ## Writing Documentation diff --git a/tutorials/getstarted/getstarted.md b/tutorials/getstarted/getstarted.md index b019d1792..b128a41d2 100644 --- a/tutorials/getstarted/getstarted.md +++ b/tutorials/getstarted/getstarted.md @@ -1,4 +1,4 @@ -@page getstarted Get Started with JavaScriptMVC +@page getstarted Get Started with JMVC @parent tutorials 2 This guide introduces the most important aspects of JavaScriptMVC (JMVC) by @@ -53,16 +53,15 @@ their own dependencies and won't load duplicate files. It looks like: }); -

        -P.S. steal('can/control') adds can/control/control.js to your project. -
        +> _P.S. `steal('can/control')` adds `can/control/control.js` to your project._ + ## License JavaScriptMVC is MIT with the following exceptions: - - [http://www.mozilla.org/rhino/ Rhino] - JS command line ([http://www.mozilla.org/MPL/ MPL] 1.1) - - [http://seleniumhq.org/ Selenium] - Browser Automation ([http://www.apache.org/licenses/LICENSE-2.0 Apache 2]) + - [Rhino](http://www.mozilla.org/rhino/) - JS command line ([MPL 1.1](http://www.mozilla.org/MPL/)) + - [Selenium](http://seleniumhq.org/) - Browser Automation ([Apache 2](http://www.apache.org/licenses/LICENSE-2.0)) These exceptions, although permissive licenses themselves, are not linked in your final production build. diff --git a/tutorials/getstarted/testing.md b/tutorials/getstarted/testing.md index 2d57a32ad..56c4cdd21 100644 --- a/tutorials/getstarted/testing.md +++ b/tutorials/getstarted/testing.md @@ -1,6 +1,8 @@ @page cookbook.testing Testing Cookbook @parent getstarted 1 +@body + JavaScriptMVC puts a tremendous emphasis on testing. It uses [FuncUnit] to easily write tests that can be run in the browser or automated. FuncUnit @@ -21,18 +23,55 @@ guide will show you how to: ## Run Tests +Open `cookbook/cookbook_test.js`. You'll notice it steals tests +for the model and controls. There +are also tests that verify the +original "Welcome to JavaScriptMVC" text that we removed. __Remove__ the +extraneous test so `cookbook_test.js` just looks like this: + +@codestart +steal( + 'funcunit', + './models/recipe_test.js', + 'cookbook/recipe/create/create_test.js', + 'cookbook/recipe/list/list_test.js', + function (S) { + + // this tests the assembly + module("cookbook", { + setup : function () { + S.open("//cookbook/index.html"); + } + }); + + test("creating a recipes adds it to the list ", function () { + + S("[name=name]").type("Ice Water"); + S("[name=description]") + .type("Pour water in a glass. Add ice cubes."); + + S("[type=submit]").click(); + + S("h3:contains(Ice Water)").exists(); + S("p:contains(Pour water in a glass. Add ice cubes.)").exists() + }); +}); +@codeend + To run all of __cookbook's__ tests, open -`cookbook/test.html` in a browser. You should -see something like [//cookbook/test.html this]. +`cookbook/test.html` in a browser. -To run those same tests with [funcunit.selenium Selenium], run: +To run those same tests with [funcunit.selenium Selenium], first you must set up a +local server, like Apache, running at the javascriptmvc root. Make sure you can +open your test page from this server, at a URL like http://localhost/javascriptmvc/cookbook/test.html. +Then run: - > ./js funcunit/open/selenium cookbook/test.html + > ./js funcunit/open/selenium http://localhost/javascriptmvc/cookbook/test.html You should see something like: -@image tutorials/getstarted/selenium-run.png +@image ../tutorials/getstarted/selenium-run.png
        If Selenium is unable to open your browsers, it's likely you have them in an @@ -47,7 +86,7 @@ this code works. If an application should be built of small, isolated modules that are glued together, its tests should reflect that. -Cookbook's modules are each designed to be built and tested independently. For example, the `cookbook/recipe/create` module has its own tests and test page. Open [//cookbook/recipe/create/test.html cookbook/recipe/create/test.html] +Cookbook's modules are each designed to be built and tested independently. For example, the `cookbook/recipe/create` module has its own tests and test page. Open `cookbook/recipe/create/test.html` and it will run the tests in `cookbook/recipe/create/create_test.js`. To test the "glue", `cookbook_test.js` loads all modules' tests diff --git a/tutorials/images/contacts_design.png b/tutorials/images/contacts_design.png new file mode 100644 index 000000000..0aa8eaa74 Binary files /dev/null and b/tutorials/images/contacts_design.png differ diff --git a/tutorials/images/contacts_preview.png b/tutorials/images/contacts_preview.png new file mode 100644 index 000000000..0309a451c Binary files /dev/null and b/tutorials/images/contacts_preview.png differ diff --git a/tutorials/installing.md b/tutorials/installing.md index 555bcce2b..dd25b21a2 100644 --- a/tutorials/installing.md +++ b/tutorials/installing.md @@ -3,46 +3,45 @@ ## Requirements -JavaScriptMVC requires [http://www.oracle.com/technetwork/java/javase/downloads/java-se-jdk-7-download-432154.html Java JRE 1.6] or greater for: +JavaScriptMVC requires [Java JRE 1.6](http://www.oracle.com/technetwork/java/javase/downloads/java-se-jdk-7-download-432154.html) or greater for: - Compression (Google Closure) - - Running [http://www.funcunit.com/ FuncUnit] tests with [http://seleniumhq.org/ Selenium] + - Running [FuncUnit](http://www.funcunit.com/) tests with [Selenium](http://seleniumhq.org/) - Easy updating - Code Generators But your backend server can be written in any language. -Download the latest [http://www.java.com/en/download/index.jsp Java JRE here]. +Download the latest [Java JRE here](http://www.java.com/en/download/index.jsp). ## Getting JavaScriptMVC There are 2 ways to get JavaScriptMVC: - - [http://javascriptmvc.com/builder.html Downloading] + - [Downloading](http://javascriptmvc.com/builder.html) - [developwithgit Installing JavaScriptMVC with Git] ## Downloading -[http://javascriptmvc.com/builder.html Download] the latest JavaScriptMVC. +[Download](http://javascriptmvc.com/builder.html) the latest JavaScriptMVC. Unzip the folder on your file system or web server. If you are using this on a webserver, unzip in a public folder where the server hosts static content. -
        PRO TIP: - Unzip these files as - high in your apps folder structure as possible (i.e. don't - put them under a javascriptmvc folder in your public directory). -
        +> TIP: Unzip these files as +high in your apps folder structure as possible (i.e. don't +put them under a javascriptmvc folder in your public directory). ## Installing JavaScriptMVC with Git. -JavaScriptMVC is comprised of six sub projects: +JavaScriptMVC is comprised of 7 sub projects: - - [http://github.com/bitovi/steal] - - [https://github.com/bitovi/canjs] - - [https://github.com/bitovi/canui] - - [https://github.com/bitovi/jquerypp] - - [http://github.com/bitovi/documentjs] - - [http://github.com/bitovi/funcunit] + - [https://github.com/bitovi/steal](http://github.com/bitovi/steal) + - [https://github.com/bitovi/canjs](https://github.com/bitovi/canjs) + - [https://github.com/bitovi/canui](https://github.com/bitovi/canui) + - [https://github.com/bitovi/jquerypp](https://github.com/bitovi/jquerypp) + - [https://github.com/bitovi/documentjs](http://github.com/bitovi/documentjs) + - [https://github.com/bitovi/funcunit](http://github.com/bitovi/funcunit) + - [https://github.com/bitovi/jmvc-generators](https://github.com/bitovi/jmvc-generators) You want to fork each project and add it as a submodule to your project in a public folder (where your server keeps static content). @@ -56,13 +55,14 @@ Forking the repos looks like: git submodule add git@github.com:_YOU_/steal.git public/steal git submodule add git@github.com:_YOU_/canjs.git public/can git submodule add git@github.com:_YOU_/canui.git public/canui -git submodule add git@github.com:_YOU_/jquerypp.git public/jquery +git submodule add git@github.com:_YOU_/jquerypp.git public/jquerypp git submodule add git@github.com:_YOU_/documentjs.git public/documentjs git submodule add git@github.com:_YOU_/funcunit.git public/funcunit +git submodule add git@github.com:_YOU_/jmvc-generators.git public/jmvc @codeend Notice that CanJS is in can folder and -jQuery++ is in the jquery folder. +jQuery++ is in the jquerypp folder. After installing the repository, run: @@ -97,24 +97,5 @@ Open a command line to that folder and run: [Lin/Mac] > ./js @codeend -This starts the [http://www.mozilla.org/rhino/ Rhino JS engine]. Type quit() to exit. +This starts the [Rhino JS engine](http://www.mozilla.org/rhino/). Type quit() to exit. -## Updating JavaScriptMVC - -We are constantly improving JMVC. If you're using git, you can -just pull changes. Otherwise, to get the latest, most -error free code, in a console, type: - -@codestart text -> ./js documentjs/update -> ./js funcunit/update -> ./js jquery/update -> ./js steal/update -> ./js can/update -> ./js canui/update -@codeend -
        - P.S. If you are using linux/mac you - want to use ./js and change \ - to /. -
        diff --git a/tutorials/jquerypp.md b/tutorials/jquerypp.md index 84cd563aa..ac2210f54 100644 --- a/tutorials/jquerypp.md +++ b/tutorials/jquerypp.md @@ -7,6 +7,6 @@ jQuery applications. It provides low-level utilities for things that jQuery doesn’t support. You can find out everything you need to know about jQuery++ -[http://jquerypp.com at its site.] jQuery++ is included with +[at its site.](http://jquerypp.com) jQuery++ is included with JavaScriptMVC by default in the jquery folder in the root of your project. \ No newline at end of file diff --git a/tutorials/migrate.md b/tutorials/migrate.md deleted file mode 100644 index 55ec32b48..000000000 --- a/tutorials/migrate.md +++ /dev/null @@ -1,288 +0,0 @@ -@page migrate Upgrading to 3.2 -@parent tutorials 10 - -There are many new feature in JavaScriptMVC 3.2 that help you build great JavaScript applications. -Although 3.2 is not strictly backwards compatible it is possible to upgrade from -and earlier version. This guide outlines the things you have to look at when upgrading from -version 3.0 or 3.1. - -## Steal - -[stealjs | Steal] has experienced a major improvement, making it even easier to manage your dependencies. -Instead of defining what you want to steal like _steal.plugins_, _steal.controllers_ or _steal.views_ you -just steal the file and Steal will figure out what to do with it based on the extension. -So if your code used to look like this: - - steal('helper').plugins('jquery/controller', 'jquery/view/ejs', 'jquery/controller/view').views('init.ejs') - .css('style').then(function($) { - $.Controller('Test.Controller', {}, { - init : function(el, ops) - { - this.element.html(this.view()); - } - }); - }); - -The new Steal looks like this: - - steal('./helper.js', 'jquery/controller', 'jquery/view/ejs', './views/init.ejs', './style.css', function($) { - $.Controller('Test.Controller', {}, { - init : function(el, ops) - { - this.element.html(this.view()); - } - }); - }); - -The rules when stealing a file: - -* ./ refers to the path of the current file -* When stealing something without extension like _jquery/controller_ Steal will first look for the plugin -file _jquery/controller/controller.js_ and if not found _jquery/controller.js_ based on your applications -root path -* ./helper.js loads helper.js in the folder of the current file -* ./views/init.ejs loads the init.ejs [jQuery.EJS | EJS view] from views subfolder in the current folder -* ./style.css loads the style.css file in the current folder - -Note that the new Steal runs asynchronously so you might have to be more careful using _steal.then()_ -if there are dependencies that have to be loaded before another. This is also important when putting code -outside of steal. Because jQuery hasn't been loaded you can not use the jQuery object. So instead of -$(document).ready use this: - - steal.then(function() { - $('body').my_plugin(); - }); - - -## Organizing your application - -Starting with version 3.1 and fully encouraged in 3.2 JavaScriptMVC follows a new approach in organizing your application structure. -Read the complete guide on [organizing | organizing your app] to get an idea. For migrating from the old structure -the following things are important: - -* The use of the _controllers_ folder is discouraged. You still can use it but organizing each controller in -their own folder with a views subfolder is the preferred way as it is a lot more obvious how your application -is split up and easier to test. -* Models reside in their own folder as they might be application wide and not controller specific -(e.g. a recipe model will be used in a recipe\_edit and a recipe\_list controller) - - -## Document controllers - -There are no document controllers in 3.2 anymore (controllers that used have the default option _{ onDocument : true }_). -Instead initialize your controllers like any [jquery.controller.plugin | jQuery plugin] in the application file. -For document wide controllers initialize them on the document itself. -An example how your main application file may look like: - - steal('my/controller', 'my/global/controller', function($) { - $(document).ready(function($) { - $(document).global_controller(); - $('.thing').my_controlller(); - }); - }); - -Note that unlike the old document controllers a global controller will not listen to a specific id. - -## Listening to model changes - -The notifications via OpenAJAX have been dropped in favor of the [jQuery.Observe Observable] mechanism. -Check out the documentation on [jquery.model.events | Model events] for detailed information on how use it for Models. - -## Controller History - -Controller history using OpenAjax has been removed in 3.2. Use [jQuery.route] for full history and routing support. - -## Wrap Removed - -The AJAX response converter 'wrap' and 'wrapMany' have been removed in favor of a [native jQuery converter http://api.jquery.com/extending-ajax/#Converters]. - -Before, you would do a _this.callback_ on the success like: - - findAll : function(params, success, error ){ - return $.ajax({ - url: '/services/recipes.json', - type: 'get', - data: params - dataType : 'json', - success: this.callback(['wrapMany',success]) - }); - } - -now becomes: - - findAll : function(params, success, error ){ - return $.ajax({ - url: '/services/recipes.json', - type: 'get', - data: params - dataType : 'json recipe.models', - success: success - }); - } - -## Callback Renamed to Proxy - -The _this.callback_ method has been deprecated and the preferred method is now _this.proxy_. - -## steal.browser.rhino removed - -The _steal.browser.rhino_ has been removed. We are using _steal.browsers_ namespace for the browser drivers. If you were using this to have rhino skip files in the build process, you can now use: _steal.isRhino_. - -## Associations - -Associations were removed in favor of [jQuery.Model.static.attributes]. Attribute type values can also represent the name of a function which is used is for associated data now. - -## Upgrade Process and Advice - -The changes listed above require significant adaptation of your code base. - -1) The word 'controllers' is no longer good for JMVC. If you have controllers in a directory of that name, change the name to something else. JMVC has new, automatic behavior that will break your system if you don't. - -Controller names must also be changed to the new directory name. Eg, - - jQuery.Controller.extend('Appname.Controllers.Controllername') - -becomes - - jQuery.Controller.extend('Appname.NewName.Controllername') - -and, the instantiation, - - appname_controllers_controllername() - -becomes - - appname_newname_controllername() - -2) The suffix "_controller" is no longer valid. Remove it from your controller names. - -3) Steal() has changed completely. It no longer has specific methods for different kinds of files. Eg, - - steal.plugins( - 'jquery/controller', - 'jquery/controller/view' - ) - -Becomes - - steal( - 'jquery/controller', - 'jquery/controller/view' - ) - -As do all the rest of the type-specific loaders: .resources(), .models(), .controllers(), .views() & .css() - -4) The base directory steal() looks in has changed. Instead of referring to the directory containing the steal() build file, it refers to the directory containing the *call*, ie, if you have - - foo/index.html containing