reimplement #each in terms of renderList

not tested yet
This commit is contained in:
Geoff Schmidt
2011-12-15 02:15:20 -08:00
committed by Nick Martin
parent 0f397a2a08
commit f0df8212ee
3 changed files with 53 additions and 92 deletions

View File

@@ -169,29 +169,36 @@ Sky.ui.render = function (render_func, events, event_data) {
/// options to include:
/// selector: minimongo selector (default: {})
/// sort: minimongo sort specification (default: natural order)
/// render: render function (from object to element)
/// render: render function (as in render(), but takes a document)
/// .. plus optionally
/// render_empty: render function for content to show when query empty.
/// still gets same event bindings.
/// events: vaguely backbone-style live event specification
/// {'click #selector #path' : function (obj) { } }
///
/// returns an object with:
/// stop(): stop updating, tear everything down and let it get GC'd
///
/// XXX rewrite using Sky.ui.render, and new GC semantics, and make it
/// return a fragment rather than plopping its results into a
/// container
Sky.ui.renderList = function (collection, options) {
/// XXX consider making render an argument rather than an option
///
/// XXX what package should this go in? depends on both liveui and minimongo..
///
/// XXX what can now be a collection, or the handle of an existing
/// findlive. messy.
Sky.ui.renderList = function (what, options) {
var frag = document.createDocumentFragment();
var context = new Sky.deps.Context;
var start = document.createComment("renderList " + collection._name);
var end = document.createComment("end " + collection._name);
var is_handle = what instanceof Collection.LiveResultsSet;
var name = (is_handle ? what.collection : what)._name;
var start = document.createComment("renderList " + name);
var end = document.createComment("end " + name);
start._used = end._used = true;
start._context = context;
frag.appendChild(start);
frag.appendChild(end);
var empty_shown = false;
var entry_starts = [];
var entry_end = function (idx) {
return (entry_starts[idx + 1] || end).previousSibling;
@@ -206,14 +213,26 @@ Sky.ui.renderList = function (collection, options) {
return this_start;
};
var query = collection.findLive(options.selector, {
sort: options.sort,
var maybe_show_empty = function () {
if (!entry_starts.length && options.render_empty) {
start.parentNode.insertBefore(
Sky.ui.render(options.render_empty, options.events), end);
empty_shown = true;
}
};
var query_opts = {
added: function (doc, before_idx) {
if (empty_shown) {
Sky.ui._remove(start.nextSibling, end.previousSibling);
empty_shown = false;
}
entry_starts.splice(before_idx, 0, insert_entry(doc, before_idx));
},
removed: function (id, at_idx) {
Sky.ui._remove(entry_starts[at_idx], entry_end(at_idx));
entry_starts.splice(at_idx, 1);
maybe_show_empty();
},
changed: function (doc, at_idx) {
var this_start = insert_entry(doc, at_idx);
@@ -228,7 +247,16 @@ Sky.ui.renderList = function (collection, options) {
entry_starts.splice(old_idx, 1);
entry_starts.splice(new_idx, 0, this_start);
}
});
};
if (is_handle) {
var query = what;
query.reconnect(query_opts);
} else {
query_opts.sort = options.sort;
var query = what.findLive(options.selector || {}, query_opts);
}
maybe_show_empty();
context.on_invalidate(function (old_context) {
query.stop();

View File

@@ -111,12 +111,13 @@ Collection.prototype.find = function (selector, options) {
// - removed (id, at_index)
// * sort: sort descriptor
//
// functions available on returned query handle:
// attributes available on returned query handle:
// * stop(): end updates
// * indexOf(id): return current index of object in result set, or -1
// * reconnect({}): replace added, changed, moved, removed, from the
// arguments, and call added to deliver the current state of the
// query (XXX ugly hack to support templating)
// * collection: the collection this query is querying
//
// iff x is a returned query handle, (x instanceof
// Collection.LiveResultsSet) is true
@@ -127,6 +128,7 @@ Collection.prototype.find = function (selector, options) {
// XXX maybe support limit/skip
// XXX it'd be helpful if removed got the object that just left the
// query, not just its id
// XXX document that initial results will definitely be delivered before we return [do, add to asana]
Collection.LiveResultsSet = function () {};
Collection.prototype.findLive = function (selector, options) {
@@ -165,7 +167,8 @@ Collection.prototype.findLive = function (selector, options) {
return i;
return -1;
},
reconnect: connect
reconnect: connect,
collection: this
});
return handle;
};

View File

@@ -17,14 +17,9 @@ if (typeof Sky === "undefined") Sky = {};
Sky._pending_partials = null; // id -> element
Sky._pending_partials_idx_nonce = 0;
// XXX another disgusting hack -- we reach into handlebars and
// extend #each to know how to cooperate with pending_partials and
// minimongo findlive.
//
// XXX XXX XXX the garbage collection implications are terrible.. we
// don't even pretend to call stop on the findlive, so every time
// we're rerendered, we kick off another findlive that runs
// .. forever!
// XXX another messy hack -- we reach into handlebars and extend #each
// to know how to cooperate with pending_partials and minimongo
// findlive.
Sky._hook_handlebars_each = function () {
Sky._hook_handlebars_each = function(){}; // install the hook only once
@@ -33,80 +28,15 @@ Sky._hook_handlebars_each = function () {
if (!(context instanceof Collection.LiveResultsSet))
return orig(context, options);
// XXX inserts an intermediate DIV!! that is lame and should be
// fixed. besides general hygiene/pride, we really need to
// support <ul>{{#each items}}<li>{{name}}</li>{{/each}}</ul>
var element = document.createElement("div");
var trim = function (markup) {
// Consider {{#each items}\n{{> item}}\n{{/each}}
//
// In that case, options.fn will return HTML that parses into
// three nodes: whitespace, the partial, whitespace. That
// won't work. I can't reconcile this logically at the moment,
// but "do what you mean" and strip the whitespace.
//
// XXX fails if a {{#if}..{{else}}..{{/if}} is at toplevel in
// a template?
var match = markup.match(/^\s*(<[\s\S]+>)\s*$/);
if (match)
markup = match[1];
return markup;
}
var render = Sky._def_template(null, function (obj) {
return trim(options.fn(obj));
});
var renderElse = Sky._def_template(null, function () {
return trim(options.inverse({}));
});
// XXX sort of lame that we always end up rendering this even if
// the query returns results 100% of the time ..
var is_empty = true;
element.appendChild(renderElse());
// XXX copied code from Sky.ui.renderList.. bleh
// (with addition of is_empty / renderElse)
context.reconnect({
added: function (obj, before_idx) {
if (is_empty) {
element.removeChild(element.childNodes[0]);
is_empty = false;
}
if (before_idx === element.childNodes.length)
element.appendChild(render(obj));
else
element.insertBefore(render(obj), element.childNodes[before_idx]);
Sky.ui._tryFocus();
},
removed: function (id, at_idx) {
element.removeChild(element.childNodes[at_idx]);
if (element.childNodes.length === 0) {
is_empty = true;
element.appendChild(renderElse());
}
},
changed: function (obj, at_idx) {
element.insertBefore(render(obj), element.childNodes[at_idx]);
element.removeChild(element.childNodes[at_idx + 1]);
Sky.ui._tryFocus();
},
moved: function (obj, old_idx, new_idx) {
var elt = element.removeChild(element.childNodes[old_idx]);
if (new_idx === element.childNodes.length)
element.appendChild(elt);
else
element.insertBefore(elt, element.childNodes[new_idx]);
}
});
var id = Sky._pending_partials_idx_nonce++;
Sky._pending_partials[id] = element;
Sky._pending_partials[id] = Sky.ui.renderList(context, {
render: Sky._def_template(null, options.fn),
render_empty: Sky._def_template(null, _.bind(options.inverse, null, {}))
});
return "<div id='" + id +
"'><!-- for replacement with findlive each --></div>";
}
};
};
// XXX namespacing