Permalink
Browse files

Adding generic Startup behavior for invoking delegators based on DOM …

…state on startup.
  • Loading branch information...
anutron committed Nov 5, 2011
1 parent a3019ff commit a85bdf1dfb2eaa2407d2fa50960b111da207e582
View
@@ -0,0 +1,43 @@
+Behavior Filter: Behavior.Startup {#Behavior.Startup}
+====================================
+
+Invokes delegators on startup when specified conditions are met. This allows you to check the state of elements in the DOM and invoke some action that is appropriate. It's especially useful for form inputs where the client (browser) maintains a state if the user reloads.
+
+### Example
+
+ <input type="checkbox" data-trigger="toggleReveal" data-togglereveal-options="
+ 'target': '.foo'
+ " data-behavior="Startup" data-startup-options="
+ 'delegators': {
+ 'reveal': {
+ 'target': 'self',
+ 'property': 'checked'
+ 'value': true
+ },
+ 'dissolve': {
+ 'targets': '.severalThings',
+ 'method': 'hasClass',
+ 'arguments': ['.someClass'],
+ 'value': true
+ }
+ }
+ "/> enable
+
+### Options
+
+* delegators - (*object*) a set of delegators to fire if their conditionals are true.
+
+### Conditionals
+
+Each delegator listed will be invoked if their conditional is true. The delegator name is the key, and the value is an object with the following properties:
+
+* target - (*string*) a css selector *relative to the element* to find a single element to test.
+* targets - (*string*) a css selector *relative to the element* to find a group of elements to test. If the conditional is true for any of them, the delegator is fired.
+* property - (*string*) a property of the target element to evaluate. Do not use with the `method` option.
+* method - (*string*) a method on the target element to invoke. Passed as arguments the `arguments` array (see below). Do not use with the `property` option.
+* arguments - (*array* of *strings*) arguments passed to the method of the target element specified in the `method` option. Ignored if the `property` option is used.
+* value - (*string*) A value to compare to either the value of the `property` of the target or the result of the `method` invoked upon it.
+
+### Notes
+
+This behavior (like all others) is only applied once (on startup or when new content is run through `Behavior.apply`). Be careful as this adds a startup cost to delegators; use wisely.
View
@@ -88,7 +88,8 @@ By default, Behavior passes the following methods to filters in addition to the
* **applyFilters** - the `apply` method of the behavior instance. This allows a Behavior to create new DOM structures and apply their behavior filters.
* **applyFilter** - the `applyFilter` method of the behavior instance. Allows you to invoke a specific behavior filter.
* **getContentElement** - returns the "container" element of the Behavior instance. By default this points to `document.body`. Set `options.container` to change it.
-* **getContainerSize** - returns the value of getContentElement().getSize(); Note that if that element is not in the DOM this will return zeros.
+* **getContainerSize** - returns the value of `getContentElement().getSize();` Note that if that element is not in the DOM this will return zeros.
+* **getDelegator** - returns the instance of [Delegator][] set with the `setDelegator` method.
* **error** - fires the behavior instance's `error` event with the arguments passed.
* **fail** - stops the filter from iterating and passes a message through to the error logger. Takes a string for the message as its only argument.
* **cleanup** - tells Behavior that you are about to retire a DOM tree and allows it to run any garbage collection methods attached to it. Be ware of circular logic here! See also: [Behavior.cleanup](#Behavior:cleanup)
@@ -300,6 +301,36 @@ Sets the default values for a filter, overriding any defaults previously defined
1. name - (*string*) The registered name of a filter.
2. defaults - (*object*) A key/value pair of defaults.
+Behavior Method: setDelegator {#Behavior:setDelegator}
+--------------------------------------------------
+
+Stores a reference to a [Delegator][] instance that is returned by `api.getDelegator()`.
+
+### Syntax
+
+ myBehavior.setDelegator(myDelegator);
+
+### Arguments
+
+1. myDelegator - (*object*) an instance of [Delegator][]
+
+### Returns
+
+* (*object*) this instance of Behavior.
+
+Behavior Method: getDelegator {#Behavior:getDelegator}
+--------------------------------------------------
+
+Returns a reference to the [Delegator][] instance that was set with `setDelegator`.
+
+### Syntax
+
+ myBehavior.getDelegator();
+
+### Returns
+
+* (*object* or *null*) whatever was set with `setDelegator`.
+
Static Methods {#StaticMethods}
==============
View
@@ -0,0 +1,37 @@
+/*
+---
+name: Behavior.Startup
+description: Invokes delegators on startup when specified conditions are met.
+requires: [/Behavior, /Delegator]
+provides: [Behavior.Startup]
+...
+*/
+
+Behavior.addGlobalFilter('Startup', {
+ setup: function(el, api){
+ //get the delegators to set up
+ var delegators = api.get('delegators');
+ if (delegators) {
+ Object.each(delegators, function(conditional, delegator){
+ var targets = [];
+ //get the target of these delegators
+ if (api.get('targets')){
+ targets = el.getElement(api.get('targets'));
+ } else if (api.get('target') && api.get('target') != 'self'){
+ var target = el.getElement(api.get('target'));
+ if (target) targets = new Elements([target]);
+ } else {
+ targets = new Elements(el);
+ }
+ if (targets.length == 0) api.fail('could not find targets for startup delegator: ', delegator, api.get('targets'));
+ //check the targets for the conditionals
+ var fire = targets.some(function(target){
+ if (conditional.property) return element.get(conditional.property) === conditional.value;
+ else if (conditional.method) return element[method].apply(element, conditional.arguments || []) === conditiona.value;
+ });
+ //if any were true, fire the delegator ON THIS ELEMENT
+ if (fire) api.getDelegator().trigger(delegator, el);
+ });
+ }
+ }
+});
View
@@ -76,6 +76,7 @@ provides: [Behavior]
this.setOptions(options);
this.API = new Class({ Extends: BehaviorAPI });
this.passMethods({
+ getDelegator: this.getDelegator.bind(this),
addEvent: this.addEvent.bind(this),
removeEvent: this.removeEvent.bind(this),
addEvents: this.addEvents.bind(this),
@@ -101,6 +102,16 @@ provides: [Behavior]
});
},
+ getDelegator: function(){
+ return this.delegator;
+ },
+
+ setDelegator: function(delegator){
+ if (!instanceOf(delegator, Delegator)) throw new Error('Behavior.setDelegator only accepts instances of Delegator.');
+ this.delegator = delegator;
+ return this;
+ },
+
getContentElement: function(){
return this.options.container || document.body;
},
View
@@ -110,20 +110,22 @@ provides: [Delegator]
},
trigger: function(name, element, event){
- if (!event || typeOf(event) == "string") event = new Event.Mock(element, event);
+ var e = event;
+ if (!e || typeOf(e) == "string") e = new Event.Mock(element, e);
+
var trigger = this.getTrigger(name);
- if (trigger && trigger.types.contains(event.type)) {
+ if (trigger && (!event || (event && trigger.types.contains(e.type)))) {
if (this.options.breakOnErrors){
- this._trigger(trigger, element, event);
+ this._trigger(trigger, element, e);
} else {
try {
- this._trigger(trigger, element, event);
- } catch(e) {
- this.fireEvent('error', ['Could not apply the trigger', name, e]);
+ this._trigger(trigger, element, e);
+ } catch(error) {
+ this.fireEvent('error', ['Could not apply the trigger', name, error]);
}
}
} else {
- this.fireEvent('error', 'Could not find a trigger with the name ' + name + ' for event: ' + event.type);
+ this.fireEvent('error', 'Could not find a trigger with the name ' + name + ' for event: ' + e.type);
}
return this;
},
@@ -24,6 +24,19 @@ if (window.describe){
describe('Behavior', function(){
+ it('should register a delegator', function(){
+ var d = new Delegator();
+ behaviorInstance.setDelegator(d);
+ expect(behaviorInstance.getDelegator()).toBe(d);
+ var error;
+ try {
+ behaviorInstance.setDelegator({});
+ } catch(e){
+ error = true;
+ }
+ expect(error).toBe(true);
+ });
+
it('should register a filter', function(){
var test1 = ClassAdder.makeAdder('one');
Behavior.addGlobalFilter('Test1', test1);
@@ -0,0 +1,62 @@
+/*
+---
+name: Behavior.Startup.Specs
+description: n/a
+requires: [Behavior/Behavior.Startup, Behavior-Tests/Behavior.SpecsHelpers]
+provides: [Behavior.Startup.Specs]
+...
+*/
+if (window.describe){
+ (function(){
+
+ describe('Behavior.Startup', function(){
+
+ var b = new Behavior();
+ var d = new Delegator({
+ getBehavior: function(){return b;}
+ });
+ b.setDelegator(d);
+ var test1count = 0, test2count = 0, test3count = 0, test4count = 0;
+ Delegator.register('click', {
+ StartupTest1: function(){ test1count++; },
+ StartupTest2: function(){ test2count++; },
+ StartupTest3: function(){ test3count++; },
+ StartupTest4: function(){ test4count++; }
+ });
+
+ var dom = new Element('div', {
+ 'data-trigger': 'StartupTest1, StartupTest2',
+ 'data-behavior': 'Startup',
+ 'data-startup-options': JSON.encode({
+ delegators: {
+ StartupTest1: {
+ target:'input#foo',
+ property:'value',
+ value:'bar'
+ },
+ StartupTest2: {
+ target:'input#foo',
+ property:'value',
+ value:'baz'
+ },
+ StartupTest3: {
+ targets: 'span.yo',
+ method:'hasClass',
+ arguments: ['oy'],
+ value: true
+ },
+ StartupTest4: {
+ targets: 'span.yo',
+ method:'get',
+ arguments:'tag',
+ value: 'div'
+ }
+ }
+ })
+ }).adopt(new Element('input#foo', {name: 'foo', value: 'bar'}))
+ .adopt(new Element('span.yo'))
+ .adopt(new Element('span.yo.oy'));
+ });
+
+ })();
+}
View
@@ -17,3 +17,4 @@ sources:
- "Behavior/Behavior.SpecsHelpers.js"
- "Behavior/Delegator.Specs.js"
- "Behavior/Element.Data.Specs.js"
+ - "Behavior/Behavior.Startup.Specs.js"
View
@@ -17,4 +17,6 @@ sources:
- "Source/Element.Data.js"
- "Source/Event.Mock.js"
- "Source/Behavior.js"
- - "Source/BehaviorAPI.js"
+ - "Source/BehaviorAPI.js"
+ - "Source/Behavior.Startup.js"
+

0 comments on commit a85bdf1

Please sign in to comment.