There is probably a very simple explanation for this (and likely a much cleaner way to do it), but I'm new and can't quite figure it out. Any assistance would help the learning process...
I have one script that displays one div or another based (show instead of hide) on what a user selects from a dropdown list. Like so:
var PickDiv = (function(){
var _obj = {};
var hideShow = function(elem){
if($(elem).val() === '1'){
$("#slider_action").show();
$("#slider_dollar").hide();
}else if($(elem).val() === '2'){
$("#slider_dollar").show();
$("#slider_action").hide();
}else{
$("#slider_dollar, #slider_action").hide();
}
};
_obj.checkValue = function(){
hideShow($('#build_opt'))
};
var events = function(){
$('#build_opt').change(function(){
hideShow(this);
});
};
$(document).ready(function(){
events ();
checkValue ();
});
return _obj;
}());
This works great and displays the right div based on what is selected from the dropdown. I thought I could reuse this same idea later in my code to have the same effect. Once the div is displayed (after making a selection related to script above), I need to provide another dropdown with additional options. The user will select one of these and then a div will display. So, I figured, I could use something like this:
var RunRate = (function(){
var _obj2 = {};
var hideShow_2 = function(elem_2){
if($(elem_2).val() === '6'){
$("#db_sign").show();
$("#app_down", "#in_redem", "#site_vis", "#cont_ent" ).hide();
}else if($(elem_2).val() === '7'){
$("#app_down").show();
else{
$("#app_down", "#in_redem", "#site_vis", "#db_sign", "#cont_ent" ).hide();
}
};
_obj2.checkValue_2 = function(){
hideShow_2($('#action_type_2'))
};
var events_2 = function(){
$('#action_type_2').change(function(){
hideShow_2(this);
});
};
$(document).ready(function(){
events_2 ();
checkValue_2 ();
});
return _obj2;
}());
Of course, this doesn't work. I tried a number of different things with no luck. Note that if I exclude the first script from my code, the second script works fine, so I know it works. I'm guessing it has something to do with the two scripts sharing a variable or something about jquery that I'm clearly missing.
Any help would be appreciated. Overall, looking to be able to do a number of these types of dependent dropdowns without interfering with one another.
Thanks for your help!
UPDATE:
Note that if in the second script, I replace:
$(document).ready(function(){
with
$( window ).load(function() {
then the problem is solved. So, clearly the problem is related to the document.ready interfering with each other, but I don't know how to fix this without this "hack" above especially if I want to use more of these dependent dropdowns. Is there a way to pass a different variable and call that instead of document?
UPDATE 2
Figured out the problem...my original code was throwing an error due to an undefined reference (checkValue). That error was causing the document ready to not work in the second function. Referenced a more detailed explanation in my answer below.
Figured out the problem thanks to this answer to a related question pointed out by #skmasq. #JustinWood was onto something with his comment on my original question. Turns out that my scripts were throwing an error ("Uncaught ReferenceError: checkValue is not defined"), which was not allowing the document ready function to work properly.
Here's the critical part of the answer :
It is important to note that each jQuery() call must actually return. If an exception is thrown in one, subsequent (unrelated) calls will never be executed.
This applies regardless of syntax. You can use jQuery(), jQuery(function() {}), $(document).ready(), whatever you like, the behavior is the same. If an early one fails, subsequent blocks will never be run.
Could be that $(document).ready() overwrites the initial job? It doesn't matter if you use different functions/variable names ...$(document).ready is the same ...
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
First, a little disclaimer: I'm no master at working with javaScript / jQuery, although I have handled it quite a few times. Recently, I have sometimes found myself being forced to make redundant code, i.e. repeat line(s) of code regarding diferent events, because I don't know how to do it in a more efficient way. A small example would be enabling a button after checking if any checkbox in a page is selected. This is done either upon page loading or after any checkbox is selected:
var checkboxes = $("input[type='checkbox']");
$('#nextfl').attr("disabled", !checkboxes.is(":checked"));
checkboxes.click(function()
{
$('#nextfl').attr("disabled", !checkboxes.is(":checked"));
});
I don't think this could be solved using the bind or on functions, for instance, since it refers to events not related to the same element. I believe it must exist a straightforward solution to this though, but as I said before, I have little experience in JS / jQ, and there are some similar situations where I have repeated dozens of lines of code, which is of course at least a bad practice.
You can always split redundant code into functions with javascript:
function doCheckboxLogic () {
$('#nextfl').attr("disabled", !checkboxes.is(":checked"));
// and any other logic that needs to be done
}
You then call that function in place of the redundant code block:
checkboxes.click(function()
{
doCheckboxLogic();
});
There's not much gained here since it's one line of code anyway, but this really helps with encapsulating more complicated blocks of logic
Best practice in this case is to extract common logic to its own function which can be called as required. Try this:
$(function() {
var $checkboxes = $("input[type='checkbox']");
function checkState() {
$('#nextfl').attr("disabled", !$checkboxes.is(":checked"));
}
$checkboxes.click(checkState); // run the function on click of a checkbox
checkState(); // run it on load of the page
});
I am using a function in two sites but when I want to implement it to third site it does not work. When I look into firebug console it says its not a function. My function is in a separate file called profilter.js look like this:
jQuery.fn.sfProductFilter = function (options) {
options = options || {};
return this.each(function () {
var pf = new SFProductFilter(this, options)
})
}
and I am calling it from a page and code is:
$(document).ready(function(){
console.log($("ul.productSmall"));
$('ul.productSmall').sfProductFilter(); //says not a function.
});
I have checked through console.log followings
1- jQuery is included already
2- If I console.log from the js file it works but inside any code block it does not
3- ul.productSmall shows right results in console.log
I can provide link of site but just not providing it so moderator wont thinks its a spam.
I have struggled a lot please let me know where I am making mistake?
(Copied from comment above.)
You are including jQuery twice, the second load happens far below the inclusion of profilter.js and destroys your custom function.
Are you including the jquery ui script? Looks like .button() is called in
function myChecks(){
$("#checkboxcontainer input[type='checkbox']").button();
}
I have a hunch this will do the trick.
I can set the onclick handler using jQuery by calling
$('#id').click(function(){
console.log('click!');
});
Also using jQuery, how can I get a reference to the function which is currently handling the click() event?
The reason is that I have another object and want to set its click handler to the same one as #id.
Update
Thank you for all the suggestions. The problem is that I do not know which function is currently handling the clicks. Keeping track of it would add state to an already complicated template-editing system.
jQuery's .click(function) method adds the function to a queue that is executed on the click event~
So actually pulling out a reference to the given function would probably be hairy-er than you expect.
As noted by others, it would be better to pass in a reference to the function; and then you already have the reference you need.
var clicky = function () { /* do stuff */ };
$('#id').click(clicky);
// Do other stuff with clicky
Update
If you really really need to get it out, try this:
jQuery._data(document.getElementById('id')).events.click[0].handler
Depending on your version of jQuery that may or may not work~ Try playing around with
jQuery._data(document.getElementById('id'))
and see what you get.
Got the idea from this section of the source:
https://github.com/jquery/jquery/blob/master/src/event.js#LC36
if you dont know the name of the function you can use
args.callee
https://developer.mozilla.org/en/JavaScript/Reference/Functions_and_function_scope/arguments/callee
function clickHandle(e){
if($(e.target) == $('#id')) {
$(newTarget).bind('click', clickHandle);
}
}
$('#id').bind('click',clickHandle);
I think this would be the most symantic way of going about it
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