Skip to content
Newer
Older
100644 322 lines (253 sloc) 10.8 KB
d30df71 @alexlawrence Initial commit.
authored
1 #declarative
2
93876cd @alexlawrence Added node.js support (untested).
authored
3 Mapper for custom user interface markup.
d30df71 @alexlawrence Initial commit.
authored
4
be7b053 @alexlawrence Minor typos.
authored
5 ###Motivation
d30df71 @alexlawrence Initial commit.
authored
6
c8876ae @alexlawrence Updated documentation.
authored
7 When writing web user interfaces without using JavaScript we are restricted to the set of native interface elements
8 defined in the HTML standard and thus implemented by the browser.
4e7b611 @alexlawrence Updated documentation.
authored
9
c8876ae @alexlawrence Updated documentation.
authored
10 Every time we want to add custom elements to our interface we do this by implementing the desired functionality
11 in JavaScript and linking the behavior to one or more DOM elements and their events. We find the elements by querying
12 the DOM using some criteria such as IDs or classes.
8909ae7 @alexlawrence Updated documentation.
authored
13
c8876ae @alexlawrence Updated documentation.
authored
14 We do this because we want to separate the behavior from the content and its structure. However in most cases
15 we are not only pulling out the behavior but also the interface configuration. Consider the following search form
be7b053 @alexlawrence Minor typos.
authored
16 with a character counter displaying how many characters are left to enter:
c8876ae @alexlawrence Updated documentation.
authored
17
18 ```html
19 <form action="/" method="POST">
20 <input id="search" name="search" type="text" maxlength="50" />
21 <span id="counter"></span>
6263f4d @alexlawrence Changed parseOptions for all browsers to eval() for nested objects. R…
authored
22 <input type="submit" />
c8876ae @alexlawrence Updated documentation.
authored
23 </form>
24 ```
4e7b611 @alexlawrence Updated documentation.
authored
25
26 ```javascript
c8876ae @alexlawrence Updated documentation.
authored
27 var countCharacters = function(input, counter) {
28 input.addEventListener('keyup', function() {
29 counter.innerHTML = (50 - this.value.length) + ' characters left';
30 });
31 };
32
33 var search = document.getElementById('search');
34 var counter = document.getElementById('counter');
35 countCharacters(search, counter);
8909ae7 @alexlawrence Updated documentation.
authored
36 ```
37
c8876ae @alexlawrence Updated documentation.
authored
38 The HTML holds nothing but the content and its structure. The script however contains the following interface
39 configuration which should actually be placed in the markup:
8909ae7 @alexlawrence Updated documentation.
authored
40
c8876ae @alexlawrence Updated documentation.
authored
41 - The maximum count of characters for a specific input element
42 - The displayed format text for a specific counter
43 - The linking between a counter and its corresponding input field
4347933 @alexlawrence Updated documentation.
authored
44
c8876ae @alexlawrence Updated documentation.
authored
45 One better way to implement this would be the following:
46
47 ```html
48 <form action="/" method="POST">
49 <input id="search" name="search" type="text" maxlength="50" />
50 <span id="counter" data-target="search" data-text="{0} characters left"></span>
6263f4d @alexlawrence Changed parseOptions for all browsers to eval() for nested objects. R…
authored
51 <input type="submit" />
c8876ae @alexlawrence Updated documentation.
authored
52 </form>
53 ```
d30df71 @alexlawrence Initial commit.
authored
54
5029510 @alexlawrence Updated readme.
authored
55 ```javascript
c8876ae @alexlawrence Updated documentation.
authored
56 var countCharacters = function(input, counter, text) {
57 var maxlength = input.getAttribute('maxlength');
5029510 @alexlawrence Updated readme.
authored
58 input.addEventListener('keyup', function() {
c8876ae @alexlawrence Updated documentation.
authored
59 counter.innerHTML = text.replace('{0}', maxlength - this.value.length);
5029510 @alexlawrence Updated readme.
authored
60 });
61 };
c8876ae @alexlawrence Updated documentation.
authored
62
63 var counter = document.getElementById('counter');
64 var input = document.getElementById(counter.getAttribute('data-target'));
65 var text = counter.getAttribute('data-text');
66 countCharacters(input, counter, text);
5029510 @alexlawrence Updated readme.
authored
67 ```
7bab01d @alexlawrence Switched build script to powershell.
authored
68
c8876ae @alexlawrence Updated documentation.
authored
69 The markup now also holds the configuration for custom interface elements but still does not contain any behavior
70 or implementation details. Note that we are using custom data attributes in order to have valid HTML markup.
71 The above example becomes even more obvious when you imagine the counter being a native HTML element:
7bab01d @alexlawrence Switched build script to powershell.
authored
72
5029510 @alexlawrence Updated readme.
authored
73 ```html
c8876ae @alexlawrence Updated documentation.
authored
74 <form action="/" method="POST">
75 <input id="search" name="search" type="text" maxlength="50" />
76 <counter for="search">{0} characters left</counter>
6263f4d @alexlawrence Changed parseOptions for all browsers to eval() for nested objects. R…
authored
77 <input type="submit" />
c8876ae @alexlawrence Updated documentation.
authored
78 </form>
5029510 @alexlawrence Updated readme.
authored
79 ```
7bab01d @alexlawrence Switched build script to powershell.
authored
80
c8876ae @alexlawrence Updated documentation.
authored
81 ###Features
82
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
83 declarative provides the possibility to declare custom interface elements in any markup and to easily map them to
84 arbitrary JavaScript code. Thus it prevents from writing similar querying and mapping code over and over again.
c8876ae @alexlawrence Updated documentation.
authored
85
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
86 Let´s assume we want to implement the previously mentioned search interface using declarative. We start off with the HTML:
c8876ae @alexlawrence Updated documentation.
authored
87
88 ```html
89 <form action="/" method="POST">
90 <input id="search" name="search" type="text" maxlength="50" />
91 <span data-widget-counter="target: 'search', text: '{0} characters left'"></span>
6263f4d @alexlawrence Changed parseOptions for all browsers to eval() for nested objects. R…
authored
92 <input type="submit" />
c8876ae @alexlawrence Updated documentation.
authored
93 </form>
94 ```
95
96 When working with custom interface elements there are three important values to consider: the **DOM element** itself,
97 the **custom type** and its **options**. In the above form there is one span element having the custom type "counter"
98 and two options. One is the ID of the input element and the other one is the format text for displaying. Note the value
99 of the "data-counter" attribute. The syntax used by declarative is equivalent to the object syntax in JavaScript
100 without the most outer curly braces. The attribute value can also be omitted (interpreted as an empty options object).
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
101
102 The next step is to register the counter as a custom type and describe how it should be mapped to JavaScript code.
103 This is done by adding a mapping to declarative:
7bab01d @alexlawrence Switched build script to powershell.
authored
104
5029510 @alexlawrence Updated readme.
authored
105 ```javascript
106 declarative.mappings.add({
107 id: 'counter',
108 prefix: 'data-widget-',
109 types: ['counter']
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
110 callback: function(counter, type, options) {
5fdfa85 @alexlawrence Fixed typo in readme.
authored
111 var input = document.getElementById(options.target);
cdd7fe1 @alexlawrence Fixed another error in the readme.
authored
112 countCharacters(input, counter, options.text);
5029510 @alexlawrence Updated readme.
authored
113 }
114 });
115 ```
7bab01d @alexlawrence Switched build script to powershell.
authored
116
c8876ae @alexlawrence Updated documentation.
authored
117 The **id** of a mapping is used for later identification. The optional **prefix** defines the string that is put before
118 the type when used as an attribute of an HTML element. While any string is valid in most cases it should start with
119 "data-" to make use of HTML custom data attributes. The **types** array defines the types declarative searches for
120 when applying the mapping. The **callback** function is called for every match of the mapping when applied.
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
121 Parameters for the callback are the DOM element, the type without the prefix and the options as an object.
7bab01d @alexlawrence Switched build script to powershell.
authored
122
c8876ae @alexlawrence Updated documentation.
authored
123 Applying the above mapping to the whole DOM is done by writing the following:
7bab01d @alexlawrence Switched build script to powershell.
authored
124
5029510 @alexlawrence Updated readme.
authored
125 ```javascript
126 declarative.apply('counter').to(document);
127 ```
7bab01d @alexlawrence Switched build script to powershell.
authored
128
1e62214 @alexlawrence Implemented async appliying of mappings, restructured internals.
authored
129 ###API
130
131 ####Creating mappings
132
133 ```javascript
134 declarative.mappings.add({
135 id: 'example mapping', // string identifier
136 prefix: 'data-attribute-prefix-', // lowercase attribute prefix (optional)
137 types: ['types', 'to', 'map'], // types that will be mapped when found
138 callback: function(element, type, options) {
139 // callback is called for every match in the current mapping when applied
140 }
141 });
142 ```
143
144 ####Applying mappings
145
146 Mappings can be applied to any DOM element:
147
148 ```javascript
149 declarative.apply('example mapping').to(document);
150 ```
151
152 Multiple mappings for the same DOM element should be passed in a single call:
153
154 ```javascript
155 declarative.apply(['example mapping', 'another mapping']).to(document);
156 ```
157
158 All available mappings can be applied at once:
159
160 ```javascript
161 declarative.applyAllMappings().to(document);
162 ```
163
164 DOM elements need to be unwrapped when using jQuery:
165
166 ```javascript
167 declarative.applyAllMappings().to($('#someElement').get(0));
168 ```
169
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
170 ####Camel cased types
171
172 declarative automatically transforms hyphenated attribute names to camel case when matching against types.
8237c33 @alexlawrence Updated readme.
authored
173 This is the same behavior as building the dataset attribute from HTML data-* attributes defined
174 <a href="http://dev.w3.org/html5/spec/single-page.html#embedding-custom-non-visible-data-with-the-data-attributes">here</a>.
175
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
176 Example:
8237c33 @alexlawrence Updated readme.
authored
177
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
178 ```html
179 <div data-widget-camel-counter="target: 'search', text: '{0} characters left'"></div>
180 ```
181
182 ```javascript
183 declarative.mappings.add({
184 id: 'camel case',
185 prefix: 'data-widget-',
186 types: ['camelCounter']
187 callback: function(counter, type, options) {
188 // type will be 'camelCounter'
189 }
190 });
191 ```
192
193 ####Distinct mappings
194
195 By default mappings are "distinct". This means that a mapping callback for a certain element is only called once
196 no matter how often the mapping is applied. This is especially useful when you encounter DOM changes but don´t want
197 to apply a mapping to a specific DOM element.
8237c33 @alexlawrence Updated readme.
authored
198
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
199 ```javascript
200 declarative.mappings.add({
201 id: 'example mapping',
202 prefix: 'data-attribute-prefix-',
203 types: ['types', 'to', 'map'],
204 callback: function(element, type, options) {},
1e62214 @alexlawrence Implemented async appliying of mappings, restructured internals.
authored
205 distinct: false
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
206 });
207 ```
208
1e62214 @alexlawrence Implemented async appliying of mappings, restructured internals.
authored
209 ####Asnychronous work
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
210
1f4e49c @alexlawrence Updated mmd and readme.
authored
211 Applying mappings works asynchronously in order to not block the JavaScript execution thread for a too long time.
212 The return value of the apply() method is a [promise](http://wiki.commonjs.org/wiki/Promises/A).
213 Waiting for mappings to be finished before executing other code can be done by writing the following:
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
214
215 ```javascript
1e62214 @alexlawrence Implemented async appliying of mappings, restructured internals.
authored
216 declarative.applyAllMappings().to(document).then(function() {
217 // do something after applying mappings
218 });
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
219 ```
1f4e49c @alexlawrence Updated mmd and readme.
authored
220 By default declarative pauses processing every 1000 ms (timeout) and waits for 20ms (waitTime).
221 These settings can easily be changed:
01bf1af @alexlawrence Improved structure, some refactorings, added distinct mappings
authored
222
223 ```javascript
1e62214 @alexlawrence Implemented async appliying of mappings, restructured internals.
authored
224 declarative.settings.mappingTimeoutMs = 200;
225 declarative.settings.mappingWaitTimeMs = 10;
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
226 ```
227
16e8b4a @alexlawrence Updated documentation.
authored
228 ###Examples
229
c8876ae @alexlawrence Updated documentation.
authored
230 While mapping one single custom type might not look too useful have a look at the following examples:
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
231
16e8b4a @alexlawrence Updated documentation.
authored
232 #####Mapping jQueryUI types
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
233
234 ```html
235 <div data-ui-draggable></div>
236 <div data-ui-progressbar="value: '100'"></div>
237 <div data-ui-dialog="closeText: 'hide'"></div>
238 <input type="text" data-ui-datepicker="minDate: '2012/03/01'" />
239 ```
240
241 ```javascript
242 declarative.mappings.add({
243 id: 'jQueryUI',
244 prefix: 'data-ui-',
245 types: ['draggable', 'progressbar', 'dialog', 'datepicker'],
246 callback: function(element, type, options) {
247 $(element)[type](options);
248 }
249 });
250
251 declarative.apply('jQueryUI').to(document);
252 ```
253
c8876ae @alexlawrence Updated documentation.
authored
254 #####Simplified mapping of jQuery.validate
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
255
256 ```html
257 <form data-validate-form>
c8876ae @alexlawrence Updated documentation.
authored
258 <input type="text" name="required" data-validate-required="is: true, withMessage: 'Required'" />
259 <input type="text" name="minlength" data-validate-minlength="is: 3, withMessage: 'Minimum of 3'" />
260 <input type="text" name="maxlength" data-validate-maxlength="is: 6, withMessage: 'Maximum of 6'" />
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
261 <input type="submit" />
262 </form>
263 ```
264
265 ```javascript
266 declarative.mappings.add({
267 id: 'jQuery.validate.form',
268 prefix: 'data-validate-',
269 types: ['form'],
270 callback: function(element) {
271 $(element).validate();
5278dae @alexlawrence Updated documentation.
authored
272 },
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
273 });
274
275 declarative.mappings.add({
276 id: 'jQuery.validate.input',
277 prefix: 'data-validate-',
278 types: ['required', 'minlength', 'maxlength'],
279 callback: function(element, type, options) {
f9071c2 @alexlawrence Updated documentation.
authored
280 var rule = {messages: {}};
c8876ae @alexlawrence Updated documentation.
authored
281 rule[type] = options.is;
282 rule.messages[type] = options.withMessage;
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
283 $(element).rules('add', rule);
284 }
285 });
286
066f054 @alexlawrence Fixed error in readme.
authored
287 declarative.apply('jQuery.validate.form').to(document).then(function() {
288 declarative.apply('jQuery.validate.input').to(document);
289 });
290
9903c6a @alexlawrence Fixed various bugs of parsing attributes.
authored
291 ```
7bab01d @alexlawrence Switched build script to powershell.
authored
292
c8876ae @alexlawrence Updated documentation.
authored
293 ###Roll your own markup language
7bab01d @alexlawrence Switched build script to powershell.
authored
294
c8876ae @alexlawrence Updated documentation.
authored
295 declarative can also map elements giving the possibility to use a custom markup language.
296 This is done by changing the mappingMode of a mapping explicitely to "element" (otherwise it is "attribute" by default).
5029510 @alexlawrence Updated readme.
authored
297
298 ```html
c8876ae @alexlawrence Updated documentation.
authored
299 <form action="/" method="POST">
300 <input id="search" name="search" type="text" maxlength="50" />
301 <counter target="search" text="{0} characters left"></counter>
302 <input type="submit">
303 </form>
5029510 @alexlawrence Updated readme.
authored
304 ```
d30df71 @alexlawrence Initial commit.
authored
305
306 ```javascript
c8876ae @alexlawrence Updated documentation.
authored
307 declarative.mappings.add({
308 id: 'counter',
309 types: ['counter'],
310 callback: function(counter, type, options) {
311 var input = document.querySelector(options.target);
312 countCharacters(input, counter, options.text);
313 },
1e62214 @alexlawrence Implemented async appliying of mappings, restructured internals.
authored
314 mappingMode: declarative.mappingModes.element
d30df71 @alexlawrence Initial commit.
authored
315 });
5029510 @alexlawrence Updated readme.
authored
316 ```
d30df71 @alexlawrence Initial commit.
authored
317
c8876ae @alexlawrence Updated documentation.
authored
318 **Notes**:
5029510 @alexlawrence Updated readme.
authored
319
c1bb437 @alexlawrence Fixed typo.
authored
320 - Using custom elements that are not part of the HTML standard might cause rendering problems and styling issues
93876cd @alexlawrence Added node.js support (untested).
authored
321 - This element mapping feature is not tested across different browsers as it is not really a best practice
Something went wrong with that request. Please try again.