I wrote a simple javascript function that creates a DOM object (in this case a tag ) and I call it in the of my html page and it doesn't seem to work. Any ideas?
function create_link() {
var link = document.createElement("a");
link.setAttribute('href', 'the_link.html');
link.setAttribute('name', 'click on link');
document.childNodes[0].childNodes[1].appendChild(link);
}
The createElement and setAttribute calls are fine, are you sure document.childNodes[0].childNodes[1] is defined?
To test, you can do: document.body.appendChild(link);, which should work.
The problem is likely with document.childNodes[0].childNodes[1]. It's suggested that you use document.getElementById(id) instead, especially since this is more resistant to changes in your HTML structure that may later be made.
In general, avoid using childNodes to navigate to specific parts of the DOM.
Related
I apologize if this is a duplicate, just haven't been able to find anything close to this myself.
The company I work for has an online reporting system that is run by an ng-app applied directly to the body tag. I have been tasked with modifying the result that returns from this ng-app. Following code is called using onload attached to the body tag.
function getElements(){
var list;
list = document.getElementsByClassName("neutral");
[].forEach.call(list, function (listItem) {
addNeutral(listItem);
});
...
Basically, trying to find anything with class "neutral" and apply results from another function to it. The addNeutral function is basically just
element.classList.add("neutralHighlight");
This code seems to run and gathers the correct list of elements, but the new class is never added and no errors occur. So long story short, is there any way to modify the output of a ng-app using code separate from the ng-app itself? Any guidance would be greatly appreciated.
Update 3/5/20
So I implemented Shaun's response and it still isn't working properly. With some debug messages, I can see that it collects the "list" variable as an HTMLCollection. The forEach function doesn't seem to even trig
function getElements(){
var list;
list = document.getElementsByClassName("neutral");
console.log(list); //Debug - Shows in console
[].forEach.call(list, function (listItem) {
console.log(listItem); //Debug - Does not show in console
addNeutral(listItem);
});
}
function addNeutral(element){
angular.element(element).addClass("neutralHighlight");
console.log("!!!end addNeutral"); //Debug - Does not show in console
}
Update 3/9/20 -SOLUTION-
Application is returning the HTML Collection, but it displays with a length of 0 (still displays the objects, but I think that's a Firefox console thing). When trying to loop through the list items, it returns null for the first item, so the function is still being called before the Angular app loads completely.
That being said, I messed around with things a bit this morning and came to a solution! I ended up using the setInterval function with a 5 second interval (since I need it to update, I may change this to optimize it later by adding onChange items to the objects I grab initially). The setTimeout that was proposed would have worked with a delay added to it. This probably isn't the most elegant solution, and there's probably a better way to do it, but this works for anyone interested.
function getElements(){
var list;
list = document.getElementsByClassName("neutral");
for (i = 0; i <= list.length; i++){
var listItem = list.item(i);
addNeutral(listItem);
}
}
function loadFunction(){
setInterval(function(){getElements()}, 5000);
}
I added <script>loadFunction()</script> right before the closing HTML tag to execute.
Update 4/21/20 -IMPROVED SOLUTION- CSS Attributes
So this is a bit specific to my scenario, but I wanted to include it for anybody else who may come across this in the future. I was actually able to accomplish this entirely with CSS attribute selectors. The tags that I wanted to edit all had specific titles assigned to them via the ng-app provided from the company, so I was able to use CSS selectors like div [title~="NotReadyForNextCall"]{<custom styling here>} to select any block that included an agent who was not ready for their next call. This is a much better solution in terms of resources required to operate and overall performance, so I hope it helps anybody looking at this down the line!
You might be able to get around this by using the angular object in your code and adding the class on an angular.element instead. AngularJS doesn't use a virtual DOM but it does use its own node references (which is what makes it so tricky to work with outside of the framework, as Lex pointed out in the comments of your question). Try:
angular.element(element).addClass("neutralHighlight");
Yes, you have access to angular outside of the app! And a last note, addClass() is available on angular.element because AngularJS comes with jqLite.
Further investigation
It looks like the above solution works if the class 'neutral' is being added in angular via the class attribute, but it looks like your app may be adding it programmatically with the ng-class directive after the DOM has rendered.
I wrapped your getElements() function in a setTimeout():
setTimeout(getElements);
This is unfortunately not a guarantee that the ng-class update will have taken place, but what it does is it executes the function after the previous digest cycle has completed and that appears to be working.
An even safer solution would be to use document.ready but again with the angular.element wrapper. This will ensure the initial DOM state has been rendered by AngularJS, including applied directives:
angular.element(document).ready(function() {
getElements();
});
EDIT: Update 3/9/20 -SOLUTION-
The solution proposed in the answer is almost identical to the setTimeout() answer given here. The only difference is setInterval() will keep executing the code every 5 seconds until you tell it to stop.
You can do this with the following:
var loadFunction = setInterval(function() {
var el = getElements();
if (el) clearInterval(loadFunction);
}, 5000);
And just return a bool in your getElements() like so:
function getElements() {
var list;
var found = false;
list = document.getElementsByClassName("neutral");
[].forEach.call(list, function (listItem) {
addNeutral(listItem);
found = true;
});
return found;
}
See: codepen.io/shaunetobias/pen/KKpXRxq
I have been writing a reusable script, let's call it a plugin although it's not jQuery, that can be initialised in a declarative way from the HTML. I have extremely simplified it to explain my question so let's say that if a user inserts a tag like:
<span data-color="green"></span>
the script will fire because the attribute data-color is found, changing the color accordingly.
This approach proved very handy because it avoids anyone using the plugin having to initialise it imperatively in their own scripts with something like:
var elem = document.getElementsByTagName('span')[0];
myPlugin.init(elem);
Moreover by going the declarative way I could get away without defining any global (in this case myPlugin), which seemed to be a nice side effect.
I simplified this situation in an example fiddle here, and as you can see a user can avoid writing any js, leaving the configuration to the HTML.
Current situation
The plugin is wrapped in a closure like so:
;(function(){
var changeColor = {
init : function(elem){
var bg = elem.getAttribute('data-color');
elem.style.background = bg;
}
};
// the plugin itslef looks for appropriate HTML elements
var elem = document.querySelectorAll('[data-color]')[0];
// it inits itself as soon as it is evaluated at page load
changeColor.init(elem);
})();
The page loads and the span gets the correct colour, so everything is fine.
The problem
What has come up lately, though, is the need to let the user re-evaluate/re-init the plugin when he needs to.
Let's say that in the first example the HTML is changed dynamically after the page is loaded, becoming:
<span data-color="purple"></span>
With the first fiddle there's no way to re-init the plugin, so I am now testing some solutions.
Possible solutions
Exposing a global
The most obvious is exposing a global. If we go this route the fiddle becomes
http://jsfiddle.net/gleezer/089om9z5/4/
where the only real difference is removing the selection of the element, leaving it to the user:
// we remove this line
// var elem = document.querySelectorAll('[data-color]')[0];
and adding something like (again, i am simplifying for the sake of the question):
window.changeColor = changeColor;
to the above code in order to expose the init method to be called from anywhere.
Although this works I am not satisfied with it. I am really looking for an alternative solution, as I don't want to lose the ease of use of the original approach and I don't want to force anyone using the script adding a new global to their projects.
Events
One solution I have found is leveraging events. By putting something like this in the plugin body:
elem.addEventListener('init', function() {
changeColor.init(elem);
}, false);
anybody will be able to just create an event an fire it accordingly. An example in this case:
var event = new CustomEvent('init', {});
span.dispatchEvent(event);
This would re-init the plugin whenever needed. A working fiddle is to be found here:
http://jsfiddle.net/gleezer/tgztjdzL/1/
The question (finally)
My question is: is there a cleaner/better way of handling this?
How can i let people using this plugin without the need of a global or having to initialise the script themselves the first time? Is event the best way or am I missing some more obvious/better solutions?
You can override Element.setAttribute to trigger your plugin:
var oldSetAttribute = Element.prototype.setAttribute;
Element.prototype.setAttribute = function(name, value) {
oldSetAttribute.call(this, name, value);
if (name === 'data-color') {
changeColor.init(this);
}
}
Pros:
User does not have to explicitly re-initialize the plugin. It will happen automatically when required.
Cons:
This will, of course, only work if the user changes data-color attributes using setAttribute, and not if they create new DOM elements using innerHTML or via some other approach.
Modifying host object prototypes is considered bad practice by many, and for good reasons. Use at your own risk.
Aloha. I have been working on a script and though I understand documentation of each constituent of the problem (and have looked over many other questions on SO), I don't understand this specific behavior in practice. Please be aware that the following code is an abbreviated subset that isolates the specific issue. Here is async.html:
<!doctype html>
<html><head><script type="text/javascript" src="asyncTest.js" async="true"></script></head>
<body><ul id="menu"><li>one</li><li>two</li><li>three</li></ul></body></html>
And here is asyncTest.js:
var _site = function() {
var load = function() {
var menuCategory = document.getElementById('menu').getElementsByTagName('li');
for(var i=0; i<menuCategory.length; i++) { alert(i+'['+menuCategory[i]+']'); }
};
return { load:load };
}();
window.addEventListener('load',_site.load(),false);
The problem is that without the async attribute in the <script> tag, this code does not properly store the <li> elements into menuCategory, as though it were running prior to the DOM being loaded (even though I thought it should fire after the entire window "object" loads). I find that strange because I am using the addEventListener() to try and run this only after the whole thing has been loaded (and it appears to run at the appropriate time in Chromium, FF, and Opera -- at least what appears to be the "appropriate time"). If anything, I think that the opposite would cause this behavior.
Can someone explain this, preferably using the old Einstein "explain it like you're explaining it to a six-year-old"? I'm obviously missing something in my reading. Thanks!
As mentioned by RobG in the comments, the problem here is that using _site.load() (with parenthesis after the call) is causing the function to be executed AND THEN assigned to the onload event. The way to correct this behavior to the desired functionality is to call it without the parenthesis: _site.load (or _site().load).
I'm trying to get a consistent, cross browser outerHtml for a jQuery element, particularly $("html").outerHtml(). For example if the page source is this:
<html><script src="blah.js"></script><div class="foo" id='bar'></p></div></html>
I want to be able to use $("html").outerHtml(); to get the HTML string including the element itself, which would be:
<html><script src="blah.js"></script><div class="foo" id="bar"><p></p></div></html>
I've been using Brandon Aaron's outerHtml plugin which looks something like this:
return $('<div>').append($("html").first().clone()).html();
However this seems to actually try to reload any externally referenced files in the document (scripts, stylesheets), which seems pretty excessive just to get the HTML source of a wrapper element. Can Javascript even put an HTML element inside a DIV element?
Locally, I get this error. Something to do with AJAX rules?
XMLHttpRequest cannot load file:///C:/demo.js?_=1311466511031. Origin null is not allowed by Access-Control-Allow-Origin.
Is there a better way to get outerHtml? I'd really like to avoid any network calls when doing this.
Wrote my own solution, which simply renders the wrapping element:
(function($){
$.fn.outerHtml = function() {
if (this.length == 0) return false;
var elem = this[0], name = elem.tagName.toLowerCase();
if (elem.outerHTML) return elem.outerHTML;
var attrs = $.map(elem.attributes, function(i) { return i.name+'="'+i.value+'"'; });
return "<"+name+(attrs.length > 0 ? " "+attrs.join(" ") : "")+">"+elem.innerHTML+"</"+name+">";
};
})(jQuery);
https://gist.github.com/1102076
jQuery outerHTML plugin from https://github.com/darlesson/jquery-outerhtml uses browser's native outerHTML when existent, as second option clone the nodes in a temporary document fragment and return its innerHTML or uses jQuery clone solution similar to Brandon Aaron's plugin.
This plugin might help you avoid the load of the reference files. In my tests in Chrome, Firefox and Internet Explorer the issue did not happen calling the code below in a page with external JavaScript files.
var outerHTML = $(document.documentElement).outerHTML();
I have lots of jquery that is running on page load and it's causing one of my elements to disappear (making it style=display:none;). I'm guessing the jquery hide() function is being applied to this element by mistake, but I can't find out which statement is causing it.
Is there any javascript,firebug,etc function or tool that will essentially act as a breakpoint/listener for the html element (perhaps one I can run in my browser)? It would be great to see what function is affecting that element on page load.
Hah! I just found the answer I wanted. Firebug has a "break on mutate" function on the HTML tab that will show you what line in the javascript file is changing an html element. It is in Firebug 1.5 and the new 1.5.2 update released today moved the icon, so I noticed it and found it purely by luck!
http://www.softwareishard.com/blog/firebug/firebug-15-break-on-next/
Thanks for your attempts guys!
You can find where the element is being accessed with getters/setters. However, the setter method doesn't seem to work with chrome... it works with the latest version of minefield [firefox beta], though... so you could test it out I guess =)
myElement.style.__defineSetter__("backgroundColor", function(val)
{
var cur = arguments.callee;
var callLog = "";
while(cur != null)
{
callLog = cur.caller + "\n" + callLog;
//alert(cur.caller);
cur = cur.caller;
}
alert(callLog);
}
);
Oh, but it won't let you actually "set" the variable. to do that, you define a dummy variable and set that. Then, when you define your getter [with __defineGetter__] you return that value