Hiding an element across multiple pages - javascript

I'm creating a chrome extension that hides a jQuery element on the page when clicked.
I need to store a reference to this element in the chrome.storage API, so when the page is loaded at a later date I can have that element hidden again.
I know the DOM tree is rebuilt on page load, and I'm not sure if this will effect anything. The element could be anything on the page as well, so not necessarily having a class/id name.
What is the best way to go about storing the reference? I'm all out of ideas on how to do this (brand new to JavaScript).
Update
As suggested by Xan, I am now using xPath to store a reference to the element.
//Get the element
var elem = e.target || e.srcElement;
$(elem).click(function () {
xPathOfElem = getElementXPath(elem); //Get xPath of element
updateStorage(xPathOfElem);
$(elem).hide("");
return false;
});
//Store it
function updateStorage(xPathOfElem) {
chrome.storage.sync.set({"element":xPathOfElem} //set xPath to storage
, function (data) {});
};
//Retrieve it on load later
function getStorage() {
chrome.storage.sync.get(null, function (data) {
$(getElementsByXPath(document, data.element)).hide(""); //get and hide element
});
}
window.onload = function () {
getStorage();
};
As Xan mentioned, not a perfect approach if the page isn't static but it does what I need it to

This is a very broad question and the reason is: in general, there is no way you can reliably pinpoint an element on a page, especially if it's dynamic.
There is no "single" solution that works for every page. However, assuming that you can devise a method of pinpointing an element, you should look into DOM XPath.
It's a rich way of describing how to find an element, much more general than class/id name. And it's just a string, so it can easily be stored. Once you have this description, you can find the element using document.evaluate.
The downside is, there is no such thing as "the XPath" of an element. You need to come up with your own method of constructing one for a given element, and like I said to do so automatically is nigh-impossible. You're certainly welcome to try an cover many common cases, but finding a universal solution is hopeless.
P.S. See this question for finding "an XPath" of an element. Again, would only work reliably in a static page.

Related

Detect newly inserted element in the document

I'm trying to detect if an element with a specific tag name has been inserted into the document. I am aware of DOMSubtreeModified and MutationObserver and I know that they can detect changes in the elements, but if the document is big and many changes are applied to the document, these two methods can become quite heavy.
One of the ideas I had was to collect all elements using getElementsByTagName and then detect a change of HTMLCollection's length property but I didn't find any method that could watch this property and trigger an event.
Another idea I had was to set an interval, but the problem with this is that an item can be deleted and inserted in between the timer and this wouldn't be detected in the interval's function.
Is there any efficient way of detecting new element insertion in the whole document? Alternatively, how can I detect change of HTMLCollection's length property?
Thanks for any answer.
Here is a thought:
var cnt=0;
var f = Element.prototype.appendChild;
Element.prototype.appendChild = function(){
f.apply(this, arguments);
console.log("added",++cnt)
};
However you will need to see if it is the same element that is added and I have not figured out how to check the remove since that is parentNode.removeChild
If you want events on all selects in jQuery all you have to do is delegate
$(document).on("change","select",function() {
// all current and future selects will have this event
});

JavaScript Id Handling

