Note these two sketches, one which has the expected and desired behavior, and one which does not. When you open these sketches you will see an image with some text on it. The text is draggable to a new position. The image should stay stationery during the repositioning. In the first sketch it does, in the second it doesn't.
The difference in the code between the two sketches is that in the first one the event is established on the active layer:
project.activeLayer.onMouseDrag = function(event) {
view.center = changeCenter(paper.view.center, event.delta.x, event.delta.y, 0.7);
}
In the second one it is established on what I understand to be the global paper.tool:
function onMouseDrag(event) {
view.center = changeCenter(paper.view.center, event.delta.x, event.delta.y, 0.7);
}
It seems to me it shouldn't matter because in the text mousedrag listener I'm stopping propagation of the event:
event.stopPropagation();
However, this only seems to take effect in the first sketch, not the second one. Why is this the case?
The difference is that declaring the "global" function onMouseDrag causes paper to create a Tool and the tool event gets called at the end of the processing regardless of whether the normal handlers chain requested that propagation be stopped. (I say "global" because when paper executes paper script it wraps your code in a function with its own scope. It then passes those "global" functions back to paper in an object returned from that function.) If any "global" functions were declared paper then creates a tool that handles them. And that tool gets called at the end of event processing regardless of whether your handler terminates processing or not.
The following code is the relevant code in paper's View.js module:
function handleMouseMove(view, point, event) {
view._handleEvent('mousemove', point, event);
var tool = view._scope.tool;
if (tool) {
tool._handleEvent(dragging && tool.responds('mousedrag')
? 'mousedrag' : 'mousemove', point, event);
}
view.update();
return tool;
}
The item's handler (your PointText handler) is being called by view._handleEvent() while the tool that was implicitly created by defining the "global" onMouseDrag is called regardless of the outcome of view._handleEvent processing.
This problem only comes up if you're using paperscript and you declare one of the global functions, e.g., onMouseDrag.
Related
This code randomly plays Audio elements. Running setup twice allows you to do this with two different arrays simultaneously, which is what I want. The problem is that #stop only stops one of the arrays playing. This actually also happens if you only call setup on one array, but click #start more than once (which I also don't want). I figure this has to do with 'intervalReturn', as it would only be specified to one setInterval.
How should I write this so that multiple invocations of setup creates distinct setIntervals which can be started only once?
Alternately, if I should approach this from a totally different angle, what would be better?
EDIT: This was fixed per the suggestions below. But I'm wondering, what is going on "under the hood" with setInterval here? Why does this behavior happen at all? (Specifically: #stop stops one but not all audio elements.)
var CMajor3 = new Array ("C3","D3","E3","F3","G3","A3","B3","C4a");
var CMajor4 = new Array ("C4b","D4","E4","F4","G4","A4","B4","C5");
var intervalReturn = null;
function pickAndPlay(pitchSet){
fyshuffle (pitchSet); // the Fischer-Yates shuffle function
var tone = document.getElementById(pitchSet[0]);
tone.currentTime = 0;
tone.play();
};
function setup(time, pitchSet){
$("#start").click(function() {
console.log("startClicked");
intervalReturn = window.setInterval(pickAndPlay, time, pitchSet)
});
$("#stop").click(function() {
console.log("stopClicked");
window.clearInterval(intervalReturn)
});
};
$(document).ready(function() {
setup(2000, CMajor3);
setup(2000, CMajor4);
});
But I'm wondering, what is going on "under the hood" with setInterval here?
Each time you call setup(), that creates additional click handlers on the #start and #stop elements. When you actually click #start, or #stop, all of the applicable handlers are called (in the same order they were bound). This is why clicking #start causes both CMajor3 and CMajor4 notes to play. You get multiple concurrent but unrelated intervals running.
With intervalReturn defined as a global variable, you only ever have the interval ID that was returned from the most recent call to setInterval(), because each time the #start click handler runs it overwrites the previous one. That's why clicking #stop only ever stops one of the intervals and there is no way to stop the others.
Moving the var intervalReturn declaration inside the setup() function helps because the way closures work in JS is that the arguments and local variables of setup(), i.e., time, pitchSet and intervalReturn (after you've moved the declaration) are accessible in the two event handlers defined in the current call to setup(). Subsequent calls to setup() create new closures with their own separate copies of those variables. So then the #stop click handler uses the individual intervalReturn relevant to its own setup(). And since both #stop handlers run, both intervals get cleared.
But you still have the problem that clicking #start# more than once without clicking #stop creates additional intervals and then within any one setup() that individual intervalReturn gets overwritten with the latest, so again #stop has no way to refer back to the previous intervals. Which is why adding if (intervalReturn === null) in the #start handler helps to only start a new interval if there is not already one running. (And then you need to add intervalReturn = null in the #stop handler because just calling clearInterval(intervalReturn) doesn't change the value of the intervalReturn variable.)
Suggestion: Update your existing console.log() statements to the following:
console.log("startClicked", pitchSet, intervalReturn);
// and
console.log("stopClicked", pitchSet, intervalReturn);
And maybe more the startClicked one to just after calling setInterval() so that it logs the interval ID that was just returned, not the previous one. That way you can see the values of all of the relevant variables and see what is happening.
I have a function that builds my d3 display, within it is a function called update() which actually deals with the nodes.
The function can be seen here
As you can see, the svg tree is created and then the update() function is run, displaying the tree nodes.
When a tree node with a certain data attribute is clicked, an html form is appended to the screen which allows a user to add data relating to that node. The user enters the data, clicks 'save' which has a jQuery based event handler attached which updates the data source.
I need to then trigger the tree to update with the node showing the new data.
I'm unsure how to trigger this update() function from an external function. I don't want to have to rebuild the entire tree every time, just redraw the nodes to show the new data.
Where is the gap in my understanding of how to do this?
This seems to be more of a JavaScript scope issue. Is tree_data already exposed to other functions (e.g. in the jQuery event handler, can you console.log(tree_data)? If so, then we just need an external reference to update.
A quick way is to assign it to the global window scope:
window.update = function(source) { ...
Of course this isn's not optimal, but it does help us narrow down your issue.
If that works, then we just need to declare a variable outside the scope of your build: function(tree_data) and then inside, on line 65, assign it to update:
var externalReferenceToUpdate;
[...]
build: function(tree_data) {
[...]
var update = function(source) {
[...]
};
externalReferenceToUpdate = update;
update(root); // moved this due to function expression vs. declaration
[...]
}
As you noticed, we had to move update(root). This is because we've "made" the function using a different method. There's a whole other Stack Overflow discussion on the ways to declare functions and their scope issues. Essentially with the second way (var name = function()), the function is not "hoisted" to the top of its scope, so we had to call update after we assigned it to a variable.
Hope this helps!
PS In that other discussion, skim over the first answer and actually focus on the second answer by Eugene.
I have a global event manager, allowing you to listen with lambdas to string event names.
// somewhere in the ModuleScript class
Event->Listen("WindowResize", [=]{
// ...
});
Now, I want to register to events from JavaScript, too. Therefore, I wrote this callback.
v8::Handle<v8::Value> ModuleScript::jsOn(const v8::Arguments& args)
{
// get pointer to class since we're in a static method
ModuleScript *module = (ModuleScript*)HelperScript::Unwrap(args.Data());
// get event name we want to register to from arguments
if(args.Length() < 1 || !args[0]->IsString())
return v8::Undefined();
string name = *v8::String::Utf8Value(args[0]);
// get callback function from arguments
if(args.Length() < 2 || !args[1]->IsFunction())
return v8::Undefined();
v8::Handle<v8::Function> callback =
v8::Local<v8::Function>::Cast(args[1]->ToObject());
// register event on global event manager
module->Event->Listen(name, [=]{
// create persistent handle so that function stays valid
// maybe this doesn't work, I don't know
v8::Persistent<v8::Function> function =
v8::Persistent<v8::Function>::New(args.GetIsolate(), callback);
// execute callback function
// causes the access violation
function->Call(function, 0, NULL);
});
return v8::Undefined();
}
When the event is triggered, the application crashes with a access violation. My thoughts are that either the function object isn't valid at this time anymore, or it is a JavaScript scope issue. But I couldn't figure it out.
What causes the access violation and how to overcome it?
I believe there are several potential issues here.
First, you're not using a persistent handle to hold the JavaScript function after ModuleScript::jsOn() terminates. By the time your event handler is invoked, the function might be gone. Consider making callback a persistent handle.
Second, your event handler needs to enter an appropriate V8 context before calling the JavaScript function. Depending on your architecture, explicitly locking and entering the V8 isolate may be required as well.
Third (and this may not be an issue in your specific scenario), you need to manage the lifetime of the V8 isolate. If your event manager fires events on background threads, you have to make sure your event handler somehow prevents the isolate from being disposed from another thread. Unfortunately this is one area where the V8 API doesn't provide much help.
Fourth, to prevent a leak, your event handler should dispose the persistent function handle after invoking the function.
Good luck!
I'm in the process of authoring a completely client side web language reference site. A problem that I encountered today; I have a side panel that is a unordered list of terms and they have onmouseover event listeners. I decided it would be a good idea to add a delay prior to execution and cancel the event at run-time if the mouse was no longer over that element. This is what I've come up with but I feel there must be a better way.
var currentXCoordinate=0
var currentYCoordinate=0
var elementFromCurrentMousePosition=0
function trackCurrentMousePosition(event) {
if (document.elementFromPoint(event.clientX, event.clientY).nodeName=="SPAN") {
elementFromCurrentMousePosition=document.elementFromPoint(event.clientX, event.clientY).parentNode
}
else {
elementFromCurrentMousePosition=document.elementFromPoint(event.clientX, event.clientY)
}
return (currentXCoordinate=event.clientX, currentYCoordinate=event.clientY, elementFromCurrentMousePosition)
}
function initPreview(event, obj) {
arg1=event
arg2=obj
setTimeout("setPreviewDataFields(arg1, arg2)", 100)
}
function setPreviewDataFields(event, obj) {
if ('bubbles' in event) {
event.stopPropagation()
}
else {
event.cancelBubble=true
}
if (elementFromCurrentMousePosition!=obj) {
return 0;
}
The code goes on to do all the wonderful stuff I want it to do if execution wasn't cancelled by the previous if statement. The problem is this method is seeming to be really processor intensive.
To sum it up: on page load all my event listeners are registered, cursor position is being tracked by a onmousemove event. Applicable list items have a onmouseover event that calls the initPreview function which just waits a given period of time before calling the actual setPreviewDataFields function. If at run-time the cursor is no longer over the list element the function stops by return 0.
Sadly that's the best I could come up with. If anyone can offer up a better solution I would be very grateful.
Why not just use mouseout to tell when the mouse leaves an element? Running all of that code every time the mouse moves isn't ideal.
Also, you really shouldn't pass a string to setTimeout like that. Instead, pass a function. As a bonus, you can get rid of those evil global variables arg1 and arg2. With those being globals, I think you will run into issues if init gets called again before the timeout expires.
I use a couple of functions heavily in my code. Now, as I'm looking for the source of high memory usage, I want to ensure that they're not the culprits.
Using jQuery, I bind and trigger custom events on the body element. When an event is triggered, I store it in a list of triggered events. One of the helper functions I use is called "waitfor". Here's some pseudocode:
waitfor = function(event, callback){
if(event_has_ever_been_called){
callback(); //RUN IMMEDIATELY
}
else{
//BIND CALLBACK TO RUN AS SOON AS THE EVENT IS TRIGGERED
$("body").bind(event, function(){
callback();
});
}
}
For example,
//ADD GOOGLE MAP TO PAGE
... listen for the google map 'idle' event,
... then call $("body").trigger("gmap.ready")
//ADD MARKERS AS SOON AS POSSIBLE (BUT NOT BEFORE)
waitfor("gmap.ready", function(){
//add markers
});
This seems very straightforward to me but I'm a little concerned that it (or any of my other functions that use anonymous callback functions) could be causing high memory usage.
Is this sufficient information to determine that this function is safe / not safe?
You should call one instead of bind to remove the handler after the event fires.
Otherwise, your function, and everything referenced by its closure, will stay referenced forever by jQuery's handler list.