~tim-alwaysreformed/reformedchurcheslocator/rcl-backbone-test_for_directory_type

« back to all changes in this revision

Viewing changes to vendor/couchapp/_attachments/jquery.evently.js

  • Committer: Tim Black
  • Date: 2012-04-11 05:59:38 UTC
  • Revision ID: tim@alwaysreformed.com-20120411055938-ol24g07q32msyg2f
Re-include default couchapp login div, remove unnecessary templates, JavaScript files, and functions, install Evently

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// $$ inspired by @wycats: http://yehudakatz.com/2009/04/20/evented-programming-with-jquery/
 
2
function $$(node) {
 
3
  var data = $(node).data("$$");
 
4
  if (data) {
 
5
    return data;
 
6
  } else {
 
7
    data = {};
 
8
    $(node).data("$$", data);
 
9
    return data;
 
10
  }
 
11
};
 
12
 
 
13
(function($) {
 
14
  // utility functions used in the implementation
 
15
  
 
16
  function forIn(obj, fun) {
 
17
    var name;
 
18
    for (name in obj) {
 
19
      if (obj.hasOwnProperty(name)) {
 
20
        fun(name, obj[name]);
 
21
      }
 
22
    }
 
23
  };
 
24
  $.forIn = forIn;
 
25
  function funViaString(fun, hint) {
 
26
    if (fun && fun.match && fun.match(/^function/)) {
 
27
      eval("var f = "+fun);
 
28
      if (typeof f == "function") {
 
29
        return function() {
 
30
          try {
 
31
            return f.apply(this, arguments);
 
32
          } catch(e) {
 
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});
 
36
            throw(e);
 
37
          }
 
38
        };
 
39
      }
 
40
    }
 
41
    return fun;
 
42
  };
 
43
  
 
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);
 
49
    } else {
 
50
      return fun;
 
51
    }
 
52
  }
 
53
 
 
54
  $.evently = {
 
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.
 
62
          args.shift();
 
63
          $(target).trigger(ev, args);
 
64
          return false;
 
65
        });
 
66
      });
 
67
    },
 
68
    paths : [],
 
69
    changesDBs : {},
 
70
    changesOpts : {}
 
71
  };
 
72
  
 
73
  function extractFrom(name, evs) {
 
74
    return evs[name];
 
75
  };
 
76
 
 
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 || {}
 
82
      ;
 
83
    $.forIn(vendor, function(k, v) {
 
84
      if (v.evently && v.evently[name]) {
 
85
        events.push(v.evently[name]);
 
86
      }
 
87
    });
 
88
    if (evently[name]) {events.push(evently[name]);}
 
89
    return $.extend.apply(null, events);
 
90
  }
 
91
 
 
92
  function extractPartials(ddoc) {
 
93
    var partials = [true, {}]
 
94
      , vendor = ddoc.vendor || {}
 
95
      , evently = ddoc.evently || {}
 
96
      ;
 
97
    $.forIn(vendor, function(k, v) {
 
98
      if (v.evently && v.evently._partials) {
 
99
        partials.push(v.evently._partials);
 
100
      }
 
101
    });
 
102
    if (evently._partials) {partials.push(evently._partials);}
 
103
    return $.extend.apply(null, partials);
 
104
  };
 
105
 
 
106
  function applyCommon(events) {
 
107
    if (events._common) {
 
108
      $.forIn(events, function(k, v) {
 
109
        events[k] = $.extend(true, {}, events._common, v);
 
110
      });
 
111
      delete events._common;
 
112
      return events;
 
113
    } else {
 
114
      return events;
 
115
    }
 
116
  }
 
117
 
 
118
  $.fn.evently = function(events, app, args) {
 
119
    var elem = $(this);
 
120
    // store the app on the element for later use
 
121
    if (app) {
 
122
      $$(elem).app = app;
 
123
    }
 
124
 
 
125
    if (typeof events == "string") {
 
126
      events = extractEvents(events, app.ddoc);
 
127
    }
 
128
    events = applyCommon(events);
 
129
    $$(elem).evently = events;
 
130
    if (app && app.ddoc) {
 
131
      $$(elem).partials = extractPartials(app.ddoc);
 
132
    }
 
133
    // setup the handlers onto elem
 
134
    forIn(events, function(name, h) {
 
135
      eventlyHandler(elem, name, h, args);
 
136
    });
 
137
    
 
138
    if (events._init) {
 
139
      elem.trigger("_init", args);
 
140
    }
 
141
    
 
142
    if (app && events._changes) {
 
143
      $("body").bind("evently-changes-"+app.db.name, function() {
 
144
        elem.trigger("_changes");        
 
145
      });
 
146
      followChanges(app);
 
147
      elem.trigger("_changes");
 
148
    }
 
149
  };
 
150
  
 
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) {
 
154
    if ($.evently.log) {
 
155
      elem.bind(name, function() {
 
156
        $.log(elem, name);
 
157
      });
 
158
    }
 
159
    if (h.path) {
 
160
      elem.pathbinder(name, h.path);
 
161
    }
 
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);
 
168
        return false;
 
169
      });
 
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);
 
174
      }
 
175
    } else {
 
176
      // an object is using the evently / mustache template system
 
177
      if (h.fun) {
 
178
        throw("e.fun has been removed, please rename to e.before")
 
179
      }
 
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);
 
