RaphaelJS animate does not work when called from an outside method - javascript

As a desktop developer I am very new to Javascript, so I often run into things that puzzle me about the language. I was working with click events on RaphaelJS shapes, and initially I was setting the state and animation of the object in a private method:
innershape.node.onclick = function () {
if (scope.state === 0) {
_setState(1);
} else {
_setState(0);
}
};
function _setState(state) {
scope.state = state;
if (scope.state === 0) {
innershape.animate({ fill: "#00FF19" }, 500);
} else {
innershape.animate({ fill: "#C05219" }, 500);
}
}
This was functioning as expected. I then decided to add an outside function that would loop through all the objects and de-select (and therefore reverse-animate) all the other shapes. The result may be seen in this jsfiddle: http://jsfiddle.net/txj4zasn/4/
The function is called properly, and the animate() function is apparently executed, but the visible animation never appears, and the color never changes. I suspect that this is something very basic to Javascript that I just don't understand. Can someone explain to me why this is happening?

Its not really very clear what you want to achieve (beyond getting the animation to work), so my initial solution I think isn't good, but I will expand on that.
The problem looks a bit like you are trying to combine two different elements, functional scope and object variables.
A quick solution would be to include...
this.id = 1;
var id = this.id; // so id now a closure to the later function
as updateSelected(id); the id here, is inside another function, so we can't use 'this.id'. But then later you are checking against z[i].id so you need that to be defined also.
jsfiddle
This all feels a bit clunky though, prone to error, and is quite hard to read. So the first question is do you need objects ? You could store information in the "data" part of a Raph element, which already is an object.
Here is an example of how I would write it, I appreciate this may not be suitable as it may be part of a bigger project which needs other elements in an object, but it may give some idea.
function updateSelected( el ) {
if( el.data('innerstate') == 1 ) {
el.animate({ fill: "#00FF19" }, 500);
el.data('innerstate',0)
} else {
el.animate({ fill: "#C05219" }, 500);
el.data('innerstate',1);
}
}
function addElement() {
var innershape = paper.rect(100,100,100, 100);
innershape.attr({fill: "#00FF19" });
innershape.data('innerstate', 0);
innershape.click( function () {
updateSelected( innershape )
} );
};
addElement();
This code I can pretty much read instantly and know how and if it will work.
jsfiddle
jsfiddle showing it combined with more than one element, or jsfiddle thats a bit more compact

Related

SnapSVG animation callback on set of elements not firing

