diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e6ab99d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,14 @@
+## Generated CouchApp
+
+This repo is a snapshot of the development going on in [The CouchApp generate command.](http://github.com/couchapp/couchapp/tree/master/templates/app/)
+
+It's easier for me to receive patches against couchapp/couchapp. This repo is intended primarily so you can deploy new versions of the example app without needing to upgrade CouchApp.
+
+Install with
+
+ couchapp push . http://localhost:5984/proto
+
+or (if you have security turned on)
+
+ couchapp push . http://myname:mypass@localhost:5984/proto
+
\ No newline at end of file
diff --git a/_attachments/couchcampfire.jpg b/_attachments/couchcampfire.jpg
new file mode 100644
index 0000000..10c0692
Binary files /dev/null and b/_attachments/couchcampfire.jpg differ
diff --git a/_attachments/couchcoon.jpg b/_attachments/couchcoon.jpg
new file mode 100644
index 0000000..64a80e5
Binary files /dev/null and b/_attachments/couchcoon.jpg differ
diff --git a/_attachments/index.html b/_attachments/index.html
index 7b20699..4569861 100644
--- a/_attachments/index.html
+++ b/_attachments/index.html
@@ -5,17 +5,66 @@
+
Please log in to create your profile. Signup is super easy, all you need is a name and password, none of the email verification or whatever. Cheers!
\ No newline at end of file
diff --git a/evently/profile/profileReady/mustache.html b/evently/profile/profileReady/mustache.html
index 1ad2bc0..ab0515b 100644
--- a/evently/profile/profileReady/mustache.html
+++ b/evently/profile/profileReady/mustache.html
@@ -1,5 +1,3 @@
-
{{#gravatar_url}}
{{/gravatar_url}}
@@ -8,7 +6,25 @@
\ No newline at end of file
diff --git a/evently/profile/profileReady/selectors/form/submit.js b/evently/profile/profileReady/selectors/form/submit.js
index bf18dd8..a97860c 100644
--- a/evently/profile/profileReady/selectors/form/submit.js
+++ b/evently/profile/profileReady/selectors/form/submit.js
@@ -1,13 +1,39 @@
-function() {
- var form = this;
+function(e) {
+ e.preventDefault();
+ var form = $(this);
+ var f = form.serializeObject();
var doc = {
created_at : new Date(),
profile : $$("#profile").profile,
- message : $("[name=message]", form).val()
+ bio : f.bio,
+ name : f.name,
+ company : f.company,
+ urls : {
+ www : f["url-www"],
+ sourcecode : f["url-sourcecode"],
+ twitter : f["url-twitter"],
+ couchdb : f["url-couchdb"]
+ },
+ email : f.email
};
- $$(this).app.db.saveDoc(doc, {
+ var db = $$(this).app.db;
+
+ db.saveDoc(doc, {
success : function() {
- $("[name=message]", form).val("");
+ $("input[name='_rev']", form).val(doc._rev);
+ var as = $("input[name='_attachments']", form).val();
+ if (as) {
+ $("[name=name]", form).val("Uploading file...");
+ // thank you cmlenz for Futon's original upload code
+ form.ajaxSubmit({
+ url: db.uri + $.couch.encodeDocId(doc._id),
+ success: function(resp) {
+ form[0].reset();
+ }
+ });
+ } else {
+ form[0].reset();
+ }
}
});
return false;
diff --git a/migrate.js b/migrate.js
new file mode 100644
index 0000000..98b4068
--- /dev/null
+++ b/migrate.js
@@ -0,0 +1,40 @@
+var couchdb = require("couchdb")
+ , client = couchdb.createClient(5984, "localhost", "xxx", "xxx")
+ , db = client.db("profiles")
+ ;
+
+db.allDocs(function(er, view) {
+ view.rows.forEach(function(r) {
+ console.log(r.id)
+ db.getDoc(r.id, {attachments:true}, function(er, doc) {
+ if (er) {
+ console.log("getDoc", er)
+ } else {
+ console.log(doc._id)
+ var rev = doc._rev;
+ var name = doc._id;
+ if (name.length < 30) {
+ delete doc._rev;
+ delete doc._id;
+ doc.name = name;
+ db.removeDoc(name, rev, function(er) {
+ if (er) {
+ console.log("error deleting", name, er)
+ } else {
+ console.log("deleted", name)
+ }
+ })
+ // console.log("update", doc)
+ // db.saveDoc(doc, function(er, resp) {
+ // if (er) {
+ // console.log(er)
+ // } else {
+ //
+ // }
+ // })
+ }
+ }
+ });
+ });
+
+});
\ No newline at end of file
diff --git a/vendor/couchapp/README.md b/vendor/couchapp/README.md
deleted file mode 100644
index 2606c1d..0000000
--- a/vendor/couchapp/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-## CouchApp - more than just a filesystem mapper
-
-This is where documentation will go for the client and server JavaScript parts of CouchApp.
\ No newline at end of file
diff --git a/vendor/couchapp/_attachments/docs.css b/vendor/couchapp/_attachments/docs.css
deleted file mode 100644
index 94b19d6..0000000
--- a/vendor/couchapp/_attachments/docs.css
+++ /dev/null
@@ -1,53 +0,0 @@
-body {
- font:1em Helvetica, sans-serif;
- margin:0;
- padding:4px;
-}
-
-h1 {
- margin:0.5em;
-}
-
-h2 {
- color:#222;
-}
-
-pre {
- padding:4px;
- margin:4px;
- background:#bbb;
-}
-
-#content {
- padding:4px;
- margin:2px;
-}
-
-#sidebar {
- float:right;
- width:34%;
-}
-
-#docs {
- -moz-box-shadow:0 0 2em #000;
- -webkit-box-shadow:0 0 2em #000;
- width:58%;
- padding:8px;
- margin:4px;
-}
-
-.example {
- background:#ffd;
- padding:4px;
- margin:4px;
- position:absolute;
-}
-
-textarea.code {
- width:100%;
-}
-
-.edit {
- float:right;
- font-size:0.8em;
-}
diff --git a/vendor/couchapp/_attachments/docs.html b/vendor/couchapp/_attachments/docs.html
deleted file mode 100644
index 77220b7..0000000
--- a/vendor/couchapp/_attachments/docs.html
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
Evently and CouchApp Docs
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/vendor/couchapp/_attachments/docs.js b/vendor/couchapp/_attachments/docs.js
deleted file mode 100644
index 7b0e4d0..0000000
--- a/vendor/couchapp/_attachments/docs.js
+++ /dev/null
@@ -1,8 +0,0 @@
-$.log = function() {
- // console.log(arguments)
-};
-
-$.couch.app(function(app) {
- $("#docs").evently(app.ddoc.vendor.couchapp.evently.docs, app);
- $.pathbinder.begin("/");
-});
\ No newline at end of file
diff --git a/vendor/couchapp/_attachments/jquery.couch.app.js b/vendor/couchapp/_attachments/jquery.couch.app.js
index da5a7fc..fc500a2 100644
--- a/vendor/couchapp/_attachments/jquery.couch.app.js
+++ b/vendor/couchapp/_attachments/jquery.couch.app.js
@@ -19,9 +19,14 @@
// });
(function($) {
-
- function Design(db, name) {
+
+ function Design(db, name, code) {
this.doc_id = "_design/"+name;
+ if (code) {
+ this.code_path = this.doc_id + "/" + code;
+ } else {
+ this.code_path = this.doc_id;
+ }
this.view = function(view, opts) {
db.view(name+'/'+view, opts);
};
@@ -30,193 +35,131 @@
};
}
- $.couch.app = $.couch.app || function(appFun, opts) {
- opts = opts || {};
- $(function() {
- var dbname = opts.db || document.location.href.split('/')[3];
- var dname = opts.design || unescape(document.location.href).split('/')[5];
- var db = $.couch.db(dbname);
- var design = new Design(db, dname);
-
- // docForm applies CouchDB behavior to HTML forms.
- // todo make this a couch.app plugin
- function docForm(formSelector, opts) {
- var localFormDoc = {};
- opts = opts || {};
- opts.fields = opts.fields || [];
+ function docForm() { alert("docForm has been moved to vendor/couchapp/lib/docForm.js, use app.require to load") };
- // turn the form into deep json
- // field names like 'author-email' get turned into json like
- // {"author":{"email":"quentin@example.com"}}
- function formToDeepJSON(form, fields, doc) {
- form = $(form);
- opts.fields.forEach(function(field) {
- var element = form.find("[name="+field+"]");
- if (element.attr('type') === 'checkbox') {
- var val = element.attr('checked');
- } else {
- var val = element.val();
- if (!val) return;
- }
- var parts = field.split('-');
- var frontObj = doc, frontName = parts.shift();
- while (parts.length > 0) {
- frontObj[frontName] = frontObj[frontName] || {};
- frontObj = frontObj[frontName];
- frontName = parts.shift();
- }
- frontObj[frontName] = val;
- });
- }
-
- // Apply the behavior
- $(formSelector).submit(function(e) {
- e.preventDefault();
- // formToDeepJSON acts on localFormDoc by reference
- formToDeepJSON(this, opts.fields, localFormDoc);
- if (opts.beforeSave) {opts.beforeSave(localFormDoc);}
- db.saveDoc(localFormDoc, {
- success : function(resp) {
- if (opts.success) {opts.success(resp, localFormDoc);}
- }
- });
-
- return false;
- });
+ function resolveModule(path, names, parents, current) {
+ parents = parents || [];
+ if (names.length === 0) {
+ if (typeof current != "string") {
+ throw ["error","invalid_require_path",
+ 'Must require a JavaScript string, not: '+(typeof current)];
+ }
+ return [current, parents];
+ }
+ var n = names.shift();
+ if (n == '..') {
+ parents.pop();
+ var pp = parents.pop();
+ if (!pp) {
+ throw ["error", "invalid_require_path", path];
+ }
+ return resolveModule(path, names, parents, pp);
+ } else if (n == '.') {
+ var p = parents.pop();
+ if (!p) {
+ throw ["error", "invalid_require_path", path];
+ }
+ return resolveModule(path, names, parents, p);
+ } else {
+ parents = [];
+ }
+ if (!current[n]) {
+ throw ["error", "invalid_require_path", path];
+ }
+ parents.push(current);
+ return resolveModule(path, names, parents, current[n]);
+ }
- // populate form from an existing doc
- function docToForm(doc) {
- var form = $(formSelector);
- // fills in forms
- opts.fields.forEach(function(field) {
- var parts = field.split('-');
- var run = true, frontObj = doc, frontName = parts.shift();
- while (frontObj && parts.length > 0) {
- frontObj = frontObj[frontName];
- frontName = parts.shift();
- }
- if (frontObj && frontObj[frontName]) {
- var element = form.find("[name="+field+"]");
- if (element.attr('type') === 'checkbox') {
- element.attr('checked', frontObj[frontName]);
- } else {
- element.val(frontObj[frontName]);
- }
- }
- });
- }
-
- if (opts.id) {
- db.openDoc(opts.id, {
- attachPrevRev : opts.attachPrevRev,
- success: function(doc) {
- if (opts.onLoad) {opts.onLoad(doc);}
- localFormDoc = doc;
- docToForm(doc);
- }});
- } else if (opts.template) {
- if (opts.onLoad) {opts.onLoad(opts.template);}
- localFormDoc = opts.template;
- docToForm(localFormDoc);
+ function makeRequire(ddoc) {
+ var moduleCache = [];
+ function getCachedModule(name, parents) {
+ var key, i, len = moduleCache.length;
+ for (i=0;i
'+a+'';
}).replace(/\@([\w\-]+)/g,function(user,name) {
- return ''+user+' ';
+ return ''+user+' ';
}).replace(/\#([\w\-\.]+)/g,function(word,tag) {
- return ''+word+' ';
+ return ''+word+' ';
});
};
$.fn.prettyDate = function() {
$(this).each(function() {
- $(this).text($.prettyDate($(this).text()));
+ var string, title = $(this).attr("title");
+ if (title) {
+ string = $.prettyDate(title);
+ } else {
+ string = $.prettyDate($(this).text());
+ }
+ $(this).text(string);
});
};
@@ -52,8 +75,9 @@ $.prettyDate = function(time){
day_diff == 1 && "yesterday" ||
day_diff < 21 && day_diff + " days ago" ||
day_diff < 45 && Math.ceil( day_diff / 7 ) + " weeks ago" ||
- day_diff < 730 && Math.ceil( day_diff / 31 ) + " months ago" ||
- Math.ceil( day_diff / 365 ) + " years ago";
+ time;
+ // day_diff < 730 && Math.ceil( day_diff / 31 ) + " months ago" ||
+ // Math.ceil( day_diff / 365 ) + " years ago";
};
$.argsToArray = function(args) {
diff --git a/vendor/couchapp/_attachments/jquery.evently.js b/vendor/couchapp/_attachments/jquery.evently.js
index 322f311..5128a20 100644
--- a/vendor/couchapp/_attachments/jquery.evently.js
+++ b/vendor/couchapp/_attachments/jquery.evently.js
@@ -22,7 +22,7 @@ function $$(node) {
}
};
$.forIn = forIn;
- function funViaString(fun) {
+ function funViaString(fun, hint) {
if (fun && fun.match && fun.match(/^function/)) {
eval("var f = "+fun);
if (typeof f == "function") {
@@ -31,7 +31,8 @@ function $$(node) {
return f.apply(this, arguments);
} catch(e) {
// IF YOU SEE AN ERROR HERE IT HAPPENED WHEN WE TRIED TO RUN YOUR FUNCTION
- // $.log({"message": "Error in evently function.", "error": e, "src" : fun});
+ $.log({"message": "Error in evently function.", "error": e,
+ "src" : fun, "hint":hint});
throw(e);
}
};
@@ -42,7 +43,7 @@ function $$(node) {
function runIfFun(me, fun, args) {
// if the field is a function, call it, bound to the widget
- var f = funViaString(fun);
+ var f = funViaString(fun, me);
if (typeof f == "function") {
return f.apply(me, args);
} else {
@@ -65,7 +66,8 @@ function $$(node) {
});
},
paths : [],
- changesDBs : {}
+ changesDBs : {},
+ changesOpts : {}
};
function extractFrom(name, evs) {
@@ -74,43 +76,71 @@ function $$(node) {
function extractEvents(name, ddoc) {
// extract events from ddoc.evently and ddoc.vendor.*.evently
- var events = [true, {}];
- $.forIn(ddoc.vendor, function(k, v) {
+ var events = [true, {}]
+ , vendor = ddoc.vendor || {}
+ , evently = ddoc.evently || {}
+ ;
+ $.forIn(vendor, function(k, v) {
if (v.evently && v.evently[name]) {
events.push(v.evently[name]);
}
});
- if (ddoc.evently[name]) {events.push(ddoc.evently[name]);}
+ if (evently[name]) {events.push(evently[name]);}
return $.extend.apply(null, events);
}
+ function extractPartials(ddoc) {
+ var partials = [true, {}]
+ , vendor = ddoc.vendor || {}
+ , evently = ddoc.evently || {}
+ ;
+ $.forIn(vendor, function(k, v) {
+ if (v.evently && v.evently._partials) {
+ partials.push(v.evently._partials);
+ }
+ });
+ if (evently._partials) {partials.push(evently._partials);}
+ return $.extend.apply(null, partials);
+ };
+
+ function applyCommon(events) {
+ if (events._common) {
+ $.forIn(events, function(k, v) {
+ events[k] = $.extend(true, {}, events._common, v);
+ });
+ delete events._common;
+ return events;
+ } else {
+ return events;
+ }
+ }
+
$.fn.evently = function(events, app, args) {
var elem = $(this);
// store the app on the element for later use
if (app) {
- $$(elem).app = app;
+ $$(elem).app = app;
}
if (typeof events == "string") {
events = extractEvents(events, app.ddoc);
}
-
+ events = applyCommon(events);
$$(elem).evently = events;
+ if (app && app.ddoc) {
+ $$(elem).partials = extractPartials(app.ddoc);
+ }
// setup the handlers onto elem
forIn(events, function(name, h) {
eventlyHandler(elem, name, h, args);
});
if (events._init) {
- // $.log("ev _init", elem);
elem.trigger("_init", args);
}
if (app && events._changes) {
- $("body").bind("evently.changes."+app.db.name, function() {
- // we want to unbind this function when the element is deleted.
- // maybe jquery 1.4.2 has this covered?
- // $.log('changes', elem);
+ $("body").bind("evently-changes-"+app.db.name, function() {
elem.trigger("_changes");
});
followChanges(app);
@@ -121,10 +151,15 @@ function $$(node) {
// eventlyHandler applies the user's handler (h) to the
// elem, bound to trigger based on name.
function eventlyHandler(elem, name, h, args) {
+ if ($.evently.log) {
+ elem.bind(name, function() {
+ $.log(elem, name);
+ });
+ }
if (h.path) {
elem.pathbinder(name, h.path);
}
- var f = funViaString(h);
+ var f = funViaString(h, name);
if (typeof f == "function") {
elem.bind(name, {args:args}, f);
} else if (typeof f == "string") {
@@ -140,7 +175,7 @@ function $$(node) {
} else {
// an object is using the evently / mustache template system
if (h.fun) {
- elem.bind(name, {args:args}, funViaString(h.fun));
+ throw("e.fun has been removed, please rename to e.before")
}
// templates, selectors, etc are intepreted
// when our named event is triggered.
@@ -165,7 +200,7 @@ function $$(node) {
// if there's a query object we run the query,
// and then call the data function with the response.
if (h.before && (!qrun || !arun)) {
- funViaString(h.before).apply(me, args);
+ funViaString(h.before, me).apply(me, args);
}
if (h.async && !arun) {
runAsync(me, h, args)
@@ -199,22 +234,22 @@ function $$(node) {
}
if (h.after) {
runIfFun(me, h.after, args);
- // funViaString(h.after).apply(me, args);
}
}
};
// todo this should return the new element
function mustachioed(me, h, args) {
+ var partials = $$(me).partials;
return $($.mustache(
runIfFun(me, h.mustache, args),
runIfFun(me, h.data, args),
- runIfFun(me, h.partials, args)));
+ runIfFun(me, $.extend(true, partials, h.partials), args)));
};
function runAsync(me, h, args) {
// the callback is the first argument
- funViaString(h.async).apply(me, [function() {
+ funViaString(h.async, me).apply(me, [function() {
renderElement(me, h,
$.argsToArray(arguments).concat($.argsToArray(args)), false, true);
}].concat($.argsToArray(args)));
@@ -232,7 +267,9 @@ function $$(node) {
var q = {};
forIn(qu, function(k, v) {
- q[k] = v;
+ if (["type", "view"].indexOf(k) == -1) {
+ q[k] = v;
+ }
});
if (qType == "newRows") {
@@ -316,34 +353,47 @@ function $$(node) {
// only start one changes listener per db
function followChanges(app) {
- var dbName = app.db.name;
+ var dbName = app.db.name, changeEvent = function(resp) {
+ $("body").trigger("evently-changes-"+dbName, [resp]);
+ };
if (!$.evently.changesDBs[dbName]) {
- connectToChanges(app, function() {
- $("body").trigger("evently.changes."+dbName);
- });
+ if (app.db.changes) {
+ // new api in jquery.couch.js 1.0
+ app.db.changes(null, $.evently.changesOpts).onChange(changeEvent);
+ } else {
+ // in case you are still on CouchDB 0.11 ;) deprecated.
+ connectToChanges(app, changeEvent);
+ }
$.evently.changesDBs[dbName] = true;
}
}
-
- function connectToChanges(app, fun) {
- function resetHXR(x) {
- x.abort();
- connectToChanges(app, fun);
+ $.evently.followChanges = followChanges;
+ // deprecated. use db.changes() from jquery.couch.js
+ // this does not have an api for closing changes request.
+ function connectToChanges(app, fun, update_seq) {
+ function changesReq(seq) {
+ var url = app.db.uri+"_changes?heartbeat=10000&feed=longpoll&since="+seq;
+ if ($.evently.changesOpts.include_docs) {
+ url = url + "&include_docs=true";
+ }
+ $.ajax({
+ url: url,
+ contentType: "application/json",
+ dataType: "json",
+ complete: function(req) {
+ var resp = $.httpData(req, "json");
+ fun(resp);
+ connectToChanges(app, fun, resp.last_seq);
+ }
+ });
};
- app.db.info({success: function(db_info) {
- var c_xhr = jQuery.ajaxSettings.xhr();
- c_xhr.open("GET", app.db.uri+"_changes?feed=continuous&since="+db_info.update_seq, true);
- c_xhr.send("");
- // todo use a timeout to prevent rapid triggers
- var t;
- c_xhr.onreadystatechange = function() {
- clearTimeout(t);
- t = setTimeout(fun, 100);
- };
- setTimeout(function() {
- resetHXR(c_xhr);
- }, 1000 * 60);
- }});
+ if (update_seq) {
+ changesReq(update_seq);
+ } else {
+ app.db.info({success: function(db_info) {
+ changesReq(db_info.update_seq);
+ }});
+ }
};
})(jQuery);
diff --git a/vendor/couchapp/_attachments/jquery.mustache.js b/vendor/couchapp/_attachments/jquery.mustache.js
index 701ed0d..5aa67de 100644
--- a/vendor/couchapp/_attachments/jquery.mustache.js
+++ b/vendor/couchapp/_attachments/jquery.mustache.js
@@ -8,13 +8,9 @@ See http://github.com/defunkt/mustache for more info.
;(function($) {
/*
- Shamless port of http://github.com/defunkt/mustache
- by Jan Lehnardt , Alexander Lang ,
- Sebastian Cohnen
+ mustache.js — Logic-less templates in JavaScript
- Thanks @defunkt for the awesome code.
-
- See http://github.com/defunkt/mustache for more info.
+ See http://mustache.github.com/ for more info.
*/
var Mustache = function() {
@@ -24,16 +20,45 @@ var Mustache = function() {
otag: "{{",
ctag: "}}",
pragmas: {},
+ buffer: [],
+ pragmas_implemented: {
+ "IMPLICIT-ITERATOR": true
+ },
+ context: {},
+
+ render: function(template, context, partials, in_recursion) {
+ // reset buffer & set context
+ if(!in_recursion) {
+ this.context = context;
+ this.buffer = []; // TODO: make this non-lazy
+ }
- render: function(template, context, partials) {
// fail fast
- if(template.indexOf(this.otag) == -1) {
- return template;
+ if(!this.includes("", template)) {
+ if(in_recursion) {
+ return template;
+ } else {
+ this.send(template);
+ return;
+ }
}
template = this.render_pragmas(template);
var html = this.render_section(template, context, partials);
- return this.render_tags(html, context, partials);
+ if(in_recursion) {
+ return this.render_tags(html, context, partials, in_recursion);
+ }
+
+ this.render_tags(html, context, partials, in_recursion);
+ },
+
+ /*
+ Sends parsed lines
+ */
+ send: function(line) {
+ if(line != "") {
+ this.buffer.push(line);
+ }
},
/*
@@ -41,56 +66,86 @@ var Mustache = function() {
*/
render_pragmas: function(template) {
// no pragmas
- if(template.indexOf(this.otag + "%") == -1) {
+ if(!this.includes("%", template)) {
return template;
}
var that = this;
- var regex = new RegExp(this.otag + "%(.+)" + this.ctag);
- return template.replace(regex, function(match, pragma) {
- that.pragmas[pragma] = true;
+ var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
+ this.ctag);
+ return template.replace(regex, function(match, pragma, options) {
+ if(!that.pragmas_implemented[pragma]) {
+ throw({message:
+ "This implementation of mustache doesn't understand the '" +
+ pragma + "' pragma"});
+ }
+ that.pragmas[pragma] = {};
+ if(options) {
+ var opts = options.split("=");
+ that.pragmas[pragma][opts[0]] = opts[1];
+ }
return "";
// ignore unknown pragmas silently
});
},
- /*
- Tries to find a partial in the global scope and render it
+ /*
+ Tries to find a partial in the curent scope and render it
*/
render_partial: function(name, context, partials) {
- if(typeof(context[name]) != "object") {
- throw({message: "subcontext for '" + name + "' is not an object"});
+ name = this.trim(name);
+ if(!partials || partials[name] === undefined) {
+ throw({message: "unknown_partial '" + name + "'"});
}
- if(!partials || !partials[name]) {
- throw({message: "unknown_partial"});
+ if(typeof(context[name]) != "object") {
+ return this.render(partials[name], context, partials, true);
}
- return this.render(partials[name], context[name], partials);
+ return this.render(partials[name], context[name], partials, true);
},
/*
- Renders boolean and enumerable sections
+ Renders inverted (^) and normal (#) sections
*/
render_section: function(template, context, partials) {
- if(template.indexOf(this.otag + "#") == -1) {
+ if(!this.includes("#", template) && !this.includes("^", template)) {
return template;
}
+
var that = this;
// CSW - Added "+?" so it finds the tighest bound, not the widest
- var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag +
- "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg");
+ var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
+ "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
+ "\\s*", "mg");
// for each {{#foo}}{{/foo}} section do...
- return template.replace(regex, function(match, name, content) {
+ return template.replace(regex, function(match, type, name, content) {
var value = that.find(name, context);
- if(that.is_array(value)) { // Enumerable, Let's loop!
- return that.map(value, function(row) {
- return that.render(content, that.merge(context,
- that.create_context(row)), partials);
- }).join('');
- } else if(value) { // boolean section
- return that.render(content, context, partials);
- } else {
- return "";
+ if(type == "^") { // inverted section
+ if(!value || that.is_array(value) && value.length === 0) {
+ // false or empty list, render it
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ } else if(type == "#") { // normal section
+ if(that.is_array(value)) { // Enumerable, Let's loop!
+ return that.map(value, function(row) {
+ return that.render(content, that.create_context(row),
+ partials, true);
+ }).join("");
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
+ return that.render(content, that.create_context(value),
+ partials, true);
+ } else if(typeof value === "function") {
+ // higher order section
+ return value.call(context, content, function(text) {
+ return that.render(text, context, partials, true);
+ });
+ } else if(value) { // boolean section
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
}
});
},
@@ -98,41 +153,43 @@ var Mustache = function() {
/*
Replace {{foo}} and friends with values from our view
*/
- render_tags: function(template, context, partials) {
- var lines = template.split("\n");
+ render_tags: function(template, context, partials, in_recursion) {
+ // tit for tat
+ var that = this;
var new_regex = function() {
- return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" +
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
that.ctag + "+", "g");
};
- // tit for tat
- var that = this;
-
var regex = new_regex();
- for (var i=0; i < lines.length; i++) {
- lines[i] = lines[i].replace(regex, function (match,operator,name) {
- switch(operator) {
- case "!": // ignore comments
- return match;
- case "=": // set new delimiters, rebuild the replace regexp
- that.set_delimiters(name);
- regex = new_regex();
- // redo the line in order to get tags with the new delimiters
- // on the same line
- i--;
- return "";
- case ">": // render partial
- return that.render_partial(name, context, partials);
- case "{": // the triple mustache is unescaped
- return that.find(name, context);
- return "";
- default: // escape the value
- return that.escape(that.find(name, context));
- }
- },this);
+ var tag_replace_callback = function(match, operator, name) {
+ switch(operator) {
+ case "!": // ignore comments
+ return "";
+ case "=": // set new delimiters, rebuild the replace regexp
+ that.set_delimiters(name);
+ regex = new_regex();
+ return "";
+ case ">": // render partial
+ return that.render_partial(name, context, partials);
+ case "{": // the triple mustache is unescaped
+ return that.find(name, context);
+ default: // escape the value
+ return that.escape(that.find(name, context));
+ }
};
- return lines.join("\n");
+ var lines = template.split("\n");
+ for(var i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
+ if(!in_recursion) {
+ this.send(lines[i]);
+ }
+ }
+
+ if(in_recursion) {
+ return lines.join("\n");
+ }
},
set_delimiters: function(delimiters) {
@@ -152,20 +209,33 @@ var Mustache = function() {
'(\\' + specials.join('|\\') + ')', 'g'
);
}
- return text.replace(arguments.callee.sRE, '\\$1');
+ return text.replace(arguments.callee.sRE, '\\$1');
},
/*
- find `name` in current `context`. That is find me a value
+ find `name` in current `context`. That is find me a value
from the view object
*/
find: function(name, context) {
name = this.trim(name);
- if(typeof context[name] === "function") {
- return context[name].apply(context);
+
+ // Checks whether a value is thruthy or false or 0
+ function is_kinda_truthy(bool) {
+ return bool === false || bool === 0 || bool;
+ }
+
+ var value;
+ if(is_kinda_truthy(context[name])) {
+ value = context[name];
+ } else if(is_kinda_truthy(this.context[name])) {
+ value = this.context[name];
+ }
+
+ if(typeof value === "function") {
+ return value.apply(context);
}
- if(context[name] !== undefined) {
- return context[name];
+ if(value !== undefined) {
+ return value;
}
// silently ignore unkown variables
return "";
@@ -173,82 +243,68 @@ var Mustache = function() {
// Utility methods
+ /* includes tag */
+ includes: function(needle, haystack) {
+ return haystack.indexOf(this.otag + needle) != -1;
+ },
+
/*
Does away with nasty characters
*/
escape: function(s) {
- return s.toString().replace(/[&"<>\\]/g, function(s) {
+ s = String(s === null ? "" : s);
+ return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
switch(s) {
- case "&": return "&";
- case "\\": return "\\\\";;
- case '"': return '\"';;
- case "<": return "<";
- case ">": return ">";
- default: return s;
+ case "&": return "&";
+ case "\\": return "\\\\";
+ case '"': return '\"';
+ case "<": return "<";
+ case ">": return ">";
+ default: return s;
}
});
},
- /*
- Merges all properties of object `b` into object `a`.
- `b.property` overwrites a.property`
- */
- merge: function(a, b) {
- var _new = {};
- for(var name in a) {
- if(a.hasOwnProperty(name)) {
- _new[name] = a[name];
- }
- };
- for(var name in b) {
- if(b.hasOwnProperty(name)) {
- _new[name] = b[name];
- }
- };
- return _new;
- },
-
// by @langalex, support for arrays of strings
create_context: function(_context) {
if(this.is_object(_context)) {
return _context;
- } else if(this.pragmas["JSTACHE-ENABLE-STRING-ARRAYS"]) {
- return {'.': _context};
+ } else {
+ var iterator = ".";
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
+ }
+ var ctx = {};
+ ctx[iterator] = _context;
+ return ctx;
}
},
is_object: function(a) {
- return a && typeof a == 'object'
+ return a && typeof a == "object";
},
- /*
- Thanks Doug Crockford
- JavaScript — The Good Parts lists an alternative that works better with
- frames. Frames can suck it, we use the simple version.
- */
is_array: function(a) {
- return (a &&
- typeof a === 'object' &&
- a.constructor === Array);
+ return Object.prototype.toString.call(a) === '[object Array]';
},
/*
Gets rid of leading and trailing whitespace
*/
trim: function(s) {
- return s.replace(/^\s*|\s*$/g, '');
+ return s.replace(/^\s*|\s*$/g, "");
},
/*
Why, why, why? Because IE. Cry, cry cry.
- */
+ */
map: function(array, fn) {
if (typeof array.map == "function") {
- return array.map(fn)
+ return array.map(fn);
} else {
var r = [];
var l = array.length;
- for(i=0;i';
- }).join(''));
+ for (var i=0; i < scripts.length; i++) {
+ document.write('