jQuery not detecting dynamic html - javascript

I have a series of folders that are represented as links using a VueJS for-loop, wit data pulled from API. For each of those links, I am using a library called x-editable to allow for the editing of the names. Essentially, when you click on the link with defined attributes (assuming you instantiate via editable(), it will bring up a pop-over).
I find that if I create a dummy link (outside of the for loop) and include the instantiation in a document.ready block, it works just fine:
EDIT: Note that the ready: function() is a custom document.ready function, defined in a Vuejs router.
ready: function() {
jQuery.fn.editable.defaults.mode = 'popup';
jQuery('.editable').editable();
},
However, none of the dynamically generated links work when they are clicked. My guess is that jQuery is simply not detecting them, as they were not made when the document was loaded.
I tried to call editable after the data was received and assigned to Vue:
//Folders
this.$http.get('/api/folders').then(function(response) {
this.state.folders = response.data;
jQuery('.editable').editable();
});
However, it seems that this doesn't work either. I read that you can bind events using .on() which will work with generated elements. However, .editable() isn't really an event (I think). Is there a way to get around this?

Related

Dynamic field generation with CSS Togglebuttons and .on() binders

Question:
I am trying to build a dynamic form using jQuery. There are a few standard html form inputs in a row in addition to a CSS stylized "hidden checkbox" toggle button. (Example)
The first row is statically coded, and there is a button to add more rows to the form for batch submissions. JQuery applied to the .on(click) event works perfectly with the static content, but newly appended form rows ignore the binding. Even jsfiddle's dynamic display doesn't appear to function, though it is possible my fiddle example has a bug that my working copy doesn't have. (Fiddle)
/* activeToggle is the selector for the container and descendants,
// .toggleHappy is the hidden input element within activeToggle */
$(activeToggle).on("click", ".toggleHappy", function(e) {
e.preventDefault();
$(this).val(($(this).val() == 0) ? 1 : 0);
console.log($(this).val());
});
I have researched and found the common mistake of using .click() instead of delegating using .on(click, selector, fn()) and am thus using the latter now.
What is frustrating is I have another .on(click) that IS working with the dynamic content. (the remove function) So, I was hoping another pair of eyes might help me find out what my mistake is. I know this is very similar to other questions on the basic subject, but I have read quite a number of those discussions first, and have applied many of them to get where I currently am.
Updates:
I tried what Madhavan suggested and it does in fact work, but as expected, only for the first-child. So dynamically added rows are not pointed to. Thanks for the fast look. I feel like it might be a selector/scope issue, but whats weird is that once the page is loaded, I can type this directly into console and it works?
//this works run from console, but doesn't fire from a real click?
$(".theContainer .data .switch .toggleHappy").first().click();
ANSWER I have been working on another project in the interim and it is starting to look like .on(event, selector, fn) is not working at all for dynamically added items. But, I had a breakthrough! The other project was slightly simplified, and I found the following:
//.theContainer is static
//.data .switch .toggleHappy is the dynamic chain created
//does not delegate and bind dynamically
$(".theContainer").on("click", ".data .switch .toggleHappy", function() {});
//DOES work!
$(".theContainer").on("click", ".toggleHappy", function() {});
It would appear that a selector like .path .to .element only works well on existing static content, while .element allows .on() to be bound and delegated properly for dynamically generated nodes. See the updated fiddle.
The part that confused me was that hopping into the console and referencing dynamic elements with the full selector DID work on dynamic elements, but the events weren't delegated to them. Thanks again for the eyes that looked over this question, hope it helps someone else, because I still haven't found this on the web yet.
It appears that delegating jQuery events with .on() will only work on static content when the selector used within .on() is a list of consecutive elements. In my code, I had a single container which was static, I delegated an event to it referencing multiple elements between the container and the destination elements. It would work locally for any number of statically identified elements, but any dynamically added ones would ignore the bindings. It also would throw no errors, and it WOULD respond to lines of script executed within the console directly, using the same selector chaining as the function. I found the following:
//.theContainer is static
//.data .switch .toggleHappy is the dynamic chain created
//does not delegate and bind dynamically
$(".theContainer").on("click", ".data .switch .toggleHappy", function() {});
//DOES work!
$(".theContainer").on("click", ".toggleHappy", function() {});
It would appear that a selector like ".path .to .element" only works well on existing static content, while ".element" allows .on() to be bound and delegated properly for dynamically generated nodes. See the updated fiddle.
The part that confused me was that hopping into the console and referencing dynamic elements with the full selector DID work on dynamic elements, but the events weren't delegated to them. Thanks again for the eyes that looked over this question, hope it helps someone else, because I still haven't found this on the web yet.
$(activeToggle).on("click", function(e) {
e.preventDefault();
$(":first-child",this).val(($(":first-child",this).val() == 0) ? 1 : 0);
console.log($(":first-child",this).val());
});
I made a change like this and works fine. But I'm not sure why it is not triggering for '.toggleHappy' instead.

