Add scripts to create myops-getqueryview:
[myops.git] / web / query / vendor / couchapp / _attachments / jquery.evently.js
diff --git a/web/query/vendor/couchapp/_attachments/jquery.evently.js b/web/query/vendor/couchapp/_attachments/jquery.evently.js
new file mode 100644 (file)
index 0000000..5128a20
--- /dev/null
@@ -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);