Related
Take the following html:
<div id="somediv" style="display:none;"></div>
<script>
document.getElementById("somediv").style.display = 'none';
</script>
somediv is already hidden, some javascript runs, effectively doing nothing. I need code that detects when style.display has been used in javascript, regardless of if style was changed or not.
I've tried MutationObserver:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutationRecord) {
alert(mutationRecord.target.id);
});
});
observer.observe(document.getElementById("somediv"), { attributes : true, attributeFilter : ['style'] });
The above only triggers if there was a style change. I need it to trigger regardless if there was a style change or not.
So I did come up with an answer. The way it works, is you grab every script tag, replace .style.display with your own function, and finally replace the DOM (which is the real trick):
//loop through <script> tags
$('script').each(function(){
var scripthtml = $(this).html();
if (scripthtml.indexOf('style.display') != -1){
scripthtml = scripthtml.replace(/.style.display = 'none'/g, ".customdisplay('none')");
scripthtml = scripthtml.replace(/.style.display = "none"/g, '.customdisplay("none")');
scripthtml = scripthtml.replace(/.style.display ='none'/g, ".customdisplay('none')");
scripthtml = scripthtml.replace(/.style.display ="none"/g, '.customdisplay("none")');
scripthtml = scripthtml.replace(/.style.display='none'/g, ".customdisplay('none')");
scripthtml = scripthtml.replace(/.style.display="none"/g, '.customdisplay("none")');
$(this).replaceWith('<script>' + scripthtml + '</script>');
}
});
Now here is my .style.display replacement function:
HTMLElement.prototype.customdisplay = function(showhide){
//insert whatever code you want to execute
this.style.display = showhide;
alert('Success! .style.display has been detected!');
};
.replaceWith is what actually changes the DOM. The only thing this script doesn't do, is it isn't able to look through included javascript files. Thank you all for your comments <3.
UPDATE:
When using replaceWith to add the script tag, ipad/iphone/ipod will execute the script tag a second time. to prevent this double execution, you need to do this:
$(this).replaceWith('<script>if (1==0){' + scripthtml + '}</script>');
Your functions will be valid, but anything outside of the function will not be executed.
I don't think you fully realise what you are asking for, but here is a short description of closest options I am aware of:
https://blog.sessionstack.com/how-javascript-works-tracking-changes-in-the-dom-using-mutationobserver-86adc7446401
Basically, MutationObserver is the major improvement over past here that is closest to what you want and supported in modern browsers. But the whole point of all this is it listens to changes.
You are basically asking to detect even non-changes. I don't see you getting out of this problem other than:
Writing a wrapper for the ways you use to change this in code and then instead of calling the change call the wrapper. Simple, easy, requires refactoring all the calls in code.
Overwriting the actual functions that make a change. This saves you the refactor but you are playing with fire here. Rewriting a well-known function on a global level means a PERMANENT source of problems to you and all developers who work on the project.
Polling - in short calling over and over on some element to check properties. Not only it detects changes with a lag of 0 to the polling interval it also uses resources and if you want to monitor everything you will have to write a recursion that descends through the current DOM from the top to each node and check it. You are gonna kill the performance with this. How hard I can't tell but I suspect either polling interval will be long thus increasing the detection lag or your performance will dive down like a gray falcon.
I have 1 big question for you:
What led you to the state where you need this?
You basically want the ability for a program to detect when it is using a specific part of itself (be that as it is, a core part, one implemented by the browser).
This sounds similar to request: hey whenever I change or not change a value in any variable in my code I should be able to react to it.
I am not trying to sound like Naggin Nancy here but instead encourage you to question train of thought that led to this being something you need and figure out whether you want to sink further time into this, because I don't think you are getting that what you desire easily and I suspect it came to be due to poor design decisions in the past.
I have the below javascript to get the UserID from a online form. This script will go through IE DOM Explorer to find the valued. But when I run the script, it is totally ignoring my "If" statement. It is just providing a value for "NewAuthUserID", without considering the "if".
(function () {
var NewAuthUserID = "";
var UserId = $('tr.background-highlight:contains("REQUESTER PROFILE") + tr').children('td:contains("User ID:")+td').text();
if ('tr.background-highlight:contains("NEW AUTHORIZED INDIVIDUAL PROFILE:"') {
var NewAuthUserID = $('td:contains("User ID:")+td:eq(2)').text();
};
alert(UserId);
alert(NewAuthUserID)
})();
Firstly, I'd suggest to check out how the if statement works: https://www.w3schools.com/js/js_if_else.asp
You need the if statement conditional to return true or false. Right now you're TRYING to use jquery to select things but even that has a syntax issues. Not only that but once the syntax is fixed it STILL won't do what you're attempting to do because you're putting something that will always evaluate to true as the conditional. That jquery selector just returns a function, not a boolean like it looks like you're intending to do. Try this:
(function(){
var NewAuthUserID = "";
var UserId=$('tr.background-highlight:contains("REQUESTER PROFILE") + tr').children('td:contains("User ID:")+td').text();
if($('tr.background-highlight').text() == "NEW AUTHORIZED INDIVIDUAL PROFILE:")){
var NewAuthUserID=$('td:contains("User ID:")+td:eq(2)').text();
}
alert(UserId);
alert(NewAuthUserID)
})();
Notice how I'm snagging the text that you're trying to test against with jquery and expressing it with a conditional instead? In this manner, it will return the boolean: true/false which is what you need to get the if statement to trigger.
Also if you check your syntax, you were missing the $() wrapper around your if statement, but you have a string that looked like it was trying to snag text via jquery.
I suggest formatting your code a bit, this always helps to debug.
The problem is you are trying to use a jQuery selector in your if statement, but you didn't include the $ to evaluate jQuery. It's just evaluating a string, wich results in TRUE (basically doing this: if(true)), so the code block is executed.
Try this instead:
javascript: (function() {
var NewAuthUserID = "";
var UserId = $('tr.background-highlight:contains("REQUESTER PROFILE") + tr').children('td:contains("User ID:")+td').text();
if ($('tr.background-highlight:contains("NEW AUTHORIZED INDIVIDUAL PROFILE:"').length > 0) {
var NewAuthUserID = $('td:contains("User ID:")+td:eq(2)').text();
};
alert(UserId);
alert(NewAuthUserID)
})();
EDIT: I added the length > 0 check on the returned object. It's possible to accomplish this with OP's code, he was just missing those two pieces. :contains is not the same as .text() ==.
Off topic response:
The way you manage/select your nodes may require a lot of maintanance in the future and is prone to errors.
For example: tr.background-highlight:contains("REQUESTER PROFILE") + tr
In words: Get me the table-row after a table-row with hilighted background, that contains "REQUESTER PROFILE".
What if you'll have to add a row in between them? what if you'll need to select the row, wether it is hilighted or not? what if further rows will be hilighted in the future, so that this selector ain't uniqu anymore? what if the label changes? maybe even the language? ...
In each of these cases you'll have to revisit (potentially all) your jquery selectors, just because some minor layout changed.
That's not very reliable.
Will you remember that when you'll get asked to do these changes? Maybe someone else will have to do these changes, will he/she know what to look for?
Tell me, do you remember the details/implications/quirks of the work you've done a week ago? not to speak about your work from a few months ago.
Better:
Use "unique" identifier to, well, identify your nodes by their role; and I'm not talking about IDs. Unique within their specific context.
The easiest way would be to use css-classes. Annotating the rows/cells so you can select the very same field as $('.ref-requester-provile .ref-user-id')
This is way more reliable and future-proof than your bulky $('tr.background-highlight:contains("REQUESTER PROFILE") + tr').children('td:contains("User ID:")+td') where your JS needs to know every little detail of your template/markup, and needs to be adapted with every little change.
Why did I prepend these classes with ref-? to distinct them from classes that are meant for styling
If you don't need to style these nodes and need these identifyer solely to reference them in your JS, I'd rather use a data-attribute. Why? Let's sum it up with:
performance: when you need to add/remove these marker; avoid unnecessary render-cycles
A cleaner seperation between style and code: classes are primarily for styling, but we don't style here.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Performance of jQuery selector with context
In the jQuery DOCS it says
By default, selectors perform their searches within the DOM starting
at the document root. However, an alternate context can be given for
the search by using the optional second parameter to the $() function.
Based on that my understanding is that a selection using a context passed in as the second parameter should be faster then the same selection without the context passed in. However I ran some tests and it seems as if this isn't the case, or at least isn't always the case.
To elaborate, I originally wanted to see if searching for multiple elements at once ($("div1, #div2")) was faster then searching for the two separately ($("#div1") $("div2")). I then decided to test it with the context and without to see how much faster it was with the context, but was surprised when it turned out that the context seemed to be slowing it down.
For example given the following basic HTML markup
<div id="testCnt">
<div id="Div0"></div>
<div id="Div1"></div>
<div id="Div2"></div>
<div id="Div3"></div>
<div id="Div4"></div>
<div id="Div5"></div>
<div id="Div6"></div>
<div id="Div7"></div>
<div id="Div8"></div>
<div id="Div9"></div>
</div>
And the following JavaScript (jQuery 1.8.2, and tested using FireBug)
$(function () {
var $dvCnt = $('#testCnt');
var dvCnt = $dvCnt[0];
console.time('Individual without cache');
for (var i = 0; i < 10000; i++) {
$('#Div0').text('Test');
$('#Div1').text('Test');
$('#Div2').text('Test');
$('#Div3').text('Test');
$('#Div4').text('Test');
$('#Div5').text('Test');
$('#Div6').text('Test');
$('#Div7').text('Test');
$('#Div8').text('Test');
$('#Div9').text('Test');
}
console.timeEnd('Individual without cache');
console.time('Individual with $cache');
for (var i = 0; i < 10000; i++) {
$('#Div0', $dvCnt).text('Test');
$('#Div1', $dvCnt).text('Test');
$('#Div2', $dvCnt).text('Test');
$('#Div3', $dvCnt).text('Test');
$('#Div4', $dvCnt).text('Test');
$('#Div5', $dvCnt).text('Test');
$('#Div6', $dvCnt).text('Test');
$('#Div7', $dvCnt).text('Test');
$('#Div8', $dvCnt).text('Test');
$('#Div9', $dvCnt).text('Test');
}
console.timeEnd('Individual with $cache');
console.time('Individual with DOM cache');
for (var i = 0; i < 10000; i++) {
$('#Div0', dvCnt).text('Test');
$('#Div1', dvCnt).text('Test');
$('#Div2', dvCnt).text('Test');
$('#Div3', dvCnt).text('Test');
$('#Div4', dvCnt).text('Test');
$('#Div5', dvCnt).text('Test');
$('#Div6', dvCnt).text('Test');
$('#Div7', dvCnt).text('Test');
$('#Div8', dvCnt).text('Test');
$('#Div9', dvCnt).text('Test');
}
console.timeEnd('Individual with DOM cache');
console.time('Multiple without cache');
for (var i = 0; i < 10000; i++) {
$('#Div0,#Div1 ,#Div2 ,#Div3 ,#Div4 ,#Div5 ,#Div6, #Div7, #Div8, #Div9').text('Test');
}
console.timeEnd('Multiple without cache');
console.time('Multiple with $cache');
for (var i = 0; i < 10000; i++) {
$('#Div0,#Div1 ,#Div2 ,#Div3 ,#Div4 ,#Div5 ,#Div6, #Div7, #Div8, #Div9', $dvCnt).text('Test');
}
console.timeEnd('Multiple with $cache');
console.time('Multiple with DOM cache');
for (var i = 0; i < 10000; i++) {
$('#Div0,#Div1 ,#Div2 ,#Div3 ,#Div4 ,#Div5 ,#Div6, #Div7, #Div8, #Div9', dvCnt).text('Test');
}
console.timeEnd('Multiple with DOM cache');
});
Here's a jsbin
I'm getting something like the following results
Individual without cache: 11490ms
Individual with $cache: 13315ms
Individual with DOM cache: 14487ms
Multiple without cache: 7557ms
Multiple with $cache: 7824ms
Multiple with DOM cache: 8589ms
Can someone shed some insight on whats going on? Specifically why the search is slowing down when the jQuery context is passed in?
EDIT:
Most of the anwsers here (as well as Performance of jQuery selector with context) basically say that that either the DOM in this example is too small to really gain much or that selecting by ID is going to be fast regardless. I understand both points, the main point of my question is why would the context slow down the search, the size of the DOM shouldn't make a difference for that, and neither should the fact that searching by ID is already very fast.
#pebble suggested that the reason that its slower is because jQuery can't use the native browser methods (getElementByID), this seems to make sense to me, but then why is it faster to search for multiple elements in one selection?
Anyway I dumped the tests into a jsPerf adding cases to search by class and was again surprised to see that the search for multiple classes with a cache this time was the fastest.
I would imagine there are lots of situations where using context will slow things down, mainly because jQuery will try and use browser native methods where it can - rather than traverse the entire dom. One example of this would be using document.getElementById as in your example.
why the slow down?
getElementById only exists on the document object - you have no way of using this on a contextual element - i.e. element.getElementById. So my theory would be that jQuery first does the id request using document.getElementById, and then, if there is a context set - scans through the parents of each element to tell if any of them exist as children of the context - thereby slowing the process down.
Other examples of selectors that may be slow
You will also find other places where depending on the selector you are using you will get performance increases - all down to what methods jQuery can use to speed up it's work. For example:
$('.className');
Would most likely translate to using getElementsByClassName or any other native method offered to select by className, However:
$('.className .anotherClassName');
Wouldn't be able to use this (as it has to take the relationship into account) and would have to use a mixture of querySelector (if it exists) and or pure javascript logic to work things out.
Having a good knowledge of what native methods are available will help you optimise your jQuery queries.
Ways to optimise
If you wish to optimise using a context, I would imagine this would prove a faster query than without:
$('div', context);
This will be because getElementsByTagName has existed since the dawn of time for a while, and can be used in pure JavaScript directly on a DOM element. However if you are to do this, it may be quicker to do the following:
$().pushStack( context[0].getElementsByTagName('div') );
or
$( context[0].getElementsByTagName('div') );
Mainly because you cut down on the jQuery function calls, although this is much less succinct. Another thing to be aware of with regards to many of the popular JavaScript environments - calling a function without arguments is a lot faster than calling with.
A relatively unused method for optimising certain jQuery selectors is to use the jQuery eq pseudo selector - this can speed things up in a similar way to using LIMIT 0,1 in SQL queries - for example:
$('h2 > a');
Would scan inside all H2s looking for A elements, however if you know from the start that there is only ever going to be one A tag within your H2s you can do this:
$('h2 > a:eq(0)');
Plus if you know there is only ever going to be one H2 - the logic is the same:
$('h2:eq(0) > a:eq(0)');
The difference between $().pushStack and $().add
In response to Jasper's comment here is the difference between the two functions:
.add:
function (a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?
[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?
d:p.unique(d))}
.pushStack:
function (a,b,c){var d=p.merge(this.constructor(),a);return
d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector
+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d}
The major difference is that .add() uses .pushStack() to acheive it's goals - add allows support for a lot more data types - even jQuery objects. Whereas .pushStack is only designed for DOM Elements, which makes it more optimal if that is what you are using :)
A quicker way to select by ID?
This is obvious, but I thought I'd put this here as sometimes things are missed - a quicker way to select an element by id would be to do the following:
$(document.getElementById('id'));
All because there is no way jQuery/Sizzle can out-do a native method, and it also means you avoid any string parsing on jQuery/Sizzle's part. It's no where near as neat as it's jQuery counterpart though, and probably wont gain that much speed increase, but it is worth mentioning as an optimisation. You could do the following if you were to use ids often.
jQuery.byid = function(id){
return jQuery(document.getElementById(id))
};
$.byid('elementid');
The above would be slightly slower that my previous example, but should still out-do jQuery.
Since you are selecting by ID, jQuery (or sizzle, i forget) is skipping ahead to the faster document.getElementById() in this case. You may get different results when using classes, however even then it may vary by browser.
You could make your testing easier using something like http://jsperf.com/
You are not going to benefit with context when you use an id since that is highly optimized in the browser.
With a id you can call out and say hey. A non programming example, you are in a room of people, you yell out a name, the person answers.
Now lets look at context. Lets say you know the name is a mans name so you separate the room into men and women. You than ask the group of men for their name. One extra step for something that is rather easy.
You will benefit when you are looking up specific things like attributes. Something that is harder for the browser to look up and is not highly optimized. Say you are looking for an input that has a specific attribute. It would be better to reference an element you know that contains it so it does not have to search every input on the page.
Now the fun part is the context selector is slower. It is better to use find. Why? It has to deal with the creation of multiple jQuery objects. :)
So instead of
$('.myClass', dvCnt).text('Test');
do
$(dvCnt).find('.myClass').text('Test');
if you are doing multiple look ups, it is better to store the first one into a variable
var myDiv = $(dvCnt)
myDiv.find('.myClass1').text('Test');
myDiv.find('.myClass2').text('Test');
But now with jQuery doing to querySelector, these optimizations are a smaller deal unless you are using the made up jQuery selectors that querySelector does not support. For browsers that do not support querySelector, the context is important.
You seem to be using #elementid attribute to perform the tests.
Remember that an ID in a HTML page is supposed to be Unique. So this will not make a difference if you give it a context or not when searching for ID..
This test might make more sense if you are trying to target elements with classes or the element tag themselves.
$('.mydiv' , $('#innerDiv')) might be faster than $('.mydiv')
So, I have some code that should do four things:
remove the ".mp4" extension from every title
change my video category
put the same description in all of the videos
put the same keywords in all of the videos
Note: All of this would be done on the YouTube upload page. I'm using Greasemonkey in Mozilla Firefox.
I wrote this, but my question is: how do I change the HTML title in the actual HTML page to the new title (which is a Javascript variable)?
This is my code:
function remove_mp4()
{
var title = document.getElementsByName("title").value;
var new_title = title.replace(title.match(".mp4"), "");
}
function add_description()
{
var description = document.getElementsByName("description").value;
var new_description = "Subscribe."
}
function add_keywords()
{
var keywords = document.getElementsByName("keywords").value;
var new_keywords = prompt("Enter keywords.", "");
}
function change_category()
{
var category = document.getElementsByName("category").value;
var new_category = "<option value="27">Education</option>"
}
remove_mp4();
add_description();
add_keywords();
change_category();
Note: If you see any mistakes in the JavaScript code, please let me know.
Note 2: If you wonder why I stored the current HTML values in variables, that's because I think I will have to use them in order to replace HTML values (I may be wrong).
A lot of things have been covered already, but still i would like to remind you that if you are looking for cross browser compatibility innerHTML won't be enough, as you may need innerText too or textContent to tackle some old versions of IE or even using some other way to modify the content of an element.
As a side note innerHTML is considered from a great majority of people as deprecated though some others still use it. (i'm not here to debate about is it good or not to use it but this is just a little remark for you to checkabout)
Regarding remarks, i would suggest minimizing the number of functions you create by creating some more generic versions for editing or adding purposes, eg you could do the following :
/*
* #param $affectedElements the collection of elements to be changed
* #param $attribute here means the attribute to be added to each of those elements
* #param $attributeValue the value of that attribute
*/
function add($affectedElements, $attribute, $attributeValue){
for(int i=0; i<$affectedElements.length; i++){
($affectedElements[i]).setAttribute($attribute, $attributeValue);
}
}
If you use a global function to do the work for you, not only your coce is gonna be easier to maintain but also you'll avoid fetching for elements in the DOM many many times, which will considerably make your script run faster. For example, in your previous code you fetch the DOM for a set of specific elements before you can add a value to them, in other words everytime your function is executed you'll have to go through the whole DOM to retrieve your elements, while if you just fetch your elements once then store in a var and just pass them to a function that's focusing on adding or changing only, you're clearly avoiding some repetitive tasks to be done.
Concerning the last function i think code is still incomplete, but i would suggest you use the built in methods for manipulating HTMLOption stuff, if i remember well, using plain JavaScript you'll find yourself typing this :
var category = document.getElem.... . options[put-index-here];
//JavaScript also lets you create <option> elements with the Option() constructor
Anyway, my point is that you would better use JavaScript's available methods to do the work instead of relying on innerHTML fpr anything you may need, i know innerHTML is the simplest and fastest way to get your work done, but if i can say it's like if you built a whole HTML page using and tags only instead of using various semantic tags that would help make everything clearer.
As a last point for future use, if you're interested by jQuery, this will give you a different way to manipulate your DOM through CSS selectors in a much more advanced way than plain JavaScript can do.
you can check out this link too :
replacement for innerHTML
I assume that your question is only about the title changing, and not about the rest; also, I assume you mean changing all elements in the document that have "title" as name attribute, and not the document title.
In that case, you could indeed use document.getElementsByName("title").
To handle the name="title" elements, you could do:
titleElems=document.getElementsByName("title");
for(i=0;i<titleElems.length;i++){
titleInner=titleElems[i].innerHTML;
titleElems[i].innerHTML=titleInner.replace(titleInner.match(".mp4"), "");
}
For the name="description" element, use this: (assuming there's only one name="description" element on the page, or you want the first one)
document.getElementsByName("description")[0].value="Subscribe.";
I wasn't really sure about the keywords (I haven't got a YouTube page in front of me right now), so this assumes it's a text field/area just like the description:
document.getElementsByName("keywords")[0].value=prompt("Please enter keywords:","");
Again, based on your question which just sets the .value of the category thingy:
document.getElementsByName("description")[0].value="<option value='27'>Education</option>";
At the last one, though, note that I changed the "27" into '27': you can't put double quotes inside a double-quoted string assuming they're handled just like any other character :)
Did this help a little more? :)
Sry, but your question is not quite clear. What exactly is your HTML title that you are referring to?
If it's an element that you wish to modify, use this :
element.setAttribute('title', 'new-title-here');
If you want to modify the window title (shown in the browser tab), you can do the following :
document.title = "the new title";
You've reading elements from .value property, so you should write back it too:
document.getElementsByName("title").value = new_title
If you are refering to changing text content in an element called title try using innerHTML
var title = document.getElementsByName("title").value;
document.getElementsByName("title").innerHTML = title.replace(title.match(".mp4"), "");
source: https://developer.mozilla.org/en-US/docs/DOM/element.innerHTML
The <title> element is an invisible one, it is only displayed indirectly - in the window or tab title. This means that you want to change whatever is displayed in the window/tab title and not the HTML code itself. You can do this by changing the document.title property:
function remove_mp4()
{
document.title = document.title.replace(title.match(".mp4"), "");
}
a quick, probably easy question whose answer is probably "best practice"
I'm following a tutorial for a custom-template mobile Safari webapp, and to change views around this code is used:
function btnSave_ClickHandler(event)
{
var views = document.getElementById('stackLayout');
var front = document.getElementById('mainScreen');
if (views && views.object && front) {
views.object.setCurrentView(front, true);
}
}
My question is just about the if conditional statement. What is this triplet saying, and why do each of those things need to be verified before the view can be changed? Does views.object just test to see if the views variable responds to the object method? Why is this important?
EDIT - This is/was the main point of this question, and it regards not Javascript as a language and how if loops work, but rather WHY these 3 things specifically need to be checked:
Under what scenarios might views and front not exist?
I don't typically write my code so redundantly. If the name of my MySQL table isn't changing, I'll just say UPDATE 'mytable' WHERE... instead of the much more verbose (and in my view, redundant)
$mytable = "TheSQLTableName";
if ($mytable == an actual table && $mytable exists && entries can be updated){
UPDATE $mytable;
}
Whereas if the table's name (or in the JS example, the view's names) ARE NOT "hard coded" but are instead a user input or otherwise mutable, I might right my code as the DashCode example has it. So tell me, can these values "go wrong" anyhow?
Thanks!
The if is testing those 3 pointers to make sure they are non-null. A null pointer is 0 which converts to false. If any of those 3 pointer are 0 (null) then it won't try to use them.
I'm not sure what dereferencing a null pointer does in Javascript but it's an error and may cause an exception. The if is just avoiding that possibility.