Detect what javascript function is changing a page element on load? - javascript

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

Related

How to modify results from a read-only ng-app?

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

Query.swap Backwards compatibility

I'm trying to use a datepicker known as flexcal on my site. It is important for me to use it accurately, to allow selection from a Jewish calendar. My site is based on jQuery v3.3.1, but flexcal was designed for jQuery v2.1.3. I thought it shouldn't cause any problems, but I came across the following error:
Uncaught TypeError: $.swap is not a function
After searching, I found here that this is a method that was intended to be private and was never documented, Anyway at the moment I'm having trouble embedding a widget on my site. Reviewing the source code of the widget reveals that the use of the method looks like this:
return $.swap(
parent,
{display:'inline-block'}, // make it visible but shrink to contents
swapper.bind(this, elem, parent.parentNode)
);
Does anyone know what the purpose of the method is, does it have a parallel alternative, or some other troubleshooting advice?
If you look at the source of jQuery.swap (https://github.com/jquery/jquery/blob/3.4.1/src/css/var/swap.js), you'll see that all it does is temporarily change some CSS attributes of the first argument (parent, in your case), run a calculation, and the restore the original attribute values. You can implement that yourself. It's particularly easy in your case, since the only CSS attribute we're temporarily changing is display:
var old_display = parent.style['display'];
parent.style['display'] = 'inline-block';
var ret = swapper.bind(this, elem, parent.parentNode).apply(parent, []);
parent.style['display'] = old_display;
return ret;

How to re-evaluate a script that doesn't expose any global in a declarative-style component

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.

Why does this simple login script work in Tampermonkey but not Greasemonkey?

I have this tiny little script that I run inside Chrome using Tampermonkey and works great.
However, when I use it in Firefox with Greasemonkey, it shows up on the active list, meaning its matching the page but it doesn't actually execute the code. I know it has to be a simple something I am overlooking but its not hitting me.
var myVar=setInterval(function(){myTimer();},100);
function myStopFunction()
{
clearInterval(myVar);
}
function myTimer()
{
var p1 = "Login";
var p2 = "mode=login";
var x = document.body.innerHTML;
if (x.match(p1) && x.match(p2)){
document.documentURI = "/ucp.php?mode=login";
}
myStopFunction();
}
Script Logic/Function
I am using a timer to prevent the script from triggering over and over in a permanent loop.
It simply detects if I am logged into a phpBB forum or not, if not send me to the login page so I can log in.
I am using document URI so that the location of the original is preserved so upon login, it takes me right back to it.
Often phpBB when you log in, it will take you back to the index page so this preserves my original intent of going to the actual link.
This script works perfectly and as expected on Chrome using TM but on Firefox using GM it doesn't trigger, am I missing something here?
From the Firefox spec:
(document.documentURI)
Returns the document location as string. It is read-only per DOM4 specification.
And, indeed, the latest spec still specifies that this attribute must be read only.
If Chrome lets you write this property, then that is non-standard behavior and maybe a bug.
Use location.assign(), or location.replace(), or just programmatically click the login button -- which often preserves the target page.

Javascript execution order via HTML5 script tag with async attribute

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).

Categories