├── .gitignore
├── .gitmodules
├── Docs
├── Behavior.Events.md
├── Behavior.Startup.md
├── Behavior.Trigger.md
├── Behavior.md
├── BehaviorAPI.md
├── Delegator.md
└── Element.Data.md
├── Gruntfile.js
├── README.md
├── Source
├── Behavior.Events.js
├── Behavior.Startup.js
├── Behavior.Trigger.js
├── Behavior.js
├── BehaviorAPI.js
├── Delegator.js
├── Element.Data.js
└── Event.Mock.js
├── Tests
├── Specs
│ ├── Behavior
│ │ ├── Behavior.Benchmarks.js
│ │ ├── Behavior.Events.Specs.js
│ │ ├── Behavior.Specs.js
│ │ ├── Behavior.SpecsHelpers.js
│ │ ├── Behavior.Startup.Specs.js
│ │ ├── Behavior.Trigger.Specs.js
│ │ ├── BehaviorAPI.Specs.js
│ │ ├── Delegator.Specs.js
│ │ └── Element.Data.Specs.js
│ ├── Benchmarks.js
│ ├── Configuration.js
│ ├── Syn.js
│ └── package.yml
└── gruntfile-options.js
├── bower.json
├── layers.png
├── license.txt
├── package.json
└── package.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | art.js
2 | node_modules
3 | behavior-specs.js
4 | behavior.js
5 | .bower.json
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Specs/Runner"]
2 | path = Specs/Runner
3 | url = git://github.com/anutron/mootools-runner.git
4 | [submodule "Specs/mootools-core"]
5 | path = Specs/mootools-core
6 | url = git://github.com/mootools/mootools-core.git
7 | [submodule "Specs/mootools-more"]
8 | path = Specs/mootools-more
9 | url = git://github.com/cloudera/mootools-more.git
10 |
--------------------------------------------------------------------------------
/Docs/Behavior.Events.md:
--------------------------------------------------------------------------------
1 | Behavior Filter: Behavior.Events {#Behavior.Events}
2 | ====================================
3 |
4 | Provides mechanism to invoke a Delegator trigger when an instance created by a Behavior filter fires an event.
5 |
6 | ### Note
7 |
8 | Behavior executes filters in DOM order. For this reason, the `addEvents` filter always fires after Behavior
9 | fires it's `apply` event (after it has run through the DOM). This means that a) any filter that has a delay on it
10 | (and doesn't return an instance on startup) will not work with `addEvent` and b) that any instance that `addEvent`
11 | references has already been instantiated. So if you are attaching an event to a class that fires, say, a `show`
12 | event whenever it changes something, but it does so immediately on instantiation, then that first event will have
13 | already been fired before your listener is attached. In this case, some additional startup logic is required on your
14 | part - either change your DOM accordingly to have a startup state or use `Behavior.Startup` to invoke the
15 | proper delegator based on the DOM state.
16 |
17 | ### Example
18 |
19 |
68 |
69 | ### Options
70 |
71 | * events - (*object*) a set of targets, the behavior filter that generated the instance, and events to monitor and the triggers to invoke when they are fired
72 |
73 | ### Basic Syntax
74 |
75 | data-addevent-options="
76 | 'events': {
77 | '.foo::BehaviorName': { // .foo is the element to find relative to this one, BehaviorName is the filter that generated the instance
78 | 'show': [ // show is the event to listen for on the instance returned by BehaviorName
79 | {
80 | '.bar::addClass': { // .bar is the element to fire the addClass trigger upon
81 | 'class': 'hide', // an argument passed to that trigger
82 | 'if': { // but only if this conditional is true.
83 | 'self::hasClass': 'baz'
84 | }
85 | }
86 | }
87 | ]
88 | }
89 | }
90 | "
91 |
92 |
93 |
94 | ### Conditionals
95 |
96 | See the notes about Conditionals in the Delegator docs. Those provided by Delegator are the basic checks against element methods or element properties as with the example above. However, Behavior.Events provides three additional conditional checks:
97 |
98 | #### Checking event arguments
99 |
100 | This allows you to fire a trigger if (or unless) the event fired on the instance received an argument of your specification. Example:
101 |
102 | data-addevent-options="
103 | 'events': {
104 | '.foo::BehaviorName': {
105 | 'show': [
106 | {
107 | '.bar::addClass': {
108 | 'class': 'hide',
109 | 'if': {
110 | 'eventArguments[0]': 'foo',
111 | 'eventArguments[1]': 'bar'
112 | }
113 | }
114 | }
115 | ]
116 | }
117 | }
118 | "
119 |
120 | In this example, the `onShow` event must have been passed two (or more) arguments: `'foo'`, and `'bar'`. Both must match.
121 |
122 | #### Checking instance properties
123 |
124 | This allows you to fire a trigger if (or unless) a specified property on the instance matches a value
125 |
126 | data-addevent-options="
127 | 'events': {
128 | '.foo::BehaviorName': {
129 | 'show': [
130 | {
131 | '.bar::addClass': {
132 | 'class': 'hide',
133 | 'if': {
134 | 'instance.foo': 'bar',
135 | 'instance.baz': 'biz'
136 | }
137 | }
138 | }
139 | ]
140 | }
141 | }
142 | "
143 |
144 | In this example, the trigger will be invoked when the `onShow` event fires but only if the instance that was created by the `BehaviorName` filter on the `.foo` element has a `.foo` property equalling `'bar'` and `.baz` property equalling `'biz'`.
145 |
146 | #### Checking instance methods
147 |
148 | This allows you to fire a trigger if (or unless) a specified method on the instance returns a value
149 |
150 | data-addevent-options="
151 | 'events': {
152 | '.foo::BehaviorName': {
153 | 'show': [
154 | {
155 | '.bar::addClass': {
156 | 'class': 'hide',
157 | 'if': {
158 | 'instance.foo()': 'bar',
159 | 'arguments': ['an argument to pass to foo()']
160 | }
161 | }
162 | }
163 | ]
164 | }
165 | }
166 | "
167 |
168 | In this example, the trigger will be invoked when the `onShow` event fires but only if the instance that was created by the `BehaviorName` filter on the `.foo` element has a `.foo` *method* that, when called with the argument `'an argument to pass to foo()'` returns `'bar'`. If you don't specify the `arguments` value it just invokes the method.
169 |
--------------------------------------------------------------------------------
/Docs/Behavior.Startup.md:
--------------------------------------------------------------------------------
1 | Behavior Filter: Behavior.Startup {#Behavior.Startup}
2 | ====================================
3 |
4 | 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.
5 |
6 | ### Example
7 |
8 | enable
25 |
26 | ### Options
27 |
28 | * delegators - (*object*) a set of delegators to fire if their conditionals are true.
29 |
30 | ### Conditionals
31 |
32 | 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:
33 |
34 | * target - (*string*) a css selector *relative to the element* to find a single element to test.
35 | * 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.
36 | * property - (*string*) a property of the target element to evaluate. Do not use with the `method` option.
37 | * 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.
38 | * 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.
39 | * 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.
40 | * delay - (*number*) If set, the trigger will be invoked after this many milliseconds have passed.
41 |
42 | ### Notes
43 |
44 | * delegator conditionals that do not have a `property` OR `method` setting will always be invoked.
45 | * 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.
46 |
--------------------------------------------------------------------------------
/Docs/Behavior.Trigger.md:
--------------------------------------------------------------------------------
1 | Behavior Filter: Behavior.Trigger {#Behavior.Trigger}
2 | ====================================
3 |
4 | Because Delegator is inefficient for mouse over/out events, this behavior
5 | allows you to invoke delegator triggers on elements when they occur using
6 | normal event monitoring.
7 |
8 |
9 | ### Example
10 |
11 |
...
30 |
31 |
32 | ### Options
33 |
34 | * triggers - (*array*) Array of configurations for triggers (see below)
35 |
36 | ### Triggers
37 |
38 | Each trigger listed includes the events to monitor (i.e. `click`) and a list of selectors that are
39 | used to find the targets to monitor. The events are attached using traditional `addEvent` calls (instead
40 | of using event delegation) on the elements that match this selector.
41 |
42 |
43 | ### Notes
44 |
45 | * updates to the DOM that include new elements that match that selector will not have their events monitored.
46 |
--------------------------------------------------------------------------------
/Docs/BehaviorAPI.md:
--------------------------------------------------------------------------------
1 | Class: BehaviorAPI {#BehaviorAPI}
2 | ==========================
3 |
4 | Provides methods to read values from annotated HTML configured for the [Behavior][] class and its associated [Filters](Behavior.md#Behavior.Filter).
5 |
6 | ### Syntax
7 |
8 | new BehaviorAPI(element[, prefix]);
9 |
10 | ### Arguments
11 |
12 | 1. element - (*element*) An element you wish to read.
13 | 2. prefix - (*string*; optional) A prefix to all the properties; a namespace.
14 |
15 | ### Notes
16 |
17 | Examples of the HTML expressions evaluated are as follows (all of the following produce the same output*):
18 |
19 | //prefered
20 | //no braces on JSON
21 |
22 |
23 |
24 | The `-options` value is parsed as JSON first (it's slightly more permissive in that you don't have to wrap it in `{}` just for convenience). Values defined here are read as defined allowing you to express arrays, numbers, booleans, etc. Functions / callbacks are generally not used by [Behavior][].
25 |
26 | If you attempt to read a value that isn't defined in this options object, the property name is attempted to be read from the property directly (e.g. `data-behaviorname-prop`). This value is *always* a string unless you specify a type. If a type is specified the value is run through the JSON parser and validated against that type.
27 |
28 | Note that filter names that contain characters other than A-Z, 0-9, or dash are stripped and what remains is case insensitive. Dots are turned to dashes. Further, camelCase properties are hyphenated to camel-case. So, for example, you would express the following:
29 |
30 |
31 | //and - note the hyphenation
32 |
33 |
34 | BehaviorAPI Method: get {#BehaviorAPI:get}
35 | ------------------------------------------
36 |
37 | Gets a value for the specified name.
38 |
39 | ### Syntax
40 |
41 | api.get(name[, name, name, name])
42 |
43 | ### Arguments
44 |
45 | 1. name - (*string*) The name of the property you wish to retrieve. Pass more than one to get back multiple.
46 |
47 | ### Example
48 |
49 | var api = new BehaviorAPI(target, 'foo');
50 | api.get('bar'); //returns the value of data-foo-bar or null
51 | api.get('bar', 'baz'); //returns {bar: 'value', baz: 'value'}
52 |
53 | ### Returns
54 |
55 | * (*mixed*) Values defined as strings will be returned as strings. Values defined in JSON will be returned as their
56 | type is evaluated. When you expect anything other than a string it's better to use [getAs](#BehaviorAPI:getAs).
57 | When more than one name is specified you'll receive an object response with key/value pairs for the name/property values.
58 |
59 | BehaviorAPI Method: getAs {#BehaviorAPI:getAs}
60 | ------------------------------------------
61 |
62 | Gets a value for the specified name and runs it through [JSON.decode][] and verifies that the value is parsed as the specified type (specifically a MooTools Type: [String](http://mootools.net/docs/core/Types/String), [Function](http://mootools.net/docs/core/Types/Function), [Array](http://mootools.net/docs/core/Types/Array), [Date](http://mootools.net/docs/more/Types/Date), etc.).
63 |
64 | ### Syntax
65 |
66 | api.getAs(Type, name[, defaultValue]);
67 |
68 | ### Arguments
69 |
70 | 1. Type - (*Type*) A MooTools Type instance (a function) that the value, when run through [JSON.decode][], should return
71 | 2. name - (*string*) The name of the value to read.
72 | 3. defaultValue - (*mixed*) The value to set if there no value found.
73 |
74 | ### Example
75 |
76 | var api = new BehaviorAPI(target, 'foo');
77 | api.getAs(Number, 'some-number');
78 |
79 | ### Returns
80 |
81 | * (*mixed*) Either returns the value as the Type you specified, the default (if provided), or undefined.
82 |
83 | BehaviorAPI Method: require {#BehaviorAPI:require}
84 | ------------------------------------------
85 |
86 | Validates that an element has a value set for a given name. Throws an error if the value is not found.
87 |
88 | ### Syntax
89 |
90 | api.require(name[, name, name]);
91 |
92 | ### Arguments
93 |
94 | 1. name - (*string*) The name of the property you wish to require. Pass more than one if needed.
95 |
96 | ### Example
97 |
98 | var api = new BehaviorAPI(target, 'foo');
99 | api.require('foo'); //throws an error if data-foo-foo is not set
100 | api.require('foo', 'bar'); //throws an error if data-foo-foo or data-foo-bar are not set
101 |
102 | ### Returns
103 |
104 | * *object* - the instance of BehaviorAPI.
105 |
106 | BehaviorAPI Method: requireAs {#BehaviorAPI:requireAs}
107 | ------------------------------------------
108 |
109 | Requires that an element has a value set for a given name that can be parsed into a given type (using [JSON.decode][]). If a value is not present or does not parse to the specified Type an error is thrown.
110 |
111 | ### Syntax
112 |
113 | api.requireAs(obj);
114 |
115 | ### Arguments
116 |
117 | 1. obj - (*object*) a set of name/Type pairs to require.
118 |
119 | ### Example
120 |
121 | api.requireAs({
122 | option1: Number,
123 | option2: Boolean
124 | });
125 |
126 | ### Returns
127 |
128 | * *object* - the instance of BehaviorAPI.
129 |
130 | BehaviorAPI Method: setDefault {#BehaviorAPI:setDefault}
131 | ------------------------------------------
132 |
133 | Sets the default values. Note that setting defaults for required properties is not useful.
134 |
135 | ### Syntax
136 |
137 | api.setDefault(name, value);
138 | api.setDefault(obj);
139 |
140 | ### Arguments
141 |
142 | 1. name - (*string*) The name of the property you wish to set.
143 | 2. value - (*mixed*) The default value for the given name.
144 |
145 | OR
146 |
147 | 1. obj - (*object*) a set of name/value pairs to use if the element doesn't have values present.
148 |
149 | ### Example
150 |
151 | api.setDefault('duration', 1000);
152 | api.setDefault({
153 | duration: 1000,
154 | link: 'chain'
155 | });
156 |
157 | ### Returns
158 |
159 | * *object* - the instance of BehaviorAPI.
160 |
161 | BehaviorAPI Method: refreshAPI {#BehaviorAPI:refreshAPI}
162 | ------------------------------------------
163 |
164 | The API class caches values read from the element to avoid the cost of DOM interaction. Once you read a value, it is never read from the element again. If you wish to refresh this to re-read the element properties, invoke this method. Note that default values are maintained.
165 |
166 | ### Syntax
167 |
168 | api.refreshAPI();
169 |
170 | ### Returns
171 |
172 | * *object* - the instance of BehaviorAPI.
173 |
174 | [Behavior]: Behavior.md
175 | [JSON.decode]: http://mootools.net/docs/core/Utilities/JSON#JSON:decode
176 |
--------------------------------------------------------------------------------
/Docs/Delegator.md:
--------------------------------------------------------------------------------
1 | Class: Delegator {#Delegator}
2 | ====================================
3 |
4 | Manager for generic (DOM) event handlers.
5 |
6 | ### Implements
7 |
8 | * [Options][], [Events][]
9 |
10 | ### Syntax
11 |
12 | new Delegator([options]);
13 |
14 | ### Arguments
15 |
16 | 1. options - (*object*; optional) a key/value set of options
17 |
18 | ### Options
19 |
20 | * breakOnErrors - (*boolean*) By default, errors thrown by triggers are caught; the onError event is fired. Set this to `true` to NOT catch these errors to allow them to be handled by the browser.
21 | * verbose - (*boolean*) If *true*, Delegator logs its activity to the console. This can create a lot of output. Defaults to *false*.
22 | * getBehavior - (*function*) Returns an instance of [Behavior](Behavior.md) so that triggers can integrate with it.
23 |
24 | ### Events
25 |
26 | * error - function invoked when a trigger is not found. Defaults to console errors if console.error is available. Also able to be invoked by triggers as `api.error`.
27 | * warn - function invoked when a trigger calls `api.warn`. Defaults to `console.warn` if present.
28 | * destroyDom - function invoked when a trigger destroys a portion of the DOM. Automatically integrated w/ Behavior's `cleanup` method if you set one in the options. Passed the element destroyed as an argument.
29 | * ammendDom - function invoked when a trigger ammends a portion of the DOM. Automatically integrated w/ Behavior's `apply` method if you set one in the options. Passed two arguments: the parent node that contains all the updated elements and an array of those elements updated.
30 | * trigger - function invoked whenever a trigger is called. Passed four arguments: `trigger` (the name of the trigger invoked), `element` (the element on which it was invoked), `event` (the event object), `result` (anything returned by the trigger's handler).
31 | * updateHistory - function invoked when a behavior changes the state of the page and wishes to change the url to match it using `history.pushState`. Passed a single argument, the new url (e.g. `api.fireEvent('updateHistory', someURL)`).
32 |
33 | ### Usage
34 |
35 | Delegator implements [Event Delegation](http://mootools.net/docs/more/Element/Element.Delegation) for reusable behaviors. Conceptually its similar to [Behavior][] in that you declare which behavior you want an element to have, but unlike Behavior's filters which are run at startup and instantiate widgets and the like, Delegator is designed to run its registered functions (which we call "triggers") at event time (such as click).
36 |
37 | This should not be confused with deferred Behavior filters (which can be run at event time, too). Behavior filters deferred to an event (such as click) are only run once and are used to instantiate something. Delegator's triggers are event handlers to be used repeatedly (a trigger might, for example, hide its parent or remove itself from the DOM or load some content via AJAX).
38 |
39 | ### Example Usage
40 |
41 | var myDelegator = new Delegator();
42 | Delegator.attach(myContainerElement);
43 | Delegator.register('click', 'hide', function(event, element, api){
44 | event.preventDefault();
45 | api.getElement('target').hide();
46 | });
47 |
48 | ### Example HTML
49 |
50 | click me to hide foo
51 |
I hide when you click the link above me!
52 |
53 | ### HTML properties
54 |
55 | Delegator uses a clearly defined API to read HTML properties off the elements it configures. See [BehaviorAPI][] for details.
56 |
57 | ### Using Multiple Triggers Together
58 |
59 | It's possible to declare more than one data trigger property for a single element (`data-trigger="disableMe submitParentForm"`)
60 |
61 | ### Integrating with Behavior
62 |
63 | If you're using [Behavior](Behavior.md) you should connect the two so that links that Delegator uses to update the DOM can have their response run through your Behavior instance's `apply` method. Example:
64 |
65 | var myBehavior = new Behavior().apply(document.body);
66 | var myDelegator = new Delegator({
67 | getBehavior: function(){ return myBehavior; }
68 | }).attach(document.body);
69 | myBehavior.setDelegator(myDelegator);
70 |
71 | ### Conventions
72 |
73 | * MooTools has the convention that classes are upper case and methods and functions are not. Becuase Delegator triggers do not necessarily instantiate classes as Behavior filters do, they are usually registered with lower case names.
74 | * Whenever a delegator trigger references another element (or elements) with its options, by convention the selector given always is relative to the element with the trigger. In the example above, the `data-hide-target` value is `!body #foo` (instead of just `#foo`). This convention is codified in the passed api methods `getElement` and `getElements` (detailed below) which will get the element(s) referenced by that selector for you, optionally throwing warnings when they aren't found.
75 |
76 | ### Conditionals
77 |
78 | Delegator also allows any trigger's options to include conditionals that will prevent or allow the trigger from being invoked if they match. These conditionals are run at event time, allowing you to program your UI for different states. Delegator provides two conditionals - `if` and `unless`. In theory this could be extended to include others like less than or greater than at some point.
79 |
80 | #### Examples
81 |
82 | ...
87 |
88 | ...
93 |
94 | Both the examples above reference the `foo` trigger and specify conditionals. The first one uses the special `if` conditional and requires that the element itself has the class "foo" (which it doesn't), and thus the trigger will not fire. The second one is nearly identical but uses the special `unless` conditional, which does the same check but verifies that it's NOT true, so that one will.
95 |
96 | There's a more verbose version of these conditionals that looks like this:
97 |
98 | ...
106 |
107 | Here we explicitly name the target (`self`), the method invoked on that element (`hasClass`) - this can be any element method, the arguments passed to that method, and the value we expect it to return. The previous examples are just shorthands that are parsed into this more verbose format.
108 |
109 | ### Multiple Triggers
110 |
111 | Delegator also provides a custom trigger called "multi" which allows a single element to invoke triggers on OTHER elements each with its own options. This allows you to have a single element that the user clicks and then it hides some elements, adds classes to others, etc.
112 |
113 | #### Example
114 |
115 |
134 |
135 | Here we have 3 different Delegator triggers we are invoking when the user clicks our link.
136 |
137 | * The first is the "someTrigger" trigger which is invoked on any child element with the "foo" class. This one has a configuration - the options for the trigger. See important note below about how these are used.
138 | * The second one just invokes the "someOtherTrigger" on any child element with the "bar" class.
139 | * The third invokes the "yetAnotherTrigger" on any element with the "baz" class *provided that it also has the "foo" class*.
140 |
141 | #### Important Notes
142 |
143 | * The configuration specified this way are passed to [BehaviorAPI][]'s `setDefault` method. This means that if the target element has its own configuration for these triggers that the configuration options specified on the element win.
144 | * Conditionals evaluated in these triggers are evaluated on the targets, not the element where they are specified. In other words, in the last example above where there's a check to see if `'self::hasClass': 'foo'"`, `self` will reference each matched element for `.baz`. These options specified here are projected on each matched element as if the trigger were there.
145 | * If you express more than one conditional statement in your `if` or `unless` object, each is evaluated and the condition **fails** if *any* of them do. In this example, each statement in the `unless` object is evaluated. If *any* are `true` (because it's an `unless` statement) the trigger is not invoked.
146 |
147 | ...
153 |
154 | * You can, if you like, still specify a conditional for the "multi" trigger. This means the entire list of triggers will be ignored if your condition fails. Example:
155 |
156 |
165 |
166 | ### Switches
167 |
168 | Finally, Delegator offers a special trigger called a `switch`. These allow you to define multiple sets of triggers to execute when a condition is met. If the user clicks this button and some radio button is selected, execute these triggers on THAT group of elements, else execute these OTHER triggers on these OTHER elements.
169 |
170 | There are two types of switches: `first` and `any`. The `first` switch iterates over your switch groups (in the order they are declared) and executes the first group whose condition is `true`, while the `any` switch iterates over all the trigger groups, executing any whose condition is true. As with all triggers, a group with no condition is treated as one that is `true` and executed.
171 |
172 | #### Examples
173 |
174 | ...
197 |
198 | In the above example, Delegator iterates over the array of trigger groups defined in the `first` switches. When it finds one that is valid, it runs the triggers defined within it. The last group in the example has no condition and is essentially treated as the default case in the eventuality that none of the previous are `true`. (Note that in this example, because the first two in the group are the same condition with one an `if` and the other an `unless`, one of them *has* to be true.)
199 |
200 | ...
223 |
224 | In this example, which is nearly identical to the first one except the switch type is `any` instead of `first`, Delegator iterates over all of the trigger groups and executes each if their conditional is `true`. While the `first` example would execute one of the first two in the group (because they are opposites in the example) and stop, the `any` example will execute one of the first two (whichever is `true`) AND the last one.
225 |
226 | #### Conditionals with Switches
227 |
228 | Note that, as with any trigger, you can have a conditional for the switch itself:
229 |
230 | ...
250 |
251 |
252 | Delegator Method: passMethod {#Delegator:passMethod}
253 | --------------------------------------------------
254 |
255 | Defines a method that will be passed to triggers. Delegator allows you to create a well defined API for triggers to reference which increases their reusability. You define this API by explicitly passing named functions to them through the Delegator instance.
256 |
257 | ### Syntax
258 |
259 | myDelegatorInstance.passMethod(name, function);
260 |
261 | ### Returns
262 |
263 | * (*object*) this instance of Delegator
264 |
265 | ### Notes
266 |
267 | By default, Delegator passes the following methods to triggers in addition to the methods defined in the [BehaviorAPI][]
268 |
269 | * addEvent - the addEvent on the behavior instance method provided by the [Events][] class.
270 | * removeEvent - the removeEvent on the behavior instance method provided by the [Events][] class.
271 | * addEvents - the addEvents on the behavior instance method provided by the [Events][] class.
272 | * removeEvents - the removeEvents on the behavior instance method provided by the [Events][] class.
273 | * fireEvents - the fireEvents on the behavior instance method provided by the [Events][] class.
274 | * attach - the [attach](#Delegator:attach) method provided by this Delegator instance.
275 | * trigger - the [trigger](#Delegator:trigger) method provided by this Delegator instance.
276 | * error - fires the Delegator instance's `error` event with the arguments passed.
277 | * fail - stops the trigger and passes a message through to the error logger. Takes a string for the message as its only argument.
278 | * getBehavior - returns the behavior instance defined in the `getBehavior` options object.
279 | * getElement - see note on getElement below.
280 | * getElements - see note on getElements below.
281 | * See the [BehaviorAPI][] for additional methods passed by default.
282 |
283 | You can add any other methods that your triggers require. In general, your filters shouldn't reference anything in your environment except these methods.
284 |
285 | ### api.getElement and api.getElements
286 |
287 | Like Behavior, Delegator provides two methods to help you get elements relative to the one with the trigger on it: `getElement` and `getElements`. These methods, given an option key, look up the key's value and find the first element using that value as a selector. This search is relative to the api's element (so, for example, to find an element by ID anywhere on the page, you'd pass "`!body #the-id`"). Returns the first element found or `null`. `getElements` returns an `Elements` instance with the result while `getElement` just returns the first.
288 |
289 | By default, these methods will throw an error (quietly, in the console, unless the `breakOnErrors` option on the Delegator instance is `true`) if the option key is not defined or no element is found, stopping execution of the trigger. Pass in an optional second argument to have it only throw the warning in the console but continue execution.
290 |
291 | #### Examples
292 |
293 | some stuff
296 |
297 |
306 |
307 | If the user did not configure a target in the options or if the selector specified in that option were to fail to find a result, execution would be stopped and an error logged to console (or thrown if `breakOnErrors` is true).
308 |
309 | some stuff
312 |
313 |
325 |
326 | `getElements` works the same way, but instead returns an array-like `Elements` object with all elements that match the selector.
327 |
328 | #### Special selectors `self` and `window`
329 |
330 | For convenience, Delegator provides two special selectors: `self` and `window`. `self` returns the element itself, while `window` returns the window. Unlike regular selectors which can contain pseudo-selectors and commas (i.e. `.foo:focused, .bar`), the `self` and `window` selectors must be on their own with no adornment. The reason for this is that some triggers (like the first example above) require that there be a selector given for an option. If the user wants to invoke the triggers's action on the element clicked, they need a way to reference it. Likewise, if they want reference the window (like scrolling it for example) they need a way to reference it.
331 |
332 |
333 | Delegator Method: passMethods {#Delegator:passMethods}
334 | --------------------------------------------------
335 |
336 | Iterates over an object of key/values passing them to the [passMethod](#Delegator:passMethod) method.
337 |
338 | ### Syntax
339 |
340 | myDelegatorInstance.passMethods(obj);
341 |
342 | ### Arguments
343 |
344 | 1. obj - (*object*) a set of name/function pairs to pass to the passMethod method.
345 |
346 | ### Returns
347 |
348 | * (*object*) this instance of Delegator
349 |
350 | Delegator Method: register {#Delegator:register}
351 | --------------------------------------------------
352 |
353 | This is both a static method and an instance method. Using the static method (`Delegator.register(...)`) will register a *global* trigger. Using the instance method will register a *local* trigger. The local trigger is used whenever both exist.
354 |
355 | ### Syntax
356 |
357 | Delegator.register(eventTypes, name, handler, overwrite);
358 | myDelegator.register(eventTypes, name, handler, overwrite);
359 | //also
360 | Delegator.register(eventTypes, object, overwrite);
361 | myDelegator.register(eventTypes, object, overwrite);
362 |
363 | ### Arguments
364 |
365 | 1. eventTypes - (*string* or *array*) The event type this trigger monitors. *It is not advised to ever use mouseout or mouseover*.
366 | 2. name - (*string*) The name of this trigger.
367 | 3. handler - (*function* or *object*) The event handler for this trigger. Passed the event, the element, and an instance of [BehaviorAPI][]. See Note about extended declaration for this argument.
368 | 4. overwrite - (*boolean*) If *true* and a trigger by this name already exists, it will be overwritten. Defaults to *false*.
369 |
370 | ### Alternate Arguments
371 |
372 | 1. eventTypes - same as above.
373 | 2. object - (*object*) a set of name/handler values to add.
374 | 3. overwrite - same as above.
375 |
376 | ### Examples
377 |
378 | //this is the same example as the one at the top of the page
379 | var myDelegator = new Delegator();
380 | myDelegator.attach(myContainerElement);
381 | //this adds a global trigger
382 | Delegator.register('click', 'hide', function(event, element, api){
383 | event.preventDefault();
384 | var target = element.getElement(api.get('target'));
385 | if (target) target.hide();
386 | });
387 |
388 | //also
389 | Delegator.register(['click', 'submit'], {
390 | Foo: function(){...},
391 | Bar: {
392 | handler: function(){...},
393 | requires: [...]
394 | }
395 | });
396 |
397 | Delegator Method: setTriggerDefaults {#Behavior:setTriggerDefaults}
398 | --------------------------------------------------
399 |
400 | Sets the default values for a trigger, overriding any defaults previously defined.
401 |
402 | ### Syntax
403 |
404 | myDelegator.setTriggerDefaults(name, defaults);
405 |
406 | ### Arguments
407 |
408 | 1. name - (*string*) The registered name of a trigger.
409 | 2. defaults - (*object*) A key/value pair of defaults.
410 |
411 | Delegator Method: cloneTrigger {#Behavior:cloneTrigger}
412 | --------------------------------------------------
413 |
414 | Clones a pre-existing trigger and sets new specified defaults. This is a great way
415 | to pre-package often-reused configurations.
416 |
417 | ### Syntax
418 |
419 | myDelegator.cloneTrigger(name, newName, defaults);
420 |
421 | ### Arguments
422 |
423 | 1. name - (*string*) The registered name of a trigger.
424 | 2. newName - (*string*) The name of the new trigger.
425 | 3. defaults - (*object*) A key/value pair of defaults.
426 |
427 | Delegator Method: getTrigger {#Delegator:getTrigger}
428 | --------------------------------------------------
429 |
430 | This is both a static method and an instance method. Using the static method (`Delegator.getTrigger(...)`) will return a *global* trigger. Using the instance method will return a *local* trigger or, if not found, the global one.
431 |
432 | ### Syntax
433 |
434 | Delegator.getTrigger(name);
435 | myDelegator.getTrigger(name);
436 |
437 | ### Arguments
438 |
439 | 1. name - (*string*) the name of the trigger to retrieve.
440 |
441 | ### Returns
442 |
443 | * trigger - (*object* or *null*) the trigger instance if found.
444 |
445 | ### Examples
446 |
447 | //this is the same example as the one at the top of the page
448 | var myDelegator = new Delegator();
449 | myDelegator.attach(myContainerElement);
450 | //this adds a global trigger
451 | Delegator.register('click', 'hide', function(event, element, api){
452 | //...
453 | });
454 |
455 | Delegator.getTrigger('hide'); //returns the GLOBAL trigger instance
456 | myDelegator.getTrigger('hide'); //returns the GLOBAL trigger instance
457 |
458 | //but if we add a local one
459 | myDelegator.register('click', 'hide', function(event, element, api){
460 | //... local version by the same name
461 | });
462 |
463 | Delegator.getTrigger('hide'); //returns the GLOBAL trigger instance
464 | myDelegator.getTrigger('hide'); //returns the LOCAL trigger instance
465 |
466 | ### Extended handlers
467 |
468 | Handlers, much like Behavior's filter declaration, are passed an instance of [BehaviorAPI][] as they often have additional configuration properties (for example, a selector to find *which* form to submit or hide or what-have-you). You can declare a handler in object notation with values for defaults and required properties. Example:
469 |
470 | myDelegator.register('click', 'hide', {
471 | require: ['target'],
472 | requireAs: {
473 | count: Number,
474 | whatever: Array
475 | },
476 | defaults: {
477 | someSelector: '#foo'
478 | },
479 | handler: function(event, element, api){...}
480 | });
481 |
482 | Elements that fail to provide the required attributes will have these filters ignored. These triggers throw errors but by default these are caught unless you set `options.breakOnErrors` to `true`.
483 |
484 | ### Included handlers
485 |
486 | Delegator includes five handlers:
487 |
488 | * `Stop` - calls `event.stop()` on the event for you; this is typically done in the registered trigger, but can be done at the element level if you include this trigger in your HTML declaration.
489 | * `PreventDefault` - similar to `Stop`, this calls `event.preventDefault()`.
490 | * `multi` - allows you to define numerous triggers to invoke on other elements (see section above on the `multi` trigger)
491 | * `first` and `any` - special types of `multi`-filter groups. See section above on "switch" filters.
492 |
493 | ### Events of note
494 |
495 | Triggers can fire events on the instance of Delegator that invokes them. See the Events section above regarding the events supported by default. In particular, if you're using this class with Behavior you should take care to connect the two and to use the `destroyDom` and `ammendDom` events.
496 |
497 | You can also have your triggers fire any other arbitrary event that you like to facilitate integration with other triggers or external objects that attach to Delegator's event model.
498 |
499 | Delegator Method: addEventTypes {#Delegator:addEventTypes}
500 | ----------------------------------------------------------
501 |
502 | Adds event types to a registered trigger.
503 |
504 | ### Syntax
505 |
506 | myDelegator.addEventTypes(triggerName, types);
507 |
508 | ### Arguments
509 |
510 | 1. triggerName - (*string*) the name of the trigger.
511 | 2. types - (*array*) the event types to add (`blur`, `click`, etc).
512 |
513 | Delegator Method: attach {#Delegator:attach}
514 | --------------------------------------------------
515 |
516 | Attaches the appropriate event listeners to the provided container.
517 |
518 | ### Syntax
519 |
520 | myDelegator.attach(container);
521 |
522 | ### Returns
523 |
524 | * (*object*) this instance of Delegator
525 |
526 | ### Notes
527 |
528 | * Attaching the event listeners to nested elements is highly discouraged.
529 |
530 | Delegator Method: detach {#Delegator:detach}
531 | --------------------------------------------------
532 |
533 | Detaches the appropriate event listeners from the provided container or, if none is provided, all of them that have previously been attached.
534 |
535 | ### Syntax
536 |
537 | myDelegator.detach([container]);
538 |
539 | ### Arguments
540 |
541 | 1. container - (*element*; optional) A DOM element (or its ID) to attach delegated events. If none is specified all previously attached elements are detached.
542 |
543 | ### Returns
544 |
545 | * (*object*) this instance of Delegator
546 |
547 | Delegator Method: fireEventForElement {#Delegator:fireEventForElement}
548 | ----------------------------------------------------------------------
549 | Fires the provided element's triggers that match the provided event type.
550 |
551 | ### Syntax
552 | myDelegator.fireEventForElement(element, eventType, [force])
553 |
554 | ### Arguments
555 |
556 | 1. element - (*element*) A DOM element which has triggers that respond to the given event
557 | 2. eventType - (*string*) the name of an event to fire
558 | 3. force - (*boolean*; optional) force the element to fire its triggers, even if they don't respond to the given event.
559 |
560 | Delegator Method: trigger {#Delegator:trigger}
561 | --------------------------------------------------
562 |
563 | Invokes a specific trigger manually.
564 |
565 | ### Syntax
566 |
567 | myDelegator.trigger(trigger, element[, event, ignoreTypes]);
568 |
569 | ### Example
570 |
571 | myDelegator.trigger('UpdateOnSubmit', myForm, 'submit'); //creates a mock "submit" event
572 |
573 | ### Arguments
574 |
575 | 1. trigger - (*string*) The name of the registered trigger to invoke.
576 | 2. element - (*element*) A DOM element (or its ID) for the trigger's target.
577 | 3. event - (*event* or *string*; optional) An optional event to pass to the trigger. If you pass in a string, a mock event will be created for that type. If none is provided a mock event is created as a "click" event.
578 | 4. ignoreTypes - (*boolean*) if `true` does not check the event type to see if it matches the trigger's specified supported methods.
579 |
580 | ### Returns
581 |
582 | * (*mixed*) - Whatever the trigger invoked returns.
583 |
584 | Static Methods
585 | ==============
586 |
587 | In addition to those listed above that are both static and instance methods...
588 |
589 | Delegator Method: debug {#Delegator:debug}
590 | --------------------------------------------------
591 |
592 | Will invoke `debugger` before executing any trigger that matches that name, allowing you to walk through that filter's invocation.
593 |
594 | ### Syntax
595 |
596 | Delegator.debug(pluginName);
597 |
598 | ### Arguments
599 |
600 | 1. pluginName - (*string*) The name of the plugin.
601 |
602 |
603 | Element Methods
604 | ===============
605 |
606 | Delegator implements the following helper methods on the Element prototype.
607 |
608 | Element Method: addTrigger {#Element:addTrigger}
609 | ------------------------------------------------------
610 |
611 | Adds a trigger to the element.
612 |
613 | ### Syntax
614 |
615 | myElement.addTrigger(name);
616 |
617 | ### Arguments
618 |
619 | 1. name - (*string*) The name of the trigger to add.
620 |
621 | ### Returns
622 |
623 | * (*element*) This element.
624 |
625 | Element Method: removeTrigger {#Element:removeTrigger}
626 | ------------------------------------------------------
627 |
628 | Removes a trigger to the element.
629 |
630 | ### Syntax
631 |
632 | myElement.removeTrigger(name);
633 |
634 | ### Arguments
635 |
636 | 1. name - (*string*) The name of the trigger to remove.
637 |
638 | ### Returns
639 |
640 | * (*element*) This element.
641 |
642 |
643 | Element Method: getTriggers {#Element:getTriggers}
644 | ------------------------------------------------------
645 |
646 | Gets an array of triggers specified on an element.
647 |
648 | ### Syntax
649 |
650 | myElement.getTriggers();
651 |
652 | ### Returns
653 |
654 | * (*array*) A list of trigger names.
655 |
656 | Element Method: hasTrigger {#Element:hasTrigger}
657 | ------------------------------------------------------
658 |
659 | Returns `true` if the element has the specified trigger.
660 |
661 | ### Syntax
662 |
663 | myElement.hasTrigger(name);
664 |
665 | ### Arguments
666 |
667 | 1. name - (*string*) The name of the trigger to check for.
668 |
669 | ### Returns
670 |
671 | * (*boolean*) Returns `true` if the element has the specified trigger.
672 |
673 | [Options]: http://mootools.net/docs/core/Class/Class.Extras#Options
674 | [Events]: http://mootools.net/docs/core/Class/Class.Extras#Events
675 | [Behavior]: Behavior
676 | [BehaviorAPI]: BehaviorAPI
677 |
--------------------------------------------------------------------------------
/Docs/Element.Data.md:
--------------------------------------------------------------------------------
1 | Native: Element {#Element}
2 | ==========================
3 |
4 | Extends the [Element][] native object with methods useful for working with HTML5 data properties.
5 |
6 | Element Method: setData {#Element:setData}
7 | ------------------------------------------
8 |
9 | Sets a value for a given data property.
10 |
11 | ### Syntax
12 |
13 | myDiv.setData(name, value)
14 |
15 | ### Example
16 |
17 | myDiv.setData('foo-bar', 'baz');
18 | //result:
19 |
20 | ### Returns
21 |
22 | * (*element*) the element
23 |
24 | Element Method: getData {#Element:getData}
25 | ------------------------------------------
26 |
27 | Gets a value for a given data property.
28 |
29 | ### Syntax
30 |
31 | myDiv.getData(name, defaultValue)
32 |
33 | ### Arguments
34 |
35 | 1. name - (*string*) the data property to get; this is prepended with "data-".
36 | 2. defaultValue - (*string, number*) the value to assign if none is set.
37 |
38 | ### Example
39 |
40 | myDiv.getData('foo-bar');
41 | //returns "baz" from:
42 |
43 | ### Returns
44 |
45 | * (*string*) the value if found, otherwise *null*.
46 |
47 | Element Method: setJSONData {#Element:setJSONData}
48 | ------------------------------------------
49 |
50 | Sets a value for a given data property, encoding it into JSON.
51 |
52 | ### Syntax
53 |
54 | myDiv.setJSONData(name, object)
55 |
56 | ### Example
57 |
58 | myDiv.setJSONData('foo-bar', [1, 2, 'foo','bar']);
59 | //result:
60 |
61 | ### Returns
62 |
63 | * (*element*) the element
64 |
65 | Element Method: getJSONData {#Element:getJSONData}
66 | ------------------------------------------
67 |
68 | Gets a value for a given data property, parsing it from JSON.
69 |
70 | ### Syntax
71 |
72 | myDiv.getJSONData(name, strict, defaultValue)
73 |
74 | ### Arguments
75 |
76 | 1. name - (*string*) the data property to get; this is prepended with "data-".
77 | 2. strict - (*boolean*) if *true* (default), will set the *JSON.decode*'s secure flag to *true*; otherwise the value is still tested but allows single quoted attributes.
78 | 3. defaultValue - (*string, number*) the value to assign if none is set.
79 |
80 | ### Example
81 |
82 | myDiv.getData('foo-bar');
83 | //returns [1, 2, 'foo','bar'] from:
84 |
85 | ### Returns
86 |
87 | * (*object*) the value if found, otherwise *null*.
88 |
89 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = function(grunt) {
4 |
5 | require('load-grunt-tasks')(grunt);
6 | var options = require('./Tests/gruntfile-options');
7 |
8 | grunt.initConfig({
9 | 'connect': options.grunt,
10 | 'packager': {
11 | source: {
12 | options: {
13 | name: {
14 | Behavior: 'Source/',
15 | More: 'node_modules/mootools-more/',
16 | Core: 'node_modules/mootools-core/'
17 | },
18 | /*
19 | only: [
20 | 'Behavior/Behavior',
21 | 'Behavior/Element.Data',
22 | 'Behavior/BehaviorAPI'
23 | ]
24 | */
25 | },
26 | src: [
27 | 'node_modules/mootools-core/Source/**/*.js',
28 | 'node_modules/mootools-more/Source/**/*.js',
29 | 'Source/**/*.js'
30 | ],
31 | dest: 'behavior.js'
32 | },
33 | specs: {
34 | options: {
35 | name: 'Specs',
36 | ignoreYAMLheader: true
37 | },
38 | src: 'Tests/Specs/Behavior/*.js',
39 | dest: 'behavior-specs.js'
40 | }
41 | },
42 | 'karma': {
43 | options: options.karma,
44 | continuous: {
45 | browsers: ['PhantomJS']
46 | }
47 | },
48 | 'clean': {
49 | all: {src: 'behavior*.js'}
50 | }
51 | });
52 |
53 | grunt.registerTask('default', ['clean', 'packager:source', 'packager:specs', 'karma:continuous']);
54 |
55 | };
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Behavior
2 |
3 | Auto-instantiates widgets/classes based on parsed, declarative HTML.
4 |
5 | ### Purpose
6 |
7 | All well-written web sites / apps that are interactive have the same basic pattern:
8 |
9 | 
10 |
11 | Each page of the site or app is esoteric. It may have any combination of interactive elements, some of which interact with each other (for example, a form validation controller might interact with an ajax controller to prevent it sending a form that isn't valid). Typically this "glue" code exists in a domready statement. It says, get *this* form and instantiate *that* class with *these* options. This code is brittle; if you change either the DOM or the code the state breaks easily. It's not reusable, it only works for a specific page state. It can easily get out of hand.
12 |
13 | Behavior attempts to abstract that domready code into something you only write once and use often. It's fast and easily customized and extended. Instead of having a domready block that, say, finds all the images on a page and turns them into a gallery, and another block that searches the page for all the links on the page and turns them into tool tips, Behavior does a single search for all the elements you've marked. Each element is passed through the filter it names, where a filter is a function (and perhaps some configuration) that you've named. Each of these functions takes that element, reads properties defined on it in a prescribed manner and invokes the appropriate UI component.
14 |
15 | ## Documentation
16 |
17 | See markdown files in the *Docs* directory.
18 |
19 | * [Behavior](Docs/Behavior.md)
20 | * [BehaviorAPI](Docs/BehaviorAPI.md)
21 | * [Element.Data](Docs/Element.Data.md)
22 |
23 | ### Why?
24 |
25 | The nutshell is that instead of having a domready function that finds the stuff in your DOM and sets up instances of classes and whatnot, you put the configuration in the HTML itself and write the code that calls "new Foo(...)" only once. Example:
26 |
27 | Instead of this:
28 |
29 | $$('form').each(function(form){
30 | new FormValidator(form, someOptions);
31 | new Form.Request(form, someOptions);
32 | });
33 | new Tips($$('.tip'));
34 | $$('.accordion').each(function(container){
35 | new Accordion(container.getElements('.toggler'), container.getElements('.section'), someOptions);
36 | });
37 | etc
38 |
39 | You do this:
40 |
41 |
42 | blah
43 |
...
44 |
45 | Think of it as delegation (as in event delegation) for class invocation. If you use domready to do your setup and you want to swap out some HTML with XHR, you need to reapply that startup selectively to only your components that you're updating, which is often painful. Not with Behavior, you just apply the filters to the response and call it a day.
46 |
47 | You do a lot less DOM selection; you only ever run `$$('[data-behavior]')` once (though some filters may run more selectors on themselves - like Accordion finding its togglers and sections).
48 |
49 | Domready setup is always closely bound to the DOM anyway, but it's also separated from it. If you change the DOM, you might break the JS that sets it up and you always have to keep it in sync. You almost can't do that here because the DOM and its configuration is closely bound and in the same place.
50 |
51 | Developers who maybe aren't interested in writing components don't need to wade into the JS to use it. This is a big deal if you're working with a team you must support.
52 |
53 | Behavior is designed for apps that are constantly updating the UI with new data from the server. It's *not* an MVC replacement though. It's designed for web development that uses HTML fragments not JSON APIs (though it can play nicely with them). If you destroy a node that has a widget initialized it's easy to make sure that widget cleans itself up. The library also allows you to create enforcement to prevent misconfiguration and an API that makes it easy to read the values of the configuration.
54 |
55 | There are some other nifty things you get out of it; you get essentially free specs tests and benchmarks because the code to create both of them is in the Behavior filter. Here's an example of what it takes to write a spec for a widget and ALSO the benchmark for it's instantiation (this uses [Behavior.SpecsHelpers.js](https://github.com/anutron/behavior/blob/master/Tests/Specs/Behavior/Behavior.SpecsHelpers.js)).
56 |
57 | Behavior.addFilterTest({
58 | filterName: 'OverText',
59 | desc: 'Creates an instance of OverText',
60 | content: '',
61 | returns: OverText
62 | });
63 |
64 | This code above can be used to validate that the HTML fragment passed in does, in fact, create an OverText instance and it can also be used with [Benchmark.js](http://benchmarkjs.com/) to see which of your filters are the most expensive. More on this stuff in a minute.
65 |
66 | ### Delegator
67 |
68 | Included in the library is also a file called Delegator which is essentially the same thing except for events. For example, let's say you have a predictable UI pattern of having a link that, when clicked, it hides a parent element. Rather than writing that code each time:
69 |
70 | document.body.addEvent("click:a.hideParent", function(e, link){
71 | e.preventDefault();
72 | link.getParent().hide();
73 | });
74 |
75 | You register this pattern with Delegator and now you just do:
76 |
77 | Hide Me!
78 |
79 | It provides essentially the same value as Behavior, but at event time. The above example is pretty straight forward so, you know, why bother, right? But consider how many of these little things you write to make a web app function. If you can create them once and configure them inline, you save yourself a lot of code.
80 |
81 | ### Stock Behaviors
82 |
83 | Check out these resources of available Behavior Filters provided by the author:
84 |
85 | * [https://github.com/anutron/more-behaviors](https://github.com/anutron/more-behaviors)
86 | * [https://github.com/anutron/clientcide](https://github.com/anutron/clientcide)
87 | * [https://github.com/anutron/mootools-bootstrap](https://github.com/anutron/mootools-bootstrap)
88 |
89 |
90 | ## Notes
91 |
92 | Below are some notes regarding the implementation. The documentation should probably be read first as it gives usage examples.
93 |
94 | * Only one selector is ever run; adding 1,000 filters doesn't affect performance.
95 | * Nodes can have numerous filters.
96 | * Nodes can have an arbitrary number of supported options for each filter (`data-behaviorname-foo="bar"`).
97 | * Nodes can define options as JSON (this is actually the preferred implementation - `data-behaviorname-options=""`).
98 | * Elements can be retired w/ custom destruction; cleaning up an element also cleans up all the children of that element that have had behaviors applied.
99 | * Behaviors are only ever applied once to an element; if you call `myBehavior.apply(document.body)` a dozen times, the elements with filters will only have those filters applied once (can be forced to override and re-apply).
100 | * Filters are instances of classes that are applied to any number of elements. They are named uniquely.
101 | * Filters can be namespaced. Declare a filter called `Foo.Bar` and reference its options as `data-foo-bar-options="..."`.
102 | * There are "global" filters that are registered for all instances of behavior.
103 | * Instance filters get precedence. This allows for libraries to provide filters (like [http://github.com/anutron/more-behaviors](http://github.com/anutron/more-behaviors)) but for a specific instance to overwrite it without affecting the global state. (This pattern is in MooTools' `FormValidator` and works pretty well).
104 | * Filters have "plugins". A requirement for Filters is that they are unaware of each other. They have no guarantee that they will be invoked in any order (the developer writing the HTML expresses their order) or that they will be invoked with others or on their own. In addition to this ignorance, it's entirely likely that in specific environments a developer might want to augment a filter with additional functionality invoked whenever the filter is. This is what plugins are for. Plugins name the filter they augment but otherwise are just filters themselves. It's possible to have plugins for plugins. When you need to make two filters aware of each other (`FilterInput` + `HtmlTable.Zebra`). Note that plugins are always executed after all the filters are, so when writing a plugin that checks for a combination of two filters it is guaranteed that both filters have been applied.
105 | * Behavior defines a bottleneck for passing environment awareness to filters (`passMethod` / `behaviorAPI`). Filters should not know too explicitly about the environment they were invoked in. If the filters, for example, had to be able to call a method on some containing class - one that has created an instance of Behavior for example, the filter shouldn't have to have a pointer to that instance itself. It would make things brittle; a change in that class would break any number of unknown filters. By forcing the code that creates the Behavior instance to declare what methods filters can use it makes a more maintainable API.
106 |
107 | ## Limitations:
108 |
109 | * Due to the DOM-searching for both creation and destruction, you can't have behavior instances inside each other.
110 |
--------------------------------------------------------------------------------
/Source/Behavior.Events.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 | name: Behavior.Events
4 | description: Allows for the triggering of delegators when classes instantiated by Behavior fire arbitrary events.
5 | requires: [/Behavior, /Delegator]
6 | provides: [Behavior.Events]
7 | ...
8 | */
9 |
10 | /*
11 |
12 |
61 |
62 | */
63 | (function(){
64 |
65 | var reggies = {
66 | eventArguments: /^eventArguments/,
67 | eventArgumentIndex: /.*\[(.*)\]/,
68 | instanceMethod: /^instance\.([a-zA-Z].*)\(/,
69 | instanceProperty: /^instance\./
70 | };
71 |
72 | var parseConditional = function(element, api, conditional, instance, eventArguments){
73 | var result = Object.every(conditional, function(value, key){
74 | // key == "eventArguments[1]"
75 | if (key.match(reggies.eventArguments)){
76 | var index = key.match(reggies.eventArgumentIndex)[1].toInt();
77 | // index == 1
78 | return eventArguments[index] == value;
79 | }
80 | // key == instance.foo()
81 | if (key.match(reggies.instanceMethod)){
82 | var method = key.match(reggies.instanceMethod)[1];
83 | if (instance[method]){
84 | if (conditional['arguments']) return instance[method].apply(instance, conditional['arguments']) == value;
85 | else return instance[method]() == value;
86 | }
87 |
88 | }
89 | // key == instance.foo
90 | if (key.match(reggies.instanceProperty)){
91 | return instance[key.split('.')[1]] == value;
92 | }
93 | return Delegator.verifyTargets(element, conditional, api);
94 | });
95 | return result;
96 | };
97 |
98 | Behavior.addGlobalFilter('addEvent', {
99 | setup: function(element, api){
100 | api.addEvent('apply:once', function(){
101 | var events = api.getAs(Object, 'events');
102 | Object.each(events, function(eventsToAdd, key){
103 | var selector = key.split('::')[0];
104 | var behaviorName = key.split('::')[1];
105 | var target = Behavior.getTarget(element, selector);
106 | if (!target) return api.warn('Could not find element at ' + selector + ' to add event to ' + behaviorName);
107 | var instance = target.getBehaviorResult(behaviorName);
108 | if (!instance) return api.warn('Could not find instance of ' + behaviorName + ' for element at ' + selector);
109 | Object.each(eventsToAdd, function(triggers, eventName){
110 | instance.addEvent(eventName, function(){
111 | var eventArgs = arguments;
112 | triggers.each(function(trigger){
113 | Object.each(trigger, function(options, delegatorTarget){
114 | var valid = true;
115 | if (options['if'] && !parseConditional(element, api, options['if'], instance, eventArgs)) valid = false;
116 | if (options['unless'] && parseConditional(element, api, options['unless'], instance, eventArgs)) valid = false;
117 |
118 | if (valid){
119 | // we've already tested these, so remove
120 | options['_if'] = options['if'];
121 | options['_unless'] = options['unless'];
122 | delete options['if'];
123 | delete options['unless'];
124 | // invoke the trigger
125 | api.getDelegator()._invokeMultiTrigger(element, null, delegatorTarget, options);
126 | // put them back
127 | options['if'] = options['_if'];
128 | options['unless'] = options['_unless'];
129 | delete options['_if'];
130 | delete options['_unless'];
131 | }
132 | });
133 | });
134 | });
135 | });
136 | });
137 | });
138 | return element;
139 | }
140 | });
141 | })();
142 |
--------------------------------------------------------------------------------
/Source/Behavior.Startup.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 | name: Behavior.Startup
4 | description: Invokes delegators on startup when specified conditions are met.
5 | requires: [/Behavior, /Delegator, /Delegator.verifyTargets]
6 | provides: [Behavior.Startup]
7 | ...
8 | */
9 | (function(){
10 | Behavior.addGlobalFilter('Startup', {
11 | setup: function(el, api){
12 | //get the delegators to set up
13 | var delegators = api.get('delegators');
14 | if (delegators){
15 | Object.each(delegators, function(conditional, delegator){
16 | var timer =(function(){
17 | //if any were true, fire the delegator ON THIS ELEMENT
18 | if (Delegator.verifyTargets(el, conditional, api)) {
19 | api.getDelegator().trigger(delegator, el);
20 | }
21 | }).delay(conditional.delay || 0)
22 | api.onCleanup(function(){
23 | clearTimeout(timer);
24 | });
25 | });
26 | }
27 | }
28 | });
29 | })();
30 |
--------------------------------------------------------------------------------
/Source/Behavior.Trigger.js:
--------------------------------------------------------------------------------
1 | /*
2 | ---
3 | name: Behavior.Trigger
4 | description: Because Delegator is inefficient for mouse over/out events, this behavior
5 | allows you to invoke delegator triggers on elements when they occur using
6 | normal event monitoring.
7 | requires: [/Behavior, /Delegator]
8 | provides: [Behavior.Trigger]
9 | ...
10 | */
11 |
12 | /*
13 |
14 |