From 38b84f5885e340f67202c148e3d364efe264c816 Mon Sep 17 00:00:00 2001 From: Alex Robson Date: Thu, 27 Oct 2011 23:26:26 -0500 Subject: [PATCH 01/48] Added template resolver stub --- ext/infuser.js | 204 +++++++++++ ext/replicant.js | 723 ++++++++++++++++++++++++++++++++++++++ ext/trafficCop.min.js | 1 + html/simple.coffee | 194 ++++++++++ html/simple.css | 141 ++++++++ html/simple.html | 81 +++++ lib/cartographer.js | 52 ++- lib/cartographer.min.js | 2 +- src/cartographer.coffee | 1 + src/main.coffee | 11 +- src/sourceResolver.coffee | 25 ++ src/template.coffee | 11 +- 12 files changed, 1428 insertions(+), 18 deletions(-) create mode 100644 ext/infuser.js create mode 100644 ext/replicant.js create mode 100644 ext/trafficCop.min.js create mode 100644 html/simple.coffee create mode 100644 html/simple.css create mode 100644 html/simple.html create mode 100644 src/sourceResolver.coffee diff --git a/ext/infuser.js b/ext/infuser.js new file mode 100644 index 0000000..43cd0e3 --- /dev/null +++ b/ext/infuser.js @@ -0,0 +1,204 @@ +(function(global, undefined) { +/* + infuser.js + Author: Jim Cowart + License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license) + Version 0.1.0 +*/ +var NO_OP = function() { }, + defaultRenderOptions = { + preRender: NO_OP, + render: function(target, template) { + if($(target).children().length === 0) { + $(target).append($(template)); + } + else { + $(target).children().replaceWith($(template)); + } + }, + postRender: NO_OP + }; +var hashStorage = { + templates: {}, + + storeTemplate: function(templateId, templateContent) { + this.templates[templateId] = templateContent; + }, + + getTemplate: function(templateId) { + return this.templates[templateId]; + }, + + purge: function() { + this.templates = {}; + } +}; +var scriptStorage = { + templateIds: [], + storeTemplate: function(templateId, templateContent) { + var node = document.getElementById(templateId); + if(node === null) { + this.templateIds.push(templateId); + node = document.createElement("script"); + node.type = "text/html"; + node.id = templateId; + document.body.appendChild(node); + } + node.text = templateContent; + }, + + getTemplate: function(templateId) { + return document.getElementById(templateId); + }, + + purge: function() { + for(var i = 0; i < this.templateIds.length; i++) { + document.body.removeChild(document.getElementById(this.templateIds[i])); + } + this.templateIds = []; + } +}; +var errorHtml = "
The template {TEMPLATEID} could not be loaded. {STATUS}
", + returnErrorTemplate = function(status, templateId, templatePath) { + return errorHtml.replace('{STATUS}', status).replace('{TEMPLATEID}', templateId).replace('{TEMPLATEURL}', templatePath); + }, + errors = []; +var helpers = { + getTemplatePath: function(templateId) { + var templateFile = infuser.config.templatePrefix + templateId + infuser.config.templateSuffix; + return infuser.config.templateUrl === undefined || infuser.config.templateUrl === "" ? + templateFile : infuser.config.templateUrl + "/" + templateFile; + }, + templateGetSuccess: function(templateId, callback) { + return function(response) { + infuser.store.storeTemplate(templateId, response); + callback(infuser.store.getTemplate(templateId)); + }; + }, + templateGetError: function(templateId, templatePath, callback) { + return function(exception) { + if($.inArray(templateId, errors) === -1) { + errors.push(templateId); + } + var templateHtml = returnErrorTemplate("HTTP Status code: " + exception.status, templateId, templatePath); + infuser.store.storeTemplate(templateId, templateHtml); + callback(infuser.store.getTemplate(templateId)); + }; + } +}; +var trafficCop = { + + inProgress: {}, + + direct: function(reqOptions) { + var key = reqOptions.type + "_" + reqOptions.dataType + "_" + reqOptions.url, + self = this; + if(!self.inProgress[key]) { + var remove = function() { + delete self.inProgress[key]; + }, + traffic = { + successCallbacks: [reqOptions.success], + errorCallbacks: [reqOptions.error], + success: function(response) { + $.each($(self.inProgress[key].successCallbacks), function(idx,item){ item(response); }); + remove(); + }, + error: function(exception) { + $.each($(self.inProgress[key].errorCallbacks), function(idx,item){ item(exception); }); + remove(); + } + }; + self.inProgress[key] = $.extend({}, reqOptions, traffic); + $.ajax(self.inProgress[key]); + } + else { + self.inProgress[key].successCallbacks.push(reqOptions.success); + self.inProgress[key].errorCallbacks.push(reqOptions.error); + } + } +}; +var infuser = { + storageOptions: { + hash: hashStorage, + script: scriptStorage + }, + + store: hashStorage, + + config: { + templateUrl: "", + templateSuffix: ".html", + templatePrefix: "", + renderInstruction: function(template, model) { return template; }, // NO_OP + domTargetResolver: function(templateId) { return "#" + templateId } // DEFAULT MAPPING + }, + + get: function(templateId, callback) { + var template = this.store.getTemplate(templateId), + templatePath, + options; + if(!template || $.inArray(templateId, errors) !== -1) { + templatePath = helpers.getTemplatePath(templateId); + options = { + "async": true, + "url":templatePath, + "dataType": "html", + "type": "GET", + "success": helpers.templateGetSuccess(templateId, callback), + "error" : helpers.templateGetError(templateId, templatePath, callback) + }; + trafficCop.direct(options); + } + else { + callback(template); + } + }, + + getSync: function(templateId) { + var template = this.store.getTemplate(templateId), + templatePath, + templateHtml, + options; + if(!template || $.inArray(templateId, errors) !== -1) { + templatePath = helpers.getTemplatePath(templateId); + templateHtml = null; + options = { + "async": false, + "url":templatePath, + "dataType": "html", + "type": "GET", + "success": function(response) { templateHtml = response;}, + "error": function(exception) { + if($.inArray(templateId) === -1) { + errors.push(templateId); + } + templateHtml = returnErrorTemplate("HTTP Status code: exception.status", templateId, templatePath); + } + }; + $.ajax(options); + if(templateHtml === null) { + templateHtml = returnErrorTemplate("An unknown error occurred.", templateId, templatePath); + } + else { + this.store.storeTemplate(templateId, templateHtml); + template = this.store.getTemplate(templateId); + } + } + return template; + }, + + infuse: function(templateId, renderOptions) { + var options = $.extend({}, defaultRenderOptions, renderOptions), + self = this, + targetElement = options.targetSelector || self.config.domTargetResolver(templateId); + self.get(templateId, function(template) { + var _template = template; + options.preRender(targetElement, _template); + _template = self.config.renderInstruction(_template, options.model); + options.render(targetElement, _template); + options.postRender(targetElement); + }); + } +}; +global.infuser = infuser; })(window); \ No newline at end of file diff --git a/ext/replicant.js b/ext/replicant.js new file mode 100644 index 0000000..a23d0d2 --- /dev/null +++ b/ext/replicant.js @@ -0,0 +1,723 @@ +(function(context) { +/* + replicant + author: Alex Robson <@A_Robson> + License: MIT ( http://www.opensource.org/licenses/mit-license ) + Version: 0.1.0 +*/ +var buildFqn, onProxyOf; +buildFqn = function(path, name) { + if (path === "") { + return name; + } else { + return "" + path + "." + name; + } +}; +onProxyOf = function(value, ifArray, ifObject, otherwise) { + var isArray, isFunction, isObject; + isObject = _(value).isObject(); + isArray = _(value).isArray(); + isFunction = _(value).isFunction(); + if (!isFunction && (isObject || isArray)) { + if (isArray) { + return ifArray(); + } else { + return ifObject(); + } + } else { + return otherwise(); + } +}; +var Dependency, DependencyManager, dependencyManager; +Dependency = function(context, key, dependencies) { + var self; + self = this; + _(dependencies).chain().each(function(x) { + return self[x] = true; + }); + this.add = function(dependency) { + return self[dependency] = true; + }; + this.isHit = function(fqn) { + return self[fqn]; + }; + this.key = key; + this.target = context; + return self; +}; +DependencyManager = function() { + var addDependency, checkDependencies, dependencies, notify, self, watchingFor; + dependencies = []; + watchingFor = null; + self = this; + addDependency = function(context, fqn, key) { + var dependency; + dependency = _.detect(dependencies, function(x) { + return x.key === fqn; + }); + if (dependency) { + return dependency.add(key); + } else { + dependency = new Dependency(context, fqn, [key]); + return dependencies.push(dependency); + } + }; + checkDependencies = function(channelName, key) { + return _(dependencies).chain().select(function(x) { + return x.isHit(key); + }).each(function(x) { + return self[channelName].publish({ + event: "wrote", + parent: x.target, + key: x.key, + info: { + value: x.target[x.key], + previous: null + } + }); + }); + }; + notify = function(key, event, info) { + return onEvent(wrapper, key, event, info); + }; + this.watchFor = function(fqn) { + return watchingFor = fqn; + }; + this.endWatch = function() { + return watchingFor = null; + }; + this.recordAccess = function(proxy, key) { + if (watchingFor) { + return addDependency(proxy, watchingFor, key); + } + }; + this.addNamespace = function(namespace) { + var channelName; + channelName = namespace + "_model"; + self[channelName] = postal.channel(channelName); + return self[channelName].subscribe(function(m) { + if (m.event === "wrote" || m.event === "added" || m.event === "removed") { + return checkDependencies(channelName, m.key); + } + }); + }; + return self; +}; +dependencyManager = new DependencyManager(); +var ArrayWrapper, ObjectWrapper, Proxy; +ObjectWrapper = function(target, onEvent, namespace, addChild, removeChild) { + var proxy; + proxy = new Proxy(this, target, onEvent, namespace, addChild, removeChild); + this.change_path = function(p) { + return proxy.change_path(p); + }; + this.addDependencyProperty = function(key, observable) { + return proxy.addDependencyProperty(key, observable); + }; + this.extractAs = function(alias) { + return replicant.create(proxy.original, null, alias); + }; + this.getOriginal = function() { + return proxy.original; + }; + this.isProxy = true; + this.subscribe = function(channelName) { + return proxy.subscribe(channelName); + }; + this.getPath = function() { + return proxy.getPath(); + }; + this.getRoot = function() { + return proxy.getRoot(); + }; + this.getChannel = function() { + return proxy.getChannel(); + }; + return this; +}; +ArrayWrapper = function(target, onEvent, namespace, addChild, removeChild) { + var proxy; + proxy = new Proxy(this, target, onEvent, namespace, addChild, removeChild); + this.change_path = function(p) { + return proxy.change_path(p); + }; + this.addDependencyProperty = function(key, observable) { + return proxy.addDependencyProperty(key, observable); + }; + this.extractAs = function(alias) { + return replicant.create(proxy.original, null, alias); + }; + this.getOriginal = function() { + return proxy.original; + }; + this.getPath = function() { + return proxy.getPath(); + }; + this.getChannel = function() { + return proxy.getChannel(); + }; + this.getRoot = function() { + return proxy.getRoot(); + }; + this.isProxy = true; + this.pop = function() { + return proxy.pop(); + }; + this.push = function(value) { + return proxy.push(value); + }; + this.shift = function() { + return proxy.shift(); + }; + this.subscribe = function(channelName) { + return proxy.subscribe(channelName); + }; + this.unshift = function(value) { + return proxy.unshift(value); + }; + this.reverse = function() { + return proxy.reverse(); + }; + this.sort = function(fn) { + return proxy.sort(fn); + }; + this.join = function(separator) { + return proxy.join(separator); + }; + this.toString = function() { + return proxy.toString(); + }; + this.splice = function() { + return proxy.splice.apply(proxy, Array.prototype.slice.call(arguments, 0)); + }; + this.slice = function() { + return proxy.slice.apply(proxy, Array.prototype.slice.call(arguments, 0)); + }; + this.indexOf = function(x) { + return proxy.indexOf(x); + }; + this.lastIndexOf = function(x) { + return proxy.lastIndexOf(x); + }; + this.concat = function() { + return proxy.concat.apply(proxy, Array.prototype.slice.call(arguments, 0)); + }; + return this; +}; +Proxy = function(wrapper, target, onEvent, namespace, addChild, removeChild) { + var addChildPath, addToParent, ancestors, createMemberProxy, createProxyFor, fullPath, getLocalFqn, getLocalPath, notify, path, proxy, proxySubscription, readHook, removeChildPath, removeFromParent, self, subject, unwindAncestralDependencies, walk; + self = this; + fullPath = namespace || (namespace = ""); + addToParent = addChild || function() {}; + getLocalPath = function() { + var parts; + parts = fullPath.split('.'); + if (parts.length > 0) { + return parts[parts.length - 1]; + } else { + return fullPath; + } + }; + path = getLocalPath(); + proxy = {}; + removeFromParent = removeChild || function() {}; + subject = target; + ancestors = []; + readHook = null; + proxySubscription = {}; + addChildPath = function(lqn, child, key) { + var fqn, isRoot, propertyName; + isRoot = ancestors.length === 0; + fqn = buildFqn(path, lqn); + propertyName = isRoot ? fqn : lqn; + Object.defineProperty(wrapper, propertyName, { + get: function() { + return child[key]; + }, + set: function(value) { + return child[key] = value; + }, + configurable: true + }); + if (child !== wrapper && !_.any(child.ancestors, function(x) { + return x === wrapper; + })) { + child.ancestors.push(wrapper); + } + return addToParent(fqn, child, key); + }; + this.subscribe = function(channelName) { + if (proxySubscription && proxySubscription.unsubscribe) { + proxySubscription.unsubscribe(); + } + return proxySubscription = postal.channel(channelName).subscribe(function(m) { + if (m.event === "onchange") { + return wrapper[m.id] = m.control.value; + } + }); + }; + this.getHandler = function() { + return onEvent; + }; + createMemberProxy = function(key) { + var fqn, isRoot; + fqn = buildFqn(path, key); + createProxyFor(true, buildFqn(fullPath, key), key); + Object.defineProperty(wrapper, key, { + get: function() { + var fqn1, value; + fqn1 = buildFqn(fullPath, key); + value = createProxyFor(false, fqn1, key); + notify(fqn1, "read", { + value: value + }); + dependencyManager.recordAccess(wrapper, fqn1); + unwindAncestralDependencies(); + return value; + }, + set: function(value) { + var fqn1, newValue, old; + fqn1 = buildFqn(fullPath, key); + old = proxy[key]; + subject[key] = value; + newValue = createProxyFor(true, fqn1, key); + return notify(fqn1, "wrote", { + value: value, + previous: old + }); + }, + configurable: true, + enumerable: true + }); + isRoot = ancestors.length === 0; + if (isRoot && fullPath !== "") { + return addChildPath(key, wrapper, key); + } else { + return addToParent(fqn, wrapper, key); + } + }; + createProxyFor = function(writing, fqn, key) { + var value; + value = subject[key]; + value = value.getOriginal ? value.getOriginal() : value; + if (writing || proxy[key] === void 0) { + proxy[key] = onProxyOf(value, function() { + return new ArrayWrapper(value, onEvent, fqn, addChildPath, removeChildPath); + }, function() { + return new ObjectWrapper(value, onEvent, fqn, addChildPath, removeChildPath); + }, function() { + return value; + }); + } + return proxy[key]; + }; + getLocalFqn = function(fqn) { + var base, parts, result; + parts = fqn.split("."); + base = subject.constructor.name; + return result = (function() { + switch (parts.length) { + case 0: + return base; + default: + return "" + base + "." + parts[parts.length - 1]; + } + })(); + }; + notify = function(key, event, info) { + return onEvent(wrapper, key, event, info); + }; + removeChildPath = function(fqn) { + delete wrapper[fqn]; + return removeFromParent(fqn); + }; + unwindAncestralDependencies = function() { + return _(ancestors).chain().select(function(x) { + return x instanceof ArrayWrapper; + }).each(function(x) { + return dependencyManager.recordAccess(x, "" + x.getPath + ".length"); + }); + }; + walk = function(target) { + var dependencyList; + _(target).chain().keys().select(function(x) { + return x !== "__dependencies__"; + }).each(function(key) { + return createMemberProxy(key); + }); + dependencyList = target.__dependencies__; + if (dependencyList) { + return _(dependencyList).chain().keys().each(function(key) { + return self.addDependencyProperty(key, dependencyList[key]); + }); + } + }; + this.change_path = function(p) { + return fullPath = p; + }; + this.getChannel = function() { + return proxySubscription; + }; + this.getHandler = function() { + return onEvent; + }; + this.getPath = function() { + return fullPath; + }; + this.getRoot = function() { + if (ancestors.length < 1) { + return wrapper; + } else { + return ancestors[ancestors.length - 1]; + } + }; + this.original = subject; + this.add = function(key, keys) { + this.recrawl(keys); + notify(buildFqn(fullPath, "length"), "wrote", { + value: subject.length, + previous: -1 + subject.length + }); + return notify(buildFqn(fullPath, key), "added", { + index: key, + value: wrapper[key] + }); + }; + this.push = function(value) { + var key; + key = -1 + subject.push(value); + return this.add(key, [key]); + }; + this.unshift = function(value) { + var _i, _ref, _results; + subject.unshift(value); + return this.add(0, (function() { + _results = []; + for (var _i = 0, _ref = subject.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); } + return _results; + }).apply(this)); + }; + this.remove = function(key, value, keys) { + this.recrawl(keys); + notify(buildFqn(fullPath, "length"), "wrote", { + value: subject.length, + previous: 1 + subject.length + }); + notify(buildFqn(fullPath, key), "removed", { + index: subject.length, + value: value + }); + return value; + }; + this.recrawl = function(keys) { + var k, _i, _len, _results; + _results = []; + for (_i = 0, _len = keys.length; _i < _len; _i++) { + k = keys[_i]; + _results.push(createMemberProxy(k)); + } + return _results; + }; + this.genReadNotices = function(keys) { + var k, _i, _len, _results; + _results = []; + for (_i = 0, _len = keys.length; _i < _len; _i++) { + k = keys[_i]; + _results.push(notify(buildFqn(fullPath, k), "read", { + value: wrapper[k] + })); + } + return _results; + }; + this.pop = function() { + var key, value; + key = subject.length - 1; + value = wrapper[key]; + subject.pop(); + removeChildPath(key); + return this.remove(key, value, []); + }; + this.shift = function() { + var key, value, _i, _ref, _results; + key = 0; + value = wrapper[key]; + subject.shift(); + removeChildPath(key); + return this.remove(key, value, (function() { + _results = []; + for (var _i = 0, _ref = subject.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); } + return _results; + }).apply(this)); + }; + this.reverse = function() { + var old; + old = wrapper; + subject.reverse(); + walk(subject); + return notify(fullPath, "reversed", { + index: subject.length, + value: wrapper, + previous: old + }); + }; + this.sort = function() { + var old; + old = wrapper; + subject.sort.apply(subject, arguments); + walk(subject); + return notify(fullPath, "sorted", { + index: subject.length, + value: wrapper, + previous: old + }); + }; + this.join = function() { + var value, _i, _ref, _results; + value = subject.join.apply(subject, arguments); + this.genReadNotices((function() { + _results = []; + for (var _i = 0, _ref = subject.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); } + return _results; + }).apply(this)); + return value; + }; + this.toString = function() { + var value, _i, _ref, _results; + value = subject.toString.apply(subject, arguments); + this.genReadNotices((function() { + _results = []; + for (var _i = 0, _ref = subject.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; 0 <= _ref ? _i++ : _i--){ _results.push(_i); } + return _results; + }).apply(this)); + return value; + }; + this.indexOf = function(x) { + var i, len, value; + i = void 0; + value = -1; + len = void 0; + if (typeof x === "ObjectWrapper" || typeof x === "ArrayWrapper") { + len = wrapper.length; + while (i < len) { + if (wrapper[i] === x) { + value = i; + break; + } + i++; + } + } else { + value = subject.indexOf(x); + } + notify(buildFqn(fullPath, value), "read", { + index: value, + value: wrapper[value] + }); + return value; + }; + this.lastIndexOf = function(x) { + var i, value; + i = wrapper.length - 1; + value = -1; + if (typeof x === "ObjectWrapper" || typeof x === "ArrayWrapper") { + while (i >= 0) { + if (wrapper[i] === x) { + value = i; + break; + } + i--; + } + } else { + value = subject.indexOf(x); + } + notify(buildFqn(fullPath, value), "read", { + index: value, + value: wrapper[value] + }); + return value; + }; + this.splice = function() { + var args, chgLen, i, k, len, newItems, stIdx, subjLen, value, _ref; + args = Array.prototype.slice.call(arguments, 0, 2); + newItems = Array.prototype.slice.call(arguments, 2); + len = newItems.length; + subjLen = subject.length; + value = []; + i = 0; + stIdx = 0; + if (args[0] >= 0) { + stIdx = i = args[0]; + } else { + stIdx = i = subject.length + args[0]; + } + chgLen = args[1] + args[0]; + while (i <= subjLen) { + if (i < chgLen) { + value.push(wrapper[i]); + this.remove(i, wrapper[i], []); + } + removeChildPath(i); + i++; + } + subject.splice.apply(subject, args.concat(newItems)); + for (k = stIdx, _ref = subject.length - 1; stIdx <= _ref ? k <= _ref : k >= _ref; stIdx <= _ref ? k++ : k--) { + this.add(k, [k]); + } + return replicant.create(value, fullPath); + }; + this.slice = function() { + var endIdx, startIdx, value; + startIdx = 0; + endIdx = subject.length; + value = []; + if (arguments[0] < 0) { + startIdx = subject.length + arguments[0]; + } else { + startIdx = arguments[0]; + } + if (arguments[1]) { + if (arguments[1] < 0) { + endIdx = subject.length + arguments[1]; + } else { + endIdx = arguments[1]; + } + } + while (startIdx < endIdx) { + value.push(wrapper[startIdx]); + startIdx++; + } + return replicant.create(value, fullPath); + }; + this.concat = function() { + var args, i, len, value; + value = []; + args = Array.prototype.slice.call(arguments, 0); + i = 0; + len = subject.length; + while (i < subject.length) { + value.push(wrapper[i]); + i++; + } + i = 0; + _.each(args, function(arg) { + if (_.isArray(arg)) { + return _.each(arg, function(item) { + return value.push(item); + }); + } else { + return value.push(arg); + } + }); + return replicant.create(value, fullPath); + }; + Object.defineProperty(wrapper, "length", { + get: function() { + var fqn1; + fqn1 = buildFqn(fullPath, "length"); + notify(fqn1, "read", { + value: subject.length + }); + dependencyManager.recordAccess(wrapper, fqn1); + return subject.length; + } + }); + Object.defineProperty(wrapper, "ancestors", { + get: function() { + return ancestors; + }, + set: function(x) { + return ancestors = x; + }, + enumerable: false, + configurable: true + }); + this.addDependencyProperty = function(key, observable) { + var fqn1, isRoot; + fqn1 = buildFqn(fullPath, key); + Object.defineProperty(wrapper, key, { + get: function() { + var result; + dependencyManager.watchFor(fqn1); + result = observable(wrapper); + dependencyManager.endWatch(); + return result; + } + }); + wrapper[key]; + isRoot = ancestors.length === 0; + if (isRoot && fullPath !== "") { + return addChildPath(key, wrapper, key); + } else { + return addToParent(buildFqn(path, key), wrapper, key); + } + }; + walk(target); + addToParent(buildFqn(path, "length"), wrapper, "length"); + return this; +}; +var Replicant; +Replicant = function() { + var add, proxies, self; + self = this; + proxies = {}; + add = function(name, proxy) { + proxies[name] = proxy; + if (!self[name]) { + return Object.defineProperty(self, name, { + get: function() { + return proxies[name]; + } + }); + } + }; + postal.channel("replicant").subscribe(function(m) { + if (m.create) { + return self.create(m.target, m.onevent, m.namespace); + } else if (m.get) { + return m.callback(proxies[m.name]); + } else { + return _(proxies).each(function(x) { + return x.getChannel().publish(m); + }); + } + }); + this.create = function(target, onevent, namespace) { + var channel, nmspc, onEvent, proxy; + onEvent = function(parent, key, event, info) { + return channel.publish({ + event: event, + parent: parent, + key: key, + info: info + }); + }; + nmspc = ""; + if (arguments.length === 2) { + if (typeof arguments[1] === "function") { + onEvent = onevent; + } else { + nmspc = onevent; + } + } else if (arguments.length === 3) { + onEvent = onevent || (onevent = onEvent); + nmspc = namespace; + } + dependencyManager.addNamespace(nmspc); + channel = postal.channel(nmspc + "_model"); + proxy = onProxyOf(target, function() { + return new ArrayWrapper(target, onEvent, nmspc); + }, function() { + return new ObjectWrapper(target, onEvent, nmspc); + }, function() { + return target; + }); + (function() { + return target; + }); + proxy.subscribe(nmspc + "_events"); + add(nmspc, proxy); + return proxy; + }; + return self; +}; +context["replicant"] = new Replicant(); +})(window); \ No newline at end of file diff --git a/ext/trafficCop.min.js b/ext/trafficCop.min.js new file mode 100644 index 0000000..925bf94 --- /dev/null +++ b/ext/trafficCop.min.js @@ -0,0 +1 @@ +(function(a,b){var c={inProgress:{},direct:function(a){var b=a.url+"_"+a.dataType+"_"+a.type;if(!this.inProgress[b]){var c=function(){delete this.inProgress[b]},d={successCallbacks:[a.success],errorCallbacks:[a.error],success:function(a){$.each(this.successCallbacks,function(b,c){c(a)}),c()},error:function(a){$.each(this.errorCallbacks,function(b,c){c(a)}),c()}};this.inProgress[b]=$.extend({},a,d),$.ajax(this.inProgress[b])}else this.inProgress[b].successCallbacks.push(a.success),this.inProgress[b].errorCallbacks.push(a.error)}};a.infuser=infuser})(window) \ No newline at end of file diff --git a/html/simple.coffee b/html/simple.coffee new file mode 100644 index 0000000..5c53102 --- /dev/null +++ b/html/simple.coffee @@ -0,0 +1,194 @@ +dom = undefined + +Ingredient = ( item, qty ) -> + + hovered: false + click: (r,x) -> JSON.stringify console.log.info + mouseover: (r,x) -> + @display.showbtn.hide = false + x.control.className = "ingredient highlight" + this.display.showbtn.class = "button inline-block" + @hovered = true + mouseout: (r,y) -> + @hovered = false + window.setTimeout ((x) -> + if not x.hovered + x.display.showbtn.hide = true + x.display.showbtn.class = "button" + y.control.className = "ingredient" + ) + , 100 + , this + display: + hide: false + item: item + qty: qty + showbtn: + class: "button" + hide: true + click: (p, x) -> + editor = this.ancestors[1].edit + display = this.ancestors[0] + display.hide = true + editor.hide = false + edit: + item: item + qty: qty + okbtn: + value: "ok" + click: (p, x) -> + editor = this.ancestors[0] + display = this.ancestors[1].display + editor.hide = true + display.hide = false + display.item = editor.item + display.qty = editor.qty + cancelbtn: + value: "cancel" + click: (p, x) -> + editor = this.ancestors[0] + display = this.ancestors[1].display + editor.hide = true + display.hide = false + hide: true + + +Step = ( step, detail ) -> + { + __template__: "steps" + step: step + detail: detail + } + +BuildIngredientList = ( list, recipe ) -> + recipe.ingredients.push( new Ingredient( x[0],x[1] ) ) for x in list + +BuildSteps = ( list, recipe ) -> + recipe.steps.push( new Step( x[0], x[1] ) ) for x in list + +Recipe = ( Title, Description, Ingredients, Steps ) -> + recipe = + title: + value: Title + hovered: false + click: (r,x) -> + cartographer.apply( "recipe", this.ancestors[0].extractAs "recipe" ) + mouseover: (r,x) -> + @hovered = true + mouseout: (r,y) -> + @hovered = false + + description: Description + ingredients: [] + steps: [] + + sort: + value: "Sort" + click: () -> + this.ancestors[0].ingredients.sort( (x,y) -> + if x.display.item < y.display.item then -1 else 1 + ) + + dumpus: + click: (root) -> + console.log JSON.stringify(root) + value: "Click for view model state" + + newIngredient: + quantity: + value: "" + click: (x, y) -> y.control.select() + item: + value: "" + click: (x, y) -> y.control.select() + btn: + click: (root) -> + list = root.ingredients + newItem = root.newIngredient + list.push( + new Ingredient newItem.item, newItem.quantity + ) + this.ancestors[0].item = "" + this.ancestors[0].quantity = "" + + __dependencies__: + stuff: (x) -> + items = _.pluck(x.ingredients, 'display.item') + "Item list: #{items.toString()}" + + BuildIngredientList( Ingredients, recipe ) + BuildSteps( Steps, recipe ) + + recipe + +recipe1 = new Recipe( + "Monkey Pot Pie", + "Savory chunks of monkey under a crispy crust", + [ + ["pastry flour","1 cup"], + ["shortening","1/2 cup"], + ["milk","1/2 cup"], + ["egg","1 large"], + ["adult monkey","1 lb"], + ["carrots","2 cups diced"], + ["corn","1 cup"], + ["celery","1 cup diced"], + ["banana","1 sliced"], + ], + [ + ["preheat","the oven to 425."], + ["combine","everything in big friggen bowl."], + ["trick","the monkey into the bowl with the banana."], + ["bake","until the monkey stops screaming."] + ] + ) + +recipe2 = new Recipe( + "Beer cheese soup", + "An excuse to eat beer", + [ + ["Pabst Blue Ribbon","6 pack"], + ["Mr. Block of Cheese",""], + ], + [ + ["eat","the entire Mr. Block of Cheese."], + ["chug","all the beer."], + ] +) + +recipes = [ recipe1, recipe2 ] + +#list = replicant.create recipes, null, "recipes" + +$( -> + repl = postal.channel("replicant") + repl.publish + create: true, + target: recipes, + namespace: "recipes" + + cart = postal.channel("cartographer") + + cart.publish + map: true + name: "list" + namespace: "recipes" + + cart.publish + map: true + name: "recipe" + namespace: "recipe" + + repl.publish + get: true + name: "recipes" + callback: (x) -> + cart.publish + apply: true + template: "list" + proxy: x + + #cartographer.map( "#recipe" ) + #cartographer.map( "#recipes > #list" ) + #cartographer.apply( "list", list ) +) diff --git a/html/simple.css b/html/simple.css new file mode 100644 index 0000000..4fe2bfe --- /dev/null +++ b/html/simple.css @@ -0,0 +1,141 @@ +body { + font-family: Georgia; + font-size: 1.2em; +} + +input { + font-size: 1em; + font-family: Georgia; + background-color: transparent; + box-shadow: .1em .1em .1em #0d3349; + border-bottom: #0d3349 solid thin; + padding: .15em .5em; +} + +input:focus { + border-top: #0d3349 solid thin; + box-shadow: -.1em -.1em .1em #0d3349; +} + +input[type=button] { + border: thin black solid; + margin: .0em .3em; + padding: .15em .5em; + box-shadow: .1em .1em .1em #0d3349; +} + +input[type=button]:hover { + text-shadow: #0d3349; + box-shadow: .15em .15em .15em #0d3349; + background-color: white; +} + +label { + font-size: 1.4em; +} + +ul { + list-style: none; +} + +span { + margin: .0em .3em; + padding: .15em .5em; + border: thin transparent solid; +} + +.button { + border: thin black solid; + margin: .0em .3em; + padding: .15em .5em; + box-shadow: .1em .1em .1em #0d3349; +} + + + +.button:hover { + + text-shadow: #0d3349; + box-shadow: .15em .15em .15em #0d3349; + background-color: #f5fAff; +} + +.hidden { + display: none; +} + +.inline-block { + display: inline-block; +} + + + +.ingredient { + display: inline-block; + height: 1.5em; + padding: .25em; + width: 30em; +} + +.ingredient:nth-child(even) { + background-color: #E2E0E6; +} + +.ingredient:hover { + background-color: #f0f8ff; +} + +.ingredient input[type=button] { + display: none; +} + +.ingredient:hover input[type=button] { + display: inline-block; +} + +.ingredient input { + display: inline-block; + margin: 0em .2em; +} + +.ingredient span { + display: inline-block; +} + +.ingredient #qty { + width: 8em; +} + +.ingredient #item { + width: 12em; +} + +#steps span { + display: inline-block; +} + +#steps li:nth-child(even) { + background-color: #E2E0E6; +} + +#steps #step { + font-weight: bold; + vertical-align: top; + width: 6em; +} + +#steps #detail { + width: 16em; +} + +.segment { + width: 40%; +} + +h2 { + font-size: 1.6em; +} + +.heading { + font-size: 1.6em; +} \ No newline at end of file diff --git a/html/simple.html b/html/simple.html new file mode 100644 index 0000000..3184c97 --- /dev/null +++ b/html/simple.html @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + +
+ Recipes + +
+ + + + + + \ No newline at end of file diff --git a/lib/cartographer.js b/lib/cartographer.js index f970d38..7f61a26 100644 --- a/lib/cartographer.js +++ b/lib/cartographer.js @@ -5,32 +5,65 @@ License: MIT ( http://www.opensource.org/licenses/mit-license ) Version: 0.1.0 */ +var SourceResolver, resolver; +SourceResolver = function() { + var checkPage, checkSources, self, sources; + self = this; + sources = []; + checkPage = function(name) { + var element; + element = $('#' + name); + if (element) { + return element[0]; + } else { + return null; + } + }; + checkSources = function(name) { + var result; + result = _.find(sources, function(s) { + return s.resolve(name); + }); + return $(result)[0]; + }; + this.addSource = function(source) { + return sources.push(source); + }; + this.resolve = function(name) { + var embedded; + embedded = checkPage(name); + return embedded || (embedded = checkSources(name)); + }; + return self; +}; +resolver = new SourceResolver(); var Cartographer; Cartographer = function() { var self; self = this; postal.channel("cartographer").subscribe(function(m) { if (m.map) { - return self.map(m.target, m.namespace); + return self.map(m.name); } else if (m.apply) { return self.apply(m.template, m.proxy, m.render, m.error); } }); this.templates = {}; - this.map = function(target, namespace) { + this.map = function(name, namespace) { var template; - template = new Template(target, namespace); - return this.templates[template.fqn] = template; + template = new Template(name, namespace); + return this.templates[name] = template; }; this.apply = function(template, proxy, render, error) { var result, templateInstance; + template = template || (template = proxy.__template__); templateInstance = this.templates[template]; if (templateInstance) { result = templateInstance.apply(proxy); if (render) { return render(result, templateInstance.fqn); } else { - return $("#" + templateInstance.fqn).replaceWith(result); + return $("#" + templateInstance.name).replaceWith(result); } } else if (error) { return error(); @@ -40,7 +73,7 @@ Cartographer = function() { }; context["cartographer"] = new Cartographer(); var Template; -Template = function(target, namespace) { +Template = function(name, namespace) { var conditionalCopy, copyProperties, crawl, createFqn, makeTag, self, setupEvents, subscribe, wireup; self = this; var eventHandlers, modelTargets, modelTargetsForCollections, templateProperties; @@ -243,7 +276,7 @@ templateProperties = { this.changeSubscription.ubsubscribe(); } return this.changesSubscription = postal.channel(channelName).subscribe(function(m) { - var addName, childKey, control, lastIndex, newElement, parentKey; + var addName, childKey, control, lastIndex, newElement, parentKey, target; if (m.event !== "read") { control = context[m.key]; lastIndex = m.key.lastIndexOf("."); @@ -307,8 +340,9 @@ templateProperties = { fn = crawl(this, model, namespace, this.element, this.apply); return fn(this.html, model); }; - this.element = $(target)[0]; - this.fqn = createFqn(namespace, this.element["id"]); + this.name = name; + this.fqn = namespace; + this.element = resolver.resolve(name); this.eventChannel = postal.channel(this.fqn + "_events"); this.html = DOMBuilder.dom; this.template = {}; diff --git a/lib/cartographer.min.js b/lib/cartographer.min.js index 2fdc436..671047b 100644 --- a/lib/cartographer.min.js +++ b/lib/cartographer.min.js @@ -1 +1 @@ -(function(a){var b;b=function(){var a;return a=this,postal.channel("cartographer").subscribe(function(b){if(b.map)return a.map(b.target,b.namespace);if(b.apply)return a.apply(b.template,b.proxy,b.render,b.error)}),this.templates={},this.map=function(a,b){var d;return d=new c(a,b),this.templates[d.fqn]=d},this.apply=function(a,b,c,d){var e,f;f=this.templates[a];if(f)return e=f.apply(b),c?c(e,f.fqn):$("#"+f.fqn).replaceWith(e);if(d)return d()},a},a.cartographer=new b;var c;c=function(a,b){var c,d,e,f,g,h,i,j,k;h=this;var l,m,n,o;return l={click:"onclick",dblclick:"ondblclick",mousedown:"onmousedown",mouseup:"onmouseup",mouseover:"onmouseover",mousemove:"onmousemove",mouseout:"onmouseout",keydown:"onkeydown",keypress:"onkeypress",keyup:"onkeyup",select:"onselect",change:"onchange",focus:"onfocus",blur:"onblur",scroll:"onscroll",resize:"onresize",submit:"onsubmit"},m={hide:"hidden",title:"title","class":"className",value:["value","textContent"]},n={hide:"hidden",title:"title",value:"value","class":"className"},o={id:"id",name:"name",title:"title",className:"class",type:"type",width:"width",height:"height",value:"value"},c=function(a,b,c,d){var e,f,g,h,i;e=a[c];if(e!==void 0){if(_.isArray(d)){i=[];for(g=0,h=d.length;g0?(j=function(){var c,f,g,h;g=d.children,h=[];for(c=0,f=g.length;c=x;0<=x?t++:t--)u.push(function(){var a,b,d;d=[];for(a=0,b=j.length;a0?(j=function(){var c,e,g,h;g=d.children,h=[];for(c=0,e=g.length;c=x;0<=x?t++:t--)u.push(function(){var a,b,d;d=[];for(a=0,b=j.length;a postal.channel("cartographer").subscribe (m) -> if m.map - self.map m.target, m.namespace + self.map m.name else if m.apply self.apply m.template, m.proxy, m.render, m.error @templates = {} - @map = ( target, namespace ) -> - template = new Template target, namespace - @templates[template.fqn] = template + @map = ( name, namespace ) -> + template = new Template name, namespace + @templates[name] = template @apply = ( template, proxy, render, error ) -> + template = template or= proxy.__template__ templateInstance = @templates[template] if templateInstance result = templateInstance.apply proxy if render render( result, templateInstance.fqn ) else - $("#" + templateInstance.fqn ).replaceWith( result ) + $("#" + templateInstance.name ).replaceWith( result ) else if error error() diff --git a/src/sourceResolver.coffee b/src/sourceResolver.coffee new file mode 100644 index 0000000..12d6a7d --- /dev/null +++ b/src/sourceResolver.coffee @@ -0,0 +1,25 @@ +SourceResolver = () -> + self = this + sources = [] + + checkPage = (name) -> + element = $( '#' + name ) + if element + return element[0] + else + null + + checkSources = (name) -> + result = _.find( sources, (s) -> s.resolve name ) + $(result)[0] + + @addSource = (source) -> + sources.push source + + @resolve = (name) -> + embedded = checkPage name + embedded or= checkSources name + + self + +resolver = new SourceResolver() \ No newline at end of file diff --git a/src/template.coffee b/src/template.coffee index 0472dcb..1a23b6c 100644 --- a/src/template.coffee +++ b/src/template.coffee @@ -1,4 +1,4 @@ -Template = (target, namespace) -> +Template = (name, namespace) -> self = this ###import "lists.coffee" #### @@ -20,6 +20,8 @@ Template = (target, namespace) -> tag = element.tagName.toUpperCase() context = context or root + + if element.children != undefined and element.children.length > 0 createChildren = ( crawl( context, root, fqn, child ) for child in element.children ) @@ -146,8 +148,11 @@ Template = (target, namespace) -> @apply = (model) -> fn = crawl this, model, namespace, @element, @apply fn @html, model - @element = $(target)[0] - @fqn = createFqn namespace, @element["id"] + #@element = $(target)[0] + #@fqn = createFqn namespace, @element["id"] + @name = name + @fqn = namespace + @element = resolver.resolve name @eventChannel = postal.channel(@fqn + "_events") @html = DOMBuilder.dom @template = {} From e88fee763e1ba453c49374a7344bec0010f21eb1 Mon Sep 17 00:00:00 2001 From: Alex Robson Date: Fri, 28 Oct 2011 08:41:29 -0500 Subject: [PATCH 02/48] Breaking everything :( --- html/simple.coffee | 17 +-- html/simple.html | 107 +++++++++-------- lib/cartographer.js | 241 ++++++++++++++++++++++---------------- lib/cartographer.min.js | 2 +- src/main.coffee | 12 +- src/sourceResolver.coffee | 25 ++-- src/template.coffee | 122 ++++++++++--------- 7 files changed, 297 insertions(+), 229 deletions(-) diff --git a/html/simple.coffee b/html/simple.coffee index 5c53102..c65233e 100644 --- a/html/simple.coffee +++ b/html/simple.coffee @@ -55,7 +55,6 @@ Ingredient = ( item, qty ) -> Step = ( step, detail ) -> { - __template__: "steps" step: step detail: detail } @@ -64,7 +63,7 @@ BuildIngredientList = ( list, recipe ) -> recipe.ingredients.push( new Ingredient( x[0],x[1] ) ) for x in list BuildSteps = ( list, recipe ) -> - recipe.steps.push( new Step( x[0], x[1] ) ) for x in list + recipe.steps.value.push( new Step( x[0], x[1] ) ) for x in list Recipe = ( Title, Description, Ingredients, Steps ) -> recipe = @@ -80,7 +79,9 @@ Recipe = ( Title, Description, Ingredients, Steps ) -> description: Description ingredients: [] - steps: [] + steps: + __template__: "steps" + value: [] sort: value: "Sort" @@ -156,9 +157,8 @@ recipe2 = new Recipe( ] ) -recipes = [ recipe1, recipe2 ] - -#list = replicant.create recipes, null, "recipes" +recipes = + list: [ recipe1, recipe2 ] $( -> repl = postal.channel("replicant") @@ -171,7 +171,7 @@ $( -> cart.publish map: true - name: "list" + name: "recipes" namespace: "recipes" cart.publish @@ -185,8 +185,9 @@ $( -> callback: (x) -> cart.publish apply: true - template: "list" + template: "recipes" proxy: x + render: (x) -> $("#recipes > #list").replaceWith(x) #cartographer.map( "#recipe" ) #cartographer.map( "#recipes > #list" ) diff --git a/html/simple.html b/html/simple.html index 3184c97..983f7bd 100644 --- a/html/simple.html +++ b/html/simple.html @@ -22,60 +22,69 @@ + -