├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── cover.png ├── dist ├── jquery.jcreate.js ├── jquery.jcreate.min.js ├── jquery.jcreate.min.js.map └── jquery.jcreate.umd.js ├── grunt ├── aliases.json ├── jasmine.js ├── jshint.js ├── string-replace.js ├── uglify.js ├── umd.js └── watch.js ├── hooks └── pre-commit ├── index.html ├── lib └── jasmine-jquery-config.js ├── package-lock.json ├── package.json ├── spec ├── .jshintrc ├── fixtures │ ├── .gitignore │ └── container.html ├── helpers │ └── empty.helper.js ├── jquery.jcreate.spec.js ├── readme.md.spec.js └── utlity.spec.js └── src └── jquery.jcreate.js /.gitignore: -------------------------------------------------------------------------------- 1 | # system files 2 | .DS_Store 3 | __MACOSX 4 | Thumbs.db 5 | 6 | # node.js 7 | /.grunt 8 | /node_modules 9 | /bower_components 10 | /npm-debug.log 11 | /yarn-error.log 12 | 13 | # jasmine 14 | /SpecRunner.html 15 | /jasmine.html 16 | /output 17 | /coverage 18 | 19 | # others 20 | /dist 21 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esversion": 3, 3 | "loopfunc": true, 4 | 5 | "-W032": true, 6 | 7 | "eqnull": true, 8 | "laxbreak": true, 9 | "laxcomma": true, 10 | "proto": true, 11 | "undef": true, 12 | "unused": true, 13 | "quotmark": "single", 14 | "globals": { 15 | "jQuery": true, 16 | "document": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "12" 4 | before_script: 5 | - npm install -g grunt-cli 6 | - npm install -g bower 7 | - bower install 8 | script: 9 | - npm test 10 | after_success: 11 | - npm install coveralls codacy-coverage 12 | - cat ./coverage/reports/lcov.info | ./node_modules/.bin/coveralls || true 13 | - cat ./coverage/reports/lcov.info | ./node_modules/.bin/codacy-coverage || true 14 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) 2 | { 3 | var data = { 4 | 5 | // 6 | files : { 7 | srcs : 'src/**/*.js', 8 | specs : 'spec/**/*.spec.js', 9 | helpers : 'spec/helpers/**/*.helper.js', 10 | 11 | src : 'src/<%= package.filename %>.js', 12 | dist : 'dist/<%= package.filename %>.js', 13 | dist_min : 'dist/<%= package.filename %>.min.js', 14 | dist_umd : 'dist/<%= package.filename %>.umd.js', 15 | }, 16 | 17 | // 18 | bower : grunt.file.readJSON('bower.json'), 19 | 20 | // 21 | }; 22 | 23 | // require it at the top and pass in the grunt instance 24 | require('time-grunt')(grunt); 25 | 26 | // 27 | require('load-grunt-config')(grunt, { 28 | data: data, 29 | loadGruntTasks: { 30 | pattern: ['grunt-*', '!grunt-template-jasmine-istanbul'] 31 | }, 32 | }); 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-2017 Marco Montalbano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jCreate 2 | 3 | [![Build Status](https://travis-ci.org/marcomontalbano/jquery-jcreate.svg?branch=master)](https://travis-ci.org/marcomontalbano/jquery-jcreate) 4 | [![Coverage Status](https://coveralls.io/repos/github/marcomontalbano/jquery-jcreate/badge.svg?branch=master)](https://coveralls.io/github/marcomontalbano/jquery-jcreate?branch=master) 5 | [![Release Notes](https://img.shields.io/github/release/marcomontalbano/jquery-jcreate.svg)](https://github.com/marcomontalbano/jquery-jcreate/releases) 6 | 7 | jCreate is a plugin for jQuery that adds a new bindable event. Did you ever look for something like `$(document).on("create", function() { ... });`? 8 | 9 | Now you can do something cool when one or more elements are created and are available on the page. 10 | 11 | See it in action with [CodePen]. 12 | 13 | 14 | ## Install 15 | 16 | ### Yarn and npm 17 | 18 | [![NPM](https://nodei.co/npm/jquery-jcreate.svg)](https://nodei.co/npm/jquery-jcreate/) 19 | 20 | You can install jCreate using [Yarn] or [npm]: 21 | 22 | ```sh 23 | # yarn 24 | yarn add jquery-jcreate 25 | 26 | # npm 27 | npm install --save jquery-jcreate 28 | ``` 29 | 30 | #### Webpack 2 31 | 32 | ```js 33 | import $ from 'jquery'; 34 | import 'jquery-jcreate'; 35 | 36 | $(document).on('create', '*', function(event) { 37 | console.log( 'created tag: ', event.$currentTarget.prop('tagName') ); 38 | }); 39 | ``` 40 | 41 | #### RequireJS 42 | 43 | ```js 44 | define(["jquery", "jquery-jcreate"], function( $ ) { 45 | 46 | $(document).on('create', '*', function(event) { 47 | console.log( 'created tag: ', event.$currentTarget.prop('tagName') ); 48 | }); 49 | }); 50 | ``` 51 | 52 | ### Bower 53 | 54 | You can install jCreate using [Bower]: 55 | 56 | ```sh 57 | bower install --save jquery-jcreate 58 | ``` 59 | 60 | And now you can include it in you project with a ` 64 | ``` 65 | 66 | ### ` 74 | ``` 75 | 76 | ## How to use 77 | 78 | jCreate works with the [jQuery Event Delegation]. 79 | 80 | ```js 81 | // bind 'create' event. 82 | $( '#dataTable tbody' ).on( 'create', 'tr', function( event ) { 83 | console.log( event.$currentTarget.text() ); 84 | }); 85 | 86 | // add a new 'row'. 87 | $( '#dataTable tbody' ).append('this is a new row!'); 88 | ``` 89 | 90 | ### Event 91 | 92 | * **type** - Describes the nature of the event. 93 | ```javascript 94 | $( document ).on('create', 'a', function( event ) { 95 | console.log( event.type ); //= "create" 96 | }); 97 | ``` 98 | 99 | * **timeStamp** - The difference in milliseconds between the time the browser created the event and January 1, 1970. 100 | 101 | * **currentTarget** - The current DOM element within the event bubbling phase. 102 | ```javascript 103 | $( document ).on('create', 'a', function( event ) { 104 | console.log( event.currentTarget === this ); //= true 105 | }); 106 | ``` 107 | 108 | * **$currentTarget** - The current DOM element within the event bubbling phase as jQuery object. 109 | ```javascript 110 | $( document ).on('create', 'a', function( event ) { 111 | console.log( event.$currentTarget.is( $(this) ) ); //= true 112 | }); 113 | ``` 114 | 115 | * **delegateTarget** - The element where the currently-called jQuery event handler was attached. 116 | ```javascript 117 | $( document ).on('create', 'a', function( event ) { 118 | console.log( event.delegateTarget === document ); //= true 119 | }); 120 | ``` 121 | 122 | * **$delegateTarget** - The jQuery element where the currently-called jQuery event handler was attached. 123 | ```javascript 124 | $( document ).on('create', 'a', function( event ) { 125 | console.log( event.$delegateTarget.is( $(document) ) ); //= true 126 | }); 127 | ``` 128 | 129 | * **options** - Method that filters data by key. 130 | ```html 131 |
132 | ``` 133 | ```javascript 134 | $( document ).on('create', 'div', function( event ) { 135 | console.log( event.options('component') ); //= {name:"hello-world"} 136 | }); 137 | ``` 138 | 139 | ### jQuery Support 140 | 141 | > jquery >= 1.8 142 | 143 | Since I use the last version of `jasmine-jquery` library in order to test my own plugin, I cannot ensure that the plugin works with jQuery 1.7 and below, due to the fact that `jasmine-jquery` uses methods that were introduced in jQuery 1.8. 144 | 145 | 146 | ## The Module Pattern 147 | 148 | > _Modules are an integral piece of any robust application's architecture and typically help in keeping the units of code for a project both cleanly separated and organized._ 149 | > 150 | > [Learning JavaScript Design Patterns - Addy Osmani] 151 | 152 | ```javascript 153 | var myModule = (function () { 154 | 155 | var module = {} 156 | , _privateVariable = 'Hello World' 157 | ; 158 | 159 | var _privateMethod = function() { 160 | return _privateVariable; 161 | }; 162 | 163 | module.publicProperty = 'Foobar'; 164 | module.publicMethod = function () { 165 | console.log( _privateMethod() ); 166 | }; 167 | 168 | return module; 169 | 170 | }()); 171 | ``` 172 | 173 | Here follows a simple example on how to use the Module pattern with jCreate. 174 | 175 | ```html 176 |
177 | ``` 178 | 179 | ```javascript 180 | var helloWorldComponent = (function () { 181 | 182 | var module = {} 183 | , _componentName = 'hello-world' 184 | ; 185 | 186 | module.greeting = function( name ) { 187 | console.log( 'Hello ' + name + '!' ); 188 | }; 189 | 190 | $(document).on('create', '[data-component~="' + _componentName + '"]', function( event ) { 191 | var options = event.options( _componentName ); //= {name:"Marco"} 192 | module.greeting( options.name ); //= Hello Marco! 193 | }); 194 | 195 | return module; 196 | }()); 197 | 198 | helloWorldComponent.greeting('Marco'); //= Hello Marco! 199 | ``` 200 | 201 | 202 | ## Development 203 | 204 | ### Install Grunt and Bower 205 | 206 | To install Grunt and Bower, you must first download and install [node.js] - which includes npm. 207 | 208 | Then, using the command line: 209 | 210 | ```sh 211 | # install `grunt-cli` globally 212 | npm install -g grunt-cli 213 | 214 | # install `bower` globally 215 | npm install -g bower 216 | 217 | # navigate to the root of your project, then run 218 | npm install 219 | bower install 220 | ``` 221 | 222 | 223 | ### Available tasks 224 | 225 | * `npm start` Start http server. 226 | * `npm run test` Validate files with [JSHint] and run unit tests with [jasmine]. 227 | * `npm run smoke` Watch for file changes and run smoke test. 228 | * `npm run build` Run full test suite and build dist folder. 229 | 230 | 231 | [Bower]: 232 | [jsDelivr]: 233 | [jQuery Event Delegation]: 234 | [node.js]: 235 | [CodePen]: 236 | [download and install node.js]: 237 | 238 | [Learning JavaScript Design Patterns - Addy Osmani]: 239 | 240 | [JSHint]: 241 | [jasmine]: 242 | [UglifyJS]: 243 | 244 | [npm]: 245 | [Yarn]: 246 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-jcreate", 3 | "description": "jCreate is a new special event for jQuery. Just use .on('create', ..); the callback will be triggered when elements are created on the page.", 4 | "homepage": "https://marcomontalbano.com/portfolio/3/jcreate", 5 | "main": [ 6 | "dist/jquery.jcreate.js" 7 | ], 8 | "authors": [ 9 | "Marco Montalbano (https://marcomontalbano.com)" 10 | ], 11 | "license": "MIT", 12 | "keywords": [ 13 | "mmontalbano", 14 | "marcomontalbano", 15 | "jquery", 16 | "jcreate", 17 | "plugin", 18 | "oncreate", 19 | "event" 20 | ], 21 | "ignore": [ 22 | "**/.*", 23 | "bower_components", 24 | "cover.png", 25 | "hooks", 26 | "docs", 27 | "grunt", 28 | "lib", 29 | "node_modules", 30 | "output", 31 | "spec", 32 | "src", 33 | "Gruntfile.js" 34 | ], 35 | "dependencies": { 36 | "jquery" : ">=1.8" 37 | }, 38 | "devDependencies": { 39 | "jquery-1.8" : "jquery#~1.8.0" , 40 | "jquery-1.9" : "jquery#~1.9.0" , 41 | "jquery-1.10" : "jquery#~1.10.0" , 42 | "jquery-1.11" : "jquery#~1.11.0" , 43 | "jquery-1.12" : "jquery#~1.12.0" , 44 | "jquery-2.0" : "jquery#~2.0.0" , 45 | "jquery-2.1" : "jquery#~2.1.0" , 46 | "jquery-2.2" : "jquery#~2.2.0" , 47 | "jquery-3.0" : "jquery#~3.0.0" , 48 | "jquery-3.1" : "jquery#~3.1.0" , 49 | "jquery-3.2" : "jquery#~3.2.0" , 50 | "jquery-3.3" : "jquery#~3.3.0" , 51 | "jquery-3.4" : "jquery#~3.4.0" , 52 | "jquery-3.5" : "jquery#~3.5.0" , 53 | "jquery-3.6" : "jquery#~3.6.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomontalbano/jquery-jcreate/d093da95421c2d6ffc0c37031f91ac85c769a5bd/cover.png -------------------------------------------------------------------------------- /dist/jquery.jcreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.jcreate.js v1.3.1 3 | * Marco Montalbano © 2011-2022 - https://marcomontalbano.com 4 | * ---------------------------------------------------------- 5 | */ 6 | (function($, domManip, append, prepend, before, after, html, replaceWith) { 7 | var _createList = [], _utility = {}; 8 | _utility.camelize = function(str) { 9 | return str.toLowerCase().replace(/[-_\.]+(.)/g, function(match, group) { 10 | return group.toUpperCase(); 11 | }); 12 | }; 13 | _utility.firstLetterToLowerCase = function(str) { 14 | return str.charAt(0).toLowerCase() + str.slice(1); 15 | }; 16 | _utility.firstLetterToUpperCase = function(str) { 17 | return str.charAt(0).toUpperCase() + str.slice(1); 18 | }; 19 | _utility.filterDataByKey = function(data, key) { 20 | var _data = {}, regexp = new RegExp("^" + key + "([A-Za-z0-9]+)$"), matches; 21 | if (typeof data !== "object") { 22 | return data; 23 | } 24 | for (var data_key in data) { 25 | if (Object.hasOwnProperty.call(data, data_key)) { 26 | matches = data_key.match(regexp); 27 | if (matches) { 28 | _data[_utility.firstLetterToLowerCase(matches[1])] = data[data_key]; 29 | } 30 | } 31 | } 32 | return _data; 33 | }; 34 | var _create = function(_createItem) { 35 | var $elements = _createItem.is_document ? $(_createItem.handleObj.selector) : _createItem.$delegateTarget.find(_createItem.handleObj.selector); 36 | $elements.each(function() { 37 | var $this = $(this), data_key = "$.event.special.create", data_sep = ",", data = $this.data(data_key) ? $this.data(data_key).split(data_sep) : []; 38 | if ($.inArray(_createItem.id, data) === -1) { 39 | data.push(_createItem.id); 40 | $this.data(data_key, data.join(data_sep)); 41 | _createItem.handleObj.handler.apply(this, [ new $.Event("create", { 42 | currentTarget: this, 43 | $currentTarget: $this, 44 | delegateTarget: _createItem.delegateTarget, 45 | $delegateTarget: _createItem.$delegateTarget, 46 | data: _createItem.handleObj.data, 47 | options: function(key) { 48 | return _utility.filterDataByKey($this.data(), _utility.camelize(key)); 49 | } 50 | }) ]); 51 | } 52 | }); 53 | }; 54 | var _domManip = function() { 55 | if (_createList.length >= 1) { 56 | var _createItem = null; 57 | for (var key in _createList) { 58 | if (_createList.hasOwnProperty(key)) { 59 | _createItem = _createList[key]; 60 | _create(_createItem); 61 | } 62 | } 63 | } 64 | return this; 65 | }; 66 | $.event.special.create = { 67 | add: function(handleObj) { 68 | var $this = $(this); 69 | var _createItem = { 70 | id: _createList.length.toString(), 71 | delegateTarget: this, 72 | $delegateTarget: $this, 73 | is_document: $this.is(document), 74 | handleObj: handleObj 75 | }; 76 | _createList.push(_createItem); 77 | _create(_createItem); 78 | }, 79 | remove: function(handleObj) { 80 | for (var _createList_key in _createList) { 81 | if (_createList.hasOwnProperty(_createList_key) && $(this).is(_createList[_createList_key].$delegateTarget) && _createList[_createList_key].handleObj.selector === handleObj.selector) { 82 | delete _createList[_createList_key]; 83 | break; 84 | } 85 | } 86 | }, 87 | utility: _utility, 88 | version: "1.3.1" 89 | }; 90 | $.fn.append = function() { 91 | return _domManip.apply(append.apply(this, arguments), arguments); 92 | }; 93 | $.fn.before = function() { 94 | return _domManip.apply(before.apply(this, arguments), arguments); 95 | }; 96 | $.fn.after = function() { 97 | return _domManip.apply(after.apply(this, arguments), arguments); 98 | }; 99 | $.fn.html = function() { 100 | return _domManip.apply(html.apply(this, arguments), arguments); 101 | }; 102 | $.fn.replaceWith = function() { 103 | return _domManip.apply(replaceWith.apply(this, arguments), arguments); 104 | }; 105 | })(jQuery, jQuery.fn.domManip, jQuery.fn.append, jQuery.fn.prepend, jQuery.fn.before, jQuery.fn.after, jQuery.fn.html, jQuery.fn.replaceWith); -------------------------------------------------------------------------------- /dist/jquery.jcreate.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * jquery.jcreate.js v1.3.1 3 | * Marco Montalbano © 2011-2022 - https://marcomontalbano.com 4 | * ---------------------------------------------------------- 5 | */ 6 | !function(a,e,t,r,n,i){function o(n){(n.is_document?a(n.handleObj.selector):n.$delegateTarget.find(n.handleObj.selector)).each(function(){var t=a(this),e="$.event.special.create",r=t.data(e)?t.data(e).split(","):[];-1===a.inArray(n.id,r)&&(r.push(n.id),t.data(e,r.join(",")),n.handleObj.handler.apply(this,[new a.Event("create",{currentTarget:this,$currentTarget:t,delegateTarget:n.delegateTarget,$delegateTarget:n.$delegateTarget,data:n.handleObj.data,options:function(e){return f.filterDataByKey(t.data(),f.camelize(e))}})]))})}function l(){var e,t;if(1<=p.length)for(t in p)p.hasOwnProperty(t)&&(e=p[t],o(e));return this}var p=[],f={camelize:function(e){return e.toLowerCase().replace(/[-_\.]+(.)/g,function(e,t){return t.toUpperCase()})},firstLetterToLowerCase:function(e){return e.charAt(0).toLowerCase()+e.slice(1)},firstLetterToUpperCase:function(e){return e.charAt(0).toUpperCase()+e.slice(1)},filterDataByKey:function(e,t){var r,n,a={},i=new RegExp("^"+t+"([A-Za-z0-9]+)$");if("object"!=typeof e)return e;for(n in e)Object.hasOwnProperty.call(e,n)&&(r=n.match(i))&&(a[f.firstLetterToLowerCase(r[1])]=e[n]);return a}};a.event.special.create={add:function(e){var t=a(this),t={id:p.length.toString(),delegateTarget:this,$delegateTarget:t,is_document:t.is(document),handleObj:e};p.push(t),o(t)},remove:function(e){for(var t in p)if(p.hasOwnProperty(t)&&a(this).is(p[t].$delegateTarget)&&p[t].handleObj.selector===e.selector){delete p[t];break}},utility:f,version:"1.3.1"},a.fn.append=function(){return l.apply(e.apply(this,arguments),arguments)},a.fn.before=function(){return l.apply(t.apply(this,arguments),arguments)},a.fn.after=function(){return l.apply(r.apply(this,arguments),arguments)},a.fn.html=function(){return l.apply(n.apply(this,arguments),arguments)},a.fn.replaceWith=function(){return l.apply(i.apply(this,arguments),arguments)}}(jQuery,(jQuery.fn.domManip,jQuery.fn.append),(jQuery.fn.prepend,jQuery.fn.before),jQuery.fn.after,jQuery.fn.html,jQuery.fn.replaceWith); 7 | //# sourceMappingURL=jquery.jcreate.min.js.map -------------------------------------------------------------------------------- /dist/jquery.jcreate.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"jquery.jcreate.min.js","sources":["../src/jquery.jcreate.js"],"names":["$","append","before","after","html","replaceWith","_create","_createItem","is_document","handleObj","selector","$delegateTarget","find","each","$this","this","data_key","data","split","inArray","id","push","join","handler","apply","Event","currentTarget","$currentTarget","delegateTarget","options","key","_utility","filterDataByKey","camelize","_domManip","_createList","length","hasOwnProperty","str","toLowerCase","replace","match","group","toUpperCase","firstLetterToLowerCase","charAt","slice","firstLetterToUpperCase","matches","_data","regexp","RegExp","Object","call","event","special","create","add","toString","is","document","remove","_createList_key","utility","version","fn","arguments","jQuery","domManip","prepend"],"mappings":";;;;;CAeC,SAASA,EAAaC,EAAiBC,EAAQC,EAAOC,EAAMC,GA2D3C,SAAVC,EAAoBC,IAEJA,EAAYC,YAAcR,EAAGO,EAAYE,UAAUC,UAAaH,EAAYI,gBAAgBC,KAAML,EAAYE,UAAUC,WAE9HG,KAAK,WAEX,IAAMC,EAAWd,EAAEe,MACbC,EAAW,yBAEXC,EAAWH,EAAMG,KAAKD,GAAYF,EAAMG,KAAKD,GAAUE,MAD5C,KAC8D,IAGpC,IAAtClB,EAAEmB,QAASZ,EAAYa,GAAIH,KAE5BA,EAAKI,KAAMd,EAAYa,IACvBN,EAAMG,KAAKD,EAAUC,EAAKK,KAPb,MAQbf,EAAYE,UAAUc,QAAQC,MAAOT,KAAM,CAAC,IAAIf,EAAEyB,MAAM,SAAU,CAC9DC,cAAkBX,KAClBY,eAAkBb,EAClBc,eAAkBrB,EAAYqB,eAC9BjB,gBAAkBJ,EAAYI,gBAC9BM,KAAkBV,EAAYE,UAAUQ,KACxCY,QAAkB,SAAUC,GACxB,OAAOC,EAASC,gBAAiBlB,EAAMG,OAAQc,EAASE,SAASH,YAQrE,SAAZI,IAII,IAAI3B,EAEKuB,EAJb,GAA0B,GAAtBK,EAAYC,OAIZ,IAASN,KAAOK,EAEPA,EAAYE,eAAgBP,KAE7BvB,EAAc4B,EAAaL,GAE3BxB,EAASC,IAKrB,OAAOQ,KAzGX,IAAMoB,EAAc,GACdJ,EAAc,CAOpBE,SAAoB,SAAUK,GAC1B,OAAOA,EAAIC,cAAcC,QAAQ,cAAe,SAASC,EAAOC,GAC5D,OAAOA,EAAMC,iBAQrBC,uBAAkC,SAAUN,GACxC,OAAOA,EAAIO,OAAO,GAAGN,cAAgBD,EAAIQ,MAAM,IAOnDC,uBAAkC,SAAUT,GACxC,OAAOA,EAAIO,OAAO,GAAGF,cAAgBL,EAAIQ,MAAM,IAGnDd,gBAA2B,SAAUf,EAAMa,GAEvC,IAEMkB,EAOIhC,EATJiC,EAAU,GACVC,EAAU,IAAIC,OAAO,IAAMrB,EAAM,mBAIvC,GAAqB,iBAATb,EACR,OAAOA,EAGX,IAAUD,KAAYC,EAEbmC,OAAOf,eAAegB,KAAKpC,EAAMD,KAElCgC,EAAUhC,EAASyB,MAAOS,MAGtBD,EAAOlB,EAASa,uBAAwBI,EAAQ,KAAS/B,EAAMD,IAK3E,OAAOiC,IAuDXjD,EAAEsD,MAAMC,QAAQC,OAChB,CA6CIC,IAAK,SAAUhD,GAEX,IAAIK,EAAQd,EAAEe,MAEVR,EAAc,CACda,GAAkBe,EAAYC,OAAOsB,WACrC9B,eAAkBb,KAClBJ,gBAAkBG,EAClBN,YAAkBM,EAAM6C,GAAGC,UAC3BnD,UAAkBA,GAGtB0B,EAAYd,KAAMd,GAElBD,EAASC,IAUbsD,OAAQ,SAAUpD,GAEd,IAAK,IAAIqD,KAAmB3B,EAExB,GAAIA,EAAYE,eAAgByB,IAAqB9D,EAAEe,MAAM4C,GAAIxB,EAAY2B,GAAiBnD,kBAAqBwB,EAAY2B,GAAiBrD,UAAUC,WAAaD,EAAUC,SACjL,QACWyB,EAAY2B,GACnB,QA8DZC,QAAShC,EAMTiC,QAAS,iBAUbhE,EAAEiE,GAAGhE,OAAS,WACV,OAAOiC,EAAUV,MAAOvB,EAAOuB,MAAOT,KAAMmD,WAAaA,YAS7DlE,EAAEiE,GAAG/D,OAAS,WACV,OAAOgC,EAAUV,MAAOtB,EAAOsB,MAAOT,KAAMmD,WAAaA,YAI7DlE,EAAEiE,GAAG9D,MAAQ,WACT,OAAO+B,EAAUV,MAAOrB,EAAMqB,MAAOT,KAAMmD,WAAaA,YAI5DlE,EAAEiE,GAAG7D,KAAO,WACR,OAAO8B,EAAUV,MAAOpB,EAAKoB,MAAOT,KAAMmD,WAAaA,YAI3DlE,EAAEiE,GAAG5D,YAAc,WACf,OAAO6B,EAAUV,MAAOnB,EAAYmB,MAAOT,KAAMmD,WAAaA,YAnStE,CAuSIC,QACAA,OAAOF,GAAGG,SACVD,OAAOF,GAAGhE,SACVkE,OAAOF,GAAGI,QACVF,OAAOF,GAAG/D,QACViE,OAAOF,GAAG9D,MACVgE,OAAOF,GAAG7D,KACV+D,OAAOF,GAAG5D"} -------------------------------------------------------------------------------- /dist/jquery.jcreate.umd.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 2 | if (root === undefined && window !== undefined) root = window; 3 | if (typeof define === 'function' && define.amd) { 4 | // AMD. Register as an anonymous module unless amdModuleId is set 5 | define(["jquery"], function (a0) { 6 | return (factory(a0)); 7 | }); 8 | } else if (typeof module === 'object' && module.exports) { 9 | // Node. Does not work with strict CommonJS, but 10 | // only CommonJS-like environments that support module.exports, 11 | // like Node. 12 | module.exports = factory(require("jquery")); 13 | } else { 14 | factory(root["jquery"]); 15 | } 16 | }(this, function (jquery) { 17 | 18 | /** 19 | * jquery.jcreate.js v1.3.1 20 | * Marco Montalbano © 2011-2022 - https://marcomontalbano.com 21 | * ---------------------------------------------------------- 22 | */ 23 | (function($, domManip, append, prepend, before, after, html, replaceWith) { 24 | var _createList = [], _utility = {}; 25 | _utility.camelize = function(str) { 26 | return str.toLowerCase().replace(/[-_\.]+(.)/g, function(match, group) { 27 | return group.toUpperCase(); 28 | }); 29 | }; 30 | _utility.firstLetterToLowerCase = function(str) { 31 | return str.charAt(0).toLowerCase() + str.slice(1); 32 | }; 33 | _utility.firstLetterToUpperCase = function(str) { 34 | return str.charAt(0).toUpperCase() + str.slice(1); 35 | }; 36 | _utility.filterDataByKey = function(data, key) { 37 | var _data = {}, regexp = new RegExp("^" + key + "([A-Za-z0-9]+)$"), matches; 38 | if (typeof data !== "object") { 39 | return data; 40 | } 41 | for (var data_key in data) { 42 | if (Object.hasOwnProperty.call(data, data_key)) { 43 | matches = data_key.match(regexp); 44 | if (matches) { 45 | _data[_utility.firstLetterToLowerCase(matches[1])] = data[data_key]; 46 | } 47 | } 48 | } 49 | return _data; 50 | }; 51 | var _create = function(_createItem) { 52 | var $elements = _createItem.is_document ? $(_createItem.handleObj.selector) : _createItem.$delegateTarget.find(_createItem.handleObj.selector); 53 | $elements.each(function() { 54 | var $this = $(this), data_key = "$.event.special.create", data_sep = ",", data = $this.data(data_key) ? $this.data(data_key).split(data_sep) : []; 55 | if ($.inArray(_createItem.id, data) === -1) { 56 | data.push(_createItem.id); 57 | $this.data(data_key, data.join(data_sep)); 58 | _createItem.handleObj.handler.apply(this, [ new $.Event("create", { 59 | currentTarget: this, 60 | $currentTarget: $this, 61 | delegateTarget: _createItem.delegateTarget, 62 | $delegateTarget: _createItem.$delegateTarget, 63 | data: _createItem.handleObj.data, 64 | options: function(key) { 65 | return _utility.filterDataByKey($this.data(), _utility.camelize(key)); 66 | } 67 | }) ]); 68 | } 69 | }); 70 | }; 71 | var _domManip = function() { 72 | if (_createList.length >= 1) { 73 | var _createItem = null; 74 | for (var key in _createList) { 75 | if (_createList.hasOwnProperty(key)) { 76 | _createItem = _createList[key]; 77 | _create(_createItem); 78 | } 79 | } 80 | } 81 | return this; 82 | }; 83 | $.event.special.create = { 84 | add: function(handleObj) { 85 | var $this = $(this); 86 | var _createItem = { 87 | id: _createList.length.toString(), 88 | delegateTarget: this, 89 | $delegateTarget: $this, 90 | is_document: $this.is(document), 91 | handleObj: handleObj 92 | }; 93 | _createList.push(_createItem); 94 | _create(_createItem); 95 | }, 96 | remove: function(handleObj) { 97 | for (var _createList_key in _createList) { 98 | if (_createList.hasOwnProperty(_createList_key) && $(this).is(_createList[_createList_key].$delegateTarget) && _createList[_createList_key].handleObj.selector === handleObj.selector) { 99 | delete _createList[_createList_key]; 100 | break; 101 | } 102 | } 103 | }, 104 | utility: _utility, 105 | version: "1.3.1" 106 | }; 107 | $.fn.append = function() { 108 | return _domManip.apply(append.apply(this, arguments), arguments); 109 | }; 110 | $.fn.before = function() { 111 | return _domManip.apply(before.apply(this, arguments), arguments); 112 | }; 113 | $.fn.after = function() { 114 | return _domManip.apply(after.apply(this, arguments), arguments); 115 | }; 116 | $.fn.html = function() { 117 | return _domManip.apply(html.apply(this, arguments), arguments); 118 | }; 119 | $.fn.replaceWith = function() { 120 | return _domManip.apply(replaceWith.apply(this, arguments), arguments); 121 | }; 122 | })(jQuery, jQuery.fn.domManip, jQuery.fn.append, jQuery.fn.prepend, jQuery.fn.before, jQuery.fn.after, jQuery.fn.html, jQuery.fn.replaceWith); 123 | 124 | })); 125 | -------------------------------------------------------------------------------- /grunt/aliases.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": [ 3 | "jshint:source", 4 | "jasmine" 5 | ], 6 | "smoke_test": [ 7 | "jshint:source", 8 | "jasmine:jquery-1.8" 9 | ], 10 | "build": [ 11 | "test", 12 | "uglify:beautify", 13 | "uglify:minify", 14 | "umd", 15 | "string-replace:version" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /grunt/jasmine.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt, options) { 2 | 3 | // 4 | var _exports = { 5 | options : { 6 | 7 | // Non-source, non-spec helper files. 8 | // In the default runner these are loaded after vendor files 9 | helpers : [ 10 | '<%= files.helpers %>' 11 | ], 12 | 13 | // Your Jasmine specs. 14 | specs : [ 15 | '<%= files.specs %>' 16 | ], 17 | 18 | // The auto-generated specfile that phantomjs will use to run your tests. 19 | // Automatically deleted upon normal runs. Use the :build flag to generate a SpecRunner manually e.g. grunt jasmine:myTask:build 20 | outfile : 'jasmine.html', 21 | 22 | // Prevents the auto-generated specfile used to run your tests from being automatically deleted. 23 | keepRunner : true, 24 | 25 | // Launches puppeteer with --allow-file-access-from-files (Fix Issue https://github.com/gruntjs/grunt-contrib-jasmine/issues/298) 26 | allowFileAccess: true, 27 | 28 | template: require('grunt-template-jasmine-istanbul'), 29 | 30 | templateOptions: { 31 | coverage: 'coverage/coverage.json', 32 | report: [ 33 | { 34 | type: 'html', 35 | options: { 36 | dir: 'coverage/html' 37 | } 38 | }, 39 | { 40 | type: 'lcov', 41 | options: { 42 | dir: 'coverage/reports' 43 | } 44 | }, 45 | { 46 | type: 'cobertura', 47 | options: { 48 | dir: 'coverage/cobertura' 49 | } 50 | }, 51 | { 52 | type: 'text-summary' 53 | } 54 | ] 55 | } 56 | }, 57 | 58 | // // Your source files. 59 | //src : '<%= files.srcs %>', 60 | }; 61 | 62 | 63 | for ( var _jquery_version in options.bower.devDependencies ) 64 | { 65 | if ( options.bower.devDependencies.hasOwnProperty( _jquery_version ) ) 66 | { 67 | if ( /^jquery-[0-9\.]+$/.test( _jquery_version ) ) 68 | { 69 | // Test against jQuery x.x.x 70 | var _jquery_path = 'bower_components/' + _jquery_version + '/'; 71 | var _jquery_file = grunt.file.exists(_jquery_path + 'jquery.js') ? _jquery_path + 'jquery.js' : _jquery_path + 'dist/jquery.js'; 72 | 73 | _exports[ _jquery_version ] = { 74 | 75 | // Your source files. 76 | src: '<%= files.srcs %>', 77 | 78 | options: { 79 | 80 | // Third party libraries like jQuery & generally anything loaded before source, specs, and helpers. 81 | vendor: [ 82 | _jquery_file, 83 | '.grunt/grunt-contrib-jasmine/boot0.js', 84 | '.grunt/grunt-contrib-jasmine/boot1.js', 85 | 'node_modules/jasmine-jquery/lib/jasmine-jquery.js', 86 | 'lib/jasmine-jquery-config.js', 87 | ], 88 | }, 89 | }; 90 | } 91 | } 92 | } 93 | 94 | return _exports; 95 | 96 | }; 97 | -------------------------------------------------------------------------------- /grunt/jshint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | jshintrc: true, 4 | }, 5 | source: { 6 | files: { 7 | src: [ '<%= files.specs %>', '<%= files.srcs %>' ], 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /grunt/string-replace.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | version: { 3 | files: { 4 | 'dist/': 'dist/**', 5 | }, 6 | options: { 7 | replacements: [{ 8 | pattern: /{{ VERSION }}/g, 9 | replacement: '<%= package.version %>' 10 | }] 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /grunt/uglify.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | banner: '' 4 | + '/**\n' 5 | + ' * <%= package.filename %>.js v<%= package.version %>\n' 6 | + ' * <%= package.author.name %> © 2011-<%= grunt.template.today("yyyy") %> - <%= package.author.url %>\n' 7 | + ' * ----------------------------------------------------------\n' 8 | + ' */\n' 9 | }, 10 | beautify: { 11 | options: { 12 | beautify: true, 13 | mangle: false, 14 | compress: false, 15 | }, 16 | files: { 17 | '<%= files.dist %>': [ '<%= files.srcs %>' ] 18 | }, 19 | }, 20 | minify: { 21 | options: { 22 | sourceMap: true, 23 | }, 24 | files: { 25 | '<%= files.dist_min %>': [ '<%= files.srcs %>' ] 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /grunt/umd.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | deps: { 4 | default: ['jquery'], 5 | } 6 | }, 7 | src: { 8 | options: { 9 | src: '<%= files.dist %>', 10 | dest: '<%= files.dist_umd %>' 11 | } 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /grunt/watch.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | build: { 3 | files: [ 4 | '<%= files.specs %>' , 5 | '<%= files.srcs %>' , 6 | '<%= files.helpers %>' , 7 | ], 8 | tasks: ['build'], 9 | options: { 10 | spawn: false, 11 | }, 12 | }, 13 | smoke_test: { 14 | files: [ 15 | '<%= files.specs %>' , 16 | '<%= files.srcs %>' , 17 | '<%= files.helpers %>' , 18 | ], 19 | tasks: ['smoke_test'], 20 | options: { 21 | spawn: false, 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | 10 | # build project 11 | npm run build 12 | git add dist -f -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jCreate • DEMO 6 | 21 | 22 | 23 |

jCreate

24 | 25 | 26 | 27 | 28 | 29 |
30 |

#1 - Getting Started

31 | 32 |
33 | 34 | 65 |
66 | 67 |
68 | 69 |
70 |

#2 - Event Options

71 | 72 |
73 | 74 | 95 |
96 | 97 |
98 | 99 |
100 |

#3 - The Module Pattern

101 | 102 | 115 | 116 |
117 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. 118 |
119 |
120 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. 121 |
122 | 123 | 145 |
146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /lib/jasmine-jquery-config.js: -------------------------------------------------------------------------------- 1 | jasmine.getFixtures().fixturesPath = 'spec/fixtures'; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-jcreate", 3 | "filename": "jquery.jcreate", 4 | "firstRelease": "2011-11-18", 5 | "version": "1.3.1", 6 | "description": "jCreate is a new special event for jQuery. Just use .on('create', ..); the callback will be triggered when elements are created on the page.", 7 | "main": "dist/jquery.jcreate.umd.js", 8 | "files": [ 9 | "dist" 10 | ], 11 | "author": { 12 | "name": "Marco Montalbano", 13 | "email": "me@marcomontalbano.com", 14 | "url": "https://marcomontalbano.com" 15 | }, 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/marcomontalbano/jquery-jcreate" 20 | }, 21 | "keywords": [ 22 | "mmontalbano", 23 | "marcomontalbano", 24 | "jquery-plugin", 25 | "jquery", 26 | "jcreate", 27 | "plugin", 28 | "oncreate", 29 | "event" 30 | ], 31 | "scripts": { 32 | "start": "serve", 33 | "test": "grunt test", 34 | "smoke": "grunt watch:smoke_test", 35 | "build": "grunt build", 36 | "prepublishOnly": "npm run build" 37 | }, 38 | "peerDependencies": { 39 | "jquery": ">=1.8" 40 | }, 41 | "devDependencies": { 42 | "grunt": "^1.4.1", 43 | "grunt-contrib-jasmine": "^3.0.0", 44 | "grunt-contrib-jshint": "^3.1.1", 45 | "grunt-contrib-uglify": "^5.0.1", 46 | "grunt-contrib-watch": "^1.1.0", 47 | "grunt-string-replace": "^1.3.1", 48 | "grunt-template-jasmine-istanbul": "github:maenu/grunt-template-jasmine-istanbul", 49 | "grunt-umd": "^3.0.0", 50 | "jasmine-jquery": "^2.1.1", 51 | "load-grunt-config": "^4.0.1", 52 | "serve": "~13.0.2", 53 | "shared-git-hooks": "^1.2.1", 54 | "time-grunt": "^2.0.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "globals": { 4 | "jasmine": true, 5 | "xdescribe": true, 6 | "describe": true, 7 | "it": true, 8 | "xit": true, 9 | "expect": true, 10 | "before": true, 11 | "beforeEach": true, 12 | "after": true, 13 | "afterEach": true, 14 | "loadFixtures": true, 15 | "spyOn": true, 16 | "console": true 17 | } 18 | } -------------------------------------------------------------------------------- /spec/fixtures/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomontalbano/jquery-jcreate/d093da95421c2d6ffc0c37031f91ac85c769a5bd/spec/fixtures/.gitignore -------------------------------------------------------------------------------- /spec/fixtures/container.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /spec/helpers/empty.helper.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcomontalbano/jquery-jcreate/d093da95421c2d6ffc0c37031f91ac85c769a5bd/spec/helpers/empty.helper.js -------------------------------------------------------------------------------- /spec/jquery.jcreate.spec.js: -------------------------------------------------------------------------------- 1 | describe('jCreate', function() { 2 | 3 | var $container 4 | , style_red = {'display' : 'block', 'margin-top' : '10px', 'color' : 'rgb(255, 0, 0)'} 5 | , style_green = {'display' : 'block', 'margin-top' : '10px', 'color' : 'rgb(0, 255, 0)'} 6 | , style_blue = {'display' : 'block', 'margin-top' : '10px', 'color' : 'rgb(0, 0, 255)'} 7 | ; 8 | 9 | beforeEach(function () 10 | { 11 | loadFixtures('container.html'); 12 | $container = jQuery('#container'); 13 | 14 | spyOn(console, 'info'); 15 | 16 | // add 'create' event to container. 17 | $container.on('create', '> div', function( e ) { 18 | e.$currentTarget.css( style_red ); 19 | }); 20 | }); 21 | 22 | afterEach(function () { 23 | $container.remove(); 24 | $container.off('create'); 25 | }); 26 | 27 | it('should provide the \'create\' special event.', function() { 28 | expect( jQuery.event.special.create ).toEqual( jasmine.any(Object) ); 29 | }); 30 | 31 | it('should execute the callback if \'on\' function is invoked.', function() 32 | { 33 | // when 34 | $container.append( jQuery('
') ); 35 | 36 | // then 37 | expect( $container.find('> div') ).toHaveCss(style_red); 38 | }); 39 | 40 | it('shouldn\'t execute the callback if \'off\' function is invoked.', function() 41 | { 42 | // when 43 | $container.off('create'); 44 | 45 | $container.append( jQuery('
') ); 46 | 47 | // then 48 | expect( $container.find('> div') ).not.toHaveCss(style_red); 49 | }); 50 | 51 | it('should pass data to the handler in event.data when the event is triggered.', function() 52 | { 53 | // given 54 | $container.on('create', 'div', { name: 'Marco' }, function( e ) { 55 | console.info('My name is ' + e.data.name); 56 | }); 57 | 58 | // when 59 | $container.append( jQuery('
') ); 60 | 61 | // then 62 | expect( console.info ).toHaveBeenCalledWith( 'My name is Marco' ); 63 | }); 64 | 65 | it('shouldn\'t break if the array object has been extended.', function() 66 | { 67 | // given 68 | Array.prototype.newCoolFunction = function() {}; 69 | 70 | // then 71 | expect(function() { 72 | $container.append( jQuery('
') ); 73 | }).not.toThrow(); 74 | }); 75 | 76 | it('should execute the callback just one time for each created element.', function() 77 | { 78 | // given 79 | var $element = jQuery('
'); 80 | 81 | // when 82 | $container.append( $element ); 83 | 84 | // then 85 | expect( $element ).toHaveCss( style_red ); 86 | 87 | // when 88 | $element.css({ 'color' : 'rgb(0,0,255)' }); 89 | $container.append( jQuery('
') ); 90 | 91 | // then 92 | expect( $container.find('> div:eq(0)') ).toHaveCss( style_blue ); 93 | expect( $container.find('> div:eq(1)') ).toHaveCss( style_red ); 94 | }); 95 | 96 | it('should execute the callback when elements are created inside other elements.', function() 97 | { 98 | // given 99 | $container.on('create', 'div.inner', function( e ) { 100 | e.$currentTarget.css( style_green ); 101 | }); 102 | 103 | var element = '
'; 104 | 105 | // when 106 | $container.append( element ); 107 | 108 | // then 109 | expect( $container.find('div.inner') ).toHaveCss( style_green ); 110 | }); 111 | 112 | describe('binding the event on jQuery(\'document\')', function() 113 | { 114 | afterEach(function () { 115 | jQuery(document).off('create'); 116 | }); 117 | 118 | it('should work.', function() 119 | { 120 | // when 121 | $container.append( jQuery('') ); 122 | 123 | var counter = 0; 124 | jQuery(document).on('create', 'a.pippo', function( e ) { 125 | counter++; 126 | e.$currentTarget.css( style_red ); 127 | }); 128 | 129 | // given 130 | $container.append( jQuery('') ); 131 | $container.append( jQuery('') ); 132 | $container.append( jQuery('') ); 133 | 134 | // when 135 | $container.append( jQuery('') ); 136 | $container.find('> span:eq(0)').replaceWith( jQuery('') ); 137 | 138 | // then 139 | expect( counter ).toBe(3); 140 | expect( $container.find('> a:eq(0)') ).toHaveCss( style_red ); 141 | expect( $container.find('> a:eq(1)') ).toHaveCss( style_red ); 142 | }); 143 | 144 | it('should execute the callback when elements are created inside other elements.', function() 145 | { 146 | // given 147 | jQuery(document).on('create', 'div.inner', function( e ) { 148 | e.$currentTarget.css( style_green ); 149 | }); 150 | 151 | var element = '
'; 152 | 153 | // when 154 | $container.append( element ); 155 | 156 | // then 157 | expect( $container.find('div.inner') ).toHaveCss( style_green ); 158 | }); 159 | }); 160 | 161 | describe('should execute the callback when', function() 162 | { 163 | var counter; 164 | 165 | beforeEach(function () 166 | { 167 | counter = 0; 168 | 169 | $container.on('create', '> div', function() { 170 | counter++; 171 | }); 172 | }); 173 | 174 | it('a new element is created before the special event declaration.', function() 175 | { 176 | // given 177 | var _inner_counter = 0; 178 | 179 | // when 180 | $container.append( jQuery('
') ); 181 | 182 | $container.on('create', '> div', function() { 183 | _inner_counter++; 184 | }); 185 | 186 | $container.append( jQuery('
') ); 187 | 188 | // then 189 | expect( _inner_counter ).toBe(2); 190 | expect( counter ).toBe(2); 191 | expect( $container.find('> div') ).toHaveCss( style_red ); 192 | }); 193 | 194 | 195 | it('the \'append\' method is invoked.', function() 196 | { 197 | // when 198 | $container.append( jQuery('
') ); 199 | $container.append( jQuery('
') ); 200 | $container.append( jQuery('') ); 201 | 202 | // then 203 | expect( counter ).toBe(2); 204 | expect( $container.find('> div') ).toHaveCss( style_red ); 205 | }); 206 | 207 | it('the \'prepend\' method is invoked.', function() 208 | { 209 | // when 210 | $container.prepend( jQuery('
') ); 211 | $container.prepend( jQuery('
') ); 212 | $container.append( jQuery('') ); 213 | 214 | // then 215 | expect( counter ).toBe(2); 216 | expect( $container.find('> div') ).toHaveCss( style_red ); 217 | }); 218 | 219 | it('the \'before\' method is invoked.', function() 220 | { 221 | // given 222 | $container.append( jQuery('') ); 223 | 224 | // when 225 | $container.find('> span').before( jQuery('
') ); 226 | $container.find('> span').before( jQuery('
') ); 227 | 228 | // then 229 | expect( counter ).toBe(2); 230 | expect( $container.find('> div') ).toHaveCss( style_red ); 231 | }); 232 | 233 | it('the \'after\' method is invoked.', function() 234 | { 235 | // given 236 | $container.append( jQuery('') ); 237 | 238 | // when 239 | $container.find('> span').after( jQuery('
') ); 240 | $container.find('> span').after( jQuery('
') ); 241 | 242 | // then 243 | expect( counter ).toBe(2); 244 | expect( $container.find('> div') ).toHaveCss( style_red ); 245 | }); 246 | 247 | it('the \'html\' method is invoked.', function() 248 | { 249 | // when 250 | $container.html( '
' + '
' + '
' + '' ); 251 | 252 | // then 253 | expect(counter).toBe(3); 254 | $container.find('> div').each(function() { 255 | expect( jQuery(this) ).toHaveCss( style_red ); 256 | }); 257 | }); 258 | 259 | it('the \'replaceWith\' method is invoked.', function() 260 | { 261 | // given 262 | $container.append( jQuery('') ); 263 | $container.append( jQuery('') ); 264 | 265 | // when 266 | $container.find('> span:eq(0)').replaceWith( jQuery('
') ); 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 | --------------------------------------------------------------------------------