Can this JavaScript be optimized? - javascript

This JS will be executed on pages with a lot of fields. Can you see anyway to improve the speed of this code? If so, can you explain what you found?
var _TextInputs = null;
function GetTextInputs()
{
if (_TextInputs == null)
{
_TextInputs = jq('input[type=text]');
}
return _TextInputs;
}
var _Spans = null;
function GetSpans()
{
if (_Spans == null)
{
_Spans = jq('span');
}
return _Spans;
}
function UpdateRate(ratefield, name)
{
GetTextInputs().filter('[' + name + ']').each(function()
{
this.value = FormatCurrencyAsString(FormatCurrencyAsFloat(ratefield.value));
CalculateCharge(name.replace('Rate', ''), jq(this).attr(name));
});
}
function CalculateCharge(name, activity_id)
{
var inputs = GetTextInputs();
var bill_field = inputs.filter('[' + name + 'Bill=' + activity_id + ']');
var rate_field = inputs.filter('[' + name + 'Rate=' + activity_id + ']');
var charge_field = GetSpans().filter('[' + name + 'Charge=' + activity_id + ']');
charge_field.text(FormatCurrencyAsString(FormatCurrencyAsFloat(bill_field.val()) * FormatCurrencyAsFloat(rate_field.val())));
}

You can:
Replace each with while
Replace val() with .value (should be fine as long as those fields are plain text ones)
Access elements by class instead of by name/type
Replace attr() with plain property access; e.g.: this.attr(name) --> this.name
These are all rather unobtrusive changes which should speed things up mainly due to cutting down on function calls.
Don't query elements on every function call if those elements are static (i.e. are not modified during your app life-cycle). Instead, store them outside the loop.

I can see that you're using attribute filters everywhere, e.g.:
_TextInputs = jq('input[type=text]');
inputs.filter('[' + name + 'Bill=' + activity_id + ']');
Attribute filters are useful, but not especially 'snappy' when compared to more direct class or ID selectors. I can't see any markup so the best I can do is suggest that you use more IDs and classes, e.g.:
jq('input.textInput');
instead of:
jq('input[type=text]');

A little off-topic, but I use and recommend Javascript Rocks. This books contains a TON of awesome JS optimisation advice by the creator of Scriptaculous. Also comes with a tool called DOM Monster which helps track down performance bottlenecks - it's an awesome compliment to Firebug as it actually tracks through the DOM looking for inefficiencies based on heuristics and DOM complexity.

Related

Create html tags with dynamic tag name in jQuery

I want to create html tags in my function like this:
function createHtmlTag(tagName) {
var newHtmlTag = $(tagName);
return newHtmlTag;
}
But when I call createHtmlTag('div') in my page this function return all my page div tags. I know $(tagName) causes this result, but I need to this method. I can solve this issue by these methods:
function createHtmlTagSecond(tagName) {
var newHtmlTag = $('<' + tagName + '></' + tagName + '>');
return newHtmlTag;
}
Using JavaScript
function createHtmlTagByJavaScript(tagName) {
var newHtmlTag = document.createElement(tagName);
return newHtmlTag;
}
My question
Is there a better way to use jQuery without adding additional marks like ('<')?
Thanks advance.
You can't avoid < or > if you want to use jQuery, but you can trim it down to var newHtmlTag = $('<' + tagName + '>');
Moreover, according to What is the most efficient way to create HTML elements using jQuery? you'd be better off using the vanilla JS approach as far as performance goes.
I found a way that I don't need to add an extra mark (combine createHtmlTagByJavaScript and createHtmlTag).
function creatHtmlTag(tagName) {
var newHtmlTag = $(document.createElement(tagName));
return newHtmlTag;
}

trouble looping through div ids with newest jquery build

