There's a website I need to check at constant intervals but it has certain JS functions programmed into it that are more aesthetic than anything else, and the result is that it eats up my CPU for no good reason, not to mention that it adds 1-2 seconds of time for the DOM to be ready, when I need it to load as quickly as possible.
For instance, the website displays several elements on it, each in its own wrapper. Each element has a title, which may be long or short. So instead of just letting the title run on and be cut off by the border (as the overflow is hidden as per the original CSS) it parses all the titles and then trims them, adding an ellipsis at the end. Now, this is purely aesthetic, of course, as you still have no access to the part of the title that's cut of, so it's the same if you just let it be cut off "behind" the hidden overflow. On the other hand, having to take care of each title element (sometimes there may even be 100 elements on screen at a time) uses up a lot of time and resources.
There are several other instances such as this one. And my question is: is there any way I can inject some JS on my side that overrides or cancels such functions as the one that affects the titles as I've described?
P.S. The website in question uses also jQuery. I don't know if that detail makes any difference.
You can't "stop a JS process" per-se (your injected javascript will be running in the same process), but you might be able to interfere with it.
Find a function which does the thing you want to stop, and just overwrite it with an empty function:
some.object.in.the.other.code.doSomething = function() {}
Note that you will need to ensure that your code injection occurs before the other script is executed. If this is not feasible, you will have to find some function further up in the call stack which will be executed after your injected script.
Related
In one of the tests, I need to scroll into view of an element which can be done via scrollIntoView() method parameterizing the script with an element located via Protractor:
var elm = element(by.id("myid"));
browser.executeScript("arguments[0].scrollIntoView();", elm.getWebElement());
But, we can also find the element directly via getElementById():
browser.executeScript("document.getElementById('myid').scrollIntoView();");
What is the difference between the two approaches?
The scrollIntoView() is chosen for sample purposes only. The logic inside the script can be more complex.
The first one will tell you explicitly when the element is missing while the second one will raise a JavaScript error saying that null doesn't have the .scrollIntoView method. So to keep the second one maintainable, you need to implicitly handle the case when the element is missing and raise an error with an appropriate message.
The first one is exposed to an unexpected/stale state. The element found by element(by.id("myid")) could be removed from the page just before executing browser.executeScript(). The second one is not exposed to this issue since the page is not updated while the script is executed.
The second one is less expensive since it executes one Selenium command (ExecuteScript), while the first one executes two (FindElement and ExecuteScript). Sending a Selenium command to the browser is relatively expensive (minimum of 25ms) and might become significant with multiple calls.
Both will produce the exact same result and will end up calling the same API on the browser side.
A couple things come to mind.
Maintenance going forward. What happens if your .scrollIntoView() element locator changes? I think I would prefer finding the element using Selenium instead of JS. Reduced chance to have typos, type checking, and so on with an IDE but the IDE won't look into the string that contains your JS.
Selenium accessibility. Since Selenium can't see invisible elements, it could affect your choice either way. Would you want an exception to be thrown if you are trying to scroll to an invisible element? Selenium would let you know where JS wouldn't. Maybe you want to intentionally scroll to an invisible element. Finding invisible elements is a job for JS but not Selenium.
There may be more but this is all I can think of off the top of my head.
Disclaimer:
I've blathered on kind-of excessively here in an attempt to provide enough context to pre-empt all questions you folks might have of me. Don't be scared by the length of this question: much of what I've written is very skim-able (especially the potential solutions I've come up with).
Goal:
The effect I'm hoping to achieve is displaying the same element (and all descendants) in multiple places on the same page. My current solution (see below for more detail) involves having to clone/copy and then append in all the other places I want it to appear in the DOM. What I'm asking for here is a better (more efficient) solution. I have a few ideas for potentially more efficient solutions (see below). Please judge/criticize/dismiss/augment those, or add your own more-brilliant-er solution!
"Why?" you ask?
Well, the element (and it's descendants) that I'm wanting to display more than once potentially has lots of attributes and contents - so cloning it, and appending it someplace else (sometimes more than one other place) can get to be quite a resource-hogging DOM manipulation operation.
Some context:
I can't describe the situation exactly (damn NDA's!) but essentially what I've got is a WYSIWYG html document editor. When a person is editing the DOM, I'm actually saving the "original" node and the "changed" node by wrapping them both in a div, hiding the "original" and letting the user modify the new ("changed") node to their heart's content. This way, the user can easily review the changes they've made before saving them.
Before, I'd just been letting the user navigate through the "diff divs" and temporarily unhiding the "original" node, to show the changes "inline". What I'm trying to do now is let the user see the whole "original" document, and their edited ("changed") document in a side-by-side view. And, potentially, I'd like to save the changes through multiple edit sessions, and show 'N' number of versions side-by-side simultaneously.
Current Solution:
My current solution to achieve this effect is the following:
Wrap the whole dang dom (well, except the "toolbars" and stuff that they aren't actually editing) in a div (that I'll call "pane1"), and create a new div (that I'll call "pane2"). Then deep-clone pane1's contents into pane2, and in pane1 only show the "original" nodes, and in pane2 only show the "changed" nodes (in the diff regions - everything outside of that would be displayed/hidden by a toggle switch in a toolbar). Then, repeat this for panes 3-through-N.
Problem with Current Solution:
If the document the user is editing gets super long, or contains pictures/videos (with different src attributes) or contains lots of fancy styling things (columns, tables and the like) then the DOM can potentially get very large/complex, and trying to clone and manipulate it can make the browser slow to a crawl or die (depending on the DOM's size/complexity and how many clones need to be made as well as the efficiency of the browser/the machine it's running on). If size is the issue I can certainly do things like actually remove the hidden nodes from the DOM, but that's yet more DOM manipulation operations hogging resources.
Potential Solutions:
1. Find a way to make the DOM more simple/lightweight
so that the cloning/manipulating that I'm currently doing is more efficient. (of course, I'm trying to do this as much as I can anyway, but perhaps it's all I can really do).
2. Create static representations of the versions with Canvas elements or something.
I've heard there's a trick where you can wrap HTML in an SVG element, then use that as an image source and draw it onto a canvas. I'd think that those static canvasses (canvi?) would have a much smaller memory footprint than cloned DOM nodes. And manipulating the DOM (hiding/showing the appropriate nodes), then drawing an image (rinse & repeat) should be quicker & more efficient than cloning a node and manipulating the clones. (maybe I'm wrong about that? Please tell me!)
I've tried this in a limited capacity, but wrapping my HTML in SVG messes with the way it's rendered in a couple of weird cases - perhaps I just need to message the elements a bit to get them to display properly.
3. Find some magic element
that just refers to another node and looks/acts like it without being a real clone (and therefore being somehow magically much more lightweight). Even if this meant that I couldn't manipulate this magic element separately from the node it's "referencing" (or its fake children) - in that case I could still use this for the unchanged parts, and hopefully shave off some memory usage/DOM Manipulation operations.
4. Perform some of the steps on the server side.
I do have the ability to execute server side code, so maybe it's a lot more efficient (some of my users might be on mobile or old devices) to get all ajax-y and send the relevant part of the DOM (could be the "root" of the document the user is editing, or just particularly heavy "diff divs") to the server to be cloned/manipulated, then request the server-manipulated "clones" and stick 'em in their appropriate panes/places.
5. Fake it to make it "feel" more efficient
Rather than doing these operations all in one go and making the browser wait till the operations are done to re-draw the UI, I could do the operations in "chunks" and let the browser re-render and catch a breather before doing the next chunk. This probably actually would result in more time spent, but to the casual user it might "feel" quicker (haha, silly fools...). In the end, I suppose, it is user experience that is what's most important.
Footnote:
Again, I'm NDA'd which prevents me from posting the actual code here, as much as I'd like to. I think I've thoroughly explained the situation (perhaps too thoroughly - if such a thing exists) so it shouldn't be necessary for you to see code to give me a general answer. If need be, I suppose I could write up some example code that differs enough from my company's IP and post it here. Let me know if you'd like me to do that, and I'll be happy to oblige (well, not really, but I'll do it anyway).
Take a look at CSS background elements. They allow you to display DOM nodes elsewhere/repeatedly. They are of course they are read-only, but should update live.
You may still have to come up with a lot of magic around them, but it is a similar solution to:
Create static representations of the versions with Canvas elements or something.
CSS background elements are also very experimental, so you may not get very far with them if you have to support a range of browsers.
To be honest, after reading the question I almost left thinking it belongs in the "too-hard-basket", but after some thought perhaps I have some ideas.
This is a really difficult problem and the more I think about it the more I realise that there is no real way to escape needing to clone. You're right in that you can create an SVG or Canvas but it won't look the same, though with a fair amount of effort I'm sure you can get quite close but not sure how efficient it will be. You could render the HTML server-side, take a snapshot and send the image to the client but that's definitely not scalable.
The only suggestions I can think of are as follows, sorry if they are long-winded:
How are you doing this clone? If you're going through each element and as you go through each you are creating a clone and copying the attributes one by one then this is heaavvvyy. I would strongly suggest using jQuery clone as my guess is that it's more efficient than your solution. Also, when you are making structural changes it might be useful to take advantage of jQuery's detach/remove (native JS: removeChild()) methods as this will take the element out of the DOM so you can alter it before reinserting.
I'm not sure how you'v got your WYSIWYG, but avoid using inputs as they are heavy. If you must then I'm assuming they don't look like inputs so just swap them out with another element and style (CSS) to match. Make sure you do these swaps before you reinsert the clone in to the DOM.
Don't literally put video at the time of showing the user comparisions. The last thing we want to do is inject 3rd party objects in to the page. Use an image, you only have to do it while comparing. Once again, do the swap before inserting the clone in to the DOM.
I'm assuming the cloned elements won't have javascript attached to them (if there is then remove it, less moving parts is more efficiency). However, the "changed" elements will probably have some JS events attached so perhaps remove them for the period of comparision.
Use Chrome/FF repaint/reflow tools to see how your page is working when you restructure the DOM. This is important because you could be doing some "awesome" animations that are costing you intense resources. See http://paulirish.com/2011/viewing-chromes-paint-cycle/
Use CSS over inline styling where possible as modern browsers are optimised to handle CSS documents
Can you make it so your users use a fast modern browser like Chrome? If it's internal then might be worth it.
Can you do these things in Silverlight or Adobe Air? These objects get special resource privileges, so this will most likely solve your problem (according to what I'm imagining the depth of the problem is)
This one is a bit left-field but could you open in another window? Modern browsers like Chrome will run the other window in its own process which may help.
No doubt you've probably looked in to these things more than I but good luck with it. Would be curious how you solved it.
You may also try: http://html2canvas.hertzen.com/
If it works for you canvas has way better support.
In order to get your "side-by-side" original doc and modified doc.. rather than cloning all pane1 into pane2.. could you just load the original document in an iframe next to the content you are editing? A lot less bulky?
You could tweak how the document is displayed when it's in an iframe (e.g. hide stuff outside editable content).
And maybe when you 'save' a change, write changes to the file (or a temp) and open it up in a new iframe? That might accomplish your "multiple edit sessions"... having multiple iframes displaying the document in various states.
Just thinking out loud...
(Sorry in advance if I'm missing/misunderstanding any of your goals/requirements)
I don't know if it's already the case for you but you should consider using jQuery library as it allows to perform different kinds of DOM elements manipulation such as create content and insert it into several elements at once or select an element on the page and insert it into another.
Have a look on .appendTo(), .html(), .text(), .addClass(), .css(), .attr(), .clone()
http://api.jquery.com/category/manipulation/
Sorry if I'm just pointing out something you already know or even work with but your NDA is in the way of a more accurate answer.
(I am still very new to Javascript and JQuery) I am trying to keep my code clean by creating one js file per html file. I also have a couple of common js file containing code used by page-specific js files.
I was searching for solutions to import/include other js files in a given js file and I came accross this one. It refers to $.getScript().
Instead of using multiple <script src="xxx.js"></script> as imports/includes in my html pages, could I move them to my js files and use $.getScript(...) instead? I mean, is it safe? Is it a good practice?
EDIT
Is it safe regarding cyclic references?
EDIT II
To make it more clear, let's imagine I have a1.js, a2.js and a3.js.
For a1.js:
$.getScript("a2.js");
$.getScript("a3.js");
...
For a2.js:
$.getScript("a3.js");
...
For a3.js:
$.getScript("a2.js");
...
There is a potential infinite loop between a2.js and a3.js. Should I handle it with something like this in each js file:
var vari;
if (typeof vari === 'undefined') {
vari = 1;
}
Regarding good Practice that answer is as always... it depends.
Your Page Style
Contra:
If you for example use a script to substitute some page fonts, the effect will be that the font change will be even more visible for the user.
If your script changes the height of some elements, this will be very noticeable.
PRO:
If you load a Script to handle a specific form, that the user first has to edit, there is no problem at all
If you load a Script that starts animation that can be substituted with a single loading animation you can do this.
If your page is a application with many views and models, you can use getScript like getJson, in this case your page speed will greatly improve.
Your Coding Style
Not every Page and Script is structured to be used this way. JQuery's $(document).ready() fires every registered handler once, even after the event occurred. This does not necessarily mean every handler works this way, certainly not the DOM2 Events.
If you have anywhere inline Scripts it will no longer work.
You can no longer guarantee a certain order your initialization code will have, so you can run in problems have to add more checks, that e.g. a expectedly visible container is still visible.
Whats the reward?
On high performance pages, you gain some thats clear. But script tags at the end of the page can do the same thing with half the work (mainly: remove inline scripts). In my opinion getScript is something like a last reward, you should not overuse it, because the potential to not only scare other developers but also your customers away is clearly there. I would only use it in the environment of a web application, because here are the real benefits
UPDATE response to your comment
Using getScript on your page should look like this:
//since you need it there will be some kind of wrapper
var initClosure = function() {...}
if(typeof optionalNamespace == 'undefined') {
$.getScript('/foo.js', initClosure);
} else {
initClosure();
}
All depending code is in initClosure, and you check a namespace, or variable name (even something like window['blub'] or simply blub will work). You will need this, since the on getScript depending function, wich typically sets default values or appends something to the dom should only be called once.
Nevertheless I don't really see the point in cyclic references, because this would mean:
load script 1 -> wait -> loaded ->load script 2 -> wait ->loaded -> [...] ->load script 1
This situation should be avoided for at least 2 reasons
The browser can not predict this. If there are several script tags, your browser will take care of parallel downloads, so the overall speed (simplified & rough) is the time the biggest file will need to load. In my Example it will take the sum of the script loads.
Initialization of your scripts will be handled twice, so any state will get lost.
I have been struggling with choosing unobtrusive javascript over defining it within the html syntax. I want to convince my self to go the unobtrusive route, but I am having trouble getting past the issues listed below. Can you please help convince me :)
1) When you bind events unobtrusively, there is extra overhead on the client's machine to find that html element, where as when you do stuff, you don't have to iterate the DOM.
2) There is a lag between when events are bound using document.ready() (jquery) and when the page loads. This is more apparent on very large sites.
3) If you bind events (onclick etc) unobtrusively, there is no way of looking at the html code and knowing that there is an event bound to a particular class or id. This can become problematic when updating the markup and not realizing that you may be effecting javascript code. Is there a naming convention when defining css elements which are used to bind javascript events (i have seen ppl use js_className)
4) For a site, there are different pieces of javascript for different pages. For example Header.html contains a nav which triggers javascript events on all pages, where as homepage.html and searchPage.html contains elements that trigger javascript on their respective pages
sudo code example:
header.html
<script src="../myJS.js"></script>
<div>Header</div>
<ul>
<li>nav1</li><li>nav2</li>
</ul>
homepage.html
<#include header.html>
<div class="homepageDiv">some stuff</div>
searchpage.html
<#include header.html>
<div class="searchpageDiv">some other stuff</div>
myJS.js
$(document).ready(function(){
$("ul.li").bind("click",doSomething());
$(".homePageDiv").bind("click",doSomethingElse());
$(".searchPageDiv").bind("click",doSomethingSearchy());
});
In this case when you are on the searchPage it will still try to look for the "homepageDiv" which does not exist and fail. This will not effect the functionality but thats an additional unnecessary traversal. I could break this up into seperate javascript files, but then the browser has to download multiple files, and I can't just serve one file and have it cached for all pages.
What is the best way to use unobtrusive javascript so that I could easily maintain a ( pretty script heavy) website, so another developer is aware of scripts being bound to html elements when they are modifying my code. And serve the code so that the client's browser is not looking for elements which do not exist on a particular page (but may exist on others).
Thanks!
You are confused. Unobtrusive JavaScript is not just about defining event handlers in a program. It's a set of rules for writing JavaScript such that the script doesn't affect the functionality of other JavaScript on the same page. JavaScript is a dynamic language. Anyone can make changes to anything. Thus if two separate scripts on the same page both define a global variable add as follows, the last one to define it will win and affect the functionality of the first script.
// script 1
var add = function (a, b) {
return a + b;
};
// script 2
add = 5;
//script 1 again
add(2, 3); // error - add is a number, not a function
Now, to answer your question directly:
The extra overhead to find an element in JavaScript and attach an event listener to it is not a lot. You can use the new DOM method document.querySelector to find an element quickly and attach an event listener to it (it takes less than 1 ms to find the element).
If you want to attach your event listeners quickly, don't do it when your document content loads. Attach your event listeners at the end of the body section or directly after the part of your HTML code to which you wish to attach the event listener.
I don't see how altering the markup could affect the JavaScript in any manner. If you try to attach an event listener to an element that doesn't exist in JavaScript, it will silently fail or throw an exception. Either way, it really won't affect the functionality of the rest of the page. In addition, a HTML designer really doesn't need to know about the events attached any element. HTML is only supposed to be used for semantic markup; CSS is used for styling; and JavaScript is used for behavior. Don't mix up the three.
God has given us free will. Use it. JavaScript supports conditional execution. There are if statements. See if homePageDiv exists and only then attach an event listener to it.
Try:
$(document).ready(function () {
$("ul.li").bind("click",doSomething());
if (document.querySelector(".homePageDiv")) {
$(".homePageDiv").bind("click",doSomethingElse());
} else {
$(".searchPageDiv").bind("click",doSomethingSearchy());
}
});
Your question had very little to do with unobtrusive JavaScript. It showed a lack of research and understanding. Thus, I'm down voting it. Sorry.
Just because jQuery.ready() executes does not mean that the page is visible to the end user. This is a behaviour defined by browsers and these days there are really 2 events to take into consideration here as mootools puts it DomReady vs Load. When jQuery executes the ready method it's talking about the dom loading loaded however this doesn't mean the page is ready to be viewed by the user, external elements which as pictures and even style sheets etc may still be loading.
Any binding you do, even extremely inefficient ones will bind a lot faster than all the external resources being loaded by the browser so IMHO user should experience no difference between the page being displayed and functionality being made available.
As for finding binding on elements in your DOM. You are really just fearing that things will get lost. This has not really been my actual experience, more often than not in your JS you can check what page you are on and only add javascript for that page (as Aadit mentioned above). After that a quick find operation in your editor should help you find anything if stuff gets lost.
Keep in mind that under true MVC the functionality has to be separate from the presentation layer. This is exactly what OO javascript or unobtrusive javascript is about. You should be able to change your DOM without breaking the functionality of the page. Yes, if you change the css class and or element id on which you bind your JS will break, however the user will have no idea of this and the page will at least appear to work. However if this is a big concern you can use OO-Javascript and put div's or span's as placeholders in your dom and use these as markers to insert functionality or tell you that it exists, you can even use html comments. However, in my experience you know the behavior of your site and hence will always know that there is some JS there.
While I understand most of your concerns about useless traversals, I do think you are nickle and dime'ing it at this point if you are worried about 1 additional traversal. Previous to IE8 it used to be the case that traversing with the tag name and id was a lot faster than my selector but this is no longer true infact browsers have evolved to be much faster when using just the selectors:
$("a#myLink") - slowest.
$("a.myLink") - faster.
$("#Link") - fastest.
$(".myLink") - fastest.
In the link below you can see that as many as 34 thousand operations per second are being performed so I doubt speed is an issue.
You can use firebug to test the speed of each in the case of a very large dom.
In Summary:
a) Don't worry about losing js code there is always ctrl+f
b) There is no lag because dom ready does not mean the page is visible to start with.
Update
Fixed order of speed in operations based on the tests results from here
However keep in mind that performances of IE < 8 are really had if you don't specify the container (this used to be the rule, now it seems to be the exception to the rule).
Say we have 2 javascript scripts trying to change the same DOM element.
One tries to enter html into a div and the other tries to move the div elsewhere on the page (for A/B testing) and we cant predict when they will finish loading and run.
Can it mess up the html page? Do the browsers know to prevent that?
You can't really call something at the same time. JavaScript isn't multi-threaded and you can only do one thing at a time.
Your example deals with the DOM which is not really related to the JavaScript engine but the DOM engine (which is multi-threaded). You can do many things concurrently in the DOM, however, since JavaScript is the interface to the DOM, JavaScript will not let you call two DOM related functions at the same time - one will always be called before the other, even though they happen concurrently inside the DOM memory space (not JavaScript's).
Well, JavaScript is single-threaded, so although it may seem that both are executing at the same time, they aren't really. If they're modifying the same element, sooner or later one will try to modify something that doesn't exist. This case, however, will be no different from any other attempt to change a nonexistent DOM element. The browsers won't try to prevent this (after all, your code tells them to do it).