I have a web page which has four tabs at the top. Clicking one of the tabs displays the appropriate page beneath. The tab selection and display is controlled by a js/jQuery function I've called 'changeTab'. Nothing uusual there.
I want to set up a (different) JS function for each tab, to run when that tab is displayed, similar to the way jQuery 'document.ready' works when the main page itself is loaded. I can put a function call at the bottom of my 'changeTab' function, such as 'tabLoaded()'. But that obviously only calls the same one function each time.
I can name the functions 'tab_1_Loaded', 'tab_2_Loaded' etc. ,but then I need some way of dynamically modifying the function call so that the number of the tab is included (I already have the tab number, I just need to work out how to insert it into the function call).
What I am hoping for is a function call like:
tab_[insert tabNum dynamiclly here]_Loaded();
Is that possible in a few lines of code?
I have read articles on Stackoverflow, but they seem do address a different problem of creating (new?) functions with a dynamically derived name. I can be quite clear what my functions are called. I need a dynamically derived call. I suspect it may be possible with 'eval' but my reading also suggests eval is to be avoided, so I've not pursued it.
My fall-back is a series of conditionals:
if(tabNum == 1) tab_1_Loaded();
if(tabNum == 2) tab_2_Loaded();
etc.
but it seems inelegant (though simple) and it certainly works in this case where the number of possibilities is small. Is there a better way that's also simple?
LATER: I've subsequently realised there's an additional complication for the particular page/tabs I'm working on right now, (though it won't apply to the entire site). This page is for on-line booking. The first tab is the booking form (visitor enters dates, number of people). The second and subsequent tabs aren't populated until the visitor clicks 'Next' and moves on to the next stage. Consequently any function call in the 'changeTabs' function is made before the contents of the tab have actually loaded, so it does't work.
To deal with that I'm going to put the call into a script at the bottom of each tab contents. I expect there are more elegant ways of doing it, but it's only one line of code, whereas all the offered solutions are actually more verbose (and harder for me to understand). I will probably still need the call from 'changeTab' to cope with the visitor flicking through the tabs before finalising the booking.
When press the tab, the call back function will always be invoked, no matter what how many call back functions all will be invoked. You cannot conditionally invoke a callback function from a key press. Ideal way to implement this would be
i) Have a single call back function for the tab event
ii) Identify the id of the element, that is currently on focus when tab is pressed inside that call back function
iii) Add conditions based on that element on focus to have your logic of functions for respective elements
Yes it's possible to do what you're asking. All functions and variables declared with global scope are methods and properties of the window object, so you can build the name of the your function as a string and reference it via bracket notation.
So assuming you have tabNumber already stored in a variable:
var functionName = "tab_"+tabNumber+"_Loaded";
window[functionName]();
(See https://codepen.io/slynagh/pen/MMVEoE)
But a better approach would be to use callback functions or else use one tab_Loaded() function which accepts the tabNumber as a parameter, eg:
function tab_Loaded(tabNumber){
if(tabNumber === 1) { do something }
else if(tabNumber === 2 ) {do something else}
//etc
}
Related
I have created one event based rule-
In adobe analytics portion I have set s.t call but how can I delay this call - “s.t call”
Here i have attached one screenshot where i actually set.
enter image description here
DTM does not currently offer a way to delay the s.t (or s.tl) trigger in Event Based Rules (EBR), so you will have to write your own javascript that ultimately makes the s.t call.
Since your screenshot shows the Adobe Analytics (AA) section in your EBR, it sounds like you have AA setup as a Tool in DTM. DTM lets you reference the AA s object within the custom code section of the AA section of a tool, but it doesn't have an official way to get a reference of AA s object outside of that scope.
So in order to achieve this, you have to put the s object into the global (window) scope in the Tool config. You can do this by adding a line in the custom code box of the AA Tool config
window.s = s;
(or if you changed the namespace to something else in the tool config, use that instead).
Next, a note about using this - I noticed in your screenshot for Page Name you have %this.getAttribute(data-search-pagename)%. Because you want to delay the s.t call, whatever you are doing (e.g. a setTimeout, popping it inside some ajax callback function, etc.) will almost certainly take you out of the scope where this properly references the element the EBR is based off of, so you will no longer be able to use this to populate the page name.
To get around this, go to the Conditions section of the EBR. Under Rule Conditions > Criteria, choose Data : Custom. In the Custom code box, you can add the following:
_satellite.setVar('data_search_pagename',this.getAttribute('data-search-pagename'));
return true;
The above will push the data-search-pagename attribute value to a Data Element named 'data_search_pagename' (or name it whatever you want, according to your conventions). The return true; part will always make this condition true, so that it is "invisible" to your rule and not actually affect whether or not the rule should trigger.
Finally, in the EBR, you will not use the AA tool section (choose the disabled option in that section). Instead, you will need to create and add your code in the Javascript / Third Party Tags section of the EBR. You will need to set whatever AA vars you had in the AA section as regular javascript variables, and for the Data Element %data_element% syntax, instead use the _satellite.getVar('data_element') javascript syntax .
I'm not sure what kind of delay you are looking to do, but here's a 100ms delay as an example (based on what is shown in your screenshot):
window.setTimeout(function() {
var s = window.s;
s.pageName = _satellite.getVar('data_search_pagename');
s.channel = _satellite.getVar('util_channel');
s.t();
},100);
If you have lots of these types of events on your site where the page view call needs to be more controlled, I would recommend using a direct call rule and aborting the initial AA PV call. In fact, it is common to create a "global page load" rule for single page applications (SPA) or for when you are waiting for ajax to return search results - for example
Here's a jQuery example of using a direct call rule within a search results page rule that will fire after ajax has completed and returned:
Javascript: jQuery - wait for callback and call DC rule
/* Ajax Detection on SRP */
$(document).ajaxComplete(function( event, xhr, settings ) {
_satellite.track('my_pageView_rule_here')
})
Hope this helps.
jQuery('selector1').click(function() {
s.prop3 = 'loremmmmm';
s.events = 'event11';
s.tl();
});
jQuery('selector2').click(function() {
s.prop14 = 'lorem isam';
s.events = 'event32';
s.tl();
});
`trying to track multiple prop and events when tracking one prop say s.prop2 = "";
and s.prop3 = "".
in this case getting value of s.prop3 also while tracking s.prop4 value it is not emptying the previous prop values any suggestion thanks in advance
Overall, your code should be changed to the following, but before you change it, please read my notes below, which explains the changes and the implications of them, versus what you have now.
jQuery('selector1').click(function() {
s.prop3 = 'loremmmmm';
s.events = 'event11';
s.linkTrackVars='prop3,events';
s.linkTrackEvents='event11';
s.tl(true,'o','selector1 clicks');
});
jQuery('selector2').click(function() {
s.prop14 = 'lorem isam';
s.events = 'event32';
s.linkTrackVars='prop14,events';
s.linkTrackEvents='event32';
s.tl(true,'o','selector2 clicks');
});
Firstly, a definition of Adobe Analytics (AA) "trigger" methods.
s.t() - This is meant for "page view" tracking, what you normally use to trigger AA calls when a page is first loaded. Data collected will count as a page view in reports. AA variables that have values when this gets called will be included in the http request.
s.tl() - This is meant for click (interaction) tracking, what you normally use to track link clicks or other interactions after a page is loaded. This will not count as a page view in your reports. Only variables and events that are set and registered in linkTrackVars and linkTrackEvents will be included in the http request. Note: other variables that are set are still there and in the cache; they just won't be included in the http request. So, think of linkTrackVars and linkTrackEvents as whitelists for the s.tl call.
Variable caching
AA "caches" variables that are explicitly set (e.g. s.prop1='foo';). Those variables continue to exist with their values for any subsequent s.t() or s.tl() call you make on the same page (it does not carry over from page to page via cookies).
Your current code
When you call s.tl() with no arguments passed, AA treats it as if s.t() were called, so any AA variables or events (assuming you don't overwrite them) already set will be included in the http request, even if they aren't "registered" in linkTrackVars and linkTrackEvents. This is the immediate reason why your variables are carried over. However, I want to also point out the fact that your code is also effectively counting these click interactions as page views, which is probably not what you intended.
What the new code does
The new code I have shown is under the assumption that you don't actually want these clicks to count as page views. So, I have added linkTrackVars and linkTrackEvents to "register" the events and variables.
Also notice how I added some arguments to s.tl. The first argument is traditionally a reference to the link that was clicked (e.g. in the click callback, where this is a reference to the link that was clicked, you would pass this as the first argument to s.tl. However, not all interactions on a site are actual links, and s.tl only works if the first argument is either a reference to an actual link object (more accurately, something with an href attribute), or boolean true. Also, the reason for passing it was for a legacy ClickMap feature that's always been buggy and is no longer supported by Adobe anyways. So, I always just pass true.
The second argument specifies what type of link or interaction it is. There are 3 available values: "d" (signifying download is initiated),"e" (specifying an exit from the site), and "o" ("other" - a general "catch-all" bucket). I don't know the context of these event handlers you have, so I just used "o". Feel free to use one of the other values if you feel they are more appropriate.
The 3rd argument is a string value to describe the link/interaction; a "label". Generally you should use something short but descriptive of the event that occurred, but honestly, most people don't really look at the native link reports in AA interface because they are basically useless as far as breaking it down or associating it with downstream activities. Which is why most people pop custom events, props, and eVars, and look at those reports, instead. So, more than likely you can just put some static, generic "click/interaction" type value (you must pop 3rd arg with something) and call it a day.
If you did indeed intend to count these as page views
Remove linkTrackVars and linkTrackEvents lines.
Remove the s.tl(..) calls and replace with s.t() (no arguments).
This is where it gets tricky - you must explicitly wipe any AA variables you do not wish to be part of the hit. You can set them to an empty string or delete them.
On that 3rd point, as you have probably guessed, that's a pain point. There are some easier workarounds for this, but I don't know the full context of your implementation to know if they are good options for you (or even available options).
For example, AA does have a s.clearVars() method but it is only available in (relatively) recent versions of the AppMeasurement library. So if you are still on the legacy H code library, or on one of the earlier versions of AppMeasurement, then this method won't be available. If it is available in your library version, then just call that first (no arguments). Then set your variables and s.t() call.
If s.clearVars() is not available to you, you can of course just define your own method. Essentially, s.clearVars() just loops through and deletes or sets empty string to all propN and eVarN variables, as well as most of the named AA variables (pageName, channel, events, etc.). Same thing as above: first call it to wipe the vars, then set the new ones and then trigger.
Depending on what version of AA code you use, it is possible to pass AA vars as an object payload (e.g. {prop1:'foo',events:'event'} as an argument to s.t() and s.tl() and they will only count for that http request and afterwards be wiped. But there are a number of things quirks/caveats to consider if you want to go this route, which is a whole other TL;DR. I suggest you read the online AA documentation about the s.t and s.tl methods for details.
I've got some issues with javascript. Which causes some problems.
I'm using DevExpress MVC GridView, ASP.Net MVC 3 and javascript.
This my problem:
I've got a gridview, with for example customers.
I want them to select the customers, and show them in a table generated by javascript so we dont get all those refreshes. And they can then add other information so that they can be saved again to another table, but thats not really important.
I perform some calculations before generating the table row from the selected customer. Another problem is, the devexpress gridview has an event that calls on each selection change instead of a nice ~100 ms wait so that the user can multiselect quick without triggering method 3/4 times.
Im keeping track of my own table through an array. And the GridView from DevExpress got his own events that can give me the right information, so no need to worry about that.
So I got a method receiveSelectionFields(Values){ //do something } where I receive that information from the gridview on every selection.
Then I check my array to see if they added or removed a selection, and which.
Then I call addtablerow(customer) or removetablerow(customer). Which removes the customer from my table and then from my array.
Because I make some heavy calculations in between, there is a ~60ms delay before the calculation is done (on mine computer). So if the users makes 2 selections in 60 ms. My array will have the wrong value (not being modified by the first call that adds/removes a customer) and my javascript will cause an error e.g. the table row is not deleted. I check on length of my own array and on the length of the received array to see if something has been added or removed.
So what did I try?
Making my method a recursive method, that when the problem occurs it waites 60 ms and then redo the method. But this isn't working properly.
I tried adding a global variable busy, which is true when the method is still busy. And false when it ends. But my browser just quits when doing that. This was the code:
while (true) {
setTimeout(function () {
if (busy === false) {
break;
}
}, 50);
}
But I got the feeling it just endlessly loops.
And these are all workarounds, there must be a nice way to solve this. Any thoughts?
In short:
I want a way to let the functions go off in synch. even if their being called asynch. by the user so that my array doesn't mess up.
Found the answer why my problem exists:
Since javascript is a synch. language (1 thread). the functions should've triggered at the right time. The problem is the callback from DevExpress Gridview for MVC Extensions. It makes a callback to the server, which responds in for example ~150ms with the selected field values. This will give an error if you quickly trigger the devexpress function twice. The second trigger has a window to return FASTER then the first trigger. Which would mean my coding of the table get ruined since I check if something has been added or removed. So when the first trigger (which returns after the second trigger) and my table gets updated. It shows the table prior to my last selection. Thus missing 1 row or has 1 more row then it should've.
So I got to make a function that retrieves all the events, and then place them in an order ~200 ms after each order. To make sure there is enough time for the callback to retrieve. Though this is ofcourse still not reliable, I think I will just change the requirements on this.
Your while loop condition is true, therefore the loop will just continue endlessly. You may want to try the following:
var int = setInterval(function () {
if(busy === false) {
clearInterval(int);
}
}, 50);
Try this instead of looping through the setTimeout over and over. If I had to guess, the break is breaking the if statement but not the while loop causing your browser to get stuck in an endless loop. With the above code, you can set an interval at which to run the code. In this instance, the code runs every 50ms. Once the condition inside the if statement is true, the setInterval is cleared causing the browser to continue executing its normal functionality.
Hope this helps.
For the moment, we're loading site-wide event-listeners from a single common.js file for a Rails project. We're aware of (most of) the trade-offs involved there, and are just trying to mitigate them. Once our basic architecture takes hold, we may move them off to separate files by controller or by view.
For the moment, the quick question is how we can activate them only when necessary, which begs the mangled, pseudo-zen question:
if an event-listener is declared in a forest when nobody is around to hear it, does it still make a sound?
In other words, if one declares a basic listener (i.e., nothing persistent like .live() or .delegate()) in the JavaScript for a given page, and the target element is not actually present on that given page, does anything really happen, other than the few cycles devoted to evaluating it and checking the DOM for the element? Is it active in memory, looking for that element? Something else? It never seems to throw an error, which is interesting, given that in other contexts a call like that would generate a null/nil/invalid type of error.
For instance:
$(document).ready(function () {
$('#element').bind('blur keyup', function);
}
Assume that #element isn't present. Does anything really happen? Moreover is it any better to wrap it in a pre-filter like:
$(document).ready(function () {
if ($('#element')) {
$('#element').bind('blur keyup', function);
}
Or, are the .js interpreters in the browsers smart enough to simply ignore a basic listener declared on an element that's not present at $(document).ready? Should we just declare the initial, simple form above, and leave it at that, or will checking for the element first somehow save us a few precious resources and/or avoid some hidden errors we're not seeing? Or is there another angle I'm missing?
JQuery was designed to work with 0+ selected elements.
If no elements were selected, nothing will happen.
Note that you will never get null when using jQuery selector. For example:
$('#IDontExist') // != null
$('#IDontExist').length === 0 // true (it's ajQuery object with
// zero selected elements).
The docs says:
If no elements match the provided selector, the new jQuery object is "empty"; that is, it contains no elements and has .length property of 0.
$('#element') if results into empty set then jQuery will not do anything.
Since jQuery always returns an object we can can call the methods on an empty set also but internally it will do the checking before applying it's logic.
Even if you want to check if the element exists before attaching the event handler you can use length property of jQuery object.
if ($('#element').length > 0) {
$('#element').bind('blur keyup', function);
}
I have an HTML document (here), which creates an iframe-based media player for a collection of songs within albums (I just used letters to define these albums and songs in the mymusic array, for simplicity).
Focusing on the top 3 iframes, the way I have set out the user interaction is to generate the HTML for forms of available albums and songs using Javascript, and write them to the iframes in the body. If you run it and make a selection in the Albums menu, you will see that the options in the Songs menu correspond with the mymusic array, so this works.
However, when I choose a song, the function nowplaying(trackindex,albumindex) should be called using an onchange event in the Songs form, the same way as in the form generated using showinitial() ... but the function does not get called.
I have ruled out the coding of nowplaying itself as a cause, because even when I change nowplaying to alert("hello"), it does not get called. So this leads me to think the problem is with the onchange attribute in "anything", but I can't see the problem. The way I coded it is no different to before, and that worked fine, so why won't this work?
Any help would be much appreciated!
Firebug is your friend....
i is not defined
function
onchange(event) {
parent.nowplaying(this.SelectedIndex,
i); }(change )
onchange is getting called, but i is not defined when calling nowplaying.
This is the result of this line:
p+="<html><head></head><body><form><select onchange='parent.nowplaying(this.SelectedIndex,i);' size='";
which is using "i" in the string, when it should append it as a variable:
p+="<html><head></head><body><form><select onchange='parent.nowplaying(this.SelectedIndex," + i + ");' size='";
To clarify, i is defined when anything(i) is called, but you aren't writing i into the code, just the letter "i". When nowplaying(this.SelectedIndex,i) is called, i is no longer defined, because you aren't inside of the anything() function anymore. You need to expand i when you append the html to p, so that the value is there and not the variable i.
function anything(i){
p+="...<select onchange='parent.nowplaying(this.SelectedIndex,i);'...";
Your onchange event handler is set from a string. When run, it will not have access to i, which is a local variable from the anything function that has long since gone away.
The simple fix would be:
p+="...<select onchange='parent.nowplaying(this.SelectedIndex,'+i+');'...";
which turns the current value of i at string-making time into an integer literal inside the string.
However, it's not generally a good idea to be creating code from strings. It's normally better to write the event handler as a normal function object:
// You will need the below workaround to get the iframe document in IE too
//
var iframe= document.getElementById('songs');
var idoc= 'contentDocument' in iframe? iframe.contentDocument : iframe.contentWindow.document;
idoc.open();
idoc.write(s);
idoc.close();
idoc.getElementsByTagName('select')[0].onchange= function() {
// This is a closure. The 'i' variable from the parent 'anything' function is
// still visible in here
//
parent.nowplaying(this.selectedIndex, i);
};
However you would generally want to avoid setting handlers from one frame on a different one. I'm not really sure what the iframes are gaining you here other than headaches. Why not just simply use positioned divs with overflow? You can still rewrite their content through innerHTML if you need to... though I would prefer to populate them using DOM methods, to avoid all the HTML-injection problems your current script has.