See JSfiddle!
I am wanting to animate a set of elements and execute a callback when finished like so:
s.selectAll('.active').animate( {
transform: matrix
},
300,
mina.linear,
function() {
//callback doesnt fire
alert('callback')
}
)
The elements are animated correctly but the callback isnt executed.
However, when I apply the animation to a group of elements, the callback is fired:
group.animate( {
transform: matrix
},
300,
mina.linear,
function() {
alert('callback')
}
)
.. But I don't want to put my selected elements in a group as this would cause more complications in other places.
Is it possible to animate a set of elements that I got via a .select() or .selectAll() while being able to fire the callback?
Thanks a lot in advance!
Edit: For future readers, you can animate a set of elements by using forEach and counting if all elements are done animating:
function hideToPoint(elements, x, y, callback) {
var finished = 0;
elements.forEach(function(e) {
e.animate( {
//do stuff
},
300,
mina.linear,
function () {
finished++;
if (finished == elements.length) {
callback();
}
}
)
})
}
I'm going to have a stab at answering a couple of problems, even though I'm not sure if related to the callback. Its hard to tell if its just the example code or not without a proper test like a jsfiddle.
However, there are at least 2 problems in the code above.
Creating a matrix is with
new Snap.Matrix(); // as opposed to Snap.matrix()
Also
elements.animate()
The problem here is that animate acts on one element (edit: looks like it can work on elements within a set, but not the callback as example here, edit2: callbacks on sets may now be supported), not multiple elements as such (you can sometimes apply somethings to a set which deals with them individually, but as far as I'm aware, thats not the case with animate).
So you either want to do a
elements.forEach( function(el) { el.animate({blah: value}, 2000, mina.linear.callback )
});
or if its an svg group (as opposed to a set), the original code would possibly work (but I would call it 'myGroup' or something instead of 'elements' for code readability and guessing what it contains)
fiddle (have included a different animation using snap animation string)

Issue with execution order in javascript

I'm relatively new to javascript so please hold it against me.
I have a bit of code which should give the user a little time to reach the submenu from the base-menu.
My problem is that the code keeps executing in a weird order.
Here is the code:
function onFocusOut() {
var tester = 0;
setTimeout(function(){menuReset(tester)},1000);
}
function menuReset(tester) {
var hoverCheck = function (event) {
alert("#navBase a has focus"); //is fired, but to late...
var tester = event.data.varTester;
var tester = 1;
};
jQuery('#navBase').on('mousemove', 'a', { varTester: tester }, hoverCheck);
jQuery('#navBase').off('mousemove', 'a', { varTester: tester }, hoverCheck);
alert(tester); //This keeps firing first, before the alert in hoverCheck
if(tester == 1){
alert("tester = 1");
return;
}
else {
jQuery('#navBase ul').hide();
jQuery('#navBase').css({'width': ''});
jQuery('#navBaseAnchor').css({
'width': '', 'color': '',
'font-size': '',
'border-bottom-style': '',
'border-bottom-width': '',
'border-bottom-color': ''});
tester = 0;
}
}
Now I keep getting the alert that "tester" is 0, before the hoverCheck function is executed (which should set "tester" to 1) and fires the alert within that function.
Could someone tell me what I'm doing wrong?
I am also fairly new to JS, but should you also be watching out for variable scope errors too?
You have declared tester locally in onFocusOut() and in menuReset(tester), and then called it as a global var outside?
From answers.oreilly.com
LOCAL - Are those that are specific to a function and only work on it.
GLOBAL - Are those that are not defined within a function and may also serve to functions unless the function has not required that
variable.
Nevermind people...
I found a way around it all.
Currently i'm setting a .focus() to the anchor involved on mouseOver. (and of course blur() on mouseleave)
Then it's real easy to check the currently focussed element using document.activeElement.
So problem solved, altough in a bit different way.
alert(tester) is the first line of code that is executing something you notice as a user. The two function calls jQuery().on() and jQuery().off() are only attaching event handlers. If you want to see a "1" in the alert, you have to quickly move your mouse before hoverCheck is executed. But probably you cannot move your hand faster than JavaScript reaching the next line, which is the alert() with tester equals "0".
A little bit different approach would be to set a Javascript timeout() to make the submenu disappear after a certain amount of time if a certain condition isn't met.
Check out this JSFiddle example
Best of luck!

if/else statement in a function: using onclick as a switch

I have looked for solutions to this on google for what seems like an eternity, but I can't seem to formulate my search correctly, or nobody has posted the code I'm looking for earlier.
I am currently trying to make a function that will modify one or several margins of a div element. I want to use an if/else statement within the function, so that the onclick event will switch between the two conditions. This is what I have been working on so far;
function facebookToggle()
{
if($('#facebooktab').style.margin-left == "-250px";)
{
document.getElementById("facebooktab").style.marginLeft="0px";
}
else
{
document.getElementById("facebooktab").style.marginLeft="-250px";
}
}
I have tried twisting it around a little, like switching between "marginLeft" and "margin-left", to see if I was just using the wrong terms.. I'm starting to wonder if it might not be possible to combine jQuery and regular javascript? I don't know.. It's all just guesses on my part at this point.
Anyway, I have a div, which is now positioned (fixed) so almost all of it is hidden outside the borders of the browser. I want the margin to change onclick so that it will be fully shown on the page. And when it is shown, I want to be able to hide it again by clicking it.
I might be approaching this in the wrong way, but I really hope someone can help me out, or even tell me another way to get the same results. Thank you for any help you can give me.
You can see it in action at: http://www.torucon.no/test/
(EDIT: By the way, I am a complete javascript novice, I have no experience with javascript prior to this experiment. Please don't be too harsh, as I am aware I probably made some really stupid mistakes in this short code.)
Fixed problem:
function facebookToggle() {
var fb = $('#facebooktab'); // save reference to element
if( fb.css('margin-left') === '-250px' ) {
fb.css('margin-left', '0px');
} else {
fb.css('margin-left', '-250px');
}
}
A jQuery object doesn't have a property called style, so
if($('#facebooktab').style.margin-left == "-250px";)
// also remove this semi-colon! ^
is going to throw an error. Some options for accessing CSS properties are (1)
document.getElementById("facebooktab").style.marginLeft;
which you have correctly used, or (2)
$('#facebooktab').css('margin-left');
Consider being consistent and using the same approach for all three cases. You can assign css properties with jQuery like
$('#facebooktab').css('margin-left', '-250px');
With these things in mind, here's a suggested rewrite:
function facebookToggle() {
var fb = $('#facebooktab'); // save reference to element
if( fb.css('margin-left') === '-250px' ) {
fb.css('margin-left', '0px');
} else {
fb.css('margin-left', '-250px');
}
}
and here's another that uses a predefined CSS class:
#facebooktab {
margin-left: -250px; /** default state */
}
.no-left-margin {
margin-left: 0px;
}
function facebookToggle() {
$('#facebooktab').toggleClass('no-left-margin');
}
toggleClass
jQuery is just a JavaScript library. It is written in JavaScript and its API is in JavaScript. Your event handler could be rewritten as follows:
function facebookToggle() {
var el = document.getElementById('facebooktab');
if (el)
el.style.marginLeft = (el.style.marginLeft == '250px' ? 0 : -250) + 'px';
}
Since you are mixing jQuery with javascript, you got mixed up. Apart from what paislee's advice. you are do this too.
if($('#facebooktab')[0].style.margin-left == "-250px";){
document.getElementById("facebooktab").style.marginLeft="0px";
}
else {
var fb = document.getElementById("facebooktab");
fb.style.marginLeft="-250px";
}

SetInterval inside scrolling events on iPhone - Why this code doesn't work?

Please, take a look at this code (I'm using Zepto http://zeptojs.com/ BTW)...
var timer = false;
$(window).bind('touchstart touchmove scroll', function (e) {
if (timer === false) {
timer = setInterval(function () {
$('footer').css('top', (this.pageYOffset + this.innerHeight - 40) + 'px');
console.log('Adjusted...');
}, 100);
}
}).bind('touchend', function () {
clearInterval(timer);
timer = false;
console.log('Cleaned it up...');
});
As you can see, I have a footer element that I'm trying to keep fixed on the bottom of the iPhone screen. I know that there are libraries that helps us make this quite easily like iScroll 4 http://cubiq.org/iscroll-4, but I was trying to see if I could make it simpler.
It turns out that the code above doesn't work properly. While I'm actually scrolling the page, for some reason setInterval doesn't execute but instead seems to pile up on the background to run every call at the same time.
At the end it doesn't do what I wanted it to do, which is to "animate" the footer and have it in place during scroll not only after. Does anyone has any idea on how such effect could be achieved on some similar manner?
Thanks!
When you pass a method to setInterval() (or any other function, for that matter), it will be invoked with a wrong this value. This problem is explained in detail in the JavaScript reference.
MDC docs
Inside your outer callback, this will be the DOM element you care about, but inside the setInterval callback, this will be window. Keep in mind that this is a keyword, not a variable, and that it is highly context sensitive.
The usual approach is to capture the value of this in a variable and then use that variable instead of this:
if(timer === false) {
var self = this; // "_that" is also a common name for the variable.
timer = setInterval(function () {
$('footer').css('top', (self.pageYOffset + self.innerHeight - 40) + 'px');
console.log('Adjusted...');
}, 100);
}
Similar issues apply to all callbacks in JavaScript, always make sure you know what this is and grab its value and build a closure over that value when it won't be what you want.

ExternalInterface and Javascript not working in harmony

The other day I posted about a Flash/Javascript issue I was having. Please see this:
Issues with javascript properly loading and seeing everything
I know how I want to fix it, but I am not in any way shape or form familiar with actionscript. I have avoided adobe products like the plague from when I was developing myself since it costs a fortune to buy and of their products, but big employers love it and pay for it so here I am. Our "Flash" guy just left the team and I inherited this issue. If you read my other post you know what is going on so I will move on. I want to make a simple call from actionscript to my javascript taht is referenced in my other post. I specifically want to call the CheckboxCollection function from inside of actionscript. I don't need to pass it any args or anything of the such from inside of actionscript. All I need it to do is run that function once the flash is done loading. The javascript function will take care of everything I need, I just HAVE TO HAVE IT called from actionscript to make everything work in harmony. I am in the middle of teaching myself all things adobe and actionscript(much to my dismay), but I really have no clue where top go from here to make this work. I have reviewed adobe documentation, but until I have a better grasp of the language as a whole I am still lost. I copied most of my actionscript on to here, but I did leave out everything that had to deal with mouseover events, since my issue is not about a mouseover and they all work like a charm. Thanks in advance!
-------------------------------------------------------------------------------------------UPDATE: I had to stop working on this to get some other things done, but I am back to step one. NO matter what I do I am having no luck making this work. I have tried all suggestions on here, and tried everything I KNOW how to do, but I am having no luck. If anyone could take a look at this post and the one that I link to (It is the companion javascript for this) and see if they can come up with anything. I have tried so many different iterations of my code there is no use putting all of my trials up for example of what doesn't work, Thanks Everyone!
/*
JavaScript External Calls
*/
function RegisterExternalCalls():void
{
if(ExternalInterface.available)
ExternalInterface.addCallback("HighlightWheel", HighlightWheel);
}
function HighlightWheel($args:String,$show:String,...arguments):void
{
$args = $args == "financial"?"center":$args;
var _obj:Object = ObjectCollection[$args].Objects.Click;
if(ObjectCollection[$args].Objects.currentObject.name.toLowerCase() == "center")
{
bcenter = true;
_obj = ObjectCollection[$args].Objects.currentObject.getChildByName("financialBtn");
}
if(CBool($show))
{
if(arguments.length > 0 && arguments[0] == "TITLE") // || $args == "center")
_obj.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OVER));
else
{
if(ObjectCollection[$args].labels.Label.toUpperCase() === "CENTER")
{
ObjectCollection["income"].Objects.Click.gotoAndPlay(2);
ObjectCollection["property"].Objects.Click.gotoAndPlay(2);
ObjectCollection["education"].Objects.Click.gotoAndPlay(2);
ObjectCollection["health"].Objects.Click.gotoAndPlay(2);
ObjectCollection["retirement"].Objects.Click.gotoAndPlay(2);
}
else
{
_obj.gotoAndPlay(2);
}
}
}
else
{
if(arguments.length > 0 && arguments[0] == "TITLE")
_obj.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_OUT));
else
{
if(ObjectCollection[$args].labels.Label.toUpperCase() === "CENTER")
{
ObjectCollection["income"].Objects.Click.gotoAndPlay(11);
ObjectCollection["property"].Objects.Click.gotoAndPlay(11);
ObjectCollection["education"].Objects.Click.gotoAndPlay(11);
ObjectCollection["health"].Objects.Click.gotoAndPlay(11);
ObjectCollection["retirement"].Objects.Click.gotoAndPlay(11);
}
else
{
_obj.gotoAndPlay(11);
}
}
}
}
function CallExternalFunction($label:String,$show:Boolean = true):void
{
var lbl:String = $label.toLowerCase().indexOf("btn") > -1?"financialTitle":$label + "Title";
if(ExternalInterface.available)
ExternalInterface.call("COUNTRY.Financial.highlightProductGroup",lbl,$show);
}
function CBool($value:String):Boolean
{
if($value == "true")
return true;
else
return false;
}
function PrintSetup($evt:MouseEvent):void
{
var pjob:PrintJob = new PrintJob();
if(pjob.start())
{
pjob.addPage(wheel);
pjob.send();
}
}
I believe you do this through ExternalInterface.call and pass the javascript function that should be called, like so:
ExternalInterface.call( "CheckboxCollection" )
If you need to pass arguments:
ExternalInterface.call( "CheckboxCollection", value1, value2 )
For more information here is the documentation
If in your JS you don't need necessarily this:
var CheckboxCollection = function()
try to change it to this:
function CheckboxCollection()
even if it seems (if the JS is still the same) you have anything nested. Maybe you can try too call it this way to (but I never tried anything similar):
ExternalInterface.call("SOME.PLACE.QuoteRequest.CheckboxCollection");

Categories

Resources