184
        return false;
 
185
      });
 
186
    }
 
187
  };
 
188
  
 
189
  $.fn.replace = function(elem) {
 
190
    // $.log("Replace", this)
 
191
    $(this).empty().append(elem);
 
192
  };
 
193
  
 
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);
 
204
    }
 
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)
 
210
    } else {
 
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;
 
217
      if (h.mustache) {
 
218
        // $.log("rendering", h.mustache)
 
219
        var newElem = mustachioed(me, h, args);
 
220
        me[act](newElem);
 
221
      }
 
222
      if (selectors) {
 
223
        if (act == "replace") {
 
224
          var s = me;
 
225
        } else {
 
226
          var s = newElem;
 
227
        }
 
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);
 
233
        });
 
234
      }
 
235
      if (h.after) {
 
236
        runIfFun(me, h.after, args);
 
237
      }
 
238
    }    
 
239
  };
 
240
  
 
241
  // todo this should return the new element
 
242
  function mustachioed(me, h, args) {
 
243
    var partials = $$(me).partials;
 
244
    return $($.mustache(
 
245
      runIfFun(me, h.mustache, args),
 
246
      runIfFun(me, h.data, args), 
 
247
      runIfFun(me, $.extend(true, partials, h.partials), args)));
 
248
  };
 
249
  
 
250
  function runAsync(me, h, args) {  
 
251
    // the callback is the first argument
 
252
    funViaString(h.async, me).apply(me, [function() {
 
253
      renderElement(me, h, 
 
254
        $.argsToArray(arguments).concat($.argsToArray(args)), false, true);
 
255
    }].concat($.argsToArray(args)));
 
256
  };
 
257
  
 
258
  
 
259
  function runQuery(me, h, args) {
 
260
    // $.log("runQuery: args", args)
 
261
    var app = $$(me).app;
 
262
    var qu = runIfFun(me, h.query, args);
 
263
    var qType = qu.type;
 
264
    var viewName = qu.view;
 
265
    var userSuccess = qu.success;
 
266
    // $.log("qType", qType)
 
267
    
 
268
    var q = {};
 
269
    forIn(qu, function(k, v) {
 
270
      if (["type", "view"].indexOf(k) == -1) {
 
271
        q[k] = v;
 
272
      }
 
273
    });
 
274
    
 
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)
 
280
        });
 
281
        if (userSuccess) userSuccess(resp);
 
282
      };
 
283
      newRows(me, app, viewName, q);
 
284
    } else {
 
285
      q.success = function(resp) {
 
286
        // $.log("runQuery success", resp)
 
287
        renderElement(me, h, [resp].concat($.argsToArray(args)), true);
 
288
        userSuccess && userSuccess(resp);
 
289
      };
 
290
      // $.log(app)
 
291
      app.view(viewName, q);      
 
292
    }
 
293
  }
 
294
  
 
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;
 
308
      });
 
309
      if (resp.rows.length > 0) {
 
310
        if (opts.descending) {
 
311
          $$(elem).highKey = resp.rows[0].key;
 
312
        } else {
 
313
          $$(elem).highKey = resp.rows[resp.rows.length -1].key;
 
314
        }
 
315
      };
 
316
      if (successCallback) {successCallback(resp, full)};
 
317
    };
 
318
    opts.success = successFun;
 
319
    
 
320
    if (opts.descending) {
 
321
      thisViewId = view + (opts.startkey ? JSON.stringify(opts.startkey) : "");
 
322
    } else {
 
323
      thisViewId = view + (opts.endkey ? JSON.stringify(opts.endkey) : "");
 
324
    }
 
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) {
 
332
          opts.endkey = hk;
 
333
          // opts.inclusive_end = false;
 
334
        } else {
 
335
          opts.startkey = hk;
 
336
        }
 
337
      }
 
338
      // $.log("add view rows", opts)
 
339
      if (!$$(elem).inFlight) {
 
340
        $$(elem).inFlight = true;
 
341
        app.view(view, opts);
 
342
      }
 
343
    } else {
 
344
      // full refresh
 
345
      // $.log("new view stuff")
 
346
      full = true;
 
347
      $$(elem).lastViewId = thisViewId;
 
348
      $$(elem).highKey = undefined;
 
349
      $$(elem).inFlight = true;
 
350
      app.view(view, opts);
 
351
    }
 
352
  };
 
353
  
 
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]);
 
358
    };
 
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);
 
363
      } else {
 
364
        // in case you are still on CouchDB 0.11 ;) deprecated.
 
365
        connectToChanges(app, changeEvent);
 
366
      }
 
367
      $.evently.changesDBs[dbName] = true;
 
368
    }
 
369
  }
 
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";
 
378
      }
 
379
      $.ajax({
 
380
        url: url,
 
381
        contentType: "application/json",
 
382
        dataType: "json",
 
383
        complete: function(req) {
 
384
          var resp = $.httpData(req, "json");
 
385
          fun(resp);
 
386
          connectToChanges(app, fun, resp.last_seq);
 
387
        }
 
388
      });
 
389
    };
 
390
    if (update_seq) {
 
391
      changesReq(update_seq);
 
392
    } else {
 
393
      app.db.info({success: function(db_info) {
 
394
        changesReq(db_info.update_seq);
 
395
      }});
 
396
    }
 
397
  };
 
398
  
 
399
})(jQuery);