I'm developing a website that allows users to open multiple pages of the same content in the same browser window via inline 'windows'.
As the content can be repeated multiple times the id's can in turn be the same and therefore I have to "handle" them each so that I can distinguish between these pages.
I currently do this by assigning on load a unique id to the script like so:
var id_key;
function load_page() {
id_key++;
load_script("test.js") //load javascript file
}
//test.js:
$(function () {
var unique_id = id_key;
//adds the `unique id ` to the end of all elements with an id attribute set. ie `mycontainer` becomes `mycontainer_1`
update_ids(unique_id);
$("#mybtn_ " + unique_id).click(function () {
//do stuff
});
}
This works fine most of the time however if multiple pages are loaded too fast the Id tends to get overwritten causing confusion and errors.
I am wondering if there is a better technique of this doing this. I have heard of backbone.js but I am not sure whether that would be helpful in this case.
There are several general approaches to solve this kind of problem:
Load the sub pages in iframes. Each iframe gets it's own ID space. Scripts in all frames can talk to each other via the parent variable as long as all documents were loaded from the same domain.
Don't use any ids. Instead, give each "window" an ID and then locate elements in the window via classes and parent-child relations. Note that an element can have more than one class.
You can then use $(selector, win) to look for elements on a certain window win. The window becomes the "Selector Context" which means jQuery will search only children of the window and nothing else.
At the start of your script, locate all important elements by ID and save them in a JavaScript object. That way, you can access them without using a jQuery selector.
For example, you could select anything with an ID and save it with .data() in the window element. After this setup, all elements would be accessible via $(win).data('id')
You can generate quite good unique ids by concatenating a date and a random number:
new Date().getTime() + Math.random()
While this is by no means perfect, I think in your use case it will suffice.
As Jack mentioned in his comment, you can pass this id to your new window as a get parameter. I once did a whole OS-like interface with this method, and it worked flawlessly.

How do I store 'this' element for later

I am new to JavaScript and jQuery so let me know if I'm way off base.
I am using the cookie plugin from here: https://github.com/carhartl/jquery-cookie.
I have a page with dynamic links. They send the user to the same page they are already on with some GET information in the URL. I want to be able to highlight the previous link clicked.
My idea was to store the element that is clicked in a cookie and then add a class to that element.
Setting the cookie
$("td.column1").on({
click: function () {
$.cookie('productCookie', this); //How do I store 'this' into the cookie?
}
});
Getting the cookie
var productValue = $.cookie('productCookie');
$(productValue).addClass("select singleselected");
Code without cookies
I know from experience that this line of code worked before I added the links, which is also before the page was being redrawn.
$(this).addClass("select singleselected");
I've tried looking into how the this keyword works but I'm afraid I'm not sure what selector the addClass method is getting or how to get the current element.
Solution Used
function getParameters(geturl, columnNumber) {
var url = geturl,
urlRegExp = new RegExp(url.replace(/\/$/, ''));
$(columnNumber).each(function () {
if (urlRegExp.test(this.href)) {
$(this).addClass("singleselected select");
}
});
}
Check this thread out: jQuery add class .active on menu
The basic idea is that you parse the current url and find the anchor tag that is active based on the url.
If your elements have IDs, you can save the element's ID in a cookie and highlight it on the next pageload. Cookie (and localStorage, for that matter) can only store strings, or things that can be serialized to a string. HTML elements are not serializable.
Cookies hold strings. You will need to store some string-based indicator for which link was clicked.
It could be an id or a row number in your table or something like that. Then, when you read the cookie upon page load, you can find the DOM element that matches that indicator and change it accordingly.
You cannot store a DOM element reference in a cookie as DOM element references are not strings and they are not persistent from one page load to the next. A specific DOM element reference only exists during a given page.
If you show the actual HTML that covers what you want to save, we could make a more specific reference about what would make sense to save in the cookie. If it's a table, perhaps a row/column count (converted to string form). If every link has a unique id, then the easiest is to just store the id.

'document' vs. 'content.document'

I'm trying to write a Firefox extension that adds elements to the loaded page. So far, I get the root element of the document via
var domBody = content.document.getElementsByTagName("BODY").item(0);
and create the new elements via
var newDiv = content.document.createElement("div");
and everything worked quite well, actually. But the problems came when I added a button with on onclick attribute. While the button is correctly displayed, I get an error. I already asked asked here, and the answer with document.createElement() (without content) works.
But if I remove the 'content.' everywhere, the real trouble starts. Firstly, domBody is null/undefined, no matter how I try to access it, e.g. document.body (And actually I add all elements _after_the document is fully loaded. At least I think so). And secondly, all other elements look differently. It's seem the style information, e.g., element.style.width="300px" are no longer considered.
In short, with 'content.document' everything looks good, but the button.onclick throws an error. with only 'document' the button works, but the elements are no longer correctly displayed. Does anybody see a solution for that.
It should work fine if you use addEventListener [MDN] (at least this is what I used). I read somewhere (I will search for it) that you cannot attach event listener via properties when creating elements in chrome code.
You still should use content.document.createElement though:
Page = function(...) {
...
};
Page.prototype = {
...
addButton : function() {
var b = content.document.createElement('button');
b.addEventListener('click', function() {
alert('OnClick');
}, false);
},
...
};
I would store a reference to content.document somewhere btw.
The existing answer doesn't have a real explanation and there are too many comments already, so I'll add another answer. When you access the content document then you are not accessing it directly - for security reasons you access it through a wrapper that exposes only actual DOM methods/properties and hides anything that the page's JavaScript might have added. This has the side-effect that properties like onclick won't work (this is actually the first point in the list of limitations of XPCNativeWrapper). You should use addEventListener instead. This has the additional advantage that more than one event listener can coexist, e.g. the web page won't remove your event listener by setting onclick itself.
Side-note: your script executes in the browser window, so document is the XUL document containing the browser's user interface. There is no <body> element because XUL documents don't have one. And adding a button won't affect the page in the selected tab, only mess up the browser's user interface. The global variable content refers to the window object of the currently selected tab so that's your entry point if you want to work with it.

Manipulate href of anchor based on class in Prototype

I'm trying to manipulate some anchors after the document is loaded to alter their href attribute using Prototype. Based on the class of an anchor, its href has to be rewritten to be used by a tracking software.
I hacked some things together but can't quite figure out why it doesn't work. It always returns undefined, not matter what I search for.
<script type="text/javascript">
var elements = $$(".replace");
for (el in elements) {
el.href = "other_href.html";
}
</script>
Link
When I alert(el) in the loop, it returns either undefined or (when I search for a.replace an extraordinary number of elements that don't even exist on my page.
Where is my thinking error in here?
Untested:
<script type="text/javascript">
document.observe('dom:loaded', function() {
$$(".replace").each(function(a) {
a.writeAttribute('href', 'other_href.html');
});
});
</script>
Link
I'm guessing your JavaScript was loaded and executed before the HTML it was supposed to manipulate was loaded by the browser. It can't manipulate what doesn't exists yet. That's where observing events comes in handy. In this case, we're waiting for the DOM to finish loading, but before the page is rendered by the browser, to make our changes.
I also took advantage of Prototype's each() functionality. It's a great way to loop through arrays and any enumerables. And writeAttribute() is a good cross-browser way to modify element attributes.

Categories

Resources