For several years I've used the waitForKeyElements() function to track changes in webpages from a userscript. However, sometimes I've found it doesn't trigger as expected and have worked around out. I've run into another example of this problem, and so am now trying to figure out what the problem is. The following is the barest example I can create.
Given a simple HTML page that looks like this:
<span class="e1">blah</span>
And some Javascript:
// function defined here https://gist.github.com/BrockA/2625891
waitForKeyElements('.e1', handle_e1, false);
function handle_e1(node) {
console.log(node.text());
alert(node.text());
}
setInterval(function() {
$('.e1').text("updated: "+Math.random());
}, 5000);
I would expect this code to trigger an alert() and a console.log() every 5 seconds. However, it only triggers once. Any ideas?
Here's a codepen that demonstrates this.
By design and default, waitForKeyElements processes a node just once. To tell it to keep checking, return true from the callback function.
You'll also want to compare the string (or whatever) to see if it has changed.
So, in this case, handle_e1() would be something like:
function handle_e1 (jNode) {
var newTxt = jNode.text ();
if (typeof this.lastTxt === "undefined" || this.lastTxt !== newTxt) {
console.log (newTxt);
this.lastTxt = newTxt;
}
return true; // Allow repeat firings for this node.
}
With the constant string comparisons though, performance might be an issue if you have a lot of this on one page. In that scenario, switching to a MutationObserver approach might be best.
Related
I try to display a loading alert on Meteor with modal package during loading of data.
'change .filterPieChart': function(evt){
Modal.show('loadingModal');
/* a little bit of work */
var data = MyCollection.find().fetch(); // takes 3 or 4 seconds
/* lot of work */
Modal.hide('loadingModal');
}
Normally, the alert is displayed at the beginning of the function, and disappears at the end. But here, the alert appears only after the loading time of the MyCollection.find(), and then disappears just behind. How to display it at the beginning of the function ??
I tried to replace Modal.show with reactive variable, and the result is the same, the changing value of reactive variable is detect at the end of the function.
From what you describe, what probably happens is that the JS engine is busy doing your computation (searching through the collection), and indeed blocks the UI, whether your other reactive variable has already been detected or not.
A simple workaround would be to give some time for the UI to show your modal by delaying the collection search (or any other intensive computation), typically with a setTimeout:
Modal.show('loadingModal');
setTimeout(function () {
/* a little bit of work */
var data = MyCollection.find().fetch(); // takes 3 or 4 seconds
/* lot of work */
Modal.hide('loadingModal');
}, 500); // delay in ms
A more complex approach could be to decrease the delay to the bare minimum by using requestAnimationFrame
I think you need to use template level subscription + reactiveVar. It is more the meteor way and your code looks consistent. As i can see you do some additional work ( retrive some data ) on the change event. Make sense to actually really retrive the data on the event instead of simulation this.
Template.TemplateName.onCreated(function () {
this.subsVar = new RelativeVar();
this.autorun( () => {
let subsVar = this.subsVar.get();
this.subscribe('publicationsName', this.subsVar);
})
})
Template.TemplateName.events({
'change .filterPieChart': function(evt){
Template.instance().collectionDate.subsVar.set('value');
Modal.show('loadingModal');
MyCollection.find().fetch();
Modal.hide('loadingModal');
}
})
Please pay attention that i didn't test this code. And you need to use the es6 arrow function.
I need to set the background color of one of the buttons in the form's ribbon. This isn't supported through Ribbon Workbench, so I have written following javascripts to achieve the same:
function setOpportunityRibbonsAppearance() {
var submitToForeCastButton = parent.document.getElementById("opportunity|NoRelationship|Form|sfw.opportunity.Button1.Button");
if (submitToForeCastButton != null) {
submitToForeCastButton.style.backgroundColor = "lightyellow";
}
}
I have registered this scripts in Form Load event. However the issue is that, I always get parent.document.getElementById as null only.
Surprisingly, I am able to see the control while running the parent.document.getElementById statement in the browser's console, and can also change the styling attributes.
Can anyone please suggest what could be wrong here?
P.S. - I understand document.getElementById is not recommended to use in CRM, however, I am left with no other choice while trying to change the appearance of some of the buttons.
Any help on this, will be much appreciated.
You could upload an icon with a yellow background, to keep everything supported. You won't see text on yellow but it might work for you. Easy and standard.
To keep it unsupported and ugly, you could just keep on trying until you make it, setInterval allows for a function to be repeated:
function setOpportunityRibbonsAppearance() {
var submitToForeCastButton = null;
var interval = setInterval(function(){
submitToForeCastButton = parent.document.getElementById("opportunity|NoRelationship|Form|sfw.opportunity.Button1.Button");
if(submitToForeCastButton != null) {
submitToForeCastButton.style.backgroundColor = "lightyellow";
clearInterval(interval);
}
}, 500); // Every 500ms. Adjust as needed, not too fast or browser will choke.
}
Its probably because your script is running before the page is fully loaded.
Try adding a delay to the to the function Put a Delay in Javascript
This is the first time I get my hands on with automation instruments in xcode The script works well for all button taps but the one making server connection. I don't know the reason
Here is the script I tried so far
var target = UIATarget.localTarget();
target.pushTimeout(4);
target.popTimeout();
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
appScroll.logElementTree();
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
The above script works up to showing the UIActivityIndicator instead of moving to next controller after success
I know There must be a very simple point I am missing. So help me out
UIAutomation attempts to make things "easy" for the developer, but in doing so it can make things very confusing. It sounds like you're getting a reference to window, waiting for a button to appear, then executing .tap() on that button.
I see that you've already considered messing with target.pushTimeout(), which is related to your issue. The timeout system lets you do something that would be impossible in any sane system: get a reference to an element before it exists. I suspect that behind-the-scenes, UIAutomation repeatedly attempts to get the reference you want -- as long as the timeout will allow.
So, in the example you've posted, it's possible for this "feature" to actually hurt you.
var window=target.frontMostApp().mainWindow()
var appScroll=window.scrollViews()[0];
UIATarget.localTarget().delay(2);
appScroll.buttons()[1].tap();
What if the view changes during the 2-second delay? Your reference to target.frontMostApp().mainWindow.scrollViews()[0] may be invalid, or it may not point to the object you think you're pointing at.
We got around this in our Illuminator framework by forgetting about the timeout system altogether, and just manually re-evaluating a given reference until it actually returns something. We called it waitForChildExistence, but the functionality is basically as follows:
var myTimeout = 3; // how long we want to wait
// this function selects an element
// relative to a parent element (target) that we will pass in
var selectorFn = function (myTarget) {
var ret = myTarget.frontMostApp().mainWindow.scrollViews()[0];
// assert that ret exists, is visible, etc
return ret;
}
// re-evaluate our selector until we get something
var element = null;
var later = get_current_time() + myTimeout;
while (element === null && get_current_time() < later) {
try {
element = selectorFn(target);
} catch (e) {
// must not have worked
}
}
// check whether element is still null
// do something with element
For cases where there is a temporary progress dialog, this code will simply wait for it to disappear before successfully returning the element you want.
I was just wondering whether there are any way (libraries, frameworks, tutorials) to do javascript tracking with another script? Basically, i want to track as the user work with the site, which function gets executed with what parameters and so on, as detailed as possible.
thanks a lot!
The extent of detail you're expecting will be challenging for any solution to gather and report on without severely slowing down your scripts -- consider that, for every call, at least 1 other call would need to occur to gather this.
You'd be better to pick a few key events (mouse clicks, etc.) and track only a few details (such as time) for them. If you're using ajax, keep JavaScript and the browser oblivious and just track this on server-side.
There's a few options but I'm not sure if there are any "great" ones. I take it Firebug/IE Dev toolbar profiling won't work because you are trying to track remote user's actions.
So, one option (I'm not highly recommending for production purposes), will work in some but not all browsers.
Essentially you overwrite every function, with a wrapper that you then inject your logging.
(I haven't tested this, trying to recall it from memory... hopefully in "pseudo code" you get the idea...)
//e.g. get all functions defined on the global window object
function logAll(){
var funcs = [];
var oldFunc;
for(var i in window){
try {
if(typeof(window[i]) == 'function'){
if(i != 'logAll'){
funcs.push(i);
}
}
} catch(ex){
//handle as desired
}
}
var x;
for(var i in funcs){
x = '_' + new Date().getTime();
window[x] = window[i];//save the old function as new function
//redefine original
window[i] = function(){
//do your logging here...
//then call the real function (and pass all params along)
call(window[x]);
};
}
};
I wonder if any of you guys can help me with what I think is observing problem.
I have an element (svg to be more specific) that I want to update every time a value somewhere is changed.
I have variable:
GetThreadTree().treeBoxObject.getFirstVisibleRow() that initially is 0. I want to run a function updateCanvas() every time value of GetThreadTree().treeBoxObject.getFirstVisibleRow() changes.
What I have is:
canvas.observe(GetThreadTree().treeBoxObject.getFirstVisibleRow(), "scroll", updateCanvas());
But it calls updateCanvas() only once, when it's called for the first time, and for some reason does not execute the code that is after it. I checked error console and nothing is there.
Any ideas?
You logic is all in the wrong place. When you update your value what ever it is that needs to be done in one place by a function, something like treeBoxObject.IncrementRow() or similar.
Then you can have that function fire an event, like onTreeBoxRowIncremented. That event is what you listen out for, when that changes then you can do your check and update whatever you like.
Excuse the weird function names just trying to use what you have.
One way of solving this problem is:
var registeredRow = 0;
function checkRow(){
var row = GetThreadTree().treeBoxObject.getFirstVisibleRow();
if (registeredRow != row) {
registeredRow = row;
updateCanvas();
}
window.setTimeout(checkRow, 1);
}
And before checkRow is called:
registeredRow = GetThreadTree().treeBoxObject.getFirstVisibleRow();
checkRow();
But it's not the most elegant solution but it works ;)