JQuery "create" event for dynamically created elements
I need to be able to dynamically create a <select> element and turn it into jQuery .combobox() . This should be an element creation event, as opposed to some click event, in which case I could just use jQuery .on() .
So does something like this exist?
$(document).on("create", "select", function() { $(this).combobox(); } I do not want to use livequery because it is very outdated.
UPDATE The selected select / combobox is loaded via ajax into the jQuery color box (modal window), so the problem is I can only trigger the combo box using colorbox onComplete , however, when changing one combobox, the other select / combobox needs to be dynamically created, so I need a more general a way to detect element creation ( select in this case).
UPDATE2 To try to explain the problem further - I have select/combobox elements created recursively, inside .combobox() also a lot of initiating code, so if I used the classic approach, for example, in @bipen answer my code would inflate to insane levels. Hope this explains the problem better.
UPDATE3 Thanks to everyone, now I understand that since the DOMNodeInserted become obsolete, there is a void in the DOM mutation, and there is no solution to this problem. I just have to review my application.
You can on DOMNodeInserted event to get the event when it is added to the document by your code.
$('body').on('DOMNodeInserted', 'select', function () { //$(this).combobox(); }); $('<select>').appendTo('body'); $('<select>').appendTo('body'); Hidden here: http://jsfiddle.net/Codesleuth/qLAB2/3/
EDIT: after reading, I just need to double check the DOMNodeInserted will not cause problems in browsers. This 2010 question suggests that IE does not support the event, so check it out if you can.
See here: [link] Warning! The DOMNodeInserted event type is defined in this specification for reference and completeness, but this specification discounts the use of this event type.
You can use the DOMNodeInserted mutations of the DOMNodeInserted (no need for delegation):
$('body').on('DOMNodeInserted',function(e){ var target = e.target; //inserted element; }); EDIT: mutation events are outdated , use a mutant observer instead
Just came up with this solution that seems to solve all my ajax issues.
For ready events, I now use this:
function loaded(selector, callback){ //trigger after page load. $(function () { callback($(selector)); }); //trigger after page update eg ajax event or jquery insert. $(document).on('DOMNodeInserted', selector, function () { callback($(this)); }); } loaded('.foo', function(el){ //some action el.css('background', 'black'); }); And for normal trigger events, I now use this:
$(document).on('click', '.foo', function () { //some action $(this).css('background', 'pink'); }); This can be done using DOM4 MutationObservers , but will only work in Firefox DOM4 MutationObservers / Chrome 18+ (for now).
However, there is an epic hack (copyright words are not mine!) That works in all browsers that support CSS3 animation, which are: IE10, Firefox 5+, Chrome 3+, Opera 12, Android 2.0+, Safari 4+. See the demo from the blog. The hack consists in using the CSS3 animation event with the given name, which is observed and acts in JavaScript.
One of the ways that seems reliable (although it is tested only in Firefox and Chrome) is to use JavaScript to listen to the animationend event (or its camelCased and prefixed, sibling animationend ) and apply the short-lived (in the demo 0.01 second) animation to type- the item you plan to add. This, of course, is not an onCreate event, but approximates (in compatible browsers) the type of the onInsertion event; The following is proof of concept:
$(document).on('webkitAnimationEnd animationend MSAnimationEnd oanimationend', function(e){ var eTarget = e.target; console.log(eTarget.tagName.toLowerCase() + ' added to ' + eTarget.parentNode.tagName.toLowerCase()); $(eTarget).draggable(); // or whatever other method you'd prefer }); In the following HTML:
<div class="wrapper"> <button class="add">add a div element</button> </div> And (abbreviated, prefix-versions-removed, although present in the script below) CSS:
/* vendor-prefixed alternatives removed for brevity */ @keyframes added { 0% { color: #fff; } } div { color: #000; /* vendor-prefixed properties removed for brevity */ animation: added 0.01s linear; animation-iteration-count: 1; } Obviously, CSS can be customized according to the location of the corresponding elements, as well as the selector used in jQuery (it should be as close as possible to the insertion point).
Event Name Documentation:
Mozilla | animationend Microsoft | MSAnimationEnd Opera | oanimationend Webkit | webkitAnimationEnd W3C | animationend Literature:
For me, body attachment does not work. Binding to a document using jQuery.bind () does.
$(document).bind('DOMNodeInserted',function(e){ var target = e.target; }); There is an adampietrasiak / jquery.initialize plugin that is based on MutationObserver , which accomplishes this simply.
$.initialize(".some-element", function() { $(this).css("color", "blue"); }); As mentioned in several other answers, mutation events are deprecated, so you should use MutationObserver instead. Since no one has given any details yet, here he is ...
JavaScript core API
The API for MutationObserver is pretty simple. It’s not as simple as mutation events, but it’s still okay.
function callback(records) { records.forEach(function (record) { var list = record.addedNodes; var i = list.length - 1; for ( ; i > -1; i-- ) { if (list[i].nodeName === 'SELECT') { // Insert code here... console.log(list[i]); } } }); } var observer = new MutationObserver(callback); var targetNode = document.body; observer.observe(targetNode, { childList: true, subtree: true }); <script> // For testing setTimeout(function() { var $el = document.createElement('select'); document.body.appendChild($el); }, 500); </script> Let it break.
var observer = new MutationObserver(callback); This creates an observer. The observer is not observing anything yet; this is exactly the case when the event listener joins.
observer.observe(targetNode, { childList: true, subtree: true }); This causes the observer to start. The first argument is the node to which the observer will monitor the changes. The second argument is options for what needs to be observed . childList means I want to keep track of adding or removing children. subtree modifier extends childList to observe changes at any point in this subtree element (otherwise, it would be easy to look at the changes directly in the <body> ). Two other important features are attributes and characterData , which means they sound.
function callback(records) { records.forEach(function (record) { In the callback, things get a little more complicated. The callback receives an array of MutationRecord s. Each MutationRecord can describe several changes of the same type ( childList , attributes or characterData ). Since I just told the supervisor to keep an eye on the childList , I will not check the type.
var list = record.addedNodes; Right here I grab the NodeList of all the child nodes that were added. This will be empty for all entries where nodes are not added (and there may be many such entries).
From there, I scroll through the added nodes and find any <select> elements.
There is nothing complicated here.
JQuery
... but you asked jQuery. Good.
(function($) { var observers = []; $.event.special.domNodeInserted = { setup: function setup(data, namespaces) { var observer = new MutationObserver(checkObservers); observers.push([this, observer, []]); }, teardown: function teardown(namespaces) { var obs = getObserverData(this); obs[1].disconnect(); observers = $.grep(observers, function(item) { return item !== obs; }); }, remove: function remove(handleObj) { var obs = getObserverData(this); obs[2] = obs[2].filter(function(event) { return event[0] !== handleObj.selector && event[1] !== handleObj.handler; }); }, add: function add(handleObj) { var obs = getObserverData(this); var opts = $.extend({}, { childList: true, subtree: true }, handleObj.data); obs[1].observe(this, opts); obs[2].push([handleObj.selector, handleObj.handler]); } }; function getObserverData(element) { var $el = $(element); return $.grep(observers, function(item) { return $el.is(item[0]); })[0]; } function checkObservers(records, observer) { var obs = $.grep(observers, function(item) { return item[1] === observer; })[0]; var triggers = obs[2]; var changes = []; records.forEach(function(record) { if (record.type === 'attributes') { if (changes.indexOf(record.target) === -1) { changes.push(record.target); } return; } $(record.addedNodes).toArray().forEach(function(el) { if (changes.indexOf(el) === -1) { changes.push(el); } }) }); triggers.forEach(function checkTrigger(item) { changes.forEach(function(el) { var $el = $(el); if ($el.is(item[0])) { $el.trigger('domNodeInserted'); } }); }); } })(jQuery); This creates a new event called domNodeInserted using the special jQuery event API . You can use it like this:
$(document).on("domNodeInserted", "select", function () { $(this).combobox(); }); I personally would suggest looking for a class, because some libraries will create select elements for testing purposes.
Naturally, you can also use .off("domNodeInserted",...) or adjust the view setting by passing the data as follows:
$(document.body).on("domNodeInserted", "select.test", { attributes: true, subtree: false }, function () { $(this).combobox(); }); This will check for the appearance of the select.test element whenever attributes are changed for elements directly inside the body.
You can see it live below or on jsFiddle .
(function($) { $(document).on("domNodeInserted", "select", function() { console.log(this); //$(this).combobox(); }); })(jQuery); <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script> // For testing setTimeout(function() { var $el = document.createElement('select'); document.body.appendChild($el); }, 500); </script> <script> (function($) { var observers = []; $.event.special.domNodeInserted = { setup: function setup(data, namespaces) { var observer = new MutationObserver(checkObservers); observers.push([this, observer, []]); }, teardown: function teardown(namespaces) { var obs = getObserverData(this); obs[1].disconnect(); observers = $.grep(observers, function(item) { return item !== obs; }); }, remove: function remove(handleObj) { var obs = getObserverData(this); obs[2] = obs[2].filter(function(event) { return event[0] !== handleObj.selector && event[1] !== handleObj.handler; }); }, add: function add(handleObj) { var obs = getObserverData(this); var opts = $.extend({}, { childList: true, subtree: true }, handleObj.data); obs[1].observe(this, opts); obs[2].push([handleObj.selector, handleObj.handler]); } }; function getObserverData(element) { var $el = $(element); return $.grep(observers, function(item) { return $el.is(item[0]); })[0]; } function checkObservers(records, observer) { var obs = $.grep(observers, function(item) { return item[1] === observer; })[0]; var triggers = obs[2]; var changes = []; records.forEach(function(record) { if (record.type === 'attributes') { if (changes.indexOf(record.target) === -1) { changes.push(record.target); } return; } $(record.addedNodes).toArray().forEach(function(el) { if (changes.indexOf(el) === -1) { changes.push(el); } }) }); triggers.forEach(function checkTrigger(item) { changes.forEach(function(el) { var $el = $(el); if ($el.is(item[0])) { $el.trigger('domNodeInserted'); } }); }); } })(jQuery); </script> The note
This jQuery code is a pretty simple implementation. This does not work when changes in other places make your selector valid.
For example, suppose your .test select selector already has a <select> in the document. Adding a test class to <body> will make the selector valid, but since I only check record.target and record.addedNodes , the event does not fire. This change should happen to the item you want to choose yourself.
This can be avoided by requesting a selector when mutations occur. I decided not to do this in order to avoid recurring events for elements that have already been processed. The right attitude towards related or common combinators would be even more difficult.
For a more complete solution, see https://github.com/pie6k/jquery.initialize , as stated in Damien Ç Ceallaigh's answer .
create a <select> with id, add it to the document .. and call .combobox
var dynamicScript='<select id="selectid"><option value="1">...</option>.....</select>' $('body').append(dynamicScript); //append this to the place your wanted. $('#selectid').combobox(); //get the id and add .combobox(); this should do the trick .. you can hide the choice if you want, and after .combobox show it ... or use find again ..
$(document).find('select').combobox() //though this is not good performancewise If you use angularjs, you can write your own directive. I had the same issue with bootstrapSwitch. I have to call $("[name='my-checkbox']").bootstrapSwitch(); in javascript, but my html input object was not created at that time. Therefore, I write my own directive and create an input element with <input type="checkbox" checkbox-switch>
In the directive, I compile the element to access through javascript to execute the jquery command (for example, your .combobox() command). It is very important to remove the attribute. Otherwise, this directive will call itself, and you created a loop
app.directive("checkboxSwitch", function($compile) { return { link: function($scope, element) { var input = element[0]; input.removeAttribute("checkbox-switch"); var inputCompiled = $compile(input)($scope.$parent); inputCompiled.bootstrapSwitch(); } } });