1
// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
3
var data = $(node).data("$$");
8
$(node).data("$$", data);
14
// utility functions used in the implementation
16
function forIn(obj, fun) {
19
if (obj.hasOwnProperty(name)) {
25
function funViaString(fun, hint) {
26
if (fun && fun.match && fun.match(/^function/)) {
28
if (typeof f == "function") {
31
return f.apply(this, arguments);
33
// IF YOU SEE AN ERROR HERE IT HAPPENED WHEN WE TRIED TO RUN YOUR FUNCTION
34
$.log({"message": "Error in evently function.", "error": e,
35
"src" : fun, "hint":hint});
44
function runIfFun(me, fun, args) {
45
// if the field is a function, call it, bound to the widget
46
var f = funViaString(fun, me);
47
if (typeof f == "function") {
48
return f.apply(me, args);
55
connect : function(source, target, events) {
56
events.forEach(function(ev) {
57
$(source).bind(ev, function() {
58
var args = $.makeArray(arguments);
59
// remove the original event to keep from stacking args extra deep
60
// it would be nice if jquery had a way to pass the original
61
// event to the trigger method.
63
$(target).trigger(ev, args);
73
function extractFrom(name, evs) {
77
function extractEvents(name, ddoc) {
78
// extract events from ddoc.evently and ddoc.vendor.*.evently
79
var events = [true, {}]
80
, vendor = ddoc.vendor || {}
81
, evently = ddoc.evently || {}
83
$.forIn(vendor, function(k, v) {
84
if (v.evently && v.evently[name]) {
85
events.push(v.evently[name]);
88
if (evently[name]) {events.push(evently[name]);}
89
return $.extend.apply(null, events);
92
function extractPartials(ddoc) {
93
var partials = [true, {}]
94
, vendor = ddoc.vendor || {}
95
, evently = ddoc.evently || {}
97
$.forIn(vendor, function(k, v) {
98
if (v.evently && v.evently._partials) {
99
partials.push(v.evently._partials);
102
if (evently._partials) {partials.push(evently._partials);}
103
return $.extend.apply(null, partials);
106
function applyCommon(events) {
107
if (events._common) {
108
$.forIn(events, function(k, v) {
109
events[k] = $.extend(true, {}, events._common, v);
111
delete events._common;
118
$.fn.evently = function(events, app, args) {
120
// store the app on the element for later use
125
if (typeof events == "string") {
126
events = extractEvents(events, app.ddoc);
128
events = applyCommon(events);
129
$$(elem).evently = events;
130
if (app && app.ddoc) {
131
$$(elem).partials = extractPartials(app.ddoc);
133
// setup the handlers onto elem
134
forIn(events, function(name, h) {
135
eventlyHandler(elem, name, h, args);
139
elem.trigger("_init", args);
142
if (app && events._changes) {
143
$("body").bind("evently-changes-"+app.db.name, function() {
144
elem.trigger("_changes");
147
elem.trigger("_changes");
151
// eventlyHandler applies the user's handler (h) to the
152
// elem, bound to trigger based on name.
153
function eventlyHandler(elem, name, h, args) {
155
elem.bind(name, function() {
160
elem.pathbinder(name, h.path);
162
var f = funViaString(h, name);
163
if (typeof f == "function") {
164
elem.bind(name, {args:args}, f);
165
} else if (typeof f == "string") {
166
elem.bind(name, {args:args}, function() {
167
$(this).trigger(f, arguments);
170
} else if ($.isArray(h)) {
171
// handle arrays recursively
172
for (var i=0; i < h.length; i++) {
173
eventlyHandler(elem, name, h[i], args);
176
// an object is using the evently / mustache template system
178
throw("e.fun has been removed, please rename to e.before")
180
// templates, selectors, etc are intepreted
181
// when our named event is triggered.
182
elem.bind(name, {args:args}, function() {
183
renderElement($(this), h, arguments);
189
$.fn.replace = function(elem) {
190
// $.log("Replace", this)
191
$(this).empty().append(elem);
194
// todo: ability to call this
195
// to render and "prepend/append/etc" a new element to the host element (me)
196
// as well as call this in a way that replaces the host elements content
197
// this would be easy if there is a simple way to get at the element we just appended
198
// (as html) so that we can attache the selectors
199
function renderElement(me, h, args, qrun, arun) {
200
// if there's a query object we run the query,
201
// and then call the data function with the response.
202
if (h.before && (!qrun || !arun)) {
203
funViaString(h.before, me).apply(me, args);
205
if (h.async && !arun) {
206
runAsync(me, h, args)
207
} else if (h.query && !qrun) {
208
// $.log("query before renderElement", arguments)
209
runQuery(me, h, args)
211
// $.log("renderElement")
212
// $.log(me, h, args, qrun)
213
// otherwise we just render the template with the current args
214
var selectors = runIfFun(me, h.selectors, args);
215
var act = (h.render || "replace").replace(/\s/g,"");
216
var app = $$(me).app;
218
// $.log("rendering", h.mustache)
219
var newElem = mustachioed(me, h, args);
223
if (act == "replace") {
228
forIn(selectors, function(selector, handlers) {
229
// $.log("selector", selector);
230
// $.log("selected", $(selector, s));
231
$(selector, s).evently(handlers, app, args);
232
// $.log("applied", selector);
236
runIfFun(me, h.after, args);
241
// todo this should return the new element
242
function mustachioed(me, h, args) {
243
var partials = $$(me).partials;
245
runIfFun(me, h.mustache, args),
246
runIfFun(me, h.data, args),
247
runIfFun(me, $.extend(true, partials, h.partials), args)));
250
function runAsync(me, h, args) {
251
// the callback is the first argument
252
funViaString(h.async, me).apply(me, [function() {
254
$.argsToArray(arguments).concat($.argsToArray(args)), false, true);
255
}].concat($.argsToArray(args)));
259
function runQuery(me, h, args) {
260
// $.log("runQuery: args", args)
261
var app = $$(me).app;
262
var qu = runIfFun(me, h.query, args);
264
var viewName = qu.view;
265
var userSuccess = qu.success;
266
// $.log("qType", qType)
269
forIn(qu, function(k, v) {
270
if (["type", "view"].indexOf(k) == -1) {
275
if (qType == "newRows") {
276
q.success = function(resp) {
277
// $.log("runQuery newRows success", resp.rows.length, me, resp)
278
resp.rows.reverse().forEach(function(row) {
279
renderElement(me, h, [row].concat($.argsToArray(args)), true)
281
if (userSuccess) userSuccess(resp);
283
newRows(me, app, viewName, q);
285
q.success = function(resp) {
286
// $.log("runQuery success", resp)
287
renderElement(me, h, [resp].concat($.argsToArray(args)), true);
288
userSuccess && userSuccess(resp);
291
app.view(viewName, q);
295
// this is for the items handler
296
// var lastViewId, highKey, inFlight;
297
// this needs to key per elem
298
function newRows(elem, app, view, opts) {
299
// $.log("newRows", arguments);
300
// on success we'll set the top key
301
var thisViewId, successCallback = opts.success, full = false;
302
function successFun(resp) {
303
// $.log("newRows success", resp)
304
$$(elem).inFlight = false;
305
var JSONhighKey = JSON.stringify($$(elem).highKey);
306
resp.rows = resp.rows.filter(function(r) {
307
return JSON.stringify(r.key) != JSONhighKey;
309
if (resp.rows.length > 0) {
310
if (opts.descending) {
311
$$(elem).highKey = resp.rows[0].key;
313
$$(elem).highKey = resp.rows[resp.rows.length -1].key;
316
if (successCallback) {successCallback(resp, full)};
318
opts.success = successFun;
320
if (opts.descending) {
321
thisViewId = view + (opts.startkey ? JSON.stringify(opts.startkey) : "");
323
thisViewId = view + (opts.endkey ? JSON.stringify(opts.endkey) : "");
325
// $.log(["thisViewId",thisViewId])
326
// for query we'll set keys
327
if (thisViewId == $$(elem).lastViewId) {
328
// we only want the rows newer than changesKey
329
var hk = $$(elem).highKey;
330
if (hk !== undefined) {
331
if (opts.descending) {
333
// opts.inclusive_end = false;
338
// $.log("add view rows", opts)
339
if (!$$(elem).inFlight) {
340
$$(elem).inFlight = true;
341
app.view(view, opts);
345
// $.log("new view stuff")
347
$$(elem).lastViewId = thisViewId;
348
$$(elem).highKey = undefined;
349
$$(elem).inFlight = true;
350
app.view(view, opts);
354
// only start one changes listener per db
355
function followChanges(app) {
356
var dbName = app.db.name, changeEvent = function(resp) {
357
$("body").trigger("evently-changes-"+dbName, [resp]);
359
if (!$.evently.changesDBs[dbName]) {
360
if (app.db.changes) {
361
// new api in jquery.couch.js 1.0
362
app.db.changes(null, $.evently.changesOpts).onChange(changeEvent);
364
// in case you are still on CouchDB 0.11 ;) deprecated.
365
connectToChanges(app, changeEvent);
367
$.evently.changesDBs[dbName] = true;
370
$.evently.followChanges = followChanges;
371
// deprecated. use db.changes() from jquery.couch.js
372
// this does not have an api for closing changes request.
373
function connectToChanges(app, fun, update_seq) {
374
function changesReq(seq) {
375
var url = app.db.uri+"_changes?heartbeat=10000&feed=longpoll&since="+seq;
376
if ($.evently.changesOpts.include_docs) {
377
url = url + "&include_docs=true";
381
contentType: "application/json",
383
complete: function(req) {
384
var resp = $.httpData(req, "json");
386
connectToChanges(app, fun, resp.last_seq);
391
changesReq(update_seq);
393
app.db.info({success: function(db_info) {
394
changesReq(db_info.update_seq);