I upgraded my website to the latest jquery build (2.1.4), and I'm trying to debug the many errors that it is throwing.
However, I keep getting the error "unrecognized expression: [id=]" on the following script:
setTimeout(function() {
$(".cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user").each(function () {
var ids = $('[id=' + this.id + ']');
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
and I can't wrap my head around it.
Any help will be appreciated.
At first I was going to write a comment advising to put support requests directly on jQuery, but then I saw the code and thought it merits some discussion.
First of all, the id attribute is a special attribute in HTML. It is supposed to hold a unique value throughout the whole document (in other words, no two elements can have the same id), so I'm finding it strange that code would ever work.
Secondly, I don't see any reason why you would use jQuery to select an element by id when a simple document.getElementById() would have done the trick. Let's say you wanted to have a jQuery element. Fine, even in that case, your jQuery selector is far from perfect. A better alternative would be $('#' + this.id);. That said, the best alternative would be a simple $(this)... no need to worry about the id at all.
Perhaps I misunderstand the new jquery build, but normally you would declare your id within the jquery wrapper with
$('#myId')
Your code is assigning
var ids = $('[id=' + this.id + ']');
which translates to this
ids = $('[id=whateverThisIdIs]');
Can you try this instead?
var ids = $('#' + this.id); // assuming this.id does not contain '#'.
Final
setTimeout(function() {
$(".cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user").each(function () {
var ids = $('#' + this.id);
// or this if you have the '#'
// var ids = $(this.id);
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
One of the comments mentions
You script implies that there are multiple ids on the page: not good.
This is true if you are using the same id, which I do not think you are.
Somewhere in your HTML is an element with one of the classes .cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user, that either has no id attribute or has an empty one.
Change the line
var ids = $('[id=' + this.id + ']');
to
var ids = $('#' + this.id);
and the error will go away.
Or do a check for an empty id:
setTimeout(function() {
$(".cab_librovisitas, .cuerpo_librovisitas, .cuerpo_librovisitas_user").each(function () {
if (!this.id) return;
var ids = $('[id=' + this.id + ']');
if (ids.length > 1 && ids[0] == this) {
$(ids[1]).remove();
}
});
}
...I can't help but wonder what the purpose of the code snippet is though... trying to remove duplicated elements...? Why are they there in the first place?

Add Remove Class to a DOM element using only JavaScript, and best of these 2 ways

What is a good approach to add class to a DOM element using JavaScript. And Remove also.
I came across this following codes for adding:
1:
Element.prototype.addClassName = function (cls) {
if (!this.hasClassName(cls)) {
this.className = [this.className, cls].join(" ");
}
};
2:
document.querySelector(element).classList.add(cls)
Both of them seem to work for me. What is the difference between them and which is the best?
1. If you are moved by the word prototype, you might want to check MDN Docs - Inheritance and prototype chain.
2. The first code you mentioned is a normal cross-browser way of adding a class to an element.
Instead of being a function declaration, its added as a method to the Element's prototype - so that when you query an Element by its id (good ol' JavaScript), you can simply call the method on the element itself.
3. The second code you have posted is per the new DOM Specification. W3 Link.
It will work only in those browsers that implement the DOM Level 4 Specs. It won't work in old browsers.
That goes the difference.
For the best method, a native method is always the best and fastest.
So for the browsers that support clasList, the second should be the best. Per my opinion though, till things (drafts) are not finalized you might want to consider a method that works cross-browser and is compatible with both JavaScript (ECMA-3) and supported DOM Spec.
A simple idea should be to change the className, a property accessible in all new and old browsers, and append your class as a string to it:
var el = document.getElementById(id);
el.className = el.className + " " + cls;
// mind the space while concatening
Of course you can add validation methods like using regex for trimming spaces while adding and removing.
By the way, I got the full part of the code you posted as the 1st Example:
Element.prototype.hasClassName = function(name) {
return new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)").test(this.className);
};
Element.prototype.addClassName = function(name) {
if (!this.hasClassName(name)) {
this.className = this.className ? [this.className, name].join(' ') : name;
}
};
Element.prototype.removeClassName = function(name) {
if (this.hasClassName(name)) {
var c = this.className;
this.className = c.replace(new RegExp("(?:^|\\s+)" + name + "(?:\\s+|$)", "g"), "");
}
};
This is jquery's code.
Adapted versions of jQuery's addClass
And jQuery's removeClass
If you look at jQuery source code, this is how they do it. This is adapted from them, but pretty much entirely written by jQuery and is their code. I am not the original author, and am merely showing how one piece can be used instead of the whole library.
jQuery's addClass:
//This code is jQuery's
function AddClassToElement(elem,value){
var rspaces = /\s+/;
var classNames = (value || "").split( rspaces );
var className = " " + elem.className + " ",
setClass = elem.className;
for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
setClass += " " + classNames[c];
}
}
elem.className = setClass.replace(/^\s+|\s+$/g,'');//trim
}
jQuery's removeClass:
//This code is jQuery's
function RemoveClassFromElement(elem,value){
var rspaces = /\s+/;
var rclass = /[\n\t]/g
var classNames = (value || "").split( rspaces );
var className = (" " + elem.className + " ").replace(rclass, " ");
for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
className = className.replace(" " + classNames[c] + " ", " ");
}
elem.className = className.replace(/^\s+|\s+$/g,'');//trim
}
Here is a working fiddle: http://jsfiddle.net/C5dvL/

How can I make this code work better?

I recently asked a question about manipulating the html select box with jQuery and have gotten this really short and sweet code as an answer.
$("#myselect").change(function (event) {
var o = $("#myselect option:selected"),
v=o.text(),
old = $("#myselect option:contains('name')"),
oldv = old.html();
oldv && old.html( oldv.replace('name: ', '') );
o.text('name: ' + v);
});
I have one problem. This code doesn't work on multiple categories and I can't seem to wrap my mind around how it can be done. So, I made the obvious changes to it:
$("select").change(function (event) {
var foo = $("select option:selected"),
bar = foo.text(),
cat = foo.parent().attr("label"),
old = $("select option:contains('" + cat + "')"),
oldbar = old.html();
oldbar && old.html( oldbar.replace(cat + ': ', '') );
foo.text(cat + ': ' + bar);
});
This now works on multiple optgroups/categories but has led to another bug. A more problematic one, at that.
It happens when you click from one category to another. Check it out on jsFiddle.
The problem with the last snippet is it uses the name of the current category to locate the last selected label to flip back. Instead, how about searching for the ":", (this won't work if you have ":" in one of your options), and then replacing that part of the string.
change line 5 to:
, old = $("select option:contains(':')")
and line 8 to:
oldbar && old.html(oldbar.replace(oldbar.substr(0, oldbar.indexOf(':') + 2),''));
Let me know if that's not working for you!
Edit: as an afterthought, you might consider adding this line
$('select').change();
As well, somewhere in the $(document).ready() event, so that when the page first renders the default value gets the prefix like (I think) you want.
I've renamed the variables since it is really a good habit to get into naming your variables and functions with meaningful names so you can juggle in your memory what is going on.
$("select").change(function () {
// We immediately invoke this function we are in which itself returns a function;
// This lets us keep lastCat private (hidden) from the rest of our script,
// while still giving us access to it below (where we need it to remember the
// last category).
var lastCat;
return function (event) {
var selected = $("select option:selected"),
selectedText = selected.text(),
cat = selected.parent().attr("label");
if (lastCat) { // This won't exist the first time we run this
oldSelection = $("select option:contains('" + lastCat + ":')");
oldHTML = oldSelection.html();
oldHTML && oldSelection.html(oldHTML.replace(lastCat + ': ', ''));
}
selected.text(cat + ': ' + selectedText);
lastCat = cat; // Remember it for next time
};
}()); // Be sure to invoke our outer function (the "closure") immediately,
// so it will produce an event function to give to the change handler,
// which still has access to the variable lastCat inside
Here's a shot in the dark. At first blush, it appears you're not saving the previous selection and you're using the selection which triggered the event in your oldbar && old.html( oldbar.replace(cat + ': ', '') ); line. You need to save the category of the previous selection in a separate var.

How to optimize jquery grep method on 30k records array

Is it possible to optimize this code? I have very low performance on this keyup event.
$('#opis').keyup(function () {
if ($('#opis').val() != "") {
var search = $.grep(
svgs, function (value) {
reg = new RegExp('^' + $('#opis').val(), 'i');
return value.match(reg) == null;
}, true);
$('#file_list').html("");
var tohtml = "";
$cnt = 0;
for (var i = 0; i < search.length; i++) {
if ($cnt <= 30) {
tohtml += "<li class='file_item'><a href='' class='preview'>" + search[i] + "</a> <a href='" + search[i] + "' class='print_file'><img src='img/add16px.png' alt='dodaj'/></li></a>";
$cnt++;
} else {
break;
}
}
$('#file_list').html(tohtml);
$(".preview").click(function () {
$('#file_preview').html('<embed src="opisy/' + $(this).html() + '" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" /> ');
$(".preview").parent().removeClass("selected");
$(this).parent().addClass("selected");
return false;
});
$(".print_file").click(function () {
if (jQuery.inArray($(this).attr('href'), prints) == -1) {
$('#print_list').append('<li>' + $(this).attr('href') + '</li>');
prints.push($(this).attr('href'));
} else {
alert("Plik znajduje się już na liście do wydruku!");
}
return false;
});
} else {
$('#file_list').html(" ");
}
});
var opis = $('#opis')[0]; // this line can go outside of keyup
var search = [];
var re = new RegExp('^' + opis.value, 'i');
for (var i = 0, len = svgs.length; i < len; i++) {
if (re.test(svgs[i])) {
search.push(svgs[i]);
}
}
It's up to 100x faster in Google Chrome, 60x in IE 6.
first thing you have to learn:
$('#opis').keyup(function() {
$this = $(this);
if($this.val()!=""){
// so *$this* instead of *$('#opis')*
// because you are reperforming a *getElementById("opis")* and you've already called it when you used the keyup method.
// and use $this instead of $(this) | pretty much the same problem
so about the grep function, maybe if you cache the results it would help in further searchs I guess, but I don't know if can help you with that
Well the thing with javascript is that it executes under the users environment and not the servers environment so optimization always varies, with large large arrays that need extensive work done on them to I would prefer to handle this server side.
Have you thought about serializing the data and passing them over to your server side, which would handle all the data calculations / modifications and return the prepared result back as the response.
You may also want to take alook at SE:Code Review for more optimization advise.
Some optimization, tips:
if($('#opis').val()!=""){ should be using '!=='.
return value.match(reg)==null; should be ===.
for(var i=0;i<search.length;i++){
reg = new RegExp(...); should be var reg ... as its not defined outside the function as a global.
Move all your variable declarations to the top of the function such as
var i,cnt,search,tohtml etc
i would advise you to start using Google Chrome, it has a built in system for memeory tracking on perticular tabs, you can go to the url about:memory in chrome, which would produce a result like so:
Image taken from: http://malektips.com/google-chrome-memory-usage.html
Each time you perform the grep, you are calling the 'matching' function once per array entry.
The matching function creates a RegExp object and then uses it to perform the match.
There are two ways you could improve this:
Create the RegExp once, outside of the function, and then use a closure to capture it inside the function, so that you don't have to keep recreating the object over and over.
It looks like all you're trying to do is to perform a case-insensitive tests to see whether the sought string is the start of a member of your array. It may be faster to do this more explicitly, using .toUpper and substring. However, that's a guess and you should test to find out.

Categories

Resources