') );
267 |
268 | // then
269 | expect( counter ).toBe(1);
270 | expect( $container.find('> div') ).toHaveCss( style_red );
271 | });
272 | });
273 |
274 | describe('\'event\' param of the callback', function()
275 | {
276 | var callback, $element;
277 |
278 | beforeEach(function ()
279 | {
280 | // given
281 | callback = jasmine.createSpy('callback');
282 | $element = jQuery('
');
283 |
284 | // TEST-FIX for jQuery < 3.0 - https://jquery.com/upgrade-guide/3.0/#breaking-change-deprecated-context-and-selector-properties-removed
285 | if ( parseInt(/^[\d]+/.exec( jQuery().jquery )[0], 10) < 3 ) {
286 | $element.context = $element.get(0);
287 | $container.context = $container.get(0);
288 | }
289 |
290 | // when
291 | $container.on('create', '> div', callback);
292 | $container.append( $element );
293 | });
294 |
295 | it('should contain \'type\'.', function()
296 | {
297 | // then
298 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
299 | type: 'create'
300 | }));
301 | });
302 |
303 | it('should contain \'timeStamp\'.', function()
304 | {
305 | // then
306 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
307 | timeStamp: jasmine.any(Number)
308 | }));
309 | });
310 |
311 | it('should contain \'currentTarget\'.', function()
312 | {
313 | // then
314 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
315 | currentTarget: $element.get(0)
316 | }));
317 | });
318 |
319 | it('should contain \'$currentTarget\'.', function()
320 | {
321 | // then
322 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
323 | $currentTarget: $element
324 | }));
325 | });
326 |
327 | it('should contain \'delegateTarget\'.', function()
328 | {
329 | // then
330 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
331 | delegateTarget: $container.get(0)
332 | }));
333 | });
334 |
335 | it('should contain \'$delegateTarget\'.', function()
336 | {
337 | // then
338 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
339 | $delegateTarget: jQuery( $container.get(0) )
340 | }));
341 | });
342 |
343 | it('should contain \'options\'.', function()
344 | {
345 | // then
346 | expect( callback ).toHaveBeenCalledWith(jasmine.objectContaining({
347 | options: jasmine.any(Function)
348 | }));
349 | });
350 | });
351 |
352 | });
353 |
--------------------------------------------------------------------------------
/spec/readme.md.spec.js:
--------------------------------------------------------------------------------
1 | describe('README.md', function() {
2 |
3 | var $container;
4 |
5 | beforeEach(function ()
6 | {
7 | loadFixtures('container.html');
8 | $container = jQuery('#container');
9 |
10 | spyOn(console, 'info');
11 | });
12 |
13 | afterEach(function () {
14 | $container.remove();
15 | $container.off('create');
16 | });
17 |
18 | it('\'The Module Pattern\' example should be true.', function()
19 | {
20 | // given
21 | var myModule = (function () {
22 |
23 | var module = {}
24 | , _privateVariable = 'Hello World'
25 | ;
26 |
27 | var _privateMethod = function() {
28 | return _privateVariable;
29 | };
30 |
31 | module.publicProperty = 'Foobar';
32 | module.publicMethod = function () {
33 | console.info( _privateMethod() );
34 | };
35 |
36 | return module;
37 |
38 | }());
39 |
40 | // when
41 | myModule.publicMethod();
42 |
43 | // then
44 | expect( myModule._privateVariable ).toBeUndefined();
45 | expect( myModule._privateMethod ).toBeUndefined();
46 | expect( myModule.publicProperty ).toEqual( 'Foobar' );
47 | expect( console.info ).toHaveBeenCalledWith( 'Hello World' );
48 | });
49 |
50 | it('\'The Module Pattern with jCreate\' example should be true.', function ()
51 | {
52 | // given
53 | var helloWorldComponent = (function () {
54 |
55 | var module = {}
56 | , _componentName = 'hello-world'
57 | ;
58 |
59 | module.greeting = function( name ) {
60 | console.info( 'Hello ' + name + '!' );
61 | };
62 |
63 | jQuery(document).on('create', '[data-component~="' + _componentName + '"]', function( event ) {
64 | var options = event.options( _componentName ); //= {name="Marco"}
65 | module.greeting( options.name ); //= Hello Marco!
66 | });
67 |
68 | return module;
69 | }());
70 |
71 | // when
72 | helloWorldComponent.greeting('Stefania');
73 | $container.append('
');
74 |
75 | // then
76 | expect( console.info ).toHaveBeenCalledWith( 'Hello Stefania!' );
77 | expect( console.info ).toHaveBeenCalledWith( 'Hello Marco!' );
78 | });
79 |
80 | });
81 |
--------------------------------------------------------------------------------
/spec/utlity.spec.js:
--------------------------------------------------------------------------------
1 | describe('jQuery.event.special.create.utility', function()
2 | {
3 | it('.firstLetterToLowerCase()', function() {
4 | expect( jQuery.event.special.create.utility.firstLetterToLowerCase('marcomontalbano') ).toEqual('marcomontalbano');
5 | expect( jQuery.event.special.create.utility.firstLetterToLowerCase('marcoMontalbano') ).toEqual('marcoMontalbano');
6 | expect( jQuery.event.special.create.utility.firstLetterToLowerCase('MarcoMontalbano') ).toEqual('marcoMontalbano');
7 | });
8 |
9 | it('.firstLetterToUpperCase()', function() {
10 | expect( jQuery.event.special.create.utility.firstLetterToUpperCase('marcomontalbano') ).toEqual('Marcomontalbano');
11 | expect( jQuery.event.special.create.utility.firstLetterToUpperCase('marcoMontalbano') ).toEqual('MarcoMontalbano');
12 | expect( jQuery.event.special.create.utility.firstLetterToUpperCase('MarcoMontalbano') ).toEqual('MarcoMontalbano');
13 | });
14 |
15 | it('.camelize()', function() {
16 | expect( jQuery.event.special.create.utility.camelize('marco-montalbano') ).toEqual('marcoMontalbano');
17 | expect( jQuery.event.special.create.utility.camelize('marco_montalbano') ).toEqual('marcoMontalbano');
18 | expect( jQuery.event.special.create.utility.camelize('marco.montalbano') ).toEqual('marcoMontalbano');
19 | expect( jQuery.event.special.create.utility.camelize('marcoMontalbano') ).toEqual('marcomontalbano');
20 | });
21 |
22 | it('.filterDataByKey()', function() {
23 | expect( jQuery.event.special.create.utility.filterDataByKey({width: 10, productKey:1, productName:'Android'}, 'product') ).toEqual({key:1, name:'Android'});
24 | expect( jQuery.event.special.create.utility.filterDataByKey('this is a string!', 'product') ).toEqual('this is a string!');
25 | });
26 |
27 | it('.filterDataByKey() shouldn\'t break if the array object has been extended.', function()
28 | {
29 | // given
30 | Array.prototype.newCoolFunction = function() {};
31 |
32 | // then
33 | expect(function() {
34 | expect( jQuery.event.special.create.utility.filterDataByKey({width: 10, productKey:1, productName:'Android'}, 'product') ).toEqual({key:1, name:'Android'});
35 | expect( jQuery.event.special.create.utility.filterDataByKey('this is a string!', 'product') ).toEqual('this is a string!');
36 | }).not.toThrow();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/src/jquery.jcreate.js:
--------------------------------------------------------------------------------
1 | ;;
2 |
3 | /**
4 | * jCreate | jQuery Special Event
5 | *
6 | * author: Marco Montalbano
7 | * first private release: Nov 18, 2011
8 | *
9 | * useful links
10 | * ------------
11 | * http://learn.jquery.com/events/event-extensions/
12 | * http://benalman.com/news/2010/03/jquery-special-events/
13 | *
14 | */
15 |
16 | (function($, domManip, append, prepend, before, after, html, replaceWith)
17 | {
18 | var _createList = []
19 | , _utility = {}
20 | ;
21 |
22 | /**
23 | * Recursively transform key strings to camel-case.
24 | * @param {string} str
25 | */
26 | _utility.camelize = function( str ) {
27 | return str.toLowerCase().replace(/[-_\.]+(.)/g, function(match, group) {
28 | return group.toUpperCase();
29 | });
30 | };
31 |
32 | /**
33 | * Returns the first letter in lowercase.
34 | * @param {string} str
35 | */
36 | _utility.firstLetterToLowerCase = function( str ) {
37 | return str.charAt(0).toLowerCase() + str.slice(1);
38 | };
39 |
40 | /**
41 | * Returns the first letter in uppercase.
42 | * @param {string} str
43 | */
44 | _utility.firstLetterToUpperCase = function( str ) {
45 | return str.charAt(0).toUpperCase() + str.slice(1);
46 | };
47 |
48 | _utility.filterDataByKey = function( data, key )
49 | {
50 | var _data = {}
51 | , regexp = new RegExp('^' + key + '([A-Za-z0-9]+)$')
52 | , matches
53 | ;
54 |
55 | if ( typeof data !== 'object' ) {
56 | return data;
57 | }
58 |
59 | for ( var data_key in data )
60 | {
61 | if ( Object.hasOwnProperty.call(data, data_key) )
62 | {
63 | matches = data_key.match( regexp );
64 |
65 | if ( matches ) {
66 | _data[ _utility.firstLetterToLowerCase( matches[1] ) ] = data[ data_key ];
67 | }
68 | }
69 | }
70 |
71 | return _data;
72 | };
73 |
74 | //
75 | var _create = function( _createItem )
76 | {
77 | var $elements = _createItem.is_document ? $( _createItem.handleObj.selector ) : _createItem.$delegateTarget.find( _createItem.handleObj.selector );
78 |
79 | $elements.each(function()
80 | {
81 | var $this = $(this)
82 | , data_key = '$.event.special.create'
83 | , data_sep = ','
84 | , data = $this.data(data_key) ? $this.data(data_key).split(data_sep) : []
85 | ;
86 |
87 | if ( $.inArray( _createItem.id, data) === -1 )
88 | {
89 | data.push( _createItem.id );
90 | $this.data(data_key, data.join(data_sep));
91 | _createItem.handleObj.handler.apply( this, [new $.Event('create', {
92 | currentTarget : this,
93 | $currentTarget : $this,
94 | delegateTarget : _createItem.delegateTarget,
95 | $delegateTarget : _createItem.$delegateTarget,
96 | data : _createItem.handleObj.data,
97 | options : function( key ) {
98 | return _utility.filterDataByKey( $this.data(), _utility.camelize(key) );
99 | }
100 | })] );
101 | }
102 | });
103 | };
104 |
105 | //
106 | var _domManip = function()
107 | {
108 | if (_createList.length >= 1)
109 | {
110 | var _createItem = null;
111 |
112 | for (var key in _createList)
113 | {
114 | if ( _createList.hasOwnProperty( key ) )
115 | {
116 | _createItem = _createList[ key ];
117 |
118 | _create( _createItem );
119 | }
120 | }
121 | }
122 |
123 | return this;
124 | };
125 |
126 | $.event.special.create =
127 | {
128 | /**
129 | * setup: function( data: Object, namespaces, eventHandle: function )
130 | * The setup hook is called the first time an event of a particular type is attached to an element;
131 | * this provides the hook an opportunity to do processing that will apply to all events of this type on this element.
132 | * The this keyword will be a reference to the element where the event is being attached and eventHandle is jQuery's event handler function.
133 | * In most cases the namespaces argument should not be used, since it only represents the namespaces of the first event being attached;
134 | * subsequent events may not have this same namespaces.
135 | *
136 | * This hook can perform whatever processing it desires,
137 | * including attaching its own event handlers to the element or to other elements and recording setup information on the element using the jQuery.data() method.
138 | * If the setup hook wants jQuery to add a browser event (via addEventListener or attachEvent, depending on browser) it should return false. In all other cases,
139 | * jQuery will not add the browser event, but will continue all its other bookkeeping for the event.
140 | * This would be appropriate, for example, if the event was never fired by the browser but invoked by .trigger().
141 | * To attach the jQuery event handler in the setup hook, use the eventHandle argument.
142 | */
143 | //setup: function( data, namespaces, eventHandle )
144 | //{
145 | //
146 | //},
147 |
148 | /**
149 | * teardown: function()
150 | * The teardown hook is called when the final event of a particular type is removed from an element.
151 | * The this keyword will be a reference to the element where the event is being cleaned up.
152 | * This hook should return false if it wants jQuery to remove the event from the browser's event system (via removeEventListener or detachEvent).
153 | * In most cases, the setup and teardown hooks should return the same value.
154 | *
155 | * If the setup hook attached event handlers or added data to an element through a mechanism such as jQuery.data(),
156 | * the teardown hook should reverse the process and remove them.
157 | * jQuery will generally remove the data and events when an element is totally removed from the document,
158 | * but failing to remove data or events on teardown will cause a memory leak if the element stays in the document.
159 | */
160 | //teardown: function()
161 | //{
162 | //
163 | //},
164 |
165 | /**
166 | * add: function( handleObj )
167 | * Each time an event handler is added to an element through an API such as .on(), jQuery calls this hook.
168 | * The this keyword will be the element to which the event handler is being added,
169 | * and the handleObj argument is as described in the section above.
170 | * The return value of this hook is ignored.
171 | */
172 | add: function( handleObj )
173 | {
174 | var $this = $(this);
175 |
176 | var _createItem = {
177 | id : _createList.length.toString(),
178 | delegateTarget : this,
179 | $delegateTarget : $this,
180 | is_document : $this.is(document),
181 | handleObj : handleObj
182 | };
183 |
184 | _createList.push( _createItem );
185 |
186 | _create( _createItem );
187 | },
188 |
189 | /**
190 | * remove: function( handleObj )
191 | * When an event handler is removed from an element using an API such as .off(), this hook is called.
192 | * The this keyword will be the element where the handler is being removed,
193 | * and the handleObj argument is as described in the section above.
194 | * The return value of this hook is ignored.
195 | */
196 | remove: function( handleObj )
197 | {
198 | for (var _createList_key in _createList)
199 | {
200 | if( _createList.hasOwnProperty( _createList_key ) && $(this).is( _createList[_createList_key].$delegateTarget ) && _createList[_createList_key].handleObj.selector === handleObj.selector )
201 | {
202 | delete _createList[_createList_key];
203 | break;
204 | }
205 | }
206 | },
207 |
208 | /**
209 | * trigger: function( event: jQuery.Event, data: Object )
210 | * Called when the .trigger() or .triggerHandler() methods are used to trigger an event for the special type from code,
211 | * as opposed to events that originate from within the browser.
212 | * The this keyword will be the element being triggered, and the event argument will be a jQuery.Event object constructed from the caller's input.
213 | * At minimum, the event type, data, namespace, and target properties are set on the event.
214 | * The data argument represents additional data passed by .trigger() if present.
215 | *
216 | * The trigger hook is called early in the process of triggering an event,
217 | * just after the jQuery.Event object is constructed and before any handlers have been called.
218 | * It can process the triggered event in any way, for example by calling event.stopPropagation() or event.preventDefault() before returning.
219 | * If the hook returns false, jQuery does not perform any further event triggering actions and returns immediately.
220 | * Otherwise, it performs the normal trigger processing, calling any event handlers for the element and bubbling
221 | * the event (unless propagation is stopped in advance or noBubble was specified for the special event) to call event handlers attached to parent elements.
222 | */
223 | //trigger: function( event, data )
224 | //{
225 | //
226 | //},
227 |
228 | /**
229 | * _default: function( event: jQuery.Event, data: Object )
230 | * When the .trigger() method finishes running all the event handlers for an event,
231 | * it also looks for and runs any method on the target object by the same name unless of the handlers called event.preventDefault().
232 | * So, .trigger( "submit" ) will execute the submit() method on the element if one exists.
233 | * When a _default hook is specified, the hook is called just prior to checking for and executing the element's default method.
234 | * If this hook returns the value false the element's default method will be called; otherwise it is not.
235 | */
236 | //_default: function( event, data )
237 | //{
238 | //
239 | //},
240 |
241 | /**
242 | * handle: function( event: jQuery.Event, data: Object )
243 | * jQuery calls a handle hook when the event has occurred and jQuery would normally call the user's event handler
244 | * specified by .on() or another event binding method. If the hook exists, jQuery calls it instead of that event handler,
245 | * passing it the event and any data passed from .trigger() if it was not a native event.
246 | * The this keyword is the DOM element being handled, and event.handleObj property has the detailed event information.
247 | *
248 | * Based in the information it has, the handle hook should decide whether to call the original handler function which is in event.handleObj.handler.
249 | * It can modify information in the event object before calling the original handler,
250 | * but must restore that data before returning or subsequent unrelated event handlers may act unpredictably.
251 | * In most cases, the handle hook should return the result of the original handler, but that is at the discretion of the hook.
252 | * The handle hook is unique in that it is the only special event function hook that is called under its original special event name
253 | * when the type is mapped using bindType and delegateType. For that reason, it is almost always an error to have anything other
254 | * than a handle hook present if the special event defines a bindType and delegateType, since those other hooks will never be called.
255 | */
256 | //handle: function( event, data )
257 | //{
258 | //
259 | //},
260 |
261 | /**
262 | * utility: object
263 | * Collection of utilities.
264 | */
265 | utility: _utility,
266 |
267 | /**
268 | * version: string
269 | * Version number.
270 | */
271 | version: '{{ VERSION }}'
272 | };
273 |
274 |
275 | //// DOM manipulation methods
276 | //$.fn.domManip = function() {
277 | // return _domManip.apply( domManip.apply( this, arguments ), arguments );
278 | //};
279 |
280 | // "append" DOM manipulation.
281 | $.fn.append = function() {
282 | return _domManip.apply( append.apply( this, arguments ), arguments );
283 | };
284 |
285 | //// "prepend" DOM manipulation.
286 | //$.fn.prepend = function() {
287 | // return _domManip.apply( prepend.apply( this, arguments ), arguments );
288 | //};
289 |
290 | // "before" DOM manipulation.
291 | $.fn.before = function() {
292 | return _domManip.apply( before.apply( this, arguments ), arguments );
293 | };
294 |
295 | // "after" DOM manipulation.
296 | $.fn.after = function() {
297 | return _domManip.apply( after.apply( this, arguments ), arguments );
298 | };
299 |
300 | // "html" DOM manipulation.
301 | $.fn.html = function() {
302 | return _domManip.apply( html.apply( this, arguments ), arguments );
303 | };
304 |
305 | // "replaceWith" DOM manipulation.
306 | $.fn.replaceWith = function() {
307 | return _domManip.apply( replaceWith.apply( this, arguments ), arguments );
308 | };
309 |
310 | }(
311 | jQuery,
312 | jQuery.fn.domManip,
313 | jQuery.fn.append,
314 | jQuery.fn.prepend,
315 | jQuery.fn.before,
316 | jQuery.fn.after,
317 | jQuery.fn.html,
318 | jQuery.fn.replaceWith
319 | ));
320 |
--------------------------------------------------------------------------------