Changes within element doesn't show/apply

fiddle: https://jsfiddle.net/aa1z1m8t/3/
I'm using a library that allows the creation of treeviews as a bootstrap component, however I think this is more of a JS question than a library-use question.
I want to change title of the li element (tree node) upon selection, but the applied changes simply do not show! As if the element doesn't refreshes.
I've tried using the library's event, I've tried using the jQuery's click event (both, click and on variants), but to no avail. I've also tried various reflow tricks.
How can I force a refresh?
Note that the treeview is being generated and appended to DOM via JS.
To sum up: I wanted to change the node's title on selection, but changes aren't applied for some reason.
i tried to understand your problem, but i am not completely sure.
https://jsfiddle.net/aa1z1m8t/11/
function CreateTreeView() {
$('#tree').treeview({
'data': treedata,
'onNodeSelected': function(event, data) {
alert(data);
}
});
}
I build a function to create the treeview and call it every time a event is fireing.

How to apply jquery to markup that is populated by an ajax call

I am dynamically building out a page depending on what the user selects (ajax calls). The problem is that I am not able to apply any jquery to the markup that is returned. When I run get the page where I want it and run jQuery in the console I get [] returned to me.
How can I run jQuery against the returned markup?
Your problem is when you call the jQuery, if you call it on $(document).ready() you will run into trouble as on $(document).ready() you have not added the content. This is because JavaScript is asynchronous.
Here is a simple solution:
$.get('page-page-to-post.php').done(function(data) {
// jQuery for dynamically called content
});
This way you add the content, and then you add jQuery functions. I ran into this problem a while back, I was thinking of JavaScript like CSS. CSS is constantly being called.
Another way you is to add a callback function. This is useful if you want to add actions to the dynamically added content from a separate script and it reduces code bloat.
// main.js
var myApp = {
load: function(callback) {
$.get('page-page-to-post.php').done(function(data) {
callback();
});
}
}
Now, you can call this function like you would $(document).ready()
//new script
myApp.load(function() {
// jQuery / JavaScript to attach to dynamically loaded content
});
You would have to append the markup to the DOM and then use JS or jQ to manipulate it (if that's what you want to do). That's why you're getting empty result set.
The mistake you might be making is trying to manipulate the DOM before it's ready... so whatever you do, do in the AJAX success callback.
It would be really helpful if you had shown some code. And you'd probably get better answers.

Modify dynamically added elements on load using jQuery

I am converting old jQuery version 1.2.6 code that we use with our portal (Liferay). Previously we used the livequery plugin to add events to dynamically added DOM objects. This is now a feature built-in to jQuery (the on() function). I have that figured out.
However, there was also a feature in livequery that allowed us to modify these dynamically loaded objects on load (i.e. not tied to certain events):
$(".myTextBox").livequery(function() { $(this).val("initial value"); });
I do not control the code when the ajax portlets get loaded in our portal, so I can't modify the content when created.
I've tried a few things to no avail. Here is the one that I thought would work, but doesn't. I added jQuery to my portlet so that it loads at the bottom of the portlet HTML and I added jQuery to the file.
<footer-portlet-javascript>myscript.js</footer-portlet-javascript>
...
$(document).ready(function() {
$(".myTextBox").val("initial value");
});
This doesn't work. If I write an alert($(".myTextBox")) it shows an object, but alert($(".myTextBox").val()) is undefined.
Any ideas of how I can get working?
From what I read, you want to wire up events to items that have not been necessarily added to the DOM yet at the time you're wire-up function fires. I also read that you are upgrading to a more recent version of jquery.
If you're using jquery 1.7+, the .on() method should provide this capability for you. If you're using something between 1.2.6 and 1.7, you'll need to use the .live() method to achieve this behavior.
$(".myTextBox").live('click', function(e){
console.log(this.value);
});
Optionally, you may want to mix in some AUI to do your wiring-up on the Liferay 'allPortletsReady' published event. Here is some code we've used to wire-up items once all portlets are finished loading:
//This is the AUI version of on document ready
// and is just used to form a 'sandbox'(clojure)
// around our code so the AUI object A is not modified
AUI().ready(function(A){
//This essentially subscribes the provided function
// to the Liferay custom event 'allPortletsReady'
// so that when it's fired, the provided function
// will be called.
Liferay.on('allPortletsReady', function(){
//Do your initialization here
myCustomPortletManager.init();
//OR
A.one("#mySelector").on('click', function(e){
//do your work here
});
//Etc.
//NOTE: jQuery ($) is valid inside this sandbox for our
//instance.
});
}):
Well you can setInterval to iterate checking new element.
setInterval(function(){
var $ele = $('.myTextBox:not(.loaded)'); // find new element that not loaded
if($ele.size() > 0){
$ele.each(function(){
// do stuff with elements
$(this).val("initial value");
}).addClass('loaded'); // flag this element is loaded
}
}, 200); // set delay as you wish
by the way, I'm not recommended this.
First, you probably want to use an ID instead of a class in this case to ensure you are referring specifically to a single element.
Where your alert is will determine whether this code is executed before or after. This would explain that you return an object, but no value. Put it after the value assignment, either on the page or temporally.
The following works just fine (http://jsfiddle.net/4WHyE/1/):
<input id="myid"/>
$('#myid').val('some value')
alert($('#myid').val())
If you must use class, then it depends on whether you want to set each class element individually or all to the same value. If you wish them all to have the same value, simply replace id with class in the above example:
<input class="myclass"/>
$('.myclass').val('myvalue')
If you wish to set unique values, you can simply iterate through them (http://jsfiddle.net/4WHyE/2/):
$('.myclass').each(function(index){
$(this).val('value' + index)
});

'document' vs. 'content.document'

I'm trying to write a Firefox extension that adds elements to the loaded page. So far, I get the root element of the document via
var domBody = content.document.getElementsByTagName("BODY").item(0);
and create the new elements via
var newDiv = content.document.createElement("div");
and everything worked quite well, actually. But the problems came when I added a button with on onclick attribute. While the button is correctly displayed, I get an error. I already asked asked here, and the answer with document.createElement() (without content) works.
But if I remove the 'content.' everywhere, the real trouble starts. Firstly, domBody is null/undefined, no matter how I try to access it, e.g. document.body (And actually I add all elements _after_the document is fully loaded. At least I think so). And secondly, all other elements look differently. It's seem the style information, e.g., element.style.width="300px" are no longer considered.
In short, with 'content.document' everything looks good, but the button.onclick throws an error. with only 'document' the button works, but the elements are no longer correctly displayed. Does anybody see a solution for that.
It should work fine if you use addEventListener [MDN] (at least this is what I used). I read somewhere (I will search for it) that you cannot attach event listener via properties when creating elements in chrome code.
You still should use content.document.createElement though:
Page = function(...) {
...
};
Page.prototype = {
...
addButton : function() {
var b = content.document.createElement('button');
b.addEventListener('click', function() {
alert('OnClick');
}, false);
},
...
};
I would store a reference to content.document somewhere btw.
The existing answer doesn't have a real explanation and there are too many comments already, so I'll add another answer. When you access the content document then you are not accessing it directly - for security reasons you access it through a wrapper that exposes only actual DOM methods/properties and hides anything that the page's JavaScript might have added. This has the side-effect that properties like onclick won't work (this is actually the first point in the list of limitations of XPCNativeWrapper). You should use addEventListener instead. This has the additional advantage that more than one event listener can coexist, e.g. the web page won't remove your event listener by setting onclick itself.
Side-note: your script executes in the browser window, so document is the XUL document containing the browser's user interface. There is no <body> element because XUL documents don't have one. And adding a button won't affect the page in the selected tab, only mess up the browser's user interface. The global variable content refers to the window object of the currently selected tab so that's your entry point if you want to work with it.

Categories

Resources