I have some problems with finding and replacing words in PHP files, especially when there is tons of them. So I thought that I will try to use javascript / jQuery.
I'd like to create table witch word_to_replace#new_word to do so.
This is my code which doesn't work (and runs very long), filter doesn't seem to work,
any advices?
(function($){
var arr = [ 'photo-board.pl przyjazny portal fotograficzny# ','Upload images from your#Upload zdjęć z ',
'Total number of images# Całkowita liczba zdjęć'];
for(var i in arr)
{
var st_to_replace = arr[i].split('#')[0];
// alert(st_to_replace);
$('*').filter(function() {
return $(this).text() == st_to_replace;
}).html(arr[i].split('#')[1]);
}
}) (jQuery)
You're getting the text() of every page element (which will include the text of child elements) and replacing within it. That means, when you get the 'body' element you replace all the text, and then you get all the elements within body, and replace all the text, etc.
Something like this may work better:
(function($){
var arr = [ 'photo-board.pl przyjazny portal fotograficzny# ','Upload images from your#Upload zdjęć z ',
'Total number of images# Całkowita liczba zdjęć'];
var bodyText = $('body').html();
$.each(arr, function(i, v) {
var words = v.split('#');
var fromTxt = words[0], toTxt = words[1];
bodyText = bodyText.replace(fromTxt, toTxt);
});
$('body').html(bodyText);
})(jQuery);
Demo here.
It's worth noting though that since this destroys and recreates the entire body content, you'll loose event handlers and data set using .data(...).
One of the performance issues of your script is the immediate call to .html every iteration of the filter. This is causing the browser to repaint the element every iteration.
You might consider editing the html detached from the dom and then, after the for and the filter loops, replacing the html in the dom.
Related
A common problem I have is the need to create multiple DOM nodes in a loop, and then activate those nodes in some way, either by applying a plugin, an event handler or similar. The activation step requires that the element actually exist first.
So you end up doing something like:
// Loop 1: Create the nodes
var HTML = '<tr id="UID">';
for(var k in Fields){ // Fields is an object!
HTML += '<td>';
HTML += '<input class="ActivateMe"/>';
HTML += '</td>';
}
var HTML += '</tr>';
$TableBody.children('tr').first().before(HTML);
// Loop 2: Activate the new nodes
$('#'+UID).children('td').children('.ActivateMe').each(function(index){
$(this).InitSomePlugin();
});
The code above is simplified for the question, but assume that each element inside a given cell can be different (maybe an input, may be a div), and might also require a different plugin (Maybe it's a color picker, maybe it's a combo box).
Is it possible to avoid looping over the data set twice and doing the insert and activate in one go? I think it may be possible by appending the nodes within the first loop, which would also allow activation in the first loop. But it is generally considered bad practice to use append in a loop rather than store all your HTML it in a var and append all the HTML at once. At the same time, looping over the same set of data two times seems inefficient too. What is the best way to handle this scenario with minimal performance impact?
Yes. Don't build a lengthy HTML string, but create the elements programmatically in the first loop so that you can direclty instantiate your plugin on them:
var $TableBody = …,
var $row = $('<tr>', {id:UID});
for(var k in Fields) { // sure that Fields is an object?
// For an array, use a normal for loop
var $cell = $('<td>');
var $input = $('<input class="ActivateMe"/>');
$input.InitSomePlugin();
$input.appendTo($cell);
$cell.appendTo($row);
}
$row.prependTo($TableBody);
You might need to do the appends before calling .InitSomePlugin(). You also might want to nest the calls and use chaining for shortening the code:
var $row = $('<tr>', {id:UID}).prependTo(…);
for(var k in Fields)
$('<input class="ActivateMe"/>')
.appendTo($('<td>').appendTo($row))
.InitSomePlugin();
$tableBody = $('table');
// Prepare an DOM object but don't append it to DOM yet.
$tr = $('<tr></tr>',{
id: "UID"
});
// Loop over any condition you would see fit.
for(var i = 0; i < 2; ++i) {
$td = $('<td></td>',{
// Prepare your input element with your event bound to it
"html": $('<input/>', {"class": "ActivateMe"}).InitSomePlugin();
});
// append your td element
$tr.append($td);
}
// Add your tr element to the beginning of your table with prepand method.
$tableBody.prepend($tr);
Since the activation step requires that the element already exists and (probably) is in the DOM, then you only have two choices here:
1) You can create each DOM element individually (with things like document.creatElement() or jQuery's $(html)) such that you have saved DOM object references that you can later use for intializing the plugin.
or
2) You can build up a string of HTML as you are doing. Insert that string, letting the browser create all the elements for you and then you will have to find the appropriate DOM elements in order to initialize the plugins.
Tests have shown that it is often the case that browsers will create lots of HTML objects faster when given a string of HTML rather than manually creating and inserting individual DOM objects so there is no particular issue with using the string of HTML.
There is no 100% right or wrong answer here. Performance is probably not the primary issue unless you have hundreds to thousands of these DOM elements. I tend to go with whichever path leads to the cleanest and simplest code.
In your case, you have to iterate over the Fields object so you can't avoid that. You have to find the first row of your table so you can't avoid that.
If you've decided that building the string of HTML is the most expedient approach to writing the code (which it probably is here), then you can't avoid refinding the objects you need to activate in the DOM.
You can be as efficient about things as possible.
Here's a little bit of streamlining that stays with the basic philosophy:
// create the new rows
var HTML = '<tr id="UID">';
for(var k in Fields) {
HTML += '<td>' + '<input class="ActivateMe"/>'+ '</td>';
}
HTML += '</tr>';
// create an insert new content, save reference to new content
var newObj = $(HTML);
$TableBody.prepend(newObj);
// now activate the plugin on the appropriate objects in the new content
newObj.find(".ActivateMe").InitSomePlugin();
Streamlining steps:
Create each cell in one statement rather than three
When you create the new row object, keep a reference to it so we don't have to find it again.
When you add the new content, use .prepend() to make it the first row rather than finding all the rows and selecting the first one
With you initalize the plugin, there's no need for a .each() loop if you're running the same jQuery method on every object. You can so it like this: newObj.find(".ActivateMe").InitSomePlugin(); without .each().
You could also create the DOM objects yourself and not use the HTML string and keep track of the objects that need to be activate as you go so they don't have to be found again:
// create the new rows
var row = $('<tr id="UID"></tr>'), input, item, activates = [];
for(var k in Fields) {
item = $('<td>');
input = $('<input class="ActivateMe"/>')
activates.push(input);
item.append(input);
row.append(item);
}
// insert row into table
$TableBody.prepend(row);
// now activate the plugin on the appropriate objects that are now inserted
$(activates).InitSomePlugin();
Purists might "like" the second option better than the first option because it's not using an HTML string, but unless you're doing this hundreds to thousands of times such that performance is paramount (in which case you'd have to test which method actually performs better and diagnose why), I can't honestly say that the second is better than the first. I like the coding simplicity of the first and finding a few class objects in a specific table just isn't an expensive operation.
So I've read that jQuery uses document fragments internally to make rendering faster. But I am wondering if anyone knows if jQuery would use createDocumentFragment in this situation where I'm appending img elements to the DOM using the each loop?
var displayArray = []; // Lots of img elements
$.each(displayArray, function()
{
$('#imgSection').append(this);
});
Or would I need to use this code in order to reduce the number of browser reflows?
var displayArray = []; // Lots of img elements
var imgHolder = $('<div/>');
$.each(displayArray, function()
{
imgHolder.append(this);
});
$('#imgSection').append(imgHolder);
Also, the displayArray is populated by other code, not shown here, that creates img elements based off of paths in a JSON file.
Thank you for any advice.
Why all the looping to add elements?
$('#imgSection').append("<div>" + displayArray .join("") + "</div>");
Okay so it is elements.
The quickest way is going to be using append with the array itself.
$("#out").append(elems);
other option using one div to append is
var div = $("<div/>").append(elems);
$("#out").append(div);
BUT appending a lot of images at once is going to be bad unless they are preloaded. That will be a bunch of http requests being queued up.
jsPerf test cases
No, if you use $.each() then jQuery won't use a DocumentFragment - jQuery has no way of knowing what you're going to do inside the loop and each iteration is independent.
The point of the document fragment is that you don't have to wrap all your new elements up in a wrapper element as you've done in your second example to limit the reflows.
jQuery apparently will use a document fragment if you pass an array of elements directly to .append() instead of iterating over them yourself.
If you really care about reflows (and have noticed the displaying to be slow), you can hide and show the image-holding element:
var displayArray = […]; // Lots of img elements
var holder = $('#imgSection').hide();
for (var i=0; i<displayArray.length; i++)
holder.append(displayArray[i]);
holder.show();
I'm working on a bookmarklet for a Sub reddit and I'm trying to grab all the usernames on a comments page so I can parse them, then come back and update info next to them, similar to what RES does. The author of each comment has a class that is prefixed with Author but then has different stuff at the end of the class name. How would I go about grabbing all the usernames?
Then once I have the list, how would I update each one with an additional icon essentially?
Any suggestions/Tutorials that do similar things would be great.
Edit: I'm not really sure what portions of the markup would be helpful without giving a huge block. Here's the same question I asked in the Javascript Subreddit. http://www.reddit.com/r/javascript/comments/yhp7j/best_way_to_find_all_the_usernames_on_a_reddit/
You should be able to Inspect the name elements and See what I'm working with.
Currently working with this: http://net.tutsplus.com/tutorials/javascript-ajax/create-bookmarklets-the-right-way/
So I've got a Hello World Style Bookmarklet working that checks for Jquery and loads it if it's not present and just throws an alert.
From a quick look at the page you linked to in your question, it seems as if the mark-up surrounding user-names is as follows (using, presumably, your user-name as an example):
<a href="http://www.reddit.com/user/DiscontentDisciple" class="author id-t2_4allq" >DiscontentDisciple</a>
If that's the case, and the jQuery library is available (again, from your question), one approach is to simply use:
var authors = [];
$('a.author').html(
function(i, h) {
var authorName = $(this).text();
if ($.inArray(authorName, authors) == -1) {
authors.push(authorName); // an array of author-names
}
return '<img src="path/to/' + encodeURIComponent(authorName) + '-image.png" / >' + h;
});
console.log(authors);
JS Fiddle proof-of-concept.
Or, similarly just use the fact that the user-name seems to be predictably the last portion of the URL in the a element's href attribute:
var authors = [];
$('a.author').html(
function(i, h) {
var authorName = this.href.split('/').pop();
if ($.inArray(authorName, authors) == -1) {
authors.push(authorName);
}
return '<img src="http://www.example.com/path/to/' + authorName+ '-image.png" />' + h;
});
console.log(authors);
JS Fiddle proof-of-concept.
Both of these approaches put the img within the a element. If you want it before the a element, then simply use:
// creates an 'authors' variable, and sets it to be an array.
var authors = [];
$('a.author').each( // iterates through each element returned by the selector
function() {
var that = this, // caches the this variable, rather than re-examining the DOM.
// takes the href of the current element, splits it on the '/' characters,
// and returns the *last* of the elements from the array formed by split()
authorName = that.href.split('/').pop();
// looks to see if the current authorName is in the authors array, if it *isn't*
// the $.inArray returns -1 (like indexOf())
if ($.inArray(authorName, authors) == -1) {
// if authorName not already in the array it's added to the array using
// push()
authors.push(authorName);
}
// creates an image element, concatenates the authorName variable into the
// src attribute-value
$('<img src="http://www.example.com/path/to/' + authorName+ '-image.png" />')
// inserts the image before the current (though converted to a jQuery
// object in order to use insertBefore()
.insertBefore($(that));
});
console.log(authors);
JS Fiddle proof-of-concept.
References:
each().
$.inArray().
insertBefore().
I currently have a sketch for a truthtable generator. While it works fine, it is rather slow. Each combination of boolean values I have added to a <table> using jQuery. For each value, a <td> element is created by jQuery and then added to the <table>. Moreover, I'm using jQuery UI for a nice looking buttonset instead of radio buttons.
In my posted code extract, table is an array containing each boolean combination. Perhaps my code is a little inscrutable but what it basically comes down to is that with 4 boolean variables (16 possibilities), 96 <td> elements are created with classes added and data attributes set. In the second part, three groups of three radio buttons are created and converted into a jQuery UI buttonset.
Using a timer I figured out that it takes approximately 0.4 seconds before everything is filled up. Not that big of a deal, but it is certainly noticeable and does not have a positive effect on the user as each time he enters a different boolean formula it takes half a second to load.
$table = $('#table');
$.each(table, function(k, v) {
$tr = $('<tr>').addClass('res').data('number', k);
$.each(v[0], function(k2, v2) {
$td = $('<td>').text(v2).addClass(v2 ? 'green notresult' : 'red notresult');
for(var i = 0; i < 4; i++) {
$td.data(i, i === k2);
}
$tr.append($td);
});
$tr.append($('<td>').addClass('spacing'));
$table.append(
$tr.append(
$('<td>').text(v[1]).addClass(v[1] ? 'green result' : 'red result')
)
);
});
// ... here is some code that's not slowing down
$radiobuttonsdiv = $('#radiobuttonsdiv');
for(var i = 0; i < 4; i++) {
var $radiobase = $('<input>').attr('type', 'radio')
.attr('name', 'a'+i)
.click(handleChange);
// .label() is a custom function of mine which adds a label to a radio button
var $radioboth = $radiobase.clone().val('both').label();
var $radiotrue = $radiobase.clone().val('true').label();
var $radiofalse = $radiobase.clone().val('false').label();
var $td1 = $('<td>').addClass('varname').html(i);
var $td2 = $('<td>').attr('id', i);
$td2.append($radioboth, $radiotrue, $radiofalse).buttonset();
var $tr = $('<tr>').append($td1, $td2);
$radiobuttonsdiv.append($tr);
}
My questions are:
How could table-filling using jQuery be optimized? Or is a table perhaps not the best solution in this scenario?
Is it perhaps possible to suspend drawing, since that might be slowing everything down?
Try to avoid using .append in a loop, especially if you're adding a lot of elements. This is always a performance killer.
A better option is to build up a string with the markup and do a single (or as few as possible) .append when your loop is finished.
I see that you're using .data, which makes things a bit more complicated, but another option is to use the metadata plugin to attach structured markup to existing tags.
To defer rendering, you could try creating a new table without adding it to the DOM like:
var myDisconnectedTable = $('<table></table>')
Then adding your rows to that table:
myDisconnectedTable.append(...)
Then append your table to the div you want it in:
$('#idOfMyDiv').append(myDisconnectedTable)
I don't know that it will help, but it's worth a shot.
In practice, what are the advantages of using createElement over innerHTML? I am asking because I'm convinced that using innerHTML is more efficient in terms of performance and code readability/maintainability but my teammates have settled on using createElement as the coding approach. I just wanna understand how createElement can be more efficient.
There are several advantages to using createElement instead of modifying innerHTML (as opposed to just throwing away what's already there and replacing it) besides safety, like Pekka already mentioned:
Preserves existing references to DOM elements when appending elements
When you append to (or otherwise modify) innerHTML, all the DOM nodes inside that element have to be re-parsed and recreated. If you saved any references to nodes, they will be essentially useless, because they aren't the ones that show up anymore.
Preserves event handlers attached to any DOM elements
This is really just a special case (although common) of the last one. Setting innerHTML will not automatically reattach event handlers to the new elements it creates, so you would have to keep track of them yourself and add them manually. Event delegation can eliminate this problem in some cases.
Could be simpler/faster in some cases
If you are doing lots of additions, you definitely don't want to keep resetting innerHTML because, although faster for simple changes, repeatedly re-parsing and creating elements would be slower. The way to get around that is to build up the HTML in a string and set innerHTML once when you are done. Depending on the situation, the string manipulation could be slower than just creating elements and appending them.
Additionally, the string manipulation code may be more complicated (especially if you want it to be safe).
Here's a function I use sometimes that make it more convenient to use createElement.
function isArray(a) {
return Object.prototype.toString.call(a) === "[object Array]";
}
function make(desc) {
if (!isArray(desc)) {
return make.call(this, Array.prototype.slice.call(arguments));
}
var name = desc[0];
var attributes = desc[1];
var el = document.createElement(name);
var start = 1;
if (typeof attributes === "object" && attributes !== null && !isArray(attributes)) {
for (var attr in attributes) {
el[attr] = attributes[attr];
}
start = 2;
}
for (var i = start; i < desc.length; i++) {
if (isArray(desc[i])) {
el.appendChild(make(desc[i]));
}
else {
el.appendChild(document.createTextNode(desc[i]));
}
}
return el;
}
If you call it like this:
make(["p", "Here is a ", ["a", { href:"http://www.google.com/" }, "link"], "."]);
you get the equivalent of this HTML:
<p>Here is a link.</p>
User bobince puts a number of cons very, very well in his critique of jQuery.
... Plus, you can make a div by saying $(''+message+'') instead of having to muck around with document.createElement('div') and text nodes. Hooray! Only... hang on. You've not escaped that HTML, and have probably just created a cross-site-scripting security hole, only on the client side this time. And after you'd spent so long cleaning up your PHP to use htmlspecialchars on the server-side, too. What a shame. Ah well, no-one really cares about correctness or security, do they?
jQuery's not wholly to blame for this. After all, the innerHTML property has been about for years, and already proved more popular than DOM. But the library certainly does encourage that style of coding.
As for performance: InnerHTML is most definitely going to be slower, because it needs to be parsed and internally converted into DOM elements (maybe using the createElement method).
InnerHTML is faster in all browsers according to the quirksmode benchmark provided by #Pointy.
As for readability and ease of use, you will find me choosing innerHTML over createElement any day of the week in most projects. But as you can see, there are many points speaking for createElement.
While innerHTML may be faster, I don't agree that it is better in terms of readability or maintenance. It may be shorter to put everything in one string, but shorter code is not always necessarily more maintainable.
String concatenation just does not scale when dynamic DOM elements need to be created as the plus' and quote openings and closings becomes difficult to track. Consider these examples:
The resulting element is a div with two inner spans whose content is dynamic. One of the class names (warrior) inside the first span is also dynamic.
<div>
<span class="person warrior">John Doe</span>
<span class="time">30th May, 2010</span>
</div>
Assume the following variables are already defined:
var personClass = 'warrior';
var personName = 'John Doe';
var date = '30th May, 2010';
Using just innerHTML and mashing everything into a single string, we get:
someElement.innerHTML = "<div><span class='person " + personClass + "'>" + personName + "</span><span class='time'>" + date + "</span></div>";
The above mess can be cleaned up with using string replacements to avoid opening and closing strings every time. Even for simple text replacements, I prefer using replace instead of string concatenation.
This is a simple function that takes an object of keys and replacement values and replaces them in the string. It assumes the keys are prefixed with $ to denote they are a special value. It does not do any escaping or handle edge cases where $ appears in the replacement value etc.
function replaceAll(string, map) {
for(key in map) {
string = string.replace("$" + key, map[key]);
}
return string;
}
var string = '<div><span class="person $type">$name</span><span class="time">$date</span></div>';
var html = replaceAll(string, {
type: personClass,
name: personName,
date: date
});
someElement.innerHTML = html;
This can be improved by separating the attributes, text, etc. while constructing the object to get more programmatic control over the element construction. For example, with MooTools we can pass object properties as a map. This is certainly more maintainable, and I would argue more readable as well. jQuery 1.4 uses a similar syntax to pass a map for initializing DOM objects.
var div = new Element('div');
var person = new Element('span', {
'class': 'person ' + personClass,
'text': personName
});
var when = new Element('span', {
'class': 'time',
'text': date
});
div.adopt([person, when]);
I wouldn't call the pure DOM approach below to be any more readable than the ones above, but it's certainly more maintainable because we don't have to keep track of opening/closing quotes and numerous plus signs.
var div = document.createElement('div');
var person = document.createElement('span');
person.className = 'person ' + personClass;
person.appendChild(document.createTextNode(personName));
var when = document.createElement('span');
when.className = 'date';
when.appendChild(document.createTextNode(date));
div.appendChild(person);
div.appendChild(when);
The most readable version would most likely result from using some sort of JavaScript templating.
<div id="personTemplate">
<span class="person <%= type %>"><%= name %></span>
<span class="time"><%= date %></span>
</div>
var div = $("#personTemplate").create({
name: personName,
type: personClass,
date: date
});
You should use createElement if you want to keep references in your code. InnerHTML can sometimes create a bug that is hard to spot.
HTML code:
<p id="parent">sample <span id='test'>text</span> about anything</p>
JS code:
var test = document.getElementById("test");
test.style.color = "red"; //1 - it works
document.getElementById("parent").innerHTML += "whatever";
test.style.color = "green"; //2 - oooops
1) you can change the color
2) you can't change color or whatever else anymore, because in the line above you added something to innerHTML and everything is re-created and you have access to something that doesn't exist anymore. In order to change it you have to again getElementById.
You need to remember that it also affects any events. You need to re-apply events.
InnerHTML is great, because it is faster and most time easier to read but you have to be careful and use it with caution. If you know what you are doing you will be OK.
Template literals (Template strings) is another option.
const container = document.getElementById("container");
const item_value = "some Value";
const item = `<div>${item_value}</div>`
container.innerHTML = item;