Nasaworldwind Web: Removing/reassigning a clickRecognizer with different callback - javascript

Good Afternoon,
There seems to not be a lot of documentation/examples using Nasa's worldwind api especially the web version. So say I have...
let clickRecognizer = new WorldWind.ClickRecognizer(wwd, clickHandler);
where wwd is the name of my WorldWind Canvas/Window and clickHandler is the function called on click. I just want to know if anyone know how do I remove this ClickRecognizer() once I add it? I have tried assigning a new ClickRecognizer() to clickRecognizer variable and setting it to null then assigning but it made no difference; the first/original clickHandler function is being used.
In short I am trying to switch between ClickRecognizer that uses different clickHandler functions but the change isn't reflected when I click (I verified that the variable (let clickRecognizer) is changed and the gestureCallbacks array (see docs) is correctly populated with the second clickHandler function).
Here are the docs, I tried using removeListener(clickHandler) to no avail.
(This isn't my actual code (ofcourse) I just wrote the question generally)
Thanks

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

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.

extend a javascript option to add functionality

I need to call "MyOtherFunction" when "MyFunction"(which creates an element) completes, without MyFunction knowing what MyOtherFunction is.
The reason I need this is for extension of a jquery powered fileupload User Control that is used in several places with different functionality. A specific page shows a header and file count for it, and when the upload completes, I need to modify the file count according to how many files are displayed(by created elements) I thought :
$(UserControl).on(MyFunction, UploadElem, MyOtherFunction);
but this route is not accomplishing anything. The most I can alter the User Control is add in a function call, but without effecting the original user control functionality.
I'm not sure if because MyFunction isn't an event and doesn't bubble up or if it just isn't possible to use a defined function as a parameter of .on() is the reason I cannot get this code to work. Any suggestions?
Easiest way I can think of, is duck punching respectively hooking that method:
var _oldMyFunction = MyFunction;
MyFunction = function() {
_oldMyFunction.apply( this, arguments );
MyOtherFunction();
};
I managed to solve my own issue, but the context is important for the answer:
// Using a Global JavaScript object I created:
GlobalNameSpace.ExtensionFunction = function(oParam1, oParam2, oParam3)
{
/// <summary>All parameters are optional</summary>
return; // For instances when it is not being overwritten, simply return
}
//In the Code for the user control:
GlobalNameSpace.UploadControl.UploadComplete(oSender, oArgs)
{
///<summary>Handles the Upload process</summary>
// process the upload
GlobalNameSpace.ExtensionFunction(oSender, oArgs);
}
//and finally in the code to extend the functionality
GlobalNameSpace.Page.Init
{
///<summary>Initializes the page</summary>
// redefine the extension function
GlobalNameSpace.ExtensionFunction = function(oSender, oArgs)
{
GlobalNameSpace.Page.Function(oSender, oArgs);
}
}
This allows me to extend anything I need it to without polluting my objects, and having something generic already existing to call on to make my changes. This solution solves my problem of needing a onCreate function for the elements I create to represent my uploaded items to trigger the header displaying the number of files. Very useful

Minimalistic example of IPython kernel javascript bi-directional communication

My goal is to write an interactive variable viewer for IPython, i.e. which allows one to see, say nested dicts/lists as a tree, and drill down (a bit like the console.log in Javascript).
I spent a lot of time trying to extract minimalistic code out of the directview example in IPython but still can't get my head around how it all works. My Python's ok but my jQuery experience is quite limited.
So I got to stripping down directview.js to the following
container.show();
var widget = $('<div/>')
element.append(widget);
var output = $('<div></div>');
$(widget).append(output)
var output_area = new IPython.OutputArea(output, false);
var callbacks = { 'output': $.proxy(output_area.handle_output, output_area) };
var code = 'print 1+1'
var msg_id = IPython.notebook.kernel.execute(code, callbacks, {silent: false});
This works when I load the directview.ipynb. However I am still not clear how to make it completely standalone (i.e. not require the directview.py, and pass another callback than the standard handle_output of IPython.OutputArea). One of the issues is the container.show() which fails when called in a standalone way.
I am lost on several aspects:
Why is there an element.append(widget) and then $(widget).append(output)? Also why is there also a need to create an Ipython.OutputArea. Isn't there a way to just create a <div id=my_output_area>blah</div> and then have the output callback fill it with the relevant data?
What's the whole .proxy thing about? I would like to create my own callback for output but when I do that and console.log() the arguments passed to the callback, they're undefined or just useless.
I appreciate that the authors of the IPython notebook have done an incredible job creating such a beautiful front-end using jQuery/websockets, and that creating developer documentation that allows beginners like me to tweak with it is asking much, but if anyone can lend a hand that would be great!
I can answer to your second question. The fact is when JavaScript calls your callback, it makes it so without specifying the context, i.e. without setting this (Pythonistas call it self). But it's possible to bound a function to this via $.proxy, which you saw in:
var callbacks = { 'output': $.proxy(output_area.handle_output, output_area) };

Watch for a property creation event?

I need to be able to determine when an object is created (not a DOM element -- a JavaScript object).
An answer to this question has some very useful looking code for creating observable properties, so you can have a function fire when a property changes.
In my situation I need to do something when the object/property is created, not an existing property changed, and my limited understanding of such matters did not help me figure out if or how I could use that code to do this after much squinting.
The situation is: page loads a bunch of scripts. Some of the scripts create things that are needed by other scripts, e.g:
ThisStuff = (function () {
// blah blah
return self;
} ());
Some other code needs to initialize this ThisStuff, whenever it's available, which may be after the DOM is done loading. The user doesn't actually need ThisStuff right away, so it's fine for it to happen whenever the script is done loading. So I would like to do something along lines of:
$(document).ready(function() {
wheneverIsAvailable(window,'ThisStuff', function(object) {
object.init(args);
})
});
I realize there are other solutions to this problem (changing script order, or loading scripts on demand) but those are difficult because of the architecture. So I'm only interested in a way to do this versus other solutions. If jQuery offers some such functionality, that's fine also as I'm using it.
You could have a setInterval checking a number of times a second to watch the specific variable. You can check whether it is created using obj.hasOwnProperty(prop). When it is created, you invoke the function, and clear the interval.
It might be dirty but it might also just work fine for you.
Edit: I coded this for you: http://jsfiddle.net/jhXJ2/2/. It also supports passing additional arguments to the function.
window.__intervals = [];
function wheneverIsAvailable(obj, prop, func) {
var id = (Math.random()+"").substring(2);
var args = arguments;
window.__intervals[id] = window.setInterval(function() {
if(obj.hasOwnProperty(prop)) {
window.clearInterval(window.__intervals[id]);
func(Array.prototype.slice.call(args, 3));
// Call function with additional parameters passed
// after func (from index 3 and on)
}
}, 1000/ 50);
}
wheneverIsAvailable(window, 'test', function() {
alert(arguments[0]);
}, 'Woot!');
window.setTimeout('window.test = 123', 1000);
This is a bit far-fetched but it might work.
You would need to use knockoutjs, a javascript library. It's awesome but is built for a slightly different purpose.
Anyways it has a dependentObservable thing which allows to fire up an event whenever a certain value changes. Now I know you want on creation but you can check whether your variable holds any value (other than what you provided initially), if yes then consider it initialize.
Let me know if you think this sounds feasible.

Categories