X-Git-Url: http://git.onelab.eu/?p=myops.git;a=blobdiff_plain;f=web%2Fquery%2Fvendor%2Fcouchapp%2F_attachments%2Fjquery.evently.js;fp=web%2Fquery%2Fvendor%2Fcouchapp%2F_attachments%2Fjquery.evently.js;h=5128a209e3d62292bcb9d3c63fdca326aa0e99bb;hp=0000000000000000000000000000000000000000;hb=85070b3d456667f238051af1a2f1f2a0c12300ab;hpb=607f0e13927eb18075c375fa9ba5527da4fcbb44 diff --git a/web/query/vendor/couchapp/_attachments/jquery.evently.js b/web/query/vendor/couchapp/_attachments/jquery.evently.js new file mode 100644 index 0000000..5128a20 --- /dev/null +++ b/web/query/vendor/couchapp/_attachments/jquery.evently.js @@ -0,0 +1,399 @@ +// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/ +function $$(node) { + var data = $(node).data("$$"); + if (data) { + return data; + } else { + data = {}; + $(node).data("$$", data); + return data; + } +}; + +(function($) { + // utility functions used in the implementation + + function forIn(obj, fun) { + var name; + for (name in obj) { + if (obj.hasOwnProperty(name)) { + fun(name, obj[name]); + } + } + }; + $.forIn = forIn; + function funViaString(fun, hint) { + if (fun && fun.match && fun.match(/^function/)) { + eval("var f = "+fun); + if (typeof f == "function") { + return function() { + try { + 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, "hint":hint}); + throw(e); + } + }; + } + } + return fun; + }; + + function runIfFun(me, fun, args) { + // if the field is a function, call it, bound to the widget + var f = funViaString(fun, me); + if (typeof f == "function") { + return f.apply(me, args); + } else { + return fun; + } + } + + $.evently = { + connect : function(source, target, events) { + events.forEach(function(ev) { + $(source).bind(ev, function() { + var args = $.makeArray(arguments); + // remove the original event to keep from stacking args extra deep + // it would be nice if jquery had a way to pass the original + // event to the trigger method. + args.shift(); + $(target).trigger(ev, args); + return false; + }); + }); + }, + paths : [], + changesDBs : {}, + changesOpts : {} + }; + + function extractFrom(name, evs) { + return evs[name]; + }; + + function extractEvents(name, ddoc) { + // extract events from ddoc.evently and ddoc.vendor.*.evently + 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 (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; + } + + 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) { + elem.trigger("_init", args); + } + + if (app && events._changes) { + $("body").bind("evently-changes-"+app.db.name, function() { + elem.trigger("_changes"); + }); + followChanges(app); + elem.trigger("_changes"); + } + }; + + // 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, name); + if (typeof f == "function") { + elem.bind(name, {args:args}, f); + } else if (typeof f == "string") { + elem.bind(name, {args:args}, function() { + $(this).trigger(f, arguments); + return false; + }); + } else if ($.isArray(h)) { + // handle arrays recursively + for (var i=0; i < h.length; i++) { + eventlyHandler(elem, name, h[i], args); + } + } else { + // an object is using the evently / mustache template system + if (h.fun) { + throw("e.fun has been removed, please rename to e.before") + } + // templates, selectors, etc are intepreted + // when our named event is triggered. + elem.bind(name, {args:args}, function() { + renderElement($(this), h, arguments); + return false; + }); + } + }; + + $.fn.replace = function(elem) { + // $.log("Replace", this) + $(this).empty().append(elem); + }; + + // todo: ability to call this + // to render and "prepend/append/etc" a new element to the host element (me) + // as well as call this in a way that replaces the host elements content + // this would be easy if there is a simple way to get at the element we just appended + // (as html) so that we can attache the selectors + function renderElement(me, h, args, qrun, arun) { + // 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, me).apply(me, args); + } + if (h.async && !arun) { + runAsync(me, h, args) + } else if (h.query && !qrun) { + // $.log("query before renderElement", arguments) + runQuery(me, h, args) + } else { + // $.log("renderElement") + // $.log(me, h, args, qrun) + // otherwise we just render the template with the current args + var selectors = runIfFun(me, h.selectors, args); + var act = (h.render || "replace").replace(/\s/g,""); + var app = $$(me).app; + if (h.mustache) { + // $.log("rendering", h.mustache) + var newElem = mustachioed(me, h, args); + me[act](newElem); + } + if (selectors) { + if (act == "replace") { + var s = me; + } else { + var s = newElem; + } + forIn(selectors, function(selector, handlers) { + // $.log("selector", selector); + // $.log("selected", $(selector, s)); + $(selector, s).evently(handlers, app, args); + // $.log("applied", selector); + }); + } + if (h.after) { + runIfFun(me, h.after, 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, $.extend(true, partials, h.partials), args))); + }; + + function runAsync(me, h, args) { + // the callback is the first argument + funViaString(h.async, me).apply(me, [function() { + renderElement(me, h, + $.argsToArray(arguments).concat($.argsToArray(args)), false, true); + }].concat($.argsToArray(args))); + }; + + + function runQuery(me, h, args) { + // $.log("runQuery: args", args) + var app = $$(me).app; + var qu = runIfFun(me, h.query, args); + var qType = qu.type; + var viewName = qu.view; + var userSuccess = qu.success; + // $.log("qType", qType) + + var q = {}; + forIn(qu, function(k, v) { + if (["type", "view"].indexOf(k) == -1) { + q[k] = v; + } + }); + + if (qType == "newRows") { + q.success = function(resp) { + // $.log("runQuery newRows success", resp.rows.length, me, resp) + resp.rows.reverse().forEach(function(row) { + renderElement(me, h, [row].concat($.argsToArray(args)), true) + }); + if (userSuccess) userSuccess(resp); + }; + newRows(me, app, viewName, q); + } else { + q.success = function(resp) { + // $.log("runQuery success", resp) + renderElement(me, h, [resp].concat($.argsToArray(args)), true); + userSuccess && userSuccess(resp); + }; + // $.log(app) + app.view(viewName, q); + } + } + + // this is for the items handler + // var lastViewId, highKey, inFlight; + // this needs to key per elem + function newRows(elem, app, view, opts) { + // $.log("newRows", arguments); + // on success we'll set the top key + var thisViewId, successCallback = opts.success, full = false; + function successFun(resp) { + // $.log("newRows success", resp) + $$(elem).inFlight = false; + var JSONhighKey = JSON.stringify($$(elem).highKey); + resp.rows = resp.rows.filter(function(r) { + return JSON.stringify(r.key) != JSONhighKey; + }); + if (resp.rows.length > 0) { + if (opts.descending) { + $$(elem).highKey = resp.rows[0].key; + } else { + $$(elem).highKey = resp.rows[resp.rows.length -1].key; + } + }; + if (successCallback) {successCallback(resp, full)}; + }; + opts.success = successFun; + + if (opts.descending) { + thisViewId = view + (opts.startkey ? JSON.stringify(opts.startkey) : ""); + } else { + thisViewId = view + (opts.endkey ? JSON.stringify(opts.endkey) : ""); + } + // $.log(["thisViewId",thisViewId]) + // for query we'll set keys + if (thisViewId == $$(elem).lastViewId) { + // we only want the rows newer than changesKey + var hk = $$(elem).highKey; + if (hk !== undefined) { + if (opts.descending) { + opts.endkey = hk; + // opts.inclusive_end = false; + } else { + opts.startkey = hk; + } + } + // $.log("add view rows", opts) + if (!$$(elem).inFlight) { + $$(elem).inFlight = true; + app.view(view, opts); + } + } else { + // full refresh + // $.log("new view stuff") + full = true; + $$(elem).lastViewId = thisViewId; + $$(elem).highKey = undefined; + $$(elem).inFlight = true; + app.view(view, opts); + } + }; + + // only start one changes listener per db + function followChanges(app) { + var dbName = app.db.name, changeEvent = function(resp) { + $("body").trigger("evently-changes-"+dbName, [resp]); + }; + if (!$.evently.changesDBs[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; + } + } + $.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); + } + }); + }; + if (update_seq) { + changesReq(update_seq); + } else { + app.db.info({success: function(db_info) { + changesReq(db_info.update_seq); + }}); + } + }; + +})(jQuery);