mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
cleaner reactive attributes; error reporting
This commit is contained in:
86
packages/ui/attrs2.js
Normal file
86
packages/ui/attrs2.js
Normal file
@@ -0,0 +1,86 @@
|
||||
|
||||
// An AttributeHandler object is responsible for updating a particular attribute
|
||||
// of a particular element. AttributeHandler subclasses implement
|
||||
// browser-specific logic for dealing with particular attributes across
|
||||
// different browsers.
|
||||
//
|
||||
// To define a new type of AttributeHandler, use
|
||||
// `var FooHandler = AttributeHandler.extend({ update: function ... })`
|
||||
// where the `update` function takes arguments `(element, oldValue, value)`.
|
||||
// The `element` argument is always the same between calls to `update` on
|
||||
// the same instance. `oldValue` and `value` are each either `null` or
|
||||
// a Unicode string of the type that might be passed to the value argument
|
||||
// of `setAttribute` (i.e. not an HTML string with character references).
|
||||
// When an AttributeHandler is installed, an initial call to `update` is
|
||||
// always made with `oldValue = null`. The `update` method can access
|
||||
// `this.name` if the AttributeHandler class is a generic one that applies
|
||||
// to multiple attribute names.
|
||||
//
|
||||
// AttributeHandlers can store custom properties on `this`, as long as they
|
||||
// don't use the names `element`, `name`, `value`, and `oldValue`.
|
||||
//
|
||||
// AttributeHandlers can't influence how attributes appear in rendered HTML,
|
||||
// only how they are updated after materialization as DOM.
|
||||
|
||||
AttributeHandler2 = function (name, value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
};
|
||||
|
||||
_extend(AttributeHandler2.prototype, {
|
||||
update: function (element, oldValue, value) {
|
||||
if (value === null) {
|
||||
if (oldValue !== null)
|
||||
element.removeAttribute(this.name);
|
||||
} else {
|
||||
element.setAttribute(this.name, this.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AttributeHandler2.extend = function (options) {
|
||||
var curType = this;
|
||||
var subType = function AttributeHandlerSubtype(/*arguments*/) {
|
||||
AttributeHandler2.apply(this, arguments);
|
||||
};
|
||||
subType.prototype = new curType;
|
||||
subType.extend = curType.extend;
|
||||
if (options)
|
||||
_.extend(subType.prototype, options);
|
||||
return subType;
|
||||
};
|
||||
|
||||
// Value of a ClassHandler is either a string or an array.
|
||||
var ClassHandler = AttributeHandler2.extend({
|
||||
update: function (element, oldValue, value) {
|
||||
var oldClasses = oldValue ? _.compact(oldValue.split(' ')) : [];
|
||||
var newClasses = value ? _.compact(value.split(' ')) : [];
|
||||
|
||||
// the current classes on the element, which we will mutate.
|
||||
var classes = _.compact(element.className.split(' '));
|
||||
|
||||
// optimize this later (to be asymptotically faster) if necessary
|
||||
_.each(oldClasses, function (c) {
|
||||
if (_.indexOf(newClasses, c) < 0)
|
||||
classes = _.without(classes, c);
|
||||
});
|
||||
_.each(newClasses, function (c) {
|
||||
if (_.indexOf(oldClasses, c) < 0 &&
|
||||
_.indexOf(classes, c) < 0)
|
||||
classes.push(c);
|
||||
});
|
||||
|
||||
element.className = classes.join(' ');
|
||||
}
|
||||
});
|
||||
|
||||
// XXX make it possible for users to register attribute handlers!
|
||||
makeAttributeHandler2 = function (name, value) {
|
||||
// XXX will need one for 'style' on IE, though modern browsers
|
||||
// seem to handle setAttribute ok.
|
||||
if (name === 'class') {
|
||||
return new ClassHandler(name, value);
|
||||
} else {
|
||||
return new AttributeHandler2(name, value);
|
||||
}
|
||||
};
|
||||
34
packages/ui/exceptions.js
Normal file
34
packages/ui/exceptions.js
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
var debugFunc;
|
||||
|
||||
// Meteor UI calls into user code in many places, and it's nice to catch exceptions
|
||||
// propagated from user code immediately so that the whole system doesn't just
|
||||
// break. Catching exceptions is easy; reporting them is hard. This helper
|
||||
// reports exceptions.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ```
|
||||
// try {
|
||||
// // ... someStuff ...
|
||||
// } catch (e) {
|
||||
// reportUIException(e);
|
||||
// }
|
||||
// ```
|
||||
//
|
||||
// An optional second argument overrides the default message.
|
||||
|
||||
reportUIException = function (e, msg) {
|
||||
if (! debugFunc)
|
||||
// adapted from Deps
|
||||
debugFunc = function () {
|
||||
return (typeof Meteor !== "undefined" ? Meteor._debug :
|
||||
((typeof console !== "undefined") && console.log ? console.log :
|
||||
function () {}));
|
||||
};
|
||||
|
||||
// In Chrome, `e.stack` is a multiline string that starts with the message
|
||||
// and contains a stack trace. Furthermore, `console.log` makes it clickable.
|
||||
// `console.log` supplies the space between the two arguments.
|
||||
debugFunc(msg || 'Exception in Meteor UI:', e.stack || e.message);
|
||||
};
|
||||
@@ -13,13 +13,14 @@ Package.on_use(function (api) {
|
||||
api.use('minimongo'); // for idStringify
|
||||
api.use('observe-sequence');
|
||||
|
||||
api.add_files(['base.js']);
|
||||
api.add_files(['exceptions.js', 'base.js']);
|
||||
|
||||
api.add_files(['dombackend.js',
|
||||
'dombackend2.js',
|
||||
'domrange.js'], 'client');
|
||||
|
||||
api.add_files(['attrs.js',
|
||||
'attrs2.js',
|
||||
'render.js',
|
||||
'render2.js',
|
||||
'components.js',
|
||||
|
||||
@@ -106,6 +106,46 @@ var insert = function (nodeOrRange, parent, before) {
|
||||
}
|
||||
};
|
||||
|
||||
// Update attributes on `elem` to the dictionary `attrs`, using the
|
||||
// dictionary of existing `handlers` if provided.
|
||||
//
|
||||
// Values in the `attrs` dictionary are in pseudo-DOM form -- a string,
|
||||
// CharRef, or array of strings and CharRefs -- but they are passed to
|
||||
// the AttributeHandler in string form.
|
||||
var updateAttributes = function(elem, attrs, handlers) {
|
||||
if (handlers) {
|
||||
for (var k in handlers) {
|
||||
if (! attrs.hasOwnProperty(k)) {
|
||||
// remove old attributes (and handlers)
|
||||
var handler = handlers[k];
|
||||
var oldValue = handler.value;
|
||||
handler.value = null;
|
||||
handler.update(elem, oldValue, null);
|
||||
delete handlers[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var k in attrs) {
|
||||
var handler;
|
||||
var oldValue;
|
||||
var value = attributeValueToString(attrs[k]);
|
||||
if ((! handlers) || (! handlers.hasOwnProperty(k))) {
|
||||
// make new handler
|
||||
checkAttributeName(k);
|
||||
handler = makeAttributeHandler2(k, value);
|
||||
if (handlers)
|
||||
handlers[k] = handler;
|
||||
oldValue = null;
|
||||
} else {
|
||||
handler = handlers[k];
|
||||
oldValue = handler.value;
|
||||
}
|
||||
handler.value = value;
|
||||
handler.update(elem, oldValue, value);
|
||||
}
|
||||
};
|
||||
|
||||
// Convert the pseudoDOM `node` into reactive DOM nodes and insert them
|
||||
// into the element or DomRange `parent`, before the node or id `before`.
|
||||
var materialize = function (node, parent, before) {
|
||||
@@ -129,13 +169,23 @@ var materialize = function (node, parent, before) {
|
||||
var elem = document.createElement(node.tagName);
|
||||
if (node.attrs) {
|
||||
var attrs = node.attrs;
|
||||
// XXX make attributes reactive!
|
||||
if (typeof attrs === 'function')
|
||||
attrs = attrs();
|
||||
_.each(attrs, function (v, k) {
|
||||
checkAttributeName(k);
|
||||
elem.setAttribute(k, attributeValueToString(v));
|
||||
});
|
||||
if (typeof attrs === 'function') {
|
||||
var attrUpdater = Deps.autorun(function (c) {
|
||||
if (! c.handlers)
|
||||
c.handlers = {};
|
||||
|
||||
try {
|
||||
updateAttributes(elem, attrs(), c.handlers);
|
||||
} catch (e) {
|
||||
reportUIException(e);
|
||||
}
|
||||
});
|
||||
UI.DomBackend2.onRemoveElement(elem, function () {
|
||||
attrUpdater.stop();
|
||||
});
|
||||
} else {
|
||||
updateAttributes(elem, attrs);
|
||||
}
|
||||
}
|
||||
_.each(node, function (child) {
|
||||
materialize(child, elem);
|
||||
|
||||
Reference in New Issue
Block a user