jQuery/Javascript: More complex Searching and Replacing in an HTML document - javascript

I have done some stuff in jQuery and Javascript before, but unfortunately I am no expert. I couldn't find any hints on how to accomplish my task with using as few resources as possible. You guys can probably help me out:
Here is what I want to do:
I want to find (using regex) all BB-code-like elements on a page that look something like this:
[ndex here=parameter randomdata]
I want to then replace each of them with the contents I receive from an ajax-call that looks like this:
call.php?ndex=here=parameter randomdata
or whatever parameters I pick up from within the according [ndex]-tag.
Here is my solution/thought-process so far:
$(document).ready(function() {
var pattern = /\[ndex\s+(.*?)\]/mg;
var documentText = $(document.body).text();
var matches = documentText.match(pattern);
$('*').each(function () {
var searchText = this;
if ($(searchText).children().length == 0) {
$.each(matches, function() {
//here is where I would need to check for a match and make a call
}
});
}
});
});
I don't really know how to work from here. My sketch seems really clunky and complicated. There must be a more elegant and straight-forward solution.
Thank you guys so much for your help. :)

i would do something like this :
function ndex_treat(n) {
// If element is ELEMENT_NODE
if(n.nodeType==1)
{
// If element node has child, we pass them to function ndex_treat
if(n.hasChildNodes())
for(var i= 0; i<n.childNodes.length; i++)
ndex_treat(n.childNodes[i]);
}
// If element is TEXT_NODE we replace [ndex ...]
else if(n.nodeType==3)
{
var matches, elemNdex, elemText;
// While there is one
while(/\[ndex\s+(.*?)\]/m.test(n.nodeValue))
{
// Taking what's before (matches[1]), the "attribute" (matches[2]) and what's after (matches[3])
matches= n.nodeValue.match(/^([\s\S]*?)\[ndex\s+(.*?)\]([\s\S]*)$/m)
// Creating a node <span class="ndex-to-replace" title="..."></span> and inserting it before current text node element
elemNdex= document.createElement("span");
elemNdex.className= 'ndex-to-replace';
elemNdex.title= matches[2];
n.parentNode.insertBefore(elemNdex, n);
// If there was text before [ndex ...] we add it as a node before
if(matches[1]!=="")
{
elemText = document.createTextNode(matches[1]);
elemNdex.parentNode.insertBefore(elemText, elemNdex);
}
// We replace content of current node with what was after [ndex ...]
n.nodeValue=matches[3];
}
}
}
$(function(){
// Get the elements we want to scan ( being sharper would be better )
$('body').each(function(){
// Passing them to function ndex_treat
ndex_treat(this);
});
// Make the ajax calls
$('.ndex-to-replace').each(function(){
// Don't know if necessary
var current= this;
$.get('call.php?ndex='+encodeURIComponent(this.title),function(data){
$(current).replaceWith(data);
});
});
});
i replaced by node rather than by jquery because i find it rather bad to work on textNode with jquery. if you don't care and would rather do the barbarian way, you could replace all the first part with simply :
$(function(){
// Get the elements we want to scan ( being sharper would be better )
$('body').each(function(){
// With no " in argument of [ndex ...]
$(this).html( $(this).html().replace(/\[ndex\s+([^"]*?)\]/mg,'<span class="ndex-to-replace" title="$1"></span>') );
// With no ' in argument of [ndex ...]
//$(this).html( $(this).html().replace(/\[ndex\s+([^']*?)\]/mg,'<span class="ndex-to-replace" title='$1'></span>') );
});
// Make the ajax calls
/* ... */
});

My advice is to keep the ajax calls to a minimum. Do the search in the first place, an on another round replace every object with the corresponding data.
$(document).ready(function() {
var pattern = /\[ndex\s+(.*?)\]/mg;
var documentText = $(document.body).text();
var matches = documentText.match(pattern);
$.ajax({
url:'call.php',
method:'POST',
data: matches,
success: function(data){
//replace every matched element with the corresponding data
});
});
You'll have to modify your call.php to take this into account though, but you are saving lots of calls to the server and thus time

Related

How to check if html if any html element contains text from some Object

I have some object with label and value. Those elements holds some data.
For now i want to loop on html elements and check, what element match this elements. Object is called ui.
Right now i want to select all elements that contains text from any of object elements.
Those elements have class .img.
Below simple javascript:
$("#project").autocomplete({
source: Items,
appendTo: ".autocomplete",
minLength: 2,
response: function (event, ui) {
if (ui.content.length == 0) {
return false;
}
var content = ui.content[0].label;
$.each($('.img'), function () {
if ($(this).is(':contains(' + content + ')')) {
return;
} else {
$(this).fadeOut(100);
}
});
}
});
This code acts as autocomplete, and i want to hide all emelents that don't match data from ui object.
This code works for me almost fine, but there is one thing that break things:
var content = ui.content[0].label;
This selects only first item from object, and i'm looking for a way, how to check all items from obejct to check if text contains data, not only from first obejct element [0].
Thanks for advice.
I create a jsfiddle for you, If I understand the problem
var matchLabelString=_.reduce(ui,function(accumulateString,current){
if(accumulateString == ""){
return current.label;
}
return accumulateString + "|"+current.label;
},"")
var regex = new RegExp("^"+matchLabelString);
$.each($('.img'), function () {
if (regex.test($(this).text())) {
return;
} else {
$(this).fadeOut(100);
}
});
https://jsfiddle.net/3o1oy5y2/
EDIT: I see your code, and I think that my code work anyway. Try it, as you can see I have used underscore library to create a string seperatad by | from array
You can add a loop in your $each callback :
$.each($('.img'), function() {
for (var i = 0; i < ui.content.length; i++) {
var content = ui.content[i].label;
...
}
});
EDIT: Change let to var

My $.each loop is quite slow. Any methods of making it faster?

I have a chrome extension that needs to look through every <p></p> on a webpage. My chrome extension looks at the p text and checks if the text is located in an array. The problem is, the array has over 3,000 elements and I'd quite like to get that up to 12,000 or more if possible.
At the current rate, that just isn't feasible because it takes the webpage about 4 extra seconds to load the page. My chrome extension runs at the end of the document so the user can technically browse the site, it just takes 4 seconds for everything to load.
Here is my code:
$.each(arrayObj, function(key, value) {
$("p").highlight(key, {caseSensitive: true, className: 'highlight-882312', wordsOnly:true });
});
$('.highlight-882312').each(function() {
var currentKey = $(this).text();
console.log(currentKey);
//do something else here that doesn't really
//apply to the question as it only runs when the user hovers over the class.
});
and then the array looks pretty simple like this:
var arrayObj = {
"keyword1": 'keyword_1_site',
"keyword2": 'keyword_2_site',
... etc
... 3,000+ more lines ...
...
}
I'm assuming $.each isn't the most efficient, and as I said, 4 seconds to load is quite a bit. Is there anything I could do to make this more efficient? Will I ever be able to hit 12,000 lines in the array?
Thanks :)
You are running global selectors for each element in array. That's quite a lot.
So at least I would suggest to replace this:
$.each(arrayObj, function(key, value) {
$("p").highlight(key, ...);
});
by this:
var $all_p = $("p");
$.each(arrayObj, function(key, value) {
$all_p.highlight(key, ...);
});
Here's a couple of ideas :
$("p") and the .highlight() options are static, so define once, use many.
apply .highlight() only to those words that actually exist on the page.
var options = {
caseSensitive: true,
className: 'highlight-882312',
wordsOnly:true
},
arrayObj_ = {},
$p = $("p");
//Populate arrayObj_ with true for every word that exists on the page
$p.each(function() {
$(this).text().split(' ').forEach(function(word) {
arrayObj_[word] = true;
});
});
//Now loop through arrayObj_ ,
// which is hopefully much smaller than arrayObj,
// and highlight those words that are represted as keys in arrayObj.
$.each(arrayObj_, function(key, value) {
if(arrayObj[key]) {
$p.highlight(key, options);
}
});
No idea whether this will be faster or slower. You will need to run tests.
Edit ...
Even better, discover words and highlight in a single pass.
var options = {
caseSensitive: true,
className: 'highlight-882312',
wordsOnly:true
},
wordsFound = {},
$p = $("p");
$p.text().split(/\b/).forEach(function(word) {
if(!wordsFound[word]) {
wordsFound[word] = true;
if(arrayObj[word]) {
$p.highlight(word, options);
}
}
});
Try this:
var p = $("p");
$.each(arrayObj, function(key, value) {
p.highlight(key, {caseSensitive: true, className: 'highlight-882312', wordsOnly:true });
});
$('.highlight-882312').each(function(idx, element) {
var currentKey = element.text();
console.log(currentKey);
// other stuff
});
Generally speaking, you want to avoid $() selectors inside loops as they're time consuming. Notice that each()'s callback receives the element that's being iterated and you don't need to select it again.
This solution might not completely solve your problem but at least is the most efficient way to do it that I can think of without knowing more details.
Refactor them to javascript for loop instead of jQuery's each loops. https://stackoverflow.com/a/14808512/1547497
i.e.
var elems = $('.highlight-882312');
for (var i = 0; i < elems.length; ++i){
}

Extracting text from a class name

I'm attempting to write a function which will scan a range of elements which contains "*-chars" as a class. Once I've found the elements I want to take that particular class (eg max-chars) and extract the part of the class before the -. I can't use a simple split as elements could contain other classes which contain a - or even -chars.
So far I've managed the following:
var getLimitType = function (el) {
var output = el.find('*[class*="-chars"]').filter(function () {
return this.className.match(/(?:^|\s)-chars/);
});
var limitType = output.val().split("-").shift();
var getVal = el.find('input').attr('maxlength');
return limitType+'_'+getVal;
};
Obviously this doesn't work as limitType is trying to perform a split on a jQuery object instead of a string, however I can't figure out what to put in the blank line, I need something which will take all the classes from that object, work out which one I am looking for and returning only that one as a text string.
Never quite found a desirable answer, however I managed to solve my issue with this work-around:
var getLimitType = function (element) {
var ml = element.find('input').attr('maxlength');
if (element.hasClass('exact-chars')) {
return 'exact_'+ml;
} else if (element.hasClass('min-chars')) {
return 'min_'+ml;
} else if (element.hasClass('max-chars')) {
return 'max_'+ml;
} else {
return 0;
}
};

Looping through jQuery objects in a collection without initializing a new jQuery object for each iteration

I find myself doing this all the time:
$myElements.each( function(index, currentHtmltmlElement) {
var $currentJqueryElement = $(currentHtmltmlElement);
// Working with $currentJqueryElement
});
Initializing a new jQuery object in each iteration is a huge performance penalty.
So i thought of doing this instead (credit also goes to decx#freenode):
for (var index = 0; index < $myElements.length; index++) {
var $currentJqueryElement = $myElements.eq(i);
// Working with $currentJqueryElement
}
But i fixed up a JSPerf test and this snippet's performance turned out to be identical to that of the first snippet! :(
Both are freaking slow! For really large collections, you could even notice the page freeze.
So i wonder what is the fast way of iterating through jQuery objects in a collection.
The way should also be as convenient to use as possible. This:
$items.each$item( function( $item, index ) { /* Working with $item directly. */ });
would be way better than the ancient for (var i=0... ineptitude.
UPD 2014-05-27
Here's an example. Imagine that you have a number of jQuery UI Sliders:
<div class="my-slider"></div>
<div class="my-slider"></div>
<div class="my-slider"></div>
$sliders = $('.my-slider').slider();
Now you would like to log to console the value of each slider. You have to do:
$sliders.each( function(index, htmlSlider) {
$current_slider = $(htmlSlider); // This step is a huge performance penalty
console.log(
$current_slider.slider('value')
);
});
So the task is to get rid of the performance penalty.
You can't simply do $sliders.slider('value') because this will output the value only of the first slider in the collection.
You can't revert to vanilla JS inside the loop because you can't access a jQuery widget without a jQuery object.
So far, all these approaches...
$sliders.each( function(index, htmlSlider) { htmlSlider });
$sliders.each( function() { this });
for (var i = 0; i < $sliders.length; i++) { $sliders.eq(i); }
$.each, $.makeArray, $.map, etc
...work with $sliders's underlying array of HTML elements and require a time-costy $( ) initialization to access jQuery features.
Try
$.each($p, function(i, v) {
test = $(v)
})
for + eq() : 702
each() : 722
this : 718
$.each() : 757 ("fastest")
http://jsperf.com/jquery-for-eq-vs-each/5
Edit
You can't split a jQuery collection into parts directly, you can only create new ones out of separate HTML elements. -lolmaus - Andrey
Mikhaylov
Try (at console, this page)
$("div"); // jQuery `collection` ? approx. `222` members, changes
// `split` 1st `101` members of jquery `obj`, or `collection`
var split_jq_obj_1 = $("div").slice(0, 111);
// `split` 2nd `101` members of jquery `obj`, or `collection`
var split_jq_obj_2 = $("div").slice(-111);
// Note `prevObject` and `selector` properties of `jQuery` `object`
// `split_jq_obj_1` and `split_jq_obj_2`
// `prevObject: e.fn.e.init[222]` , `selector: "div.slice(0,111)"`,
// `prevObject: e.fn.e.init[222]`, `selector: "div.slice(-111)"`)
// check results
console.log($("div"), split_jq_obj_1, split_jq_obj_2);
console.log($("div").length, split_jq_obj_1.length, split_jq_obj_2.length);
other possible approaches
// `jquery` `object`, or `objects`
var arr = $.makeArray($.map($("div"), function(value, index) {
return ( index < 111 ? [$(value)] : null )
}), $.map($("div"), function(value, index) {
return ( index >= 111 ? [$(value)] : null )
}));
console.log(arr); // `collection` of `jQuery` `objects`
// iterate `jquery` object`, or `objects`,
// find `length` of `class` `item-content`
$.each($(arr), function(index, value) {
console.log($(value).find(".item-content").length) // `29` (see `iteration` at `console`)
});
// check results
$(".item-content").length; // `29`
Edit 2014-05-28
Try this (pattern)
$(function () {
var sliders = $('.sliiider').slider({
min: 0,
max: 100
});
$('html').click(function () {
$.when(sliders)
.then(function (data) {
for (var i = 0; i < data.length; i++) {
// `$()` jQuery `wrapper` _not_ utilized,
// not certain about quantifying `cost`, if any,
// of utilizing jquery's `deferred` `object`s
// test by sliding several `sliders`,
// then `click` `document`, or `html`
console.log(data.eq(i).slider("value"))
};
})
});
});
jsfiddle http://jsfiddle.net/guest271314/8gVee/
fwiw, this pattern also appears to work, again without utilizing jquery's init wrapper $()
$(function () {
var sliders = $('.sliiider').slider({
min: 0,
max: 100
});
$('html').click(function () {
$.when(sliders)
.then(function (data) {
// Note, 2nd parameter to `.each()` not utilized, here
data.each(function (i) {
// `$()` jQuery `init` `wrapper` _not_ utilized,
// within `.each()` `loop`,
// same result as utilizing `for` loop,
// "performance", or "fastest" not tested as yet
console.log(data.eq(i).slider("value"));
})
});
});
})
jsfiddle http://jsfiddle.net/guest271314/G944X/
Just use this keyword? Seems to be fastest, not to mention (objectively) easiest to understand.
$myElements.each( function() {
var $currentJqueryElement = $(this);
});
http://jsperf.com/jquery-for-eq-vs-each/3
If performance is a problem, why not skip out on jQuery and use pure JS?
Answer to update
If you want to get the slider values (which seem to be just the amount of css style "left" , you could do something such as:
var elems = document.getElementsByClassName('my-slider');
for (var i = 0; i < elems.length; i++){
console.log(elems[i].style.left); // logs : xx.xx%
}
Would it be faster? Maybe. Will the approach fit in all use cases? Most likely not, but for that specific question it should work.
I'll be happy if you prove me wrong, but i came to a conclusion that a jQuery collection is a uniform object that cannot be iterated through and only the HTML elements it represents are stored as separate entities that can be enumerated. You can't split a jQuery collection into parts directly, you can only create new ones out of separate HTML elements.
Thus, revert to native JS features inside the loop whenever possible to improve performance.

Javascript Shorthand for getElementById

Is there any shorthand for the JavaScript document.getElementById? Or is there any way I can define one? It gets repetitive retyping that over and over.
var $ = function( id ) { return document.getElementById( id ); };
$( 'someID' )
Here I used $, but you can use any valid variable name.
var byId = function( id ) { return document.getElementById( id ); };
byId( 'someID' )
To save an extra character you could pollute the String prototype like this:
pollutePrototype(String, '绎', {
configurable: false, // others must fail
get: function() {
return document.getElementById(this);
},
set: function(element) {
element.id = this;
}
});
function pollutePrototype(buildIn, name, descr) {
var oldDescr = Object.getOwnPropertyDescriptor(buildIn.prototype, name);
if (oldDescr && !oldDescr.configurable) {
console.error('Unable to replace ' + buildIn.name + '.prototype.' + name + '!');
} else {
if (oldDescr) {
console.warn('Replacing ' + buildIn.name + '.prototype.' + name + ' might cause unexpected behaviour.');
}
Object.defineProperty(buildIn.prototype, name, descr);
}
}
It works in some browsers and you can access elements this way:
document.body.appendChild(
'footer'.绎 = document.createElement('div')
);
'footer'.绎.textContent = 'btw nice browser :)';
I have chosen the name of the property almost randomly. If you actually wanted to use this shorthand I would suggest coming up with something easier to type.
You can easily create shorthand easily yourself:
function getE(id){
return document.getElementById(id);
}
id's are saved to the window.
HTML
<div id='logo'>logo</div>
JS
logo.innerHTML;
is the same as writing:
document.getElementById( 'logo' ).innerHtml;
I don't suggest using the former method as it is not common practice.
A quick alternative to contribute:
HTMLDocument.prototype.e = document.getElementById
Then just do:
document.e('id');
There's a catch, it doesn't work in browsers that don't let you extend prototypes (e.g. IE6).
(Shorthand for not only getting element by ID, but also getting element by class :P)
I use something like
function _(s){
if(s.charAt(0)=='#')return [document.getElementById(s.slice(1))];
else if(s.charAt(0)=='.'){
var b=[],a=document.getElementsByTagName("*");
for(i=0;i<a.length;i++)if(a[i].className.split(' ').indexOf(s.slice(1))>=0)b.push(a[i]);
return b;
}
}
Usage : _(".test") returns all elements with class name test, and _("#blah") returns an element with id blah.
<script>
var _ = function(eId)
{
return getElementById(eId);
}
</script>
<script>
var myDiv = _('id');
</script>
There are several good answers here and several are dancing around jQuery-like syntax, but not one mentions actually using jQuery. If you're not against trying it, check out jQuery. It let's you select elements super easy like this..
By ID:
$('#elementId')
By CSS class:
$('.className')
By element type:
$('a') // all anchors on page
$('inputs') // all inputs on page
$('p a') // all anchors within paragaphs on page
There's none built-in.
If you don't mind polluting the global namespace, why not:
function $e(id) {
return document.getElementById(id);
}
EDIT - I changed the function name to be something unusual, but short and not otherwise clashing with jQuery or anything else that uses a bare $ sign.
I frequently use:
var byId='getElementById'
var byClass='getElementsByClass'
var byTag='getElementsByTag'
var mydiv=document[byId]('div')
/* as document["getElementById"] === document.getElementById */
I think it's better than a external function (e.g. $() or byId()) because you can do things like this:
var link=document[byId]('list')[byClass]('li')[0][byTag]('a')[0]
Btw, don't use jQuery for this, jQuery is much, much slower than document.getElementById(), an external function like $() or byId(), or my method: http://jsperf.com/document-getelementbyid-vs-jquery/5
Yes, it gets repetitive to use the same function over and over each time with a different argument:
var myImage = document.getElementById("myImage");
var myDiv = document.getElementById("myDiv");
So a nice thing would be a function that takes all those arguments at the same time:
function getElementsByIds(/* id1, id2, id3, ... */) {
var elements = {};
for (var i = 0; i < arguments.length; i++) {
elements[arguments[i]] = document.getElementById(arguments[i]);
}
return elements;
}
Then you would have references to all your elements stored in one object:
var el = getElementsByIds("myImage", "myDiv");
el.myImage.src = "test.gif";
But you would still have to list all those ids.
You could simplify it even more if you want all elements with ids:
function getElementsWithIds() {
var elements = {};
var elementList = document.querySelectorAll("[id]");
for (var i = 0; i < elementList.length; i++) {
elements[elementList[i].id] = elementList[i];
}
return elements;
}
But it would be pretty expensive to call this function if you have many elements.
So, theoretically, if you would use the with keyword you could write code like this:
with (getElementsByIds('myButton', 'myImage', 'myTextbox')) {
myButton.onclick = function() {
myImage.src = myTextbox.value;
};
}
But I don't want to promote the use of with. Probably there's a better way to do it.
Well, you could create a shorthand function, that's what I do.
function $(element) {
return document.getElementById(element);
}
and then when you wanted to get it, you just do
$('yourid')
Also, another useful trick that I found, is that if you want to get the value or innerHTML of an item ID, you can make functions like this:
function $val(el) {
return $(el).value;
}
function $inner(el) {
return $(el).innerHTML;
}
Hope you like it!
I actually made a kind of mini javascript library based on this whole idea.
Here it is.
If this is on your own site, consider using a library like jQuery to give you this and many other useful shorthands that also abstract away browser differences. Personally, if I wrote enough code to be bothered by the longhand, I would include jQuery.
In jQuery, the syntax would be $("#someid"). If you then want the actual DOM element and not the jQuery wrapper, it's $("#someid")[0], but you could most likely do whatever you're after with the jQuery wrapper.
Or, if you're using this in a browser developer console, research their built-in utilities. As someone else mentioned, the Chrome JavaScript console includes a $("someid") method, and you can also click an element in the developer tools "Elements" view and then reference it with $0 from the console. The previously selected element becomes $1 and so on.
If the only issue here is typing, maybe you should just get yourself a JavaScript editor with intellisense.
If the purpose is to get shorter code, then you could consider a JavaScript library like jQuery, or you can just write your own shorthand functions, like:
function byId(string) {return document.getElementById(string);}
I used to do the above for better performance. What I learnt last year is that with compression techniques the server does it automatically for you, so my shortening technique was actually making my code heavier. Now I am just happy with typing the whole document.getElementById.
If you are asking for a shorthand function...
<!DOCTYPE html>
<html>
<body>
The content of the body element is displayed in your browser.
<div id="d1">DIV</div>
<script>
var d=document;
d.g=document.getElementById;
d.g("d1").innerHTML = "catch";
</script>
</body>
</html>
or
<!DOCTYPE html>
<html>
<body>
The content of the body element is displayed in your browser.
<div id="d1">DIV</div>
<script>
var w=window;
w["d1"].innerHTML = "catch2";
</script>
</body>
Arrow functions make is shorter.
var $id = (id) => document.getElementById(id);
wrap the document.querySelectorAll ... a jquery like select
function $(selector){
var s = document.querySelectorAll(selector);
return s.length > 1 ? s : s[0];
}
// usage: $('$myId')
Well, if the id of the element does not compete with any properties of the global object, you don't have to use any function.
myDiv.appendChild(document.createTextNode("Once I was myDiv. "));
myDiv.id = "yourDiv";
yourDiv.appendChild(document.createTextNode("But now I'm yourDiv."));
edit: But you don't want to make use of this 'feature'.
Another wrapper:
const IDS = new Proxy({}, {
get: function(target, id) {
return document.getElementById(id); } });
IDS.camelCaseId.style.color = 'red';
IDS['dash-id'].style.color = 'blue';
<div id="camelCaseId">div 1</div>
<div id="dash-id">div 2</div>
This, in case you don't want to use the unthinkable, see above.
You can use a wrapper function like :
const byId = (id) => document.getElementById(id);
Or
Assign document.getElementById to a variable by binding it with document object.
const byId = document.getElementById.bind(document);
Note: In second approach, If you don't bind document.getElementById with document you'll get error :
Uncaught TypeError: Illegal invocation
What Function.bind does is it creates a new function with its this keyword set to value that you provide as argument to Function.bind.
Read docs for Function.bind
const $id = id => document.getElementById(id);
...
$id('header')
I just use: function id(id) {return document.getElementById(id);}", called by id(target id).action;
It works for me in Chrome, Edge & Firefox, though not in Safari or Opera.
I wrote this yesterday and found it quite useful.
function gid(id, attribute) {
var x = 'document.getElementById("'+id+'")';
if(attribute) x += '.'+attribute;
eval('x =' + x);
return x;
}
This is how you use it.
// Get element by ID
var node = gid('someID'); //returns <p id='someID' class='style'>Hello World</p>
// returns 'Hello World'
// var getText = document.GetElementById('someID').innerText;
var getText = gid('someID', 'innerText');
// Get parent node
var parentNode = gid('someID', 'parentNode');

Categories