From 517a75d0d7f4ffce5b2746004b515392519fc340 Mon Sep 17 00:00:00 2001 From: Peter Michaux <petermichaux@gmail.com> Date: Mon, 17 Dec 2012 20:22:48 -0700 Subject: [PATCH] rewrite Maria TodoMVC example to use the latest version of Maria and the new TodoMVC template --- .../maria/lib/aristocrat/aristocrat.js | 6 +- .../maria/lib/director/director.js | 715 ++++++++++++++++++ .../maria/lib/maria/maria.js | 332 ++++---- .../maria/src/css/app.css | 4 + .../maria/src/css/base.css | 414 ++++++++++ .../maria/src/css/todos.css | 511 ------------- .../maria/src/index.html | 23 +- .../maria/src/js/bootstrap.js | 18 +- .../src/js/controllers/TodoController.js | 39 +- .../src/js/controllers/TodosController.js | 28 + .../js/controllers/TodosInputController.js | 32 - .../js/controllers/TodosToolbarController.js | 21 - .../maria/src/js/models/TodoModel.js | 40 +- .../maria/src/js/models/TodosModel.js | 45 +- .../maria/src/js/namespace.js | 2 +- .../maria/src/js/templates/TodoTemplate.js | 15 +- .../src/js/templates/TodosAppTemplate.js | 14 - .../src/js/templates/TodosInputTemplate.js | 13 - .../src/js/templates/TodosListTemplate.js | 10 - .../src/js/templates/TodosStatsTemplate.js | 10 - .../maria/src/js/templates/TodosTemplate.js | 40 + .../src/js/templates/TodosToolbarTemplate.js | 15 - .../maria/src/js/util.js | 15 + .../maria/src/js/views/TodoView.js | 50 +- .../maria/src/js/views/TodosAppView.js | 29 - .../maria/src/js/views/TodosInputView.js | 22 - .../maria/src/js/views/TodosListView.js | 7 - .../maria/src/js/views/TodosStatsView.js | 10 - .../maria/src/js/views/TodosToolbarView.js | 19 - .../maria/src/js/views/TodosView.js | 56 ++ 30 files changed, 1556 insertions(+), 999 deletions(-) create mode 100644 labs/architecture-examples/maria/lib/director/director.js create mode 100644 labs/architecture-examples/maria/src/css/app.css create mode 100644 labs/architecture-examples/maria/src/css/base.css delete mode 100644 labs/architecture-examples/maria/src/css/todos.css create mode 100644 labs/architecture-examples/maria/src/js/controllers/TodosController.js delete mode 100644 labs/architecture-examples/maria/src/js/controllers/TodosInputController.js delete mode 100644 labs/architecture-examples/maria/src/js/controllers/TodosToolbarController.js delete mode 100644 labs/architecture-examples/maria/src/js/templates/TodosAppTemplate.js delete mode 100644 labs/architecture-examples/maria/src/js/templates/TodosInputTemplate.js delete mode 100644 labs/architecture-examples/maria/src/js/templates/TodosListTemplate.js delete mode 100644 labs/architecture-examples/maria/src/js/templates/TodosStatsTemplate.js create mode 100644 labs/architecture-examples/maria/src/js/templates/TodosTemplate.js delete mode 100644 labs/architecture-examples/maria/src/js/templates/TodosToolbarTemplate.js create mode 100644 labs/architecture-examples/maria/src/js/util.js delete mode 100644 labs/architecture-examples/maria/src/js/views/TodosAppView.js delete mode 100644 labs/architecture-examples/maria/src/js/views/TodosInputView.js delete mode 100644 labs/architecture-examples/maria/src/js/views/TodosListView.js delete mode 100644 labs/architecture-examples/maria/src/js/views/TodosStatsView.js delete mode 100644 labs/architecture-examples/maria/src/js/views/TodosToolbarView.js create mode 100644 labs/architecture-examples/maria/src/js/views/TodosView.js diff --git a/labs/architecture-examples/maria/lib/aristocrat/aristocrat.js b/labs/architecture-examples/maria/lib/aristocrat/aristocrat.js index 9d214015..72e64fdf 100644 --- a/labs/architecture-examples/maria/lib/aristocrat/aristocrat.js +++ b/labs/architecture-examples/maria/lib/aristocrat/aristocrat.js @@ -1,11 +1,11 @@ /* -Aristocrat version 1 +Aristocrat version 2 Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/aristocrat/blob/master/LICENSE */ -var aristocrat = aristocrat || {}; +var aristocrat = {}; (function() { @@ -78,7 +78,7 @@ aristocrat.removeClass(document.body, 'king'); var re = getRegExp(className); while (re.test(el.className)) { // in case multiple occurrences el.className = el.className.replace(re, ' '); - } + } }; /** diff --git a/labs/architecture-examples/maria/lib/director/director.js b/labs/architecture-examples/maria/lib/director/director.js new file mode 100644 index 00000000..ec161260 --- /dev/null +++ b/labs/architecture-examples/maria/lib/director/director.js @@ -0,0 +1,715 @@ + + +// +// Generated on Tue Dec 04 2012 01:06:43 GMT-0800 (PST) by Nodejitsu, Inc (Using Codesurgeon). +// Version 1.1.8 +// + +(function (exports) { + + +/* + * browser.js: Browser specific functionality for director. + * + * (C) 2011, Nodejitsu Inc. + * MIT LICENSE + * + */ + +if (!Array.prototype.filter) { + Array.prototype.filter = function(filter, that) { + var other = [], v; + for (var i = 0, n = this.length; i < n; i++) { + if (i in this && filter.call(that, v = this[i], i, this)) { + other.push(v); + } + } + return other; + }; +} + +if (!Array.isArray){ + Array.isArray = function(obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; + }; +} + +var dloc = document.location; + +function dlocHashEmpty() { + // Non-IE browsers return '' when the address bar shows '#'; Director's logic + // assumes both mean empty. + return dloc.hash === '' || dloc.hash === '#'; +} + +var listener = { + mode: 'modern', + hash: dloc.hash, + history: false, + + check: function () { + var h = dloc.hash; + if (h != this.hash) { + this.hash = h; + this.onHashChanged(); + } + }, + + fire: function () { + if (this.mode === 'modern') { + this.history === true ? window.onpopstate() : window.onhashchange(); + } + else { + this.onHashChanged(); + } + }, + + init: function (fn, history) { + var self = this; + this.history = history; + + if (!Router.listeners) { + Router.listeners = []; + } + + function onchange(onChangeEvent) { + for (var i = 0, l = Router.listeners.length; i < l; i++) { + Router.listeners[i](onChangeEvent); + } + } + + //note IE8 is being counted as 'modern' because it has the hashchange event + if ('onhashchange' in window && (document.documentMode === undefined + || document.documentMode > 7)) { + // At least for now HTML5 history is available for 'modern' browsers only + if (this.history === true) { + // There is an old bug in Chrome that causes onpopstate to fire even + // upon initial page load. Since the handler is run manually in init(), + // this would cause Chrome to run it twise. Currently the only + // workaround seems to be to set the handler after the initial page load + // http://code.google.com/p/chromium/issues/detail?id=63040 + setTimeout(function() { + window.onpopstate = onchange; + }, 500); + } + else { + window.onhashchange = onchange; + } + this.mode = 'modern'; + } + else { + // + // IE support, based on a concept by Erik Arvidson ... + // + var frame = document.createElement('iframe'); + frame.id = 'state-frame'; + frame.style.display = 'none'; + document.body.appendChild(frame); + this.writeFrame(''); + + if ('onpropertychange' in document && 'attachEvent' in document) { + document.attachEvent('onpropertychange', function () { + if (event.propertyName === 'location') { + self.check(); + } + }); + } + + window.setInterval(function () { self.check(); }, 50); + + this.onHashChanged = onchange; + this.mode = 'legacy'; + } + + Router.listeners.push(fn); + + return this.mode; + }, + + destroy: function (fn) { + if (!Router || !Router.listeners) { + return; + } + + var listeners = Router.listeners; + + for (var i = listeners.length - 1; i >= 0; i--) { + if (listeners[i] === fn) { + listeners.splice(i, 1); + } + } + }, + + setHash: function (s) { + // Mozilla always adds an entry to the history + if (this.mode === 'legacy') { + this.writeFrame(s); + } + + if (this.history === true) { + window.history.pushState({}, document.title, s); + // Fire an onpopstate event manually since pushing does not obviously + // trigger the pop event. + this.fire(); + } else { + dloc.hash = (s[0] === '/') ? s : '/' + s; + } + return this; + }, + + writeFrame: function (s) { + // IE support... + var f = document.getElementById('state-frame'); + var d = f.contentDocument || f.contentWindow.document; + d.open(); + d.write("<script>_hash = '" + s + "'; onload = parent.listener.syncHash;<script>"); + d.close(); + }, + + syncHash: function () { + // IE support... + var s = this._hash; + if (s != dloc.hash) { + dloc.hash = s; + } + return this; + }, + + onHashChanged: function () {} +}; + +var Router = exports.Router = function (routes) { + if (!(this instanceof Router)) return new Router(routes); + + this.params = {}; + this.routes = {}; + this.methods = ['on', 'once', 'after', 'before']; + this._methods = {}; + + this._insert = this.insert; + this.insert = this.insertEx; + + this.historySupport = (window.history != null ? window.history.pushState : null) != null + + this.configure(); + this.mount(routes || {}); +}; + +Router.prototype.init = function (r) { + var self = this; + this.handler = function(onChangeEvent) { + var newURL = onChangeEvent && onChangeEvent.newURL || window.location.hash; + var url = self.history === true ? self.getPath() : newURL.replace(/.*#/, ''); + self.dispatch('on', url); + }; + + listener.init(this.handler, this.history); + + if (this.history === false) { + if (dlocHashEmpty() && r) { + dloc.hash = r; + } else if (!dlocHashEmpty()) { + self.dispatch('on', dloc.hash.replace(/^#/, '')); + } + } + else { + var routeTo = dlocHashEmpty() && r ? r : !dlocHashEmpty() ? dloc.hash.replace(/^#/, '') : null; + if (routeTo) { + window.history.replaceState({}, document.title, routeTo); + } + + // Router has been initialized, but due to the chrome bug it will not + // yet actually route HTML5 history state changes. Thus, decide if should route. + if (routeTo || this.run_in_init === true) { + this.handler(); + } + } + + return this; +}; + +Router.prototype.explode = function () { + var v = this.history === true ? this.getPath() : dloc.hash; + if (v[1] === '/') { v=v.slice(1) } + return v.slice(1, v.length).split("/"); +}; + +Router.prototype.setRoute = function (i, v, val) { + var url = this.explode(); + + if (typeof i === 'number' && typeof v === 'string') { + url[i] = v; + } + else if (typeof val === 'string') { + url.splice(i, v, s); + } + else { + url = [i]; + } + + listener.setHash(url.join('/')); + return url; +}; + +// +// ### function insertEx(method, path, route, parent) +// #### @method {string} Method to insert the specific `route`. +// #### @path {Array} Parsed path to insert the `route` at. +// #### @route {Array|function} Route handlers to insert. +// #### @parent {Object} **Optional** Parent "routes" to insert into. +// insert a callback that will only occur once per the matched route. +// +Router.prototype.insertEx = function(method, path, route, parent) { + if (method === "once") { + method = "on"; + route = function(route) { + var once = false; + return function() { + if (once) return; + once = true; + return route.apply(this, arguments); + }; + }(route); + } + return this._insert(method, path, route, parent); +}; + +Router.prototype.getRoute = function (v) { + var ret = v; + + if (typeof v === "number") { + ret = this.explode()[v]; + } + else if (typeof v === "string"){ + var h = this.explode(); + ret = h.indexOf(v); + } + else { + ret = this.explode(); + } + + return ret; +}; + +Router.prototype.destroy = function () { + listener.destroy(this.handler); + return this; +}; + +Router.prototype.getPath = function () { + var path = window.location.pathname; + if (path.substr(0, 1) !== '/') { + path = '/' + path; + } + return path; +}; +function _every(arr, iterator) { + for (var i = 0; i < arr.length; i += 1) { + if (iterator(arr[i], i, arr) === false) { + return; + } + } +} + +function _flatten(arr) { + var flat = []; + for (var i = 0, n = arr.length; i < n; i++) { + flat = flat.concat(arr[i]); + } + return flat; +} + +function _asyncEverySeries(arr, iterator, callback) { + if (!arr.length) { + return callback(); + } + var completed = 0; + (function iterate() { + iterator(arr[completed], function(err) { + if (err || err === false) { + callback(err); + callback = function() {}; + } else { + completed += 1; + if (completed === arr.length) { + callback(); + } else { + iterate(); + } + } + }); + })(); +} + +function paramifyString(str, params, mod) { + mod = str; + for (var param in params) { + if (params.hasOwnProperty(param)) { + mod = params[param](str); + if (mod !== str) { + break; + } + } + } + return mod === str ? "([._a-zA-Z0-9-]+)" : mod; +} + +function regifyString(str, params) { + var matches, last = 0, out = ""; + while (matches = str.substr(last).match(/[^\w\d\- %@&]*\*[^\w\d\- %@&]*/)) { + last = matches.index + matches[0].length; + matches[0] = matches[0].replace(/^\*/, "([_.()!\\ %@&a-zA-Z0-9-]+)"); + out += str.substr(0, matches.index) + matches[0]; + } + str = out += str.substr(last); + var captures = str.match(/:([^\/]+)/ig), length; + if (captures) { + length = captures.length; + for (var i = 0; i < length; i++) { + str = str.replace(captures[i], paramifyString(captures[i], params)); + } + } + return str; +} + +function terminator(routes, delimiter, start, stop) { + var last = 0, left = 0, right = 0, start = (start || "(").toString(), stop = (stop || ")").toString(), i; + for (i = 0; i < routes.length; i++) { + var chunk = routes[i]; + if (chunk.indexOf(start, last) > chunk.indexOf(stop, last) || ~chunk.indexOf(start, last) && !~chunk.indexOf(stop, last) || !~chunk.indexOf(start, last) && ~chunk.indexOf(stop, last)) { + left = chunk.indexOf(start, last); + right = chunk.indexOf(stop, last); + if (~left && !~right || !~left && ~right) { + var tmp = routes.slice(0, (i || 1) + 1).join(delimiter); + routes = [ tmp ].concat(routes.slice((i || 1) + 1)); + } + last = (right > left ? right : left) + 1; + i = 0; + } else { + last = 0; + } + } + return routes; +} + +Router.prototype.configure = function(options) { + options = options || {}; + this.extend(this.methods); + this.recurse = options.recurse || this.recurse || false; + this.async = options.async || false; + this.delimiter = options.delimiter || "/"; + this.strict = typeof options.strict === "undefined" ? true : options.strict; + this.notfound = options.notfound; + this.resource = options.resource; + this.history = options.html5history && this.historySupport || false; + this.run_in_init = this.history === true && options.run_handler_in_init !== false; + this.every = { + after: options.after || null, + before: options.before || null, + on: options.on || null + }; + return this; +}; + +Router.prototype.param = function(token, matcher) { + if (token[0] !== ":") { + token = ":" + token; + } + var compiled = new RegExp(token, "g"); + this.params[token] = function(str) { + return str.replace(compiled, matcher.source || matcher); + }; +}; + +Router.prototype.on = Router.prototype.route = function(method, path, route) { + var self = this; + if (!route && typeof path == "function") { + route = path; + path = method; + method = "on"; + } + if (Array.isArray(path)) { + return path.forEach(function(p) { + self.on(method, p, route); + }); + } + if (path.source) { + path = path.source.replace(/\\\//ig, "/"); + } + if (Array.isArray(method)) { + return method.forEach(function(m) { + self.on(m.toLowerCase(), path, route); + }); + } + path = path.split(new RegExp(this.delimiter)); + path = terminator(path, this.delimiter); + this.insert(method, this.scope.concat(path), route); +}; + +Router.prototype.dispatch = function(method, path, callback) { + var self = this, fns = this.traverse(method, path, this.routes, ""), invoked = this._invoked, after; + this._invoked = true; + if (!fns || fns.length === 0) { + this.last = []; + if (typeof this.notfound === "function") { + this.invoke([ this.notfound ], { + method: method, + path: path + }, callback); + } + return false; + } + if (this.recurse === "forward") { + fns = fns.reverse(); + } + function updateAndInvoke() { + self.last = fns.after; + self.invoke(self.runlist(fns), self, callback); + } + after = this.every && this.every.after ? [ this.every.after ].concat(this.last) : [ this.last ]; + if (after && after.length > 0 && invoked) { + if (this.async) { + this.invoke(after, this, updateAndInvoke); + } else { + this.invoke(after, this); + updateAndInvoke(); + } + return true; + } + updateAndInvoke(); + return true; +}; + +Router.prototype.invoke = function(fns, thisArg, callback) { + var self = this; + if (this.async) { + _asyncEverySeries(fns, function apply(fn, next) { + if (Array.isArray(fn)) { + return _asyncEverySeries(fn, apply, next); + } else if (typeof fn == "function") { + fn.apply(thisArg, fns.captures.concat(next)); + } + }, function() { + if (callback) { + callback.apply(thisArg, arguments); + } + }); + } else { + _every(fns, function apply(fn) { + if (Array.isArray(fn)) { + return _every(fn, apply); + } else if (typeof fn === "function") { + return fn.apply(thisArg, fns.captures || []); + } else if (typeof fn === "string" && self.resource) { + self.resource[fn].apply(thisArg, fns.captures || []); + } + }); + } +}; + +Router.prototype.traverse = function(method, path, routes, regexp, filter) { + var fns = [], current, exact, match, next, that; + function filterRoutes(routes) { + if (!filter) { + return routes; + } + function deepCopy(source) { + var result = []; + for (var i = 0; i < source.length; i++) { + result[i] = Array.isArray(source[i]) ? deepCopy(source[i]) : source[i]; + } + return result; + } + function applyFilter(fns) { + for (var i = fns.length - 1; i >= 0; i--) { + if (Array.isArray(fns[i])) { + applyFilter(fns[i]); + if (fns[i].length === 0) { + fns.splice(i, 1); + } + } else { + if (!filter(fns[i])) { + fns.splice(i, 1); + } + } + } + } + var newRoutes = deepCopy(routes); + newRoutes.matched = routes.matched; + newRoutes.captures = routes.captures; + newRoutes.after = routes.after.filter(filter); + applyFilter(newRoutes); + return newRoutes; + } + if (path === this.delimiter && routes[method]) { + next = [ [ routes.before, routes[method] ].filter(Boolean) ]; + next.after = [ routes.after ].filter(Boolean); + next.matched = true; + next.captures = []; + return filterRoutes(next); + } + for (var r in routes) { + if (routes.hasOwnProperty(r) && (!this._methods[r] || this._methods[r] && typeof routes[r] === "object" && !Array.isArray(routes[r]))) { + current = exact = regexp + this.delimiter + r; + if (!this.strict) { + exact += "[" + this.delimiter + "]?"; + } + match = path.match(new RegExp("^" + exact)); + if (!match) { + continue; + } + if (match[0] && match[0] == path && routes[r][method]) { + next = [ [ routes[r].before, routes[r][method] ].filter(Boolean) ]; + next.after = [ routes[r].after ].filter(Boolean); + next.matched = true; + next.captures = match.slice(1); + if (this.recurse && routes === this.routes) { + next.push([ routes.before, routes.on ].filter(Boolean)); + next.after = next.after.concat([ routes.after ].filter(Boolean)); + } + return filterRoutes(next); + } + next = this.traverse(method, path, routes[r], current); + if (next.matched) { + if (next.length > 0) { + fns = fns.concat(next); + } + if (this.recurse) { + fns.push([ routes[r].before, routes[r].on ].filter(Boolean)); + next.after = next.after.concat([ routes[r].after ].filter(Boolean)); + if (routes === this.routes) { + fns.push([ routes["before"], routes["on"] ].filter(Boolean)); + next.after = next.after.concat([ routes["after"] ].filter(Boolean)); + } + } + fns.matched = true; + fns.captures = next.captures; + fns.after = next.after; + return filterRoutes(fns); + } + } + } + return false; +}; + +Router.prototype.insert = function(method, path, route, parent) { + var methodType, parentType, isArray, nested, part; + path = path.filter(function(p) { + return p && p.length > 0; + }); + parent = parent || this.routes; + part = path.shift(); + if (/\:|\*/.test(part) && !/\\d|\\w/.test(part)) { + part = regifyString(part, this.params); + } + if (path.length > 0) { + parent[part] = parent[part] || {}; + return this.insert(method, path, route, parent[part]); + } + if (!part && !path.length && parent === this.routes) { + methodType = typeof parent[method]; + switch (methodType) { + case "function": + parent[method] = [ parent[method], route ]; + return; + case "object": + parent[method].push(route); + return; + case "undefined": + parent[method] = route; + return; + } + return; + } + parentType = typeof parent[part]; + isArray = Array.isArray(parent[part]); + if (parent[part] && !isArray && parentType == "object") { + methodType = typeof parent[part][method]; + switch (methodType) { + case "function": + parent[part][method] = [ parent[part][method], route ]; + return; + case "object": + parent[part][method].push(route); + return; + case "undefined": + parent[part][method] = route; + return; + } + } else if (parentType == "undefined") { + nested = {}; + nested[method] = route; + parent[part] = nested; + return; + } + throw new Error("Invalid route context: " + parentType); +}; + + + +Router.prototype.extend = function(methods) { + var self = this; + function extend(method) { + self._methods[method] = true; + function route() { + var extra = arguments.length === 1 ? [ method, "" ] : [ method ]; + self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments))); + } + if (!~self.methods.indexOf(method)) { + if (self._methods[method]) { + if (self[method] === route) return; + } + self[method] = route; + } + } + methods.forEach(function(method) { + extend(method); + }); +}; + +Router.prototype.runlist = function(fns) { + var runlist = this.every && this.every.before ? [ this.every.before ].concat(_flatten(fns)) : _flatten(fns); + if (this.every && this.every.on) { + runlist.push(this.every.on); + } + runlist.captures = fns.captures; + runlist.source = fns.source; + return runlist; +}; + +Router.prototype.mount = function(routes, path) { + if (!routes || typeof routes !== "object" || Array.isArray(routes)) { + return; + } + var self = this; + path = path || []; + if (!Array.isArray(path)) { + path = path.split(self.delimiter); + } + function insertOrMount(route, local) { + var rename = route, parts = route.split(self.delimiter), routeType = typeof routes[route], isRoute = parts[0] === "" || !self._methods[parts[0]], event = isRoute ? "on" : rename; + if (isRoute) { + rename = rename.slice((rename.match(new RegExp(self.delimiter)) || [ "" ])[0].length); + parts.shift(); + } + if (isRoute && routeType === "object" && !Array.isArray(routes[route])) { + local = local.concat(parts); + self.mount(routes[route], local); + return; + } + if (isRoute) { + local = local.concat(rename.split(self.delimiter)); + local = terminator(local, self.delimiter); + } + self.insert(event, local, routes[route]); + } + for (var route in routes) { + if (routes.hasOwnProperty(route)) { + insertOrMount(route, path.slice(0)); + } + } +}; + + + +}(typeof exports === "object" ? exports : window)); \ No newline at end of file diff --git a/labs/architecture-examples/maria/lib/maria/maria.js b/labs/architecture-examples/maria/lib/maria/maria.js index 9998cff2..3374f952 100644 --- a/labs/architecture-examples/maria/lib/maria/maria.js +++ b/labs/architecture-examples/maria/lib/maria/maria.js @@ -4,7 +4,7 @@ Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/evento/blob/master/LICENSE -*/var evento = evento || {}; +*/var evento = {}; /** @property evento.EventTarget @@ -186,7 +186,7 @@ et.dispatchEvent({type:'change', extraData:'abc'}); */ evento.EventTarget.prototype.dispatchEvent = function(evt) { - // Want to ensure we don't alter the evt object passed in as it + // Want to ensure we don't alter the evt object passed in as it // may be a bubbling event. So clone it and then setting currentTarget // won't break some event that is already being dispatched. evt = create(evt); @@ -204,9 +204,9 @@ et.dispatchEvent({type:'change', extraData:'abc'}); // // Without making a copy, one listener removing // an already-called listener would result in skipping - // a not-yet-called listener. One listener removing + // a not-yet-called listener. One listener removing // a not-yet-called listener would result in skipping that - // not-yet-called listner. The worst case scenario + // not-yet-called listner. The worst case scenario // is a listener adding itself again which would // create an infinite loop. // @@ -285,14 +285,14 @@ o.dispatchEvent({type:'change'}); (typeof pt[p] === 'function')) { obj[p] = pt[p]; } - } + } evento.EventTarget.call(obj); }; }()); /** -@property evento.addEventListener +@property evento.on @parameter element {EventTarget} The object you'd like to observe. @@ -324,27 +324,27 @@ var o = { }; // late binding. handleEvent is found when each event is dispatched -evento.addEventListener(document.body, 'click', o); +evento.on(document.body, 'click', o); // late binding. handleClick is found when each event is dispatched -evento.addEventListener(document.body, 'click', o, 'handleClick'); +evento.on(document.body, 'click', o, 'handleClick'); // early binding. The supplied function is bound now -evento.addEventListener(document.body, 'click', o, o.handleClick); -evento.addEventListener(document.body, 'click', o, function(){}); +evento.on(document.body, 'click', o, o.handleClick); +evento.on(document.body, 'click', o, function(){}); // supplied function will be called with document.body as this object -evento.addEventListener(document.body, 'click', function(){}); +evento.on(document.body, 'click', function(){}); // The following form is supported but is not neccessary given the options // above and it is recommended you avoid it. -evento.addEventListener(document.body, 'click', this.handleClick, this); +evento.on(document.body, 'click', this.handleClick, this); */ /** -@property evento.removeEventListener +@property evento.off @parameter element {EventTarget} The object you'd like to stop observing. @@ -360,32 +360,32 @@ Removes added listener matching the element/type/listener/auxArg combination exa If this combination is not found there are no errors. var o = {handleEvent:function(){}, handleClick:function(){}}; -evento.removeEventListener(document.body, 'click', o); -evento.removeEventListener(document.body, 'click', o, 'handleClick'); -evento.removeEventListener(document.body, 'click', o, fn); -evento.removeEventListener(document.body, 'click', fn); -evento.removeEventListener(document.body, 'click', this.handleClick, this); +evento.off(document.body, 'click', o); +evento.off(document.body, 'click', o, 'handleClick'); +evento.off(document.body, 'click', o, fn); +evento.off(document.body, 'click', fn); +evento.off(document.body, 'click', this.handleClick, this); */ /** -@property evento.purgeEventListener +@property evento.purge @parameter listener {EventListener} The listener object that should stop listening. @description -Removes all registrations of the listener added through evento.addEventListener. +Removes all registrations of the listener added through evento.on. This purging should be done before your application code looses its last reference -to listener. (This can also be done with more work using evento.removeEventListener for +to listener. (This can also be done with more work using evento.off for each registeration.) If the listeners are not removed or purged, the listener will continue to observe the EventTarget and cannot be garbage collected. In an MVC application this can lead to "zombie views" if the model data cannot be garbage collected. Event listeners need to be removed from event targets in browsers with circular reference memory leak problems (i.e. old versions of Internet Explorer.) -The primary motivation for this purge function is to easy cleanup in MVC View destroy +The primary motivation for this purge function is to easy cleanup in MVC View destroy methods. For example, var APP_BoxView = function(model, controller) { @@ -397,8 +397,8 @@ var APP_BoxView = function(model, controller) { // implementing the EventTarget interface using listener objects // and specifying method name using the same subscription interface. // - evento.addEventListener(this.rootEl, 'click', this, 'handleClick'); - evento.addEventListener(this.model, 'change', this, 'handleModelChange'); + evento.on(this.rootEl, 'click', this, 'handleClick'); + evento.on(this.model, 'change', this, 'handleModelChange'); }; APP_BoxView.prototype.handleClick = function() { @@ -415,7 +415,7 @@ APP_BoxView.prototype.destroy = function() { // to DOM nodes, model objects, or anything else implementing // the EventTarget interface in one fell swoop. // - evento.purgeEventListener(this); + evento.purge(this); }; */ @@ -471,7 +471,7 @@ APP_BoxView.prototype.destroy = function() { return -1; } - evento.addEventListener = function(element, type, listener, /*optional*/ auxArg) { + evento.on = function(element, type, listener, /*optional*/ auxArg) { // Want to call createBundle with the same number of arguments // that were passed to this function. Using apply preserves // the number of arguments. @@ -480,45 +480,45 @@ APP_BoxView.prototype.destroy = function() { if (indexOfBundle(listener._evento_bundles, bundle) >= 0) { // do not add the same listener twice return; - } + } } else { listener._evento_bundles = []; } if (typeof bundle.element.addEventListener === 'function') { - bundle.element.addEventListener(bundle.type, bundle.wrappedHandler, false); + bundle.element.addEventListener(bundle.type, bundle.wrappedHandler, false); } else if ((typeof bundle.element.attachEvent === 'object') && (bundle.element.attachEvent !== null)) { bundle.element.attachEvent('on'+bundle.type, bundle.wrappedHandler); } else { - throw new Error('evento.addEventListener: Supported EventTarget interface not found.'); + throw new Error('evento.on: Supported EventTarget interface not found.'); } listener._evento_bundles.push(bundle); }; - var remove = evento.removeEventListener = function(element, type, listener, /*optional*/ auxArg) { + var remove = evento.off = function(element, type, listener, /*optional*/ auxArg) { if (listener._evento_bundles) { var i = indexOfBundle(listener._evento_bundles, createBundle.apply(null, arguments)); if (i >= 0) { var bundle = listener._evento_bundles[i]; if (typeof bundle.element.removeEventListener === 'function') { bundle.element.removeEventListener(bundle.type, bundle.wrappedHandler, false); - } + } else if ((typeof bundle.element.detachEvent === 'object') && (bundle.element.detachEvent !== null)) { bundle.element.detachEvent('on'+bundle.type, bundle.wrappedHandler); - } + } else { - throw new Error('evento.removeEventListener: Supported EventTarget interface not found.'); - } + throw new Error('evento.off: Supported EventTarget interface not found.'); + } listener._evento_bundles.splice(i, 1); } } }; - evento.purgeEventListener = function(listener) { + evento.purge = function(listener) { if (listener._evento_bundles) { var bundles = listener._evento_bundles.slice(0); for (var i = 0, ilen = bundles.length; i < ilen; i++) { @@ -535,12 +535,13 @@ APP_BoxView.prototype.destroy = function() { }()); /* -Hijos version 0 - JavaScript classes for building tree structures and the composite design pattern +Hijos version 2 Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/hijos/blob/master/LICENSE -*/var hijos = hijos || {}; +*/ +var hijos = {}; /** @property hijos.Leaf @@ -914,12 +915,12 @@ hijos.Node.mixin = function(obj) { hijos.Node.call(obj); }; /* -Arbutus version 1 +Arbutus version 2 Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/arbutus/blob/master/LICENSE -*/var arbutus = arbutus || {}; +*/var arbutus = {}; (function() { var trimLeft = /^\s+/, @@ -962,7 +963,6 @@ https://github.com/petermichaux/arbutus/blob/master/LICENSE var parser = doc.createElement('div'); var fragment = doc.createDocumentFragment(); parser.innerHTML = this.before + html + this.after; - // console.log(parser.innerHTML); var node = this.getFirstResult(parser); var nextNode; while (node) { @@ -1025,13 +1025,13 @@ don't want to have a loop of thousands with calls to this function. }()); /* -Grail version 2 +Grail version 3 Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/grail/blob/master/LICENSE */ -var grail = grail || {}; +var grail = {}; (function() { var trimLeft = /^\s+/; @@ -1211,13 +1211,13 @@ The rest of the details are the same as for grail.findAll. }()); /* -Hormigas version 1 +Hormigas version 3 Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/hormigas/blob/master/LICENSE */ -var hormigas = hormigas || {}; +var hormigas = {}; (function() { var nextId = 0; @@ -1268,6 +1268,25 @@ Harmony Set proposal and the Array.prototype iterators. /** +@property hormigas.ObjectSet.prototype.isEmpty + +@description + +Returns true if set is empty. Otherwise returns false. + +var alpha = {}; +var set = new hormigas.ObjectSet(alpha); +set.isEmpty(); // false +set['delete'](alpha); +set.isEmpty(); // true + +*/ + hormigas.ObjectSet.prototype.isEmpty = function() { + return this.length < 1; + }; + +/** + @property hormigas.ObjectSet.prototype.has @parameter element @@ -1656,12 +1675,28 @@ hormigas.ObjectSet.mixin = function(obj) { hormigas.ObjectSet.call(obj); }; /* -Maria release candidate 1 - an MVC framework for JavaScript applications +Maria release candidate 5 - an MVC framework for JavaScript applications Copyright (c) 2012, Peter Michaux All rights reserved. Licensed under the Simplified BSD License. https://github.com/petermichaux/maria/blob/master/LICENSE -*/var maria = maria || {}; +*/var maria = {}; +// Not all browsers supported by Maria have Object.create + +maria.create = (function() { + function F() {} + return function(obj) { + F.prototype = obj; + return new F(); + }; +}()); +maria.borrow = function(sink, source) { + for (var p in source) { + if (Object.prototype.hasOwnProperty.call(source, p)) { + sink[p] = source[p]; + } + } +}; // "this" must be a constructor function // mix the "subclass" function into your constructor function // @@ -1669,10 +1704,13 @@ maria.subclass = function(namespace, name, options) { options = options || {}; var properties = options.properties; var SuperConstructor = this; - var Constructor = namespace[name] = function() { - SuperConstructor.apply(this, arguments); - }; - var prototype = Constructor.prototype = new SuperConstructor(); + var Constructor = namespace[name] = + Object.prototype.hasOwnProperty.call(options, 'constructor') ? + options.constructor : + function() { + SuperConstructor.apply(this, arguments); + }; + var prototype = Constructor.prototype = maria.create(SuperConstructor.prototype); prototype.constructor = Constructor; if (properties) { maria.borrow(prototype, properties); @@ -1681,18 +1719,17 @@ maria.subclass = function(namespace, name, options) { SuperConstructor.subclass.apply(this, arguments); }; }; -maria.borrow = function(sink, source) { - for (var p in source) { - if (Object.prototype.hasOwnProperty.call(source, p)) { - sink[p] = source[p]; - } - } +maria.on = function() { + evento.on.apply(this, arguments); +}; + +maria.off = function() { + evento.off.apply(this, arguments); +}; + +maria.purge = function() { + evento.purge.apply(this, arguments); }; -maria.borrow(maria, evento); -maria.borrow(maria, hijos); -maria.borrow(maria, arbutus); -maria.borrow(maria, grail); -maria.borrow(maria, hormigas); /** @property maria.Model @@ -1716,7 +1753,7 @@ when a "change" event is dispatched on the model objects. alert('The model changed!'); } }; - maria.addEventListener(model, 'change', view, 'update'); + maria.on(model, 'change', view, 'update'); The model can dispatch a "change" event on itself when the model changes. @@ -1741,7 +1778,7 @@ including that data on the event object. An event listener can be removed from a model object. - maria.removeEventListener(model, 'change', view, 'update'); + maria.off(model, 'change', view, 'update'); A particularly useful pattern is using maria.Model as the "superclass" of your application's model. The following example shows how this @@ -1751,7 +1788,7 @@ See maria.Model.subclass for a more compact way to accomplish the same. checkit.TodoModel = function() { maria.Model.apply(this, arguments); }; - checkit.TodoModel.prototype = new maria.Model(); + checkit.TodoModel.prototype = maria.create(maria.Model.prototype); checkit.TodoModel.prototype.constructor = checkit.TodoModel; checkit.TodoModel.prototype._content = ''; checkit.TodoModel.prototype._isDone = false; @@ -1759,7 +1796,7 @@ See maria.Model.subclass for a more compact way to accomplish the same. return this._content; }; checkit.TodoModel.prototype.setContent = function(content) { - content = ('' + content).replace(/^\s+|\s+$/g, ''); + content = checkit.trim('' + content); if (this._content !== content) { this._content = content; this.dispatchEvent({type: 'change'}); @@ -1779,14 +1816,6 @@ See maria.Model.subclass for a more compact way to accomplish the same. this.setDone(!this.isDone()); }; -The above TodoModel example does not have an "initialize" method; -however, if some special initialization is requried, maria.Model will -automatically call your "initialize" method. - - checkit.TodoModel.prototype.initialize = function() { - alert('Another to-do has been created. You better get busy.'); - }; - When a model's "destroy" method is called, a "destroy" event is dispatched and all event listeners who've been added for this event type will be notified. @@ -1796,13 +1825,11 @@ using "addParentEventTarget" and "removeParentEventTarget".) */ maria.Model = function() { - maria.EventTarget.call(this); - this.initialize(); + evento.EventTarget.call(this); }; -maria.EventTarget.mixin(maria.Model.prototype); - -maria.Model.prototype.initialize = function() {}; +maria.Model.prototype = maria.create(evento.EventTarget.prototype); +maria.Model.prototype.constructor = maria.Model; maria.Model.prototype.destroy = function() { this.dispatchEvent({type: 'destroy'}); @@ -1829,7 +1856,7 @@ with those elements. You can create an empty set model object. - var setModel = new maria.SetModel(); + var setModel = new maria.SetModel(); What makes a set model object interesting in comparison to a set is that a set model object is a model object that dispatches "change" @@ -1840,7 +1867,7 @@ events when elements are added or deleted from the the set. alert(setModel.length + ' element(s) in the set.'); } }; - maria.addEventListener(setModel, 'change', view, 'update'); + maria.on(setModel, 'change', view, 'update'); You can add elements to the set. Adding an element that is already in the set has no effect. The add method returns @@ -1948,11 +1975,8 @@ to accomplish the same. checkit.TodosModel = function() { maria.SetModel.apply(this, arguments); }; - checkit.TodosModel.prototype = new maria.SetModel(); + checkit.TodosModel.prototype = maria.create(maria.SetModel.prototype); checkit.TodosModel.prototype.constructor = checkit.TodosModel; - checkit.TodosModel.prototype.isEmpty = function() { - return this.length === 0; - }; checkit.TodosModel.prototype.getDone = function() { return this.filter(function(todo) { return todo.isDone(); @@ -1989,14 +2013,14 @@ example. This can complement well the flyweight pattern used in a view. */ maria.SetModel = function() { - maria.ObjectSet.apply(this, arguments); + hormigas.ObjectSet.apply(this, arguments); maria.Model.call(this); }; -maria.SetModel.prototype = new maria.Model(); +maria.SetModel.prototype = maria.create(maria.Model.prototype); maria.SetModel.prototype.constructor = maria.SetModel; -maria.ObjectSet.mixin(maria.SetModel.prototype); +hormigas.ObjectSet.mixin(maria.SetModel.prototype); // Wrap the set mutator methods to dispatch events. @@ -2006,11 +2030,11 @@ maria.SetModel.prototype.add = function() { var added = []; for (var i = 0, ilen = arguments.length; i < ilen; i++) { var argument = arguments[i]; - if (maria.ObjectSet.prototype.add.call(this, argument)) { + if (hormigas.ObjectSet.prototype.add.call(this, argument)) { added.push(argument); if ((typeof argument.addEventListener === 'function') && (typeof argument.removeEventListener === 'function')) { - argument.addEventListener('destroy', this); + argument.addEventListener('destroy', this); } if ((typeof argument.addParentEventTarget === 'function') && // want to know can remove later @@ -2032,7 +2056,7 @@ maria.SetModel.prototype['delete'] = function() { var deleted = []; for (var i = 0, ilen = arguments.length; i < ilen; i++) { var argument = arguments[i]; - if (maria.ObjectSet.prototype['delete'].call(this, argument)) { + if (hormigas.ObjectSet.prototype['delete'].call(this, argument)) { deleted.push(argument); if (typeof argument.removeEventListener === 'function') { argument.removeEventListener('destroy', this); @@ -2051,7 +2075,7 @@ maria.SetModel.prototype['delete'] = function() { maria.SetModel.prototype.empty = function() { var deleted = this.toArray(); - var result = maria.ObjectSet.prototype.empty.call(this); + var result = hormigas.ObjectSet.prototype.empty.call(this); if (result) { for (var i = 0, ilen = deleted.length; i < ilen; i++) { var element = deleted[i]; @@ -2140,14 +2164,12 @@ A view has a controller. You can get the current controller. view.getController(); The view's controller is created lazily the first time the -getController method is called. The view's +getController method is called. The view's getDefaultControllerConstructor method returns the constructor function to create the controller object and the getDefaultController actually calls that constructor. Your application may redefine or override either of these methods. -A view's initialize method is called when the view is constructed. - A view has a destroy method which should be called before your application looses its last reference to the view. @@ -2183,7 +2205,7 @@ accomplish the same. myapp.MyView = function() { maria.View.apply(this, arguments); }; - myapp.MyView.prototype = new maria.View(); + myapp.MyView.prototype = maria.create(maria.View.prototype); myapp.MyView.prototype.constructor = myapp.MyView; myapp.MyView.prototype.getModelActions = function() { return { @@ -2204,36 +2226,24 @@ accomplish the same. alert('another method'); }; -The above MyView example does not have an "initialize" method; -however, if some special initialization is requried, maria.View -will automatically call your "initialize" method. - - myapp.MyView.prototype.initialize = function() { - alert('Another view has been created.'); - }; - */ maria.View = function(model, controller) { - maria.Node.call(this); - this.initialize(); + hijos.Node.call(this); this.setModel(model); this.setController(controller); }; -maria.Node.mixin(maria.View.prototype); - -maria.View.prototype.initialize = function() { - // to be overridden by concrete view subclasses -}; +maria.View.prototype = maria.create(hijos.Node.prototype); +maria.View.prototype.constructor = maria.View; maria.View.prototype.destroy = function() { - maria.purgeEventListener(this); + maria.purge(this); this._model = null; if (this._controller) { this._controller.destroy(); this._controller = null; } - maria.Node.prototype.destroy.call(this); + hijos.Node.prototype.destroy.call(this); }; maria.View.prototype.update = function() { @@ -2279,7 +2289,7 @@ maria.View.prototype._setModelAndController = function(model, controller) { eventMap = this._lastModelActions; for (type in eventMap) { if (Object.prototype.hasOwnProperty.call(eventMap, type)) { - maria.removeEventListener(this._model, type, this, eventMap[type]); + maria.off(this._model, type, this, eventMap[type]); } } delete this._lastModelActions; @@ -2288,7 +2298,7 @@ maria.View.prototype._setModelAndController = function(model, controller) { eventMap = this._lastModelActions = this.getModelActions() || {}; for (type in eventMap) { if (Object.prototype.hasOwnProperty.call(eventMap, type)) { - maria.addEventListener(model, type, this, eventMap[type]); + maria.on(model, type, this, eventMap[type]); } } } @@ -2410,7 +2420,7 @@ the same. checkit.TodoView = function() { maria.ElementView.apply(this, arguments); }; - checkit.TodoView.prototype = new maria.ElementView(); + checkit.TodoView.prototype = maria.create(maria.ElementView.prototype); checkit.TodoView.prototype.constructor = checkit.TodoView; checkit.TodoView.prototype.getDefaultControllerConstructor = function() { return checkit.TodoController; @@ -2445,8 +2455,7 @@ the same. checkit.TodoView.prototype.buildData = function() { var model = this.getModel(); var content = model.getContent(); - this.find('.todo-content').innerHTML = - content.replace('&', '&').replace('<', '<'); + this.find('.todo-content').innerHTML = checkit.escapeHTML(content); this.find('.check').checked = model.isDone(); aristocrat[model.isDone() ? 'addClass' : 'removeClass'](this.find('.todo'), 'done'); }; @@ -2478,7 +2487,7 @@ maria.ElementView = function(model, controller, doc) { this.setDocument(doc); }; -maria.ElementView.prototype = new maria.View(); +maria.ElementView.prototype = maria.create(maria.View.prototype); maria.ElementView.prototype.constructor = maria.ElementView; maria.ElementView.prototype.getDocument = function() { @@ -2513,7 +2522,7 @@ maria.ElementView.prototype.build = function() { maria.ElementView.prototype.buildTemplate = function() { // parseHTML returns a DocumentFragment so take firstChild as the rootEl - this._rootEl = maria.parseHTML(this.getTemplate(), this.getDocument()).firstChild; + this._rootEl = arbutus.parseHTML(this.getTemplate(), this.getDocument()).firstChild; }; (function() { @@ -2527,9 +2536,9 @@ maria.ElementView.prototype.buildTemplate = function() { eventType = matches[1], selector = matches[2], methodName = uiActions[key], - elements = maria.findAll(selector, this._rootEl); + elements = this.findAll(selector, this._rootEl); for (var i = 0, ilen = elements.length; i < ilen; i++) { - maria.addEventListener(elements[i], eventType, this, methodName); + maria.on(elements[i], eventType, this, methodName); } } } @@ -2548,10 +2557,6 @@ maria.ElementView.prototype.buildChildViews = function() { } }; -maria.ElementView.prototype.update = function() { - // to be overridden by concrete ElementView subclasses -}; - maria.ElementView.prototype.getContainerEl = function() { return this.build(); }; @@ -2571,11 +2576,11 @@ maria.ElementView.prototype.removeChild = function(oldChild) { }; maria.ElementView.prototype.find = function(selector) { - return maria.find(selector, this.build()); + return grail.find(selector, this.build()); }; maria.ElementView.prototype.findAll = function(selector) { - return maria.findAll(selector, this.build()); + return grail.findAll(selector, this.build()); }; /** @@ -2617,7 +2622,7 @@ maria.SetView.subclass for a more compact way to accomplish the same. checkit.TodosListView = function() { maria.SetView.apply(this, arguments); }; - checkit.TodosListView.prototype = new maria.SetView(); + checkit.TodosListView.prototype = maria.create(maria.SetView.prototype); checkit.TodosListView.prototype.constructor = checkit.TodosListView; checkit.TodosListView.prototype.getTemplate = function() { return checkit.TodosListTemplate; @@ -2631,7 +2636,7 @@ maria.SetView = function() { maria.ElementView.apply(this, arguments); }; -maria.SetView.prototype = new maria.ElementView(); +maria.SetView.prototype = maria.create(maria.ElementView.prototype); maria.SetView.prototype.constructor = maria.SetView; maria.SetView.prototype.buildChildViews = function() { @@ -2733,7 +2738,7 @@ to accomplish the same. checkit.TodoController = function() { maria.Controller.apply(this, arguments); }; - checkit.TodoController.prototype = new maria.Controller(); + checkit.TodoController.prototype = maria.create(maria.Controller.prototype); checkit.TodoController.prototype.constructor = checkit.TodoController; checkit.TodoController.prototype.onClickCheck = function() { this.getModel().toggleDone(); @@ -2743,10 +2748,10 @@ to accomplish the same. }; checkit.TodoController.prototype.onKeyupInput = function() { var view = this.getView(); - if (/\S/.test(view.getInputValue())) { - view.showToolTip(); - } else { + if (checkit.isBlank(view.getInputValue())) { view.hideToolTip(); + } else { + view.showToolTip(); } }; checkit.TodoController.prototype.onKeypressInput = function(evt) { @@ -2759,25 +2764,13 @@ to accomplish the same. var value = view.getInputValue(); view.hideToolTip(); view.showDisplay(); - if (!/^\s*$/.test(value)) { + if (!checkit.isBlank(value)) { this.getModel().setContent(value); } }; -The above TodoController example does not have an "initialize" method; -however, if some special initialization is requried, maria.Controller -will automatically call your "initialize" method. - - checkit.TodoController.prototype.initialize = function() { - alert('Another to-do controller has been created.'); - }; - */ -maria.Controller = function() { - this.initialize(); -}; - -maria.Controller.prototype.initialize = function() {}; +maria.Controller = function() {}; maria.Controller.prototype.destroy = function() { this._model = null; @@ -2791,6 +2784,9 @@ maria.Controller.prototype.getModel = function() { return this._model; }; +// setModel is intended to be called *only* by +// the view _setModelAndController method. +// Do otherwise at your own risk. maria.Controller.prototype.setModel = function(model) { this._model = model; }; @@ -2799,6 +2795,9 @@ maria.Controller.prototype.getView = function() { return this._view; }; +// setView is intended to be called *only* by +// the view _setModelAndController method. +// Do otherwise at your own risk. maria.Controller.prototype.setView = function(view) { this._view = view; }; @@ -2822,7 +2821,7 @@ for maria.Model. return this._content; }, setContent: function(content) { - content = ('' + content).replace(/^\s+|\s+$/g, ''); + content = checkit.trim('' + content); if (this._content !== content) { this._content = content; this.dispatchEvent({type: 'change'}); @@ -2845,7 +2844,9 @@ for maria.Model. }); */ -maria.Model.subclass = maria.subclass; +maria.Model.subclass = function() { + maria.subclass.apply(this, arguments); +}; /** @property maria.SetModel.subclass @@ -2860,9 +2861,6 @@ for maria.SetModel. maria.SetModel.subclass(checkit, 'TodosModel', { properties: { - isEmpty: function() { - return this.length === 0; - }, getDone: function() { return this.filter(function(todo) { return todo.isDone(); @@ -2894,7 +2892,9 @@ for maria.SetModel. }); */ -maria.SetModel.subclass = maria.Model.subclass; +maria.SetModel.subclass = function() { + maria.Model.subclass.apply(this, arguments); +}; /** @property maria.View.subclass @@ -2981,8 +2981,7 @@ for maria.ElementView. buildData: function() { var model = this.getModel(); var content = model.getContent(); - this.find('.todo-content').innerHTML = - content.replace('&', '&').replace('<', '<'); + this.find('.todo-content').innerHTML = checkit.escapeHTML(content); this.find('.check').checked = model.isDone(); aristocrat[model.isDone() ? 'addClass' : 'removeClass'](this.find('.todo'), 'done'); }, @@ -3012,12 +3011,11 @@ for maria.ElementView. This subclassing function implements options following the "convention over configuration" philosophy. The checkit.TodoView will, -by convention, use the checkit.TodoModel, checkit.TodoController +by convention, use the checkit.TodoController and checkit.TodoTemplate objects. All of these can be configured -explicitely if these conventions do not match your view's needs. +explicitly if these conventions do not match your view's needs. maria.ElementView.subclass(checkit, 'TodoView', { - modelConstructor : checkit.TodoModel , controllerConstructor: checkit.TodoController, template : checkit.TodoTemplate , uiActions: { @@ -3028,7 +3026,6 @@ objects in the application's namespace object (i.e. the checkit object in this example). maria.ElementView.subclass(checkit, 'TodoView', { - modelConstructorName : 'TodoModel' , controllerConstructorName: 'TodoController', templateName : 'TodoTemplate' , uiActions: { @@ -3090,7 +3087,6 @@ function equivalent to the more verbose example shown in the documentation for maria.SetView. maria.SetView.subclass(checkit, 'TodosListView', { - modelConstructor: checkit.TodosModel, properties: { createChildView: function(todoModel) { return new checkit.TodoView(todoModel); @@ -3099,7 +3095,9 @@ documentation for maria.SetView. }); */ -maria.SetView.subclass = maria.ElementView.subclass; +maria.SetView.subclass = function() { + maria.ElementView.subclass.apply(this, arguments); +}; /** @property maria.Controller.subclass @@ -3122,10 +3120,10 @@ the documentation for maria.Controller. }, onKeyupInput: function() { var view = this.getView(); - if (/\S/.test(view.getInputValue())) { - view.showToolTip(); - } else { + if (checkit.isBlank(view.getInputValue())) { view.hideToolTip(); + } else { + view.showToolTip(); } }, onKeypressInput: function(evt) { @@ -3138,7 +3136,7 @@ the documentation for maria.Controller. var value = view.getInputValue(); view.hideToolTip(); view.showDisplay(); - if (!/^\s*$/.test(value)) { + if (!checkit.isBlank(value)) { this.getModel().setContent(value); } } @@ -3146,4 +3144,6 @@ the documentation for maria.Controller. }); */ -maria.Controller.subclass = maria.subclass; +maria.Controller.subclass = function() { + maria.subclass.apply(this, arguments); +}; diff --git a/labs/architecture-examples/maria/src/css/app.css b/labs/architecture-examples/maria/src/css/app.css new file mode 100644 index 00000000..79f1fc19 --- /dev/null +++ b/labs/architecture-examples/maria/src/css/app.css @@ -0,0 +1,4 @@ +ul.completed li.incompleted, +ul.incompleted li.completed { + display: none; +} diff --git a/labs/architecture-examples/maria/src/css/base.css b/labs/architecture-examples/maria/src/css/base.css new file mode 100644 index 00000000..8d1db3a6 --- /dev/null +++ b/labs/architecture-examples/maria/src/css/base.css @@ -0,0 +1,414 @@ +html, +body { + margin: 0; + padding: 0; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + color: inherit; + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; +} + +body { + font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1.4em; + background: #eaeaea url('bg.png'); + color: #4d4d4d; + width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#todoapp { + background: #fff; + background: rgba(255, 255, 255, 0.9); + margin: 130px 0 40px 0; + border: 1px solid #ccc; + position: relative; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.15); +} + +#todoapp:before { + content: ''; + border-left: 1px solid #f5d6d6; + border-right: 1px solid #f5d6d6; + width: 2px; + position: absolute; + top: 0; + left: 40px; + height: 100%; +} + +#todoapp input::-webkit-input-placeholder { + font-style: italic; +} + +#todoapp input:-moz-placeholder { + font-style: italic; + color: #a9a9a9; +} + +#todoapp h1 { + position: absolute; + top: -120px; + width: 100%; + font-size: 70px; + font-weight: bold; + text-align: center; + color: #b3b3b3; + color: rgba(255, 255, 255, 0.3); + text-shadow: -1px -1px rgba(0, 0, 0, 0.2); + -webkit-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + -ms-text-rendering: optimizeLegibility; + -o-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +#header { + padding-top: 15px; + border-radius: inherit; +} + +#header:before { + content: ''; + position: absolute; + top: 0; + right: 0; + left: 0; + height: 15px; + z-index: 2; + border-bottom: 1px solid #6c615c; + background: #8d7d77; + background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); + background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -moz-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -o-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); + border-top-left-radius: 1px; + border-top-right-radius: 1px; +} + +#new-todo, +.edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + line-height: 1.4em; + border: 0; + outline: none; + color: inherit; + padding: 6px; + border: 1px solid #999; + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + -o-box-sizing: border-box; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + -o-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.02); + z-index: 2; + box-shadow: none; +} + +#main { + position: relative; + z-index: 2; + border-top: 1px dotted #adadad; +} + +label[for='toggle-all'] { + display: none; +} + +#toggle-all { + position: absolute; + top: -42px; + left: -4px; + width: 40px; + text-align: center; + border: none; /* Mobile Safari */ +} + +#toggle-all:before { + content: '»'; + font-size: 28px; + color: #d9d9d9; + padding: 0 25px 7px; +} + +#toggle-all:checked:before { + color: #737373; +} + +#todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +#todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px dotted #ccc; +} + +#todo-list li:last-child { + border-bottom: none; +} + +#todo-list li.editing { + border-bottom: none; + padding: 0; +} + +#todo-list li.editing .edit { + display: block; + width: 506px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +#todo-list li.editing .view { + display: none; +} + +#todo-list li .toggle { + text-align: center; + width: 40px; + /* auto, since non-WebKit browsers doesn't support input styling */ + height: auto; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; /* Mobile Safari */ + -webkit-appearance: none; + /*-moz-appearance: none;*/ + -ms-appearance: none; + -o-appearance: none; + appearance: none; +} + +#todo-list li .toggle:after { + content: '✔'; + line-height: 43px; /* 40 + a couple of pixels visual adjustment */ + font-size: 20px; + color: #d9d9d9; + text-shadow: 0 -1px 0 #bfbfbf; +} + +#todo-list li .toggle:checked:after { + color: #85ada7; + text-shadow: 0 1px 0 #669991; + bottom: 1px; + position: relative; +} + +#todo-list li label { + word-break: break-word; + padding: 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + -webkit-transition: color 0.4s; + -moz-transition: color 0.4s; + -ms-transition: color 0.4s; + -o-transition: color 0.4s; + transition: color 0.4s; +} + +#todo-list li.completed label { + color: #a9a9a9; + text-decoration: line-through; +} + +#todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 22px; + color: #a88a8a; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + -ms-transition: all 0.2s; + -o-transition: all 0.2s; + transition: all 0.2s; +} + +#todo-list li .destroy:hover { + text-shadow: 0 0 1px #000, + 0 0 10px rgba(199, 107, 107, 0.8); + -webkit-transform: scale(1.3); + -moz-transform: scale(1.3); + -ms-transform: scale(1.3); + -o-transform: scale(1.3); + transform: scale(1.3); +} + +#todo-list li .destroy:after { + content: '✖'; +} + +#todo-list li:hover .destroy { + display: block; +} + +#todo-list li .edit { + display: none; +} + +#todo-list li.editing:last-child { + margin-bottom: -1px; +} + +#footer { + color: #777; + padding: 0 15px; + position: absolute; + right: 0; + bottom: -31px; + left: 0; + height: 20px; + z-index: 1; + text-align: center; +} + +#footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 31px; + left: 0; + height: 50px; + z-index: -1; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + 0 6px 0 -3px rgba(255, 255, 255, 0.8), + 0 7px 1px -3px rgba(0, 0, 0, 0.3), + 0 43px 0 -6px rgba(255, 255, 255, 0.8), + 0 44px 2px -6px rgba(0, 0, 0, 0.2); +} + +#todo-count { + float: left; + text-align: left; +} + +#filters { + margin: 0; + padding: 0; + list-style: none; + position: absolute; + right: 0; + left: 0; +} + +#filters li { + display: inline; +} + +#filters li a { + color: #83756f; + margin: 2px; + text-decoration: none; +} + +#filters li a.selected { + font-weight: bold; +} + +#clear-completed { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + background: rgba(0, 0, 0, 0.1); + font-size: 11px; + padding: 0 10px; + border-radius: 3px; + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); +} + +#clear-completed:hover { + background: rgba(0, 0, 0, 0.15); + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); +} + +#info { + margin: 65px auto 0; + color: #a6a6a6; + font-size: 12px; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); + text-align: center; +} + +#info a { + color: inherit; +} + +/* + Hack to remove background from Mobile Safari. + Can't use it globally since it destroys checkboxes in Firefox and Opera +*/ +@media screen and (-webkit-min-device-pixel-ratio:0) { + #toggle-all, + #todo-list li .toggle { + background: none; + } + + #todo-list li .toggle { + height: 40px; + } + + #toggle-all { + top: -56px; + left: -15px; + width: 65px; + height: 41px; + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + -webkit-appearance: none; + appearance: none; + } +} + +.hidden{ + display:none; +} diff --git a/labs/architecture-examples/maria/src/css/todos.css b/labs/architecture-examples/maria/src/css/todos.css deleted file mode 100644 index f0ae34d9..00000000 --- a/labs/architecture-examples/maria/src/css/todos.css +++ /dev/null @@ -1,511 +0,0 @@ -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, font, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td { - margin: 0; - padding: 0; - border: 0; - outline: 0; - font-style: inherit; - font-size: 100%; - font-family: inherit; - vertical-align: baseline; -} -body { - line-height: 1; - color: black; - background: white; -} -ol, ul { - list-style: none; -} -a img { - border: none; -} - -html { - background: #eeeeee; -} -body { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 1.4em; - background: #eeeeee; - color: #333333; -} - -.todos-app { - width: 480px; - margin: 0 auto 40px; - background: white; - padding: 20px; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 5px 6px 0; -} -.todos-app h1 { - font-size: 36px; - font-weight: bold; - text-align: center; - padding: 20px 0 30px 0; - line-height: 1; -} - -.create-todo { - position: relative; -} -.create-todo input { - width: 466px; - font-size: 24px; - font-family: inherit; - line-height: 1.4em; - border: 0; - outline: none; - padding: 6px; - border: 1px solid #999999; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; -} - -.create-todo span { - position: absolute; - z-index: 999; - width: 170px; - left: 50%; - margin-left: -85px; -} - -.todos-list { - margin-top: 10px; -} -.todos-list li { - padding: 12px 20px 11px 0; - position: relative; - font-size: 24px; - line-height: 1.1em; - border-bottom: 1px solid #cccccc; -} -.todos-list li:after { - content: "\0020"; - display: block; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} -.todos-list li.editing { - padding: 0; - border-bottom: 0; -} -.todos-list .editing .display, -.todos-list .edit { - display: none; -} -.todos-list .editing .edit { - display: block; - position: relative; -} -.todos-list .editing input { - width: 444px; - font-size: 24px; - font-family: inherit; - margin: 0; - line-height: 1.6em; - border: 0; - outline: none; - padding: 10px 7px 0px 27px; - border: 1px solid #999999; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; -} -.todos-list .check { - position: relative; - top: 9px; - margin: 0 10px 0 7px; - float: left; -} -.todos-list .todo-content { - width:400px; - display:block; -} -.todos-list .done .todo-content { - text-decoration: line-through; - color: #777777; -} -.todos-list .todo-destroy { - cursor: pointer; - position: absolute; - right: 5px; - top: 14px; - display:none; -} -.todos-list .todo-hover .todo-destroy { - display:block; -} - -.todos-stats { - *zoom: 1; - margin-top: 10px; - color: #777777; -} -.todos-stats:after { - content: "\0020"; - display: block; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} -.todos-stats .todo-count { - float: left; -} -.todos-stats .todo-count .number { - font-weight: bold; - color: #333333; -} -.todos-stats .todo-clear { - float: right; -} -.todos-stats .todo-clear a { - color: #777777; - font-size: 12px; -} -.todos-stats .todo-clear a:visited { - color: #777777; -} -.todos-stats .todo-clear a:hover { - color: #336699; -} - -/* -* François 'cahnory' Germain -*/ -.ui-tooltip, .ui-tooltip-top, .ui-tooltip-right, .ui-tooltip-bottom, .ui-tooltip-left { - color:#ffffff; - cursor:normal; - display:-moz-inline-stack; - display:inline-block; - font-size:12px; - font-family:arial; - padding:.5em 1em; - position:relative; - text-align:center; - text-shadow:0 -1px 1px #111111; - -webkit-border-top-left-radius:4px ; - -webkit-border-top-right-radius:4px ; - -webkit-border-bottom-right-radius:4px ; - -webkit-border-bottom-left-radius:4px ; - -khtml-border-top-left-radius:4px ; - -khtml-border-top-right-radius:4px ; - -khtml-border-bottom-right-radius:4px ; - -khtml-border-bottom-left-radius:4px ; - -moz-border-radius-topleft:4px ; - -moz-border-radius-topright:4px ; - -moz-border-radius-bottomright:4px ; - -moz-border-radius-bottomleft:4px ; - border-top-left-radius:4px ; - border-top-right-radius:4px ; - border-bottom-right-radius:4px ; - border-bottom-left-radius:4px ; - -o-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444; - -moz-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444; - -khtml-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444; - -webkit-box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444; - box-shadow:0 1px 2px #000000, inset 0 0 0 1px #222222, inset 0 2px #666666, inset 0 -2px 2px #444444; - background-color:#3b3b3b; - background-image:-moz-linear-gradient(top,#555555,#222222); - background-image:-webkit-gradient(linear,left top,left bottom,color-stop(0,#555555),color-stop(1,#222222)); - filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222); - -ms-filter:progid:DXImageTransform.Microsoft.gradient(startColorStr=#555555,EndColorStr=#222222); -} -.ui-tooltip:after, .ui-tooltip-top:after, .ui-tooltip-right:after, .ui-tooltip-bottom:after, .ui-tooltip-left:after { - content:"\25B8"; - display:block; - font-size:2em; - height:0; - line-height:0; - position:absolute; -} -.ui-tooltip:after, .ui-tooltip-bottom:after { - color:#2a2a2a; - bottom:0; - left:1px; - text-align:center; - text-shadow:1px 0 2px #000000; - -o-transform:rotate(90deg); - -moz-transform:rotate(90deg); - -khtml-transform:rotate(90deg); - -webkit-transform:rotate(90deg); - width:100%; -} -.ui-tooltip-top:after { - bottom:auto; - color:#4f4f4f; - left:-2px; - top:0; - text-align:center; - text-shadow:none; - -o-transform:rotate(-90deg); - -moz-transform:rotate(-90deg); - -khtml-transform:rotate(-90deg); - -webkit-transform:rotate(-90deg); - width:100%; -} -.ui-tooltip-right:after { - color:#222222; - right:-0.375em; - top:50%; - margin-top:-.05em; - text-shadow:0 1px 2px #000000; - -o-transform:rotate(0); - -moz-transform:rotate(0); - -khtml-transform:rotate(0); - -webkit-transform:rotate(0); -} -.ui-tooltip-left:after { - color:#222222; - left:-0.375em; - top:50%; - margin-top:.1em; - text-shadow:0 -1px 2px #000000; - -o-transform:rotate(180deg); - -moz-transform:rotate(180deg); - -khtml-transform:rotate(180deg); - -webkit-transform:rotate(180deg); -} - - - -/*the following changes require some cleanup and integration with the above.**/ - -/* line 9 */ - - -/* line 17 */ -.todos-app { - background: white; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 2px 6px 0; - -moz-border-radius-bottomleft: 5px; - -webkit-border-bottom-left-radius: 5px; - -o-border-bottom-left-radius: 5px; - -ms-border-bottom-left-radius: 5px; - -khtml-border-bottom-left-radius: 5px; - border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -webkit-border-bottom-right-radius: 5px; - -o-border-bottom-right-radius: 5px; - -ms-border-bottom-right-radius: 5px; - -khtml-border-bottom-right-radius: 5px; - border-bottom-right-radius: 5px; -} -/* line 24 */ - - -/* line 32 */ -.todos-app .content .create-todo { - position: relative; -} -/* line 34 */ -.todos-app .content .create-todo input { - font-size: 24px; - font-family: inherit; - line-height: 1.4em; - border: 0; - outline: none; - padding: 6px; - border: 1px solid #999999; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; -} - -/* line 47 */ -.todos-app .content .create-todo span { - position: absolute; - z-index: 999; - width: 170px; - left: 50%; - margin-left: -85px; -} -/* line 55 */ -.todos-app .content ul.todos-list { - margin-top: 10px; -} -/* line 57 */ -.todos-app .content ul.todos-list li { - padding: 15px 20px 15px 0; - position: relative; - font-size: 24px; - border-bottom: 1px solid #cccccc; - *zoom: 1; -} -/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */ -.todos-app .content ul.todos-list li:after { - content: "\0020"; - display: block; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} -/* line 64 */ -.todos-app .content ul.todos-list li.editing { - padding: 0; - border-bottom: 0; -} -/* line 67 */ -.todos-app .content ul.todos-list li.editing .todo-input { - display: block; - width: 466px; - font-size: 24px; - font-family: inherit; - line-height: 1.4em; - border: 0; - outline: none; - padding: 6px; - border: 1px solid #999999; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; - box-shadow: rgba(0, 0, 0, 0.2) 0 1px 2px 0 inset; -} -/* line 79 */ -.todos-app .content ul.todos-list li.editing .todo-content { - display: none; -} -/* line 81 */ -.todos-app .content ul.todos-list li.editing .todo-check { - display: none; -} -/* line 83 */ -.todos-app .content ul.todos-list li.editing .todo-destroy { - display: none !important; -} -/* line 85 */ -.todos-app .content ul.todos-list li .todo-input { - display: none; -} -/* line 87 */ -.todos-app .content ul.todos-list li .todo-check { - position: relative; - top: 6px; - margin: 0 10px 0 7px; - float: left; -} -/* line 93 */ -.todos-app .content ul.todos-list li.done .todo-content { - text-decoration: line-through; - color: #777777; -} -/* line 109 */ -.todos-app .todos-stats { - *zoom: 1; - margin-top: 10px; - color: #555555; - -moz-border-radius-bottomleft: 5px; - -webkit-border-bottom-left-radius: 5px; - -o-border-bottom-left-radius: 5px; - -ms-border-bottom-left-radius: 5px; - -khtml-border-bottom-left-radius: 5px; - border-bottom-left-radius: 5px; - -moz-border-radius-bottomright: 5px; - -webkit-border-bottom-right-radius: 5px; - -o-border-bottom-right-radius: 5px; - -ms-border-bottom-right-radius: 5px; - -khtml-border-bottom-right-radius: 5px; - border-bottom-right-radius: 5px; - background: #f4fce8; - border-top: 1px solid #ededed; - padding: 0 20px; - line-height: 36px; -} -/* line 22, /opt/ree/lib/ruby/gems/1.8/gems/compass-0.10.5/frameworks/compass/stylesheets/compass/utilities/general/_clearfix.scss */ -.todos-app .todos-stats:after { - content: "\0020"; - display: block; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} -/* line 118 */ -.todos-app .todos-stats .todo-count { - float: left; -} -/* line 120 */ -.todos-app .todos-stats .todo-count .number { - font-weight: bold; - color: #555555; -} -/* line 123 */ -.todos-app .todos-stats .todo-clear { - float: right; -} -/* line 125 */ -.todos-app .todos-stats .todo-clear a { - display: block; - line-height: 20px; - text-decoration: none; - -moz-border-radius: 12px; - -webkit-border-radius: 12px; - -o-border-radius: 12px; - -ms-border-radius: 12px; - -khtml-border-radius: 12px; - border-radius: 12px; - background: rgba(0, 0, 0, 0.1); - color: #555555; - font-size: 11px; - margin-top: 8px; - padding: 0 10px 1px; - -moz-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - -o-box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; - box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 0 0; -} -/* line 136 */ -.todos-app .todos-stats .todo-clear a:hover, .todos-app .todos-stats .todo-clear a:focus { - background: rgba(0, 0, 0, 0.15); - -moz-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - -o-box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; - box-shadow: rgba(0, 0, 0, 0.3) 0 -1px 0 0; -} -/* line 139 */ -.todos-app .todos-stats .todo-clear a:active { - position: relative; - top: 1px; -} - -.todos-app .copyright { - padding:0.5em; - text-align:center; -} - -.todos-app ul.todos-toolbar { - list-style-type: none; - margin: 0; - margin-top: 10px; - padding: 0; -} -.todos-app .todos-toolbar li { - margin: 0; - padding: 0; - padding-right: 1em; - cursor: pointer; - display: inline; - font-size: 90%; - text-decoration: underline; -} diff --git a/labs/architecture-examples/maria/src/index.html b/labs/architecture-examples/maria/src/index.html index 1d2a72b1..7c23438f 100644 --- a/labs/architecture-examples/maria/src/index.html +++ b/labs/architecture-examples/maria/src/index.html @@ -1,9 +1,11 @@ <!DOCTYPE html> <html> <head> - <title>To-do App</title> + <meta charset="utf-8"> + <title>Maria • TodoMVC</title> - <link href="css/todos.css" rel="stylesheet" type="text/css"> + <link href="css/base.css" rel="stylesheet"> + <link href="css/app.css" rel="stylesheet"> </head> <body> @@ -17,22 +19,15 @@ <script src="../lib/maria/maria.js"></script> <script src="../lib/aristocrat/aristocrat.js"></script> + <script src="../lib/director/director.js"></script> <script src="js/namespace.js"></script> + <script src="js/util.js"></script> <script src="js/models/TodoModel.js"></script> <script src="js/models/TodosModel.js"></script> - <script src="js/templates/TodosInputTemplate.js"></script> - <script src="js/views/TodosInputView.js"></script> - <script src="js/templates/TodosListTemplate.js"></script> - <script src="js/views/TodosListView.js"></script> - <script src="js/templates/TodosToolbarTemplate.js"></script> - <script src="js/views/TodosToolbarView.js"></script> - <script src="js/controllers/TodosToolbarController.js"></script> - <script src="js/templates/TodosStatsTemplate.js"></script> - <script src="js/views/TodosStatsView.js"></script> - <script src="js/templates/TodosAppTemplate.js"></script> - <script src="js/views/TodosAppView.js"></script> - <script src="js/controllers/TodosInputController.js"></script> + <script src="js/templates/TodosTemplate.js"></script> + <script src="js/views/TodosView.js"></script> + <script src="js/controllers/TodosController.js"></script> <script src="js/templates/TodoTemplate.js"></script> <script src="js/views/TodoView.js"></script> <script src="js/controllers/TodoController.js"></script> diff --git a/labs/architecture-examples/maria/src/js/bootstrap.js b/labs/architecture-examples/maria/src/js/bootstrap.js index 34215fe6..042d77d1 100644 --- a/labs/architecture-examples/maria/src/js/bootstrap.js +++ b/labs/architecture-examples/maria/src/js/bootstrap.js @@ -1,4 +1,4 @@ -maria.addEventListener(window, 'load', function() { +maria.on(window, 'load', function() { var loading = document.getElementById('loading'); loading.parentNode.removeChild(loading); @@ -7,7 +7,7 @@ maria.addEventListener(window, 'load', function() { var store = localStorage.getItem('todos-maria'); model = store ? checkit.TodosModel.fromJSON(JSON.parse(store)) : new checkit.TodosModel(); - evento.addEventListener(model, 'change', function() { + maria.on(model, 'change', function() { localStorage.setItem('todos-maria', JSON.stringify(model.toJSON())); }); } @@ -15,6 +15,20 @@ maria.addEventListener(window, 'load', function() { model = new checkit.TodosModel(); } + var routes = { + '/': function() { + model.setMode('all'); + }, + '/active': function() { + model.setMode('incompleted'); + }, + '/completed': function() { + model.setMode('completed'); + } + }; + var router = Router(routes); + router.init(); + var view = new checkit.TodosAppView(model); document.body.appendChild(view.build()); }); diff --git a/labs/architecture-examples/maria/src/js/controllers/TodoController.js b/labs/architecture-examples/maria/src/js/controllers/TodoController.js index c4dde1c7..32613316 100644 --- a/labs/architecture-examples/maria/src/js/controllers/TodoController.js +++ b/labs/architecture-examples/maria/src/js/controllers/TodoController.js @@ -1,40 +1,29 @@ maria.Controller.subclass(checkit, 'TodoController', { properties: { - onMouseoverRoot: function() { - this.getView().showHoverState(); - }, - onMouseoutRoot: function() { - this.getView().hideHoverState(); - }, - onClickCheck: function() { - this.getModel().toggleDone(); - }, onClickDestroy: function() { this.getModel().destroy(); }, - onDblclickDisplay: function() { - this.getView().showEdit(); + onClickToggle: function() { + this.getModel().toggleCompleted(); }, - onKeyupInput: function() { - var view = this.getView(); - if (/\S/.test(view.getInputValue())) { - view.showToolTip(); - } else { - view.hideToolTip(); - } + onDblclickLabel: function() { + this.getView().showEdit(); }, - onKeypressInput: function(evt) { - if (evt.keyCode === 13) { - this.onBlurInput(); + onKeyupEdit: function(evt) { + if (checkit.isEnterKeyCode(evt.keyCode)) { + this.onBlurEdit(); } }, - onBlurInput: function() { + onBlurEdit: function() { + var model = this.getModel(); var view = this.getView(); var value = view.getInputValue(); - view.hideToolTip(); view.showDisplay(); - if (!/^\s*$/.test(value)) { - this.getModel().setContent(value); + if (checkit.isBlank(value)) { + model.destroy(); + } + else { + model.setTitle(value); } } } diff --git a/labs/architecture-examples/maria/src/js/controllers/TodosController.js b/labs/architecture-examples/maria/src/js/controllers/TodosController.js new file mode 100644 index 00000000..6ad8dfb3 --- /dev/null +++ b/labs/architecture-examples/maria/src/js/controllers/TodosController.js @@ -0,0 +1,28 @@ +maria.Controller.subclass(checkit, 'TodosAppController', { + properties: { + onKeyupNewTodo: function(evt) { + if (checkit.isEnterKeyCode(evt.keyCode)) { + var view = this.getView(); + var value = view.getInputValue(); + if (!checkit.isBlank(value)) { + var todo = new checkit.TodoModel(); + todo.setTitle(value); + this.getModel().add(todo); + view.clearInput(); + } + } + }, + onClickToggleAll: function() { + var model = this.getModel(); + if (model.isAllCompleted()) { + model.markAllIncompleted(); + } + else { + model.markAllCompleted(); + } + }, + onClickClearCompleted: function() { + this.getModel().deleteCompleted(); + } + } +}); diff --git a/labs/architecture-examples/maria/src/js/controllers/TodosInputController.js b/labs/architecture-examples/maria/src/js/controllers/TodosInputController.js deleted file mode 100644 index b836176f..00000000 --- a/labs/architecture-examples/maria/src/js/controllers/TodosInputController.js +++ /dev/null @@ -1,32 +0,0 @@ -maria.Controller.subclass(checkit, 'TodosInputController', { - properties: { - onFocusInput: function() { - this.onKeyupInput(); - }, - onBlurInput: function() { - this.getView().hideToolTip(); - }, - onKeyupInput: function() { - var view = this.getView(); - if (/\S/.test(view.getInputValue())) { - view.showToolTip(); - } else { - view.hideToolTip(); - } - }, - onKeypressInput: function(evt) { - if (evt.keyCode != 13) { - return; - } - var view = this.getView(); - var value = view.getInputValue(); - if (/^\s*$/.test(value)) { // don't create an empty Todo - return; - } - var todo = new checkit.TodoModel(); - todo.setContent(value); - this.getModel().add(todo); - view.clearInput(); - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/controllers/TodosToolbarController.js b/labs/architecture-examples/maria/src/js/controllers/TodosToolbarController.js deleted file mode 100644 index 38ca642b..00000000 --- a/labs/architecture-examples/maria/src/js/controllers/TodosToolbarController.js +++ /dev/null @@ -1,21 +0,0 @@ -maria.Controller.subclass(checkit, 'TodosToolbarController', { - properties: { - onClickAllCheckbox: function() { - var model = this.getModel(); - if (model.isAllDone()) { - model.markAllUndone(); - } else { - model.markAllDone(); - } - }, - onClickMarkAllDone: function() { - this.getModel().markAllDone(); - }, - onClickMarkAllUndone: function() { - this.getModel().markAllUndone(); - }, - onClickDeleteDone: function() { - this.getModel().deleteDone(); - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/models/TodoModel.js b/labs/architecture-examples/maria/src/js/models/TodoModel.js index 4e332f88..0e4b6888 100644 --- a/labs/architecture-examples/maria/src/js/models/TodoModel.js +++ b/labs/architecture-examples/maria/src/js/models/TodoModel.js @@ -1,34 +1,34 @@ maria.Model.subclass(checkit, 'TodoModel', { properties: { - _content: '', - _isDone: false, - getContent: function() { - return this._content; + _title: '', + _completed: false, + getTitle: function() { + return this._title; }, - setContent: function(content) { - content = ('' + content).replace(/^\s+|\s+$/g, ''); - if (this._content !== content) { - this._content = content; + setTitle: function(title) { + title = checkit.trim('' + title); + if (this._title !== title) { + this._title = title; this.dispatchEvent({type: 'change'}); } }, - isDone: function() { - return this._isDone; + isCompleted: function() { + return this._completed; }, - setDone: function(isDone) { - isDone = !!isDone; - if (this._isDone !== isDone) { - this._isDone = isDone; + setCompleted: function(completed) { + completed = !!completed; + if (this._completed !== completed) { + this._completed = completed; this.dispatchEvent({type: 'change'}); } }, - toggleDone: function() { - this.setDone(!this.isDone()); + toggleCompleted: function() { + this.setCompleted(!this.isCompleted()); }, toJSON: function() { return { - content: this._content, - is_done: this._isDone + title: this._title, + completed: this._completed }; } } @@ -36,7 +36,7 @@ maria.Model.subclass(checkit, 'TodoModel', { checkit.TodoModel.fromJSON = function(todoJSON) { var model = new checkit.TodoModel(); - model._content = todoJSON.content; - model._isDone = todoJSON.is_done; + model._title = todoJSON.title; + model._completed = todoJSON.completed; return model; }; diff --git a/labs/architecture-examples/maria/src/js/models/TodosModel.js b/labs/architecture-examples/maria/src/js/models/TodosModel.js index 0a933ff2..29aeaea5 100644 --- a/labs/architecture-examples/maria/src/js/models/TodosModel.js +++ b/labs/architecture-examples/maria/src/js/models/TodosModel.js @@ -1,34 +1,49 @@ maria.SetModel.subclass(checkit, 'TodosModel', { properties: { - isEmpty: function() { - return this.length === 0; + _mode: 'all', + getPossibleModes: function() { + return ['all', 'incompleted', 'completed']; }, - getDone: function() { + getMode: function() { + return this._mode; + }, + setMode: function(mode) { + if (this.getPossibleModes().some(function(m) {return m === mode;})) { + if (this._mode !== mode) { + this._mode = mode; + this.dispatchEvent({type: 'change'}); + } + } + else { + throw new Error('checkit.TodosModel.prototype.setMode: unsupported mode "'+mode+'".'); + } + }, + getCompleted: function() { return this.filter(function(todo) { - return todo.isDone(); + return todo.isCompleted(); }); }, - getUndone: function() { + getIncompleted: function() { return this.filter(function(todo) { - return !todo.isDone(); + return !todo.isCompleted(); }); }, - isAllDone: function() { - return this.length > 0 && - (this.getDone().length === this.length); + isAllCompleted: function() { + return (this.length > 0) && + (this.getCompleted().length === this.length); }, - markAllDone: function() { + markAllCompleted: function() { this.forEach(function(todo) { - todo.setDone(true); + todo.setCompleted(true); }); }, - markAllUndone: function() { + markAllIncompleted: function() { this.forEach(function(todo) { - todo.setDone(false); + todo.setCompleted(false); }); }, - deleteDone: function() { - this['delete'].apply(this, this.getDone()); + deleteCompleted: function() { + this['delete'].apply(this, this.getCompleted()); }, toJSON: function() { return this.map(function(todo) { diff --git a/labs/architecture-examples/maria/src/js/namespace.js b/labs/architecture-examples/maria/src/js/namespace.js index a6de85ee..99267cd5 100644 --- a/labs/architecture-examples/maria/src/js/namespace.js +++ b/labs/architecture-examples/maria/src/js/namespace.js @@ -1 +1 @@ -var checkit = checkit || {}; +var checkit = {}; diff --git a/labs/architecture-examples/maria/src/js/templates/TodoTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodoTemplate.js index d6de208b..20630920 100644 --- a/labs/architecture-examples/maria/src/js/templates/TodoTemplate.js +++ b/labs/architecture-examples/maria/src/js/templates/TodoTemplate.js @@ -7,14 +7,11 @@ // included here. // checkit.TodoTemplate = - '<li class="todo">' + - '<div class="display">' + - '<input class="check" type="checkbox">' + - '<span class="todo-content"></span>' + - '<span class="todo-destroy">delete</span>' + - '</div>' + - '<div class="edit">' + - '<input class="todo-input" type="text">' + - '<span class="ui-tooltip-top" style="display:none;">Press enter to update this task.</span>' + + '<li>' + + '<div class="view">' + + '<input class="toggle" type="checkbox">' + + '<label></label>' + + '<button class="destroy"></span>' + '</div>' + + '<input class="edit">' + '</li>'; diff --git a/labs/architecture-examples/maria/src/js/templates/TodosAppTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodosAppTemplate.js deleted file mode 100644 index ebcfbb7e..00000000 --- a/labs/architecture-examples/maria/src/js/templates/TodosAppTemplate.js +++ /dev/null @@ -1,14 +0,0 @@ -// In a full development environment this template would be expressed -// in a file containing only HTML and be compiled to the following as part -// of the server/build functionality. -// -// Due to the limitations of a simple example that does not require -// any special server environment to try, the manually compiled version is -// included here. -// -checkit.TodosAppTemplate = - '<div class="todos-app">' + - '<h1>Todos</h1>' + - '<div class="content"></div>' + - '<p class="copyright">Another fine widget from AlphaBeta.</p>' + - '</div>'; diff --git a/labs/architecture-examples/maria/src/js/templates/TodosInputTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodosInputTemplate.js deleted file mode 100644 index 7f2b6d22..00000000 --- a/labs/architecture-examples/maria/src/js/templates/TodosInputTemplate.js +++ /dev/null @@ -1,13 +0,0 @@ -// In a full development environment this template would be expressed -// in a file containing only HTML and be compiled to the following as part -// of the server/build functionality. -// -// Due to the limitations of a simple example that does not require -// any special server environment to try, the manually compiled version is -// included here. -// -checkit.TodosInputTemplate = - '<div class="create-todo">' + - '<input class="new-todo" placeholder="What needs to be done?" type="text">' + - '<span class="ui-tooltip-top" style="display:none;">Press enter to add this task.</span>' + - '</div>'; diff --git a/labs/architecture-examples/maria/src/js/templates/TodosListTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodosListTemplate.js deleted file mode 100644 index 8a3c98db..00000000 --- a/labs/architecture-examples/maria/src/js/templates/TodosListTemplate.js +++ /dev/null @@ -1,10 +0,0 @@ -// In a full development environment this template would be expressed -// in a file containing only HTML and be compiled to the following as part -// of the server/build functionality. -// -// Due to the limitations of a simple example that does not require -// any special server environment to try, the manually compiled version is -// included here. -// -checkit.TodosListTemplate = - '<ul class="todos-list"></ul>'; diff --git a/labs/architecture-examples/maria/src/js/templates/TodosStatsTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodosStatsTemplate.js deleted file mode 100644 index 0d6b4e28..00000000 --- a/labs/architecture-examples/maria/src/js/templates/TodosStatsTemplate.js +++ /dev/null @@ -1,10 +0,0 @@ -// In a full development environment this template would be expressed -// in a file containing only HTML and be compiled to the following as part -// of the server/build functionality. -// -// Due to the limitations of a simple example that does not require -// any special server environment to try, the manually compiled version is -// included here. -// -checkit.TodosStatsTemplate = - '<div class="todos-stats"><strong class="todos-count"></strong> todos in list</div>'; diff --git a/labs/architecture-examples/maria/src/js/templates/TodosTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodosTemplate.js new file mode 100644 index 00000000..6e2bb4df --- /dev/null +++ b/labs/architecture-examples/maria/src/js/templates/TodosTemplate.js @@ -0,0 +1,40 @@ +// In a full development environment this template would be expressed +// in a file containing only HTML and be compiled to the following as part +// of the server/build functionality. +// +// Due to the limitations of a simple example that does not require +// any special server environment to try, the manually compiled version is +// included here. +// +checkit.TodosAppTemplate = + '<section id="todoapp">' + + '<header id="header">' + + '<h1>todos</h1>' + + '<input id="new-todo" placeholder="What needs to be done?" autofocus>' + + '</header>' + + '<section id="main">' + + '<input id="toggle-all" type="checkbox">' + + '<label for="toggle-all">Mark all as completed</label>' + + '<ul id="todo-list"></ul>' + + '</section>' + + '<footer id="footer">' + + '<span id="todo-count"></span>' + + '<ul id="filters">' + + '<li>' + + '<a class="all-filter" href="#/">All</a>' + + '</li>' + + '<li>' + + '<a class="incompleted-filter" href="#/active">Active</a>' + + '</li>' + + '<li>' + + '<a class="completed-filter" href="#/completed">Completed</a>' + + '</li>' + + '</ul>' + + '<button id="clear-completed"></button>' + + '</footer>' + + '</section>' + + '<footer id="info">' + + '<p>Double-click to edit a todo</p>' + + '<p>Inspired by the official <a href="https://github.com/maccman/spine.todos">Spine.Todos</a></p>' + + '<p>Created by <a href="http://github.com/petermichaux">Peter Michaux</a></p>' + + '</footer>'; diff --git a/labs/architecture-examples/maria/src/js/templates/TodosToolbarTemplate.js b/labs/architecture-examples/maria/src/js/templates/TodosToolbarTemplate.js deleted file mode 100644 index 97edf4fa..00000000 --- a/labs/architecture-examples/maria/src/js/templates/TodosToolbarTemplate.js +++ /dev/null @@ -1,15 +0,0 @@ -// In a full development environment this template would be expressed -// in a file containing only HTML and be compiled to the following as part -// of the server/build functionality. -// -// Due to the limitations of a simple example that does not require -// any special server environment to try, the manually compiled version is -// included here. -// -checkit.TodosToolbarTemplate = - '<ul class="todos-toolbar">' + - '<li><input type="checkbox" class="allCheckbox"></li> ' + - '<li class="markallDone">mark all as complete</li> ' + - '<li class="markallUndone">mark all as incomplete</li> ' + - '<li class="deleteComplete">delete complete</li>' + - '</ul>'; diff --git a/labs/architecture-examples/maria/src/js/util.js b/labs/architecture-examples/maria/src/js/util.js new file mode 100644 index 00000000..e46d095b --- /dev/null +++ b/labs/architecture-examples/maria/src/js/util.js @@ -0,0 +1,15 @@ +checkit.trim = function(str) { + return str.replace(/^\s+|\s+$/g, ''); +}; + +checkit.isBlank = function(str) { + return /^\s*$/.test(str); +}; + +checkit.escapeHTML = function(str) { + return str.replace('&', '&').replace('<', '<'); +}; + +checkit.isEnterKeyCode = function(keyCode) { + return keyCode === 13; +}; diff --git a/labs/architecture-examples/maria/src/js/views/TodoView.js b/labs/architecture-examples/maria/src/js/views/TodoView.js index 98379796..e8e698e1 100644 --- a/labs/architecture-examples/maria/src/js/views/TodoView.js +++ b/labs/architecture-examples/maria/src/js/views/TodoView.js @@ -1,49 +1,37 @@ maria.ElementView.subclass(checkit, 'TodoView', { uiActions: { - 'mouseover .todo' : 'onMouseoverRoot' , - 'mouseout .todo' : 'onMouseoutRoot' , - 'click .check' : 'onClickCheck' , - 'click .todo-destroy': 'onClickDestroy' , - 'dblclick .todo-content': 'onDblclickDisplay', - 'keyup .todo-input' : 'onKeyupInput' , - 'keypress .todo-input' : 'onKeypressInput' , - 'blur .todo-input' : 'onBlurInput' + 'click .destroy': 'onClickDestroy' , + 'click .toggle' : 'onClickToggle' , + 'dblclick label' : 'onDblclickLabel', + 'keyup .edit' : 'onKeyupEdit' , + 'blur .edit' : 'onBlurEdit' }, properties: { buildData: function() { var model = this.getModel(); - var content = model.getContent(); - this.find('.todo-content').innerHTML = - content.replace('&', '&').replace('<', '<'); - this.find('.check').checked = model.isDone(); - aristocrat[model.isDone() ? 'addClass' : 'removeClass'](this.find('.todo'), 'done'); + + var item = this.find('li'); + aristocrat.removeClass(item, '(in|)completed'); + aristocrat.addClass(item, (model.isCompleted() ? 'completed' : 'incompleted')); + + this.find('label').innerHTML = checkit.escapeHTML(model.getTitle()); + + this.find('.toggle').checked = model.isCompleted(); }, update: function() { this.buildData(); }, showEdit: function() { - var input = this.find('.todo-input'); - input.value = this.getModel().getContent(); - aristocrat.addClass(this.find('.todo'), 'editing'); - input.focus(); + var input = this.find('.edit'); + input.value = this.getModel().getTitle(); + aristocrat.addClass(this.find('li'), 'editing'); + input.select(); }, showDisplay: function() { - aristocrat.removeClass(this.find('.todo'), 'editing'); + aristocrat.removeClass(this.find('li'), 'editing'); }, getInputValue: function() { - return this.find('.todo-input').value; - }, - showHoverState: function() { - aristocrat.addClass(this.find('.todo'), 'todo-hover'); - }, - hideHoverState: function() { - aristocrat.removeClass(this.find('.todo'), 'todo-hover'); - }, - showToolTip: function() { - this.find('.ui-tooltip-top').style.display = 'block'; - }, - hideToolTip: function() { - this.find('.ui-tooltip-top').style.display = 'none'; + return this.find('.edit').value; } } }); diff --git a/labs/architecture-examples/maria/src/js/views/TodosAppView.js b/labs/architecture-examples/maria/src/js/views/TodosAppView.js deleted file mode 100644 index 16bbf2ce..00000000 --- a/labs/architecture-examples/maria/src/js/views/TodosAppView.js +++ /dev/null @@ -1,29 +0,0 @@ -// All the subviews of a TodosAppView will always have -// the same model as the TodosAppView has. -// -maria.ElementView.subclass(checkit, 'TodosAppView', { - properties: { - getContainerEl: function() { - return this.find('.content'); // child views will be appended to this element - }, - initialize: function() { - this.appendChild(new checkit.TodosInputView()); - this.appendChild(new checkit.TodosToolbarView()); - this.appendChild(new checkit.TodosListView()); - this.appendChild(new checkit.TodosStatsView()); - }, - insertBefore: function(newChild, oldChild) { - newChild.setModel(this.getModel()); - maria.ElementView.prototype.insertBefore.call(this, newChild, oldChild); - }, - setModel: function(model) { - if (model !== this.getModel()) { - maria.ElementView.prototype.setModel.call(this, model); - var childViews = this.childNodes; - for (var i = 0, ilen = childViews.length; i < ilen; i++) { - childViews[i].setModel(model); - } - } - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/views/TodosInputView.js b/labs/architecture-examples/maria/src/js/views/TodosInputView.js deleted file mode 100644 index 79218a6b..00000000 --- a/labs/architecture-examples/maria/src/js/views/TodosInputView.js +++ /dev/null @@ -1,22 +0,0 @@ -maria.ElementView.subclass(checkit, 'TodosInputView', { - uiActions: { - 'focus .new-todo': 'onFocusInput' , - 'blur .new-todo': 'onBlurInput' , - 'keyup .new-todo': 'onKeyupInput' , - 'keypress .new-todo': 'onKeypressInput' - }, - properties: { - getInputValue: function() { - return this.find('.new-todo').value; - }, - clearInput: function() { - this.find('.new-todo').value = ''; - }, - showToolTip: function() { - this.find('.ui-tooltip-top').style.display = 'block'; - }, - hideToolTip: function() { - this.find('.ui-tooltip-top').style.display = 'none'; - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/views/TodosListView.js b/labs/architecture-examples/maria/src/js/views/TodosListView.js deleted file mode 100644 index 8ad16355..00000000 --- a/labs/architecture-examples/maria/src/js/views/TodosListView.js +++ /dev/null @@ -1,7 +0,0 @@ -maria.SetView.subclass(checkit, 'TodosListView', { - properties: { - createChildView: function(todoModel) { - return new checkit.TodoView(todoModel); - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/views/TodosStatsView.js b/labs/architecture-examples/maria/src/js/views/TodosStatsView.js deleted file mode 100644 index 8f455260..00000000 --- a/labs/architecture-examples/maria/src/js/views/TodosStatsView.js +++ /dev/null @@ -1,10 +0,0 @@ -maria.ElementView.subclass(checkit, 'TodosStatsView', { - properties: { - buildData: function() { - this.find('.todos-count').innerHTML = this.getModel().length; - }, - update: function() { - this.buildData(); - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/views/TodosToolbarView.js b/labs/architecture-examples/maria/src/js/views/TodosToolbarView.js deleted file mode 100644 index 54737ccf..00000000 --- a/labs/architecture-examples/maria/src/js/views/TodosToolbarView.js +++ /dev/null @@ -1,19 +0,0 @@ -maria.ElementView.subclass(checkit, 'TodosToolbarView', { - uiActions: { - 'click .allCheckbox' : 'onClickAllCheckbox' , - 'click .markallDone' : 'onClickMarkAllDone' , - 'click .markallUndone' : 'onClickMarkAllUndone', - 'click .deleteComplete': 'onClickDeleteDone' - }, - properties: { - buildData: function() { - var model = this.getModel(); - var checkbox = this.find('.allCheckbox'); - checkbox.checked = model.isAllDone(); - checkbox.disabled = model.isEmpty(); - }, - update: function() { - this.buildData(); - } - } -}); diff --git a/labs/architecture-examples/maria/src/js/views/TodosView.js b/labs/architecture-examples/maria/src/js/views/TodosView.js new file mode 100644 index 00000000..81d2a080 --- /dev/null +++ b/labs/architecture-examples/maria/src/js/views/TodosView.js @@ -0,0 +1,56 @@ +maria.SetView.subclass(checkit, 'TodosAppView', { + uiActions: { + 'keyup #new-todo' : 'onKeyupNewTodo' , + 'click #toggle-all' : 'onClickToggleAll' , + 'click #clear-completed': 'onClickClearCompleted' + }, + properties: { + buildData: function() { + var model = this.getModel(); + + var checkbox = this.find('#toggle-all'); + checkbox.checked = model.isAllCompleted(); + checkbox.disabled = model.isEmpty(); + + var todoList = this.find('#todo-list'); + model.getPossibleModes().forEach(function(mode) { + aristocrat.removeClass(todoList, mode); + }); + aristocrat.addClass(todoList, model.getMode()); + + var incompletedLength = model.getIncompleted().length; + this.find('#todo-count').innerHTML = + '<b>' + incompletedLength + '</b> ' + + ((incompletedLength === 1) ? 'item' : 'items') + + ' left'; + + var selected = this.find('.selected'); + if (selected) { + aristocrat.removeClass(selected, 'selected'); + } + aristocrat.addClass(this.find('.' + model.getMode() + '-filter'), 'selected'); + + var completedLength = model.getCompleted().length; + var clearButton = this.find('#clear-completed'); + clearButton.style.display = (completedLength > 0) ? '' : 'none'; + clearButton.innerHTML = 'Clear completed (' + completedLength + ')'; + }, + update: function(evt) { + maria.SetView.prototype.update.call(this, evt); + this.buildData(); + }, + getContainerEl: function() { + // child views will be appended to this element + return this.find('#todo-list'); + }, + createChildView: function(todoModel) { + return new checkit.TodoView(todoModel); + }, + getInputValue: function() { + return this.find('#new-todo').value; + }, + clearInput: function() { + this.find('#new-todo').value = ''; + } + } +}); -- 2.30.9