Better performance for selecting options by value with jQuery? - javascript

I am trying to work out some performance problems with some JavaScript I've been working on for a few days. One of the pieces of the functions is below:
var removeAddress = function(pk) {
var startTime = new Date();
jQuery('.add_driver select.primary_address:has(option[value=' + pk + ']:selected)').each(function(c, o) {
console.log("Shouldn't get here yet...");
showInputs(o);
});
console.log('removeAddress1: ', (new Date() - startTime) / 1000);
jQuery('.add_driver select.primary_address option[value=' + pk + ']').remove();
console.log('removeAddress2: ', (new Date() - startTime) / 1000);
};
This code is quite peppy in Firefox:
removeAddress1: 0.004
removeAddress2: 0.023
But in IE8 it is another story:
LOG: removeAddress1: 0.203
LOG: removeAddress2: 0.547
The form in question is a 20-person in put form with first name, last name, and 5 address fields. I've also put in a drop down for selecting other addresses already existing in the form (.primary_address). This code is removing an address from the primary address select boxes.
I'm trying to nail down why this is taking so long, and the only thing which stands out is the option[value=????] section. This was the most practical way to find the elements in question, so I ran with it. Is there something about these two selectors which is causing IE to lose its lunch?

The option element is always temperamental. Perhaps it's simpler to just get all the SELECT elements and then simply query their values. The selected OPTION always will give its value property to the SELECT as well. So:
jQuery('.add_driver select.primary_address').filter(function() {
return $(this).value === pk;
});
jQuery('.add_driver select.primary_address[value='+pk+']');
Maybe one of those will be faster - not sure if the second will work.

You can likely speed this up a lot by breaking down your uber-selector string.
To start, begin with an id, or even better a cached element. Then get your select elements using .children(). Instead of using the :has selector use .has(). Methods are generally faster than complex selector syntax because jQ doesn't have to parts a string to figure out what you mean. Then, as Rafael said, you can skip the :selected and just look at the value of the matched select's.
formElem = document.getElementById('formId');
jQuery('.add_driver', formElem)
.children('select.primary_address')
.has('[value=' + pk + ']')
.each(...);
Passing formElem as the second arg uses it as the context for the search so jQ doesn't have to start at the root.
To .remove() the elements either cache the jQuery object from above or chain it on after the .each() so you don't have to reselect everything again.

Maybe precompute $('formId .add_driver select') outside of the removeAddress function, then reuse that so the removeAddress() doesn't have to enumerate so many elements.

Related

Properly renaming cloned inputs JQuery

I'm attempting to craft my own cloning function that changes the name of cloned inputs so they can be collected by PHP.
on W3C I found this simple function to build from
$("button").click(function(){
$("p").clone().appendTo("body");
});
I modified it to clone a fieldset with multiple inputs and it worked. I took it a step farther to try to make it rename and integer the name attribute of the different input elements and now it doesn't even clone the field.
Here's what I have so far.
$("#p20_01_yes").click(function(){
var num = $('.clonedInput').length,
newNum = new Number(num + 1);
$("#cloner").clone($("input, textarea, select").each().attr('name'+newNum)).appendTo("#page20");
});
I just tried to post a snippet of the HTML but I got a warning telling me the limit is 30,000 characters so here's a js fiddle that shows everything.
https://jsfiddle.net/Optiq/krjztsm2/
I structured the JQuery the way I did because I figured it would be more appropriate to do the renaming inside of the clone function then append the results to the page rather than appending it then going back and separating it from everything else to dig back through... figured that would make more sense to the computer. Is this the right way to look at it?
You were pretty close already. I'm not sure if my answer will work perfectly (seeing that the JSFiddle is pretty messy), but you could just adapt the classes and ids afterwards.
You just need to split your tasks into two parts:
Clone the fieldset
Update the input elements names (and possibly ids as well?)
Here is an example of how this could work:
$("#p20_01_yes").click(function(){
var num = $('.clonedInput').length,
newNum = new Number(num + 1);
var $clonedFieldset = $("#cloner").clone().appendTo("#page20");
$clonedFieldset.find("input, textarea, select ").each(function() {
var $item = $(this);
$item.attr('name', $item.attr('name') + newNum);
});
});

What is the best way to access an element through a data-attribute whose value is an object (JSON)?

Say I have the following element:
<div class='selector' data-object='{"primary_key":123, "foreign_key":456}'></div>
If I run the following, I can see the object in the console.
console.log($('.selector').data('object'));
I can even access data like any other object.
console.log($('selector').data('object').primary_key); //returns 123
Is there a way to select this element based on data in this attribute? The following does not work.
$('.selector[data-object.foreign_key=456]');
I can loop over all instances of the selector
var foreign_key = 456;
$('.selector').each(function () {
if ($(this).data('object').foreign_key == foreign_key) {
// do something
}
});
but this seems inefficient. Is there a better way to do this? Is this loop actually slower than using a selector?
You can try the contains selector:
var key_var = 456;
$(".selector[data-object*='foreign_key:" + key_var + "']");
I think that you may gain a little speed here over the loop in your example because in your example jQuery is JSON parsing the value of the attribute. In this case it's most likely using the JS native string.indexOf(). The potential downside here would be that formatting will be very important. If you end up with an extra space character between the colon and the key value, the *= will break.
Another potential downside is that the above will also match the following:
<div class='selector' data-object="{primary_key:123, foreign_key:4562}"></div>
The key is clearly different but will still match the pattern. You can include the closing bracket } in the matching string:
$(".selector[data-object*='foreign_key:" + key_var + "}']");
But then again, formatting becomes a key issue. A hybrid approach could be taken:
var results = $(".selector[data-object*='" + foreign_key + "']").filter(function () {
return ($(this).data('object').foreign_key == foreign_key)
});
This will narrow the result to only elements that have the number sequence then make sure it is the exact value with the filter.
With a "contains" attribute selector.
$('selector[data-object*="foreign_key:456"]')

jquery offset method doesn't always work / exist

Good morning and happy new year everyone!
I've run into a snag on something and need to figure out a solution or an alternative, and I don't know how to approach this. I actually hope it's something easy; meaning one of you all have dealt with this already.
The problem is that I'm doing rollovers that contain information. They're divs that get moved to the absolute location. Now I've tried this with jquery 1.6 - 1.9.1. Of course this has to work in multiple browsers.
What needs to happen is on rollover show a div, and when you rollout of that div, make it hide.
...
// .columnItem is class level and works
$(".columnItem").mouseleave(function() {
$(this).css("display", "none");
});
...
$(".column").mouseenter(function() {
var currentItem = $(this)[0]; // this is where the problem is
// hide all .columnItems
$(".columnItem").css("display", "none");
// i get this error: Object #<HTMLDivElement> has no method 'offset' (viewing in chrome console)
var offsetTop = currentItem.offset().top;
var columnInfoPanel = $("#column" + currentItem.innerText);
});
So the immediate thought of some would be don't use $(this)[0]. Instead, I should use $(this), and you are correct! Where the other problem comes into play is by removing the array index, currentItem.innerText is now undefined.
The only thing I can think of is I'll have to mix both, but it seems like there should be a way to use the selector and get both options.
What have you all done?
Thanks,
Kelly
Replace:
var currentItem = $(this)[0];
With:
var currentItem = $(this).eq(0);
This creates a new jQuery object containing only the first element, so offset will work.
Then you can use either currentItem[0].innerText or currentItem.text(), whichever you prefer.
Skip the [0] at the beginning as you are saying.
But then change the last line to:
var columnInfoPanel = $("#column" + currentItem[0].innerText);
De-referencing the jQuery selector gives you the DOM-object.
If you want to stick to pure jQuery, the .text() / .html() methods will give you the same functionality.

Cloned row requesting same function [duplicate]

This question already exists:
Closed 10 years ago.
Possible Duplicate:
Call same function by a cloned list row
I am trying to make a simple calculation to work.
I have the following running:
http://jsfiddle.net/vSyK6/41/
Basically, the way it works now is this:
When you select an option on the drop down list it will display the content based on the option selected. Then when you select the same option again it will add, basically clone the same row.
Now, when the second option is selected "Option2" it will display an empty textbox. When you enter a number it will or should call the a function where we make a basic calculation. The function is already in the script.
However, when we have two empty textboxes it should call the same calculation function but calculate seperately and puts it in a different div. The div# where we display the amount is a called "amount"
Basically, it should work like this:
First Empty textbox -> 100 -> 100 * 22.38 = display result in div#1
Second Empty textbox -> 230 -> 230 * 22.38 = display in div#2
any idea on how to accomplish that ?
When cloning elements the id is cloned as well. It is best practice to create a new ID for the cloned elements, which will also help in accomplishing what you want. The same goes for the name attribute as well.
With a few modification to your code, http://jsfiddle.net/dNQVQ/3/, I was able to get what you were after. Let me first say that this might not be the ideal way to go, but it is a start. Like I said earlier the key is going to be setting unique ids for the cloned elements. What I did in this example was use a index as part of the list element id that is cloned with a matching index in an 'amount' div. This way when an input is updated the index is retrieved and then used to update the appropriate div. Additionally, I moved the function that did the calculation and updates to an anonymous function in the settimeout call. This makes it easy to use a reference to the updated input in the function call.
Joining the party quite late here :) Here is one vernon: http://jsfiddle.net/KVPwm/
ALso if its assignment bruv, put an assignment homework tag!
People around SO community are awesome folks so be truthful, guys will help man!
Use .on instead of live - recommendation. i.e. upgrade your JQ source if keen read this - What's wrong with the jQuery live method?
you have 2 document.ready functions also I chained few things for you.
Also think of using isNan check as well.
Rest you can read the code and play around a bit to make it more concise.
I have added 2 divs and using the id number to populate the stuff accordingly.
This should fit the cause :)
code
$("document").ready(function() {
/////////////////////////////////CALUCATIONS/////////////////////////////////
//setup before functions
var typingTimer; //timer identifier
var doneTypingInterval = 0; //time in ms, 5 second for example
$('input[name=Input2], input[name=Input1]').live('keyup', function() {
var str = $(this).prop("id");
var pattern = /[0-9]+/g;
var matches = str.match(pattern);
amount = parseFloat($(this).val()) * 22.38;
typingTimer = setTimeout(doneTyping(matches), doneTypingInterval);
});
$('#Input2').keydown(function() {
clearTimeout(typingTimer);
});
function doneTyping(matches) {
$('#amount'+matches).text(amount.toFixed(2) + " lbs");
}
$("#List-Option1,#List-Option2").hide();
$('#category').change(function() {
var str = $('#category').val();
if (str == 'Option1') {
var option1 = $("#List-Option1:first").clone().show();
$('#box li:last').after(option1);
}
if (str == 'Option2') {
var option2 = $("#List-Option2:first").clone().show();
$('#box li:last').after(option2);
}
});
});​

each() function returning Null or Objects

Hi I am newbie in programming and I am trying to learn and make it work with each(). Bear with me. I try my best to learn from here and you.
I am trying to go through the item per product in a catalog for the specific prices: either original and sale from the page.
then calculate the % for discount
print discount %
check to compare the percent to color the background: brown, yellow and red.
Now, I test each line to see if it works or not.
salecost = $(this).find('#sale').html(); returns a few nulls before displays amount with dollar signs. Weird couldn't figure that one out. Replace() isn't working right - couldn't get it working. It is supposed to remove dollar sign.
Also, I am not sure how it goes with compare -- do i write statement correctly?
Thank you in advance for the help
var salecost;
var originalcost;
var percentDiscount;
var percent;
function calculate(sale, original)
{
percentDiscount = eval((sale/original)*100);
document.getElementById("percentoff").innerHTML=parseInt(percentDiscount) + '%';
}
$(document).ready(function(index){
$('.item').each(function(){
salecost = $(this).find('#sale').html();
salecost = salecost.replace(/[^\d\.]/g,"");
alert (salecost);
originalcost = $('#sale').html();
originalcost = originalcost.replace(/[^\d\.]/g,"");
alert (originalcost);
percent = calculate(salecost,originalcost);
alert(percent);
if(percent<30)
{
$("div#percentoff").css({"background-color":"brown", "padding":"5px 0"});
}
if(percent<50){
$("div#percentoff").css({"background-color":"yellow", "padding":"5px 0"});
}
if(percent<70){
$("div#percentoff").css({"background-color":"red", "padding":"5px 0"});
}
});
});
This is not required
$(this).find('#sale').html();
You can replace this using
$("#sale").html();
since id will be unique in a document. If you have more than one element with the same id then your HTML is invalid.
Edit
Remove the id from the span tag.
<span class="price">S$319</span>
This will find all the spans[here only 1] with class name price inside the parent div. No need to use .html() here, you can use .text()
$(this).find('span.price').text();
You should move your var statements to within the each function so they're not global.
You don't need to you the eval, (sale/original)*100 will work by it self.
It's better to use consistent style, your calculate function could be written using jquery.
$("#percentoff").html(percentDiscount) + '%');
casting is unnecessary for most cases in javascript, concatenating a number with a string will produce a string.
your calculate function should return percentDiscount;
Instead of using .css() it would be better to use .addClass that way it's easy to undo with .removeClass, the style is all the the style sheet and you can use jquery to select the elements with the class.

Categories

Resources