How to monitor when the body 'data' attribute changes in jQuery? - javascript

I have data-segment attribute in my body tag that is changed by a slider. I need to trigger a function based on the value of this, when it is changed.
I'm not sure how to attach an event listener in this case though?

`There is no reliable cross-browser way to receive an event when a DOM node attribute is changed. Some browsers support "DOM mutation events", but you shouldn't rely on them, and you may Google that phrase to learn about the ill-fated history of that technology.
If the slider control does not fire a custom event (I would think most modern ones do), then your best bet is to set up a setInterval() method to poll the value.

You can use MutationObserver, which is an API available in every browser and avoid polling with setInterval. If you have a reference to your element in element, you could do this:
const mutationObserver = new MutationObserver(callback);
mutationObserver.observe(element, { attributes: true });
function callback() {
// This function will be called every time attributes are
// changed, including `data-` attributes.
}
Other changes in your element can be easily detected with this API, and you can even get the old and new values of the property that changes in your callback tweaking the configuration object in the call to observe. All the documentation about this can be read in MDN: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit

Since in my use case all of my .data('type', 'value') is set inside javascript block anyway, so in my case I just put a .change() chain right after the .data(...) and access the update using the normal $("#oh-my-data").change()
Which works fine for me, see the demo.
$("#oh-my-data").change(function() {
$("#result").text($("#result").text() + ' ' + $(this).data('value'));
})
$("#oh-my-data").data('value', 'something1').change();
$("#oh-my-data").data('value', 'something2').change();
$("#oh-my-data").data('value', 'something3').change();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="oh-my-data" type="hidden" data-value="" /> Result: <span id="result"></span>
But this solution have a problem, which is that if the .data() is set by an external library, then this will not work since you can't add the .change() on top of it.

Related

TypeError: $(...).on is not a function with prototype.js

I want to add a filtered listener to the change event of a form's children but I am getting weird results from the $ selector.
I call the selector with the id of the form $("exportForm") and try to call the .on(...) method on it, getting the error in question.
Inspecting the returned element I seem to find an array with numbers as ownProperties names. Indexing them in the console $(...)[1] returns the single children of the form. In the proto property there seem to be no trace of Prototype.js methods which should be added by the selector.
What is going wrong? What to look for to get it working?
PS: Prototype.js version is 1.6.1
You're right. on() was added in 1.7, so you can't use it here. What the on method gives you is "lazy evaluation". When you call
$(document).on('eventName', 'css selector', function(){ ... });
...you get an observer that doesn't have to be initialized after the page loads, or after parts of the page are replaced by Ajax callbacks. You can replace a part of the page, then click on it again, and the observer will just work.
The old way to set up an observer was this:
$(document).observe('dom:loaded', function(){
$('theId').observe('eventName', function(){
// do something
});
});
The outer observer waits until the page is finished loading, then the inner one observes some object for an event and does something with that. This will work, as long as the DOM doesn't change after it loads. If that happens, then you have to re-register the inner listener manually, since its targets have changed, and it no longer will work. The dom:loaded event only fires once, when the page itself is loaded the first time.
In your case, it seems that you want to duplicate the behavior of the Form.Element.Observer() class method in Prototype, which sets up a timed poll of all a form's child elements, and fires a callback when any of them change. You really ought to look at the documentation for that method, as it is a really bulletproof way to do what you're trying to do. But in case you really want to roll your own, here's how you would write an observer that could listen for events on multiple elements:
$(document).observe('dom:loaded', function(){
$$('#exportForm input').invoke('observe', 'change', function(evt, elm){
// evt is the event itself, you can cancel, log, whatever
// elm is a reference to the form element, you can do elm.getValue(), etc.
});
});
This uses the "double-dollar" method to get an array of elements, so any form input inside the form with the ID exportForm is captured.
invoke() applies the same callback and arguments to an array of elements, so this is setting up a separate observe method for each form element -- not efficient if you have lots of inputs! You can try to listen for the change event on the form itself, so you only have one observer method, but depending on the browsers you need to support, you may find that these events don't always bubble from the individual form input up to the form itself. You'll have to test carefully.
Each form element observed will be seen in isolation: you won't have access to the rest of the form inputs from within the callback, just that one.
That's why the Form.Element.Observer is so powerful -- it gives you a hash of the entire form each time it fires, so you can do things like create previews or validations on a frequent-enough basis to appear "live".

Triggering change on input

I have written some code that changes an input quantity on a magento 1.9 ecommerce website.
jQuery("input.qty").val("10");
The problem is the javascript that triggers the total to update doesn't fire. I have found the code responsible and it looks like this:
(function() {
var qtyFields = $('super-product-list').select('input.qty');
qtyFields.each(function(el) {
el.observe("change", updateGroupedPrice);
});
Event.observe(window, "load", updateGroupedPrice);
function updateGroupedPrice() {
//do stuff
}
})();
I think this is using prototype.js but I tried to isolate it in a codepen but couldn't get it working.
I have tried to trigger the change event like so:
jQuery("input.qty").trigger("change")
But it does not work. I also ran through a load of other events but in the dev tools it shows the code listening on "change".
Does anyone know why I can't trigger the change?
Since the page is using Prototype.js, you ought to keep using that to trigger your change. If you introduce jQuery into this, you're a) loading another complete duplicate of what Prototype already does, and b) asking for a lot of trouble isolating the fact that $() is a method in both libraries.
Your jQuery is a little fishy to me, too. You're setting the value of one picker (I imagine) and yet you are addressing it with a classname, so potentially there is more than one select.qty in the page, and all of them will change to value 10, firing off (potentially) multiple callback functions.
The Prototype code you see here is setting up a "listener" for changes on what you would address in jQuery as$(#super-product-list input.qty) inputs.
jQuery always treats $() as returning an array of objects, and thus all of its methods act on the array, even if it only contains one member. Prototype has two different methods for accessing elements in the DOM: $('some_id'), which always returns one element (or none, if no match), and $$('some css selector'), which always returns an array (of zero or more matching elements). You would write (or use native) callback methods differently, depending on which accessor you used to gather the element(s).
If you want to change one of these inputs, you will need to isolate it before you set its value.
Let's say there are three select pickers with the classname qty in your #super-product-list element. You want to change the third one to 10:
$('super-product-list').select('input.qty').last().setValue('10');
Or, much smarter than this, you add an ID to the third one, and then your code is much shorter:
$('quantity_3').setValue('10');
In either case, this will send the "change" event from your select, and the updateGroupedPrice method will observe that and do whatever you have coded it to do.
You won't need to (and should not ever) trigger the change event -- that's a "native" event, and the browser owns it. jQuery's trigger() (which is fire() in Prototype, is used exclusively for "synthetic events", like you see in Bootstrap: show.bs.modal, hide.bs.modal, etc. You can spot these by the punctuation in their names; usually dots or colons to namespace the events and avoid collisions with other code.
Finally, if you really, really, really wanted to change every single #super-product-list select.qty element on the whole page to '10', you would do this in Prototype.js:
$$('#super-product-list select.qty').invoke('setValue', 10);

Proper Object Reference by ID Using Javascript

I have some text that I wish to display, or not, based on a test. I have placed the text in a <span /id="..."> block, and have found examples that show referencing the id directly (if...then below) or by using the document.getElementById function (...else below).
Both seem to work in my test case. I gather that using the getElementById function is correct. Is it also correct to reference without calling that function, or is this just a case where it works in this browser now, but may break using a different browser or browser version?
Is there a better method do accomplish this?
<span id="myText">Some text to display or hide</span>
<script type="text/javascript">
function SetVisibility()
{
if (button.checked)
{
myText.style.visibility="visible";
}
else
{
document.getElementById("myText").style.visibility="hidden";
}
}
According to this answer, it is a bad idea to use inline HTML event attributes.
You can also add an event listener by using getElementById then adding addEventListener() to your button element like this:
document.getElementById("myButton").addEventListener("click", function() {
var myText = document.getElementById("myText");
myText.style.visibility = myText.style.visibility === "hidden" ? "visible" : "hidden";
})
<span id="myText">Some text to display or hide</span>
<button id="myButton">SetVisible</button>
It would be best to use eventListener() to set up event handlers.
As mentioned in the comments, the support for this seems pretty widespread -- it started in IE and seems to work across Chrome/Firefox/Safari as of my testing just now. However, using global variables is often considered an anti-pattern outside of exceptional cases where it makes sense -- native web APIs or situations where you need to be able to access something globally, for example. Otherwise you run the risk of overwriting or being overwritten by other code trying to compete for those names. In short -- in this case, it is almost always better to use getElementById, although it is good to be aware that this feature exists.

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)
});

When jQuery .attr('onclick') function return a event object?

I am just debugging jQuery in FireBug and wonderig about the return value of
$('.a-selector').attr('onclick');
It turns out to be an onclick(event), but I have read some code before and the author just uses it like this:
$('.a-selector').attr('onclick').replace(..., ...);
which means it can be treated as a String Object. But it reports an error when I use like this.
My jQuery version is 1.5.2. So I wonder when the jQuery changes the API and what is the best way to change the onclick event defined in the HTML.
When jQuery .attr('onclick') function return a event object?
In jQuery < 1.6. That's because prior to 1.6, .attr() did a mix between retrieving properties and attributes where it saw fit, newer versions removed that layer of witchery and now have proper methods for retrieving attributes (.attr) and properties (.prop).
Here's a fiddle demonstrating the above.
ps. BTW, it doesn't return an event object, but rather a function object that serves as event handler. =]
Also, 2 side notes: You should always upgrade your jquery to the latest version when viable (currently 1.8.3), it comes with more features, better performance and lots of bug fixes.
And you shouldn't really be using onclicks when you have jQuery, that goes against the Web 2.0 standards of separation of structure (html) and behavior (js) - jQuery itself provides cross-browser handler attaching with the methods .on() (for jQuery 1.7+), and .bind/.delegate/.live for older versions.
I assume that you want to change the value of your onclick attribute of some element.
$('.a-selector').attr('onclick',''); // leave 2nd parameter blank if you want to remove its value
OR
$('.a-selector').attr('onclick','myfunc()');
############################### Edit
You can define your function as below :
<script>
function myfunc(){
// Do stuff here
}
</script>
is it a possibility for you to reset the click event?
$('.a-selector').unbind('click').click(function() {
// new function
});
to replace text in an javascript-code is normally not that what javascript should do. I think there is a much better solution possible.

Categories

Resources