I have an angular animation on an ng-repeat (using jQuery's animate function), and want to update a "Visible" property on the model when each animation completes. The purpose is to orchestrate multiple animations. I am finding this works fine in the enter function, but does not work in the leave function. Here is the code:
app.animation('.step-animation',
function () {
return {
enter: function (element, done) {
var scope = angular.element(element).scope();
jQuery(element).css({ opacity: 0 });
jQuery(element).animate({
opacity: 1
}, 2000, done);
return function (cancelled) {
if (cancelled) {
jQuery(element).stop();
} else {
scope.$apply(function () {
scope.step.Visible = true;
});
}
}
},
leave: function(element, done) {
var scope = angular.element(element).scope();
jQuery(element).animate({
opacity: 0
}, 1000, done);
return function (cancelled) {
if (cancelled) {
jQuery(element).stop();
} else {
scope.$apply(function () {
scope.step.Visible = false;
});
}
}
}
}
});
The Visible property on my step object is being successfully set to true after the enter animation completes, but it is not updated on the leave animation. Also - I notice that the $parent property is null on the scope in the leave function, but populated on the scope in the enter function. It seems like leave is isolated, and enter is not. How can I update the scope from the leave function?
UPDATE
I have discovered that the return function is getting called twice in the leave function - once with cancelled = true. No idea what is causing this, or how to troubleshoot.
For each element in the iterated collection a DOM template will be rendered and a new scope will be created and associated with it.
The leave function in your code is called when one of these templates is removed from the DOM (for example due to filter). When the template is removed its associated scope is destroyed.
This means that when the following function executes the associated scope will already have been destroyed:
return function (cancelled) {
if (cancelled) {
jQuery(element).stop();
} else {
scope.$apply(function () {
scope.step.Visible = false;
});
}
};
You can verify this by adding the following inside it:
console.log(scope.$$destroyed);
When a scope is destroyed one of the things that happens it that its $apply function is set to an empty function:
function noop() {}
This means that the anonymous function (the one that sets Visible to false) that you pass to scope.$apply will never be executed, since $apply is just an empty function and doesn't do anything with its passed arguments.
To solve this you can inject $rootScope and use that instead:
return function(cancelled) {
if (cancelled) {
jQuery(element).stop();
} else {
$rootScope.$apply(function() {
scope.step.Visible = false;
});
}
};
Demo: http://plnkr.co/edit/jdNtcOSzq9BC9vxjmmLy?p=preview
Also note that the element will already be a jqLite element (or jQuery element if jQuery is loaded before AngularJS) so you can replace the following:
var scope = angular.element(element).scope();
With:
var scope = element.scope();
And all occurences of:
jQuery(element)
With just:
element
Related
I have a function:
function loremIpsum() {
$('.element').fadeIn()
}
to show a hidden Element - The issue is, that a user can go back with a second button and call the loremIpsum() function again, but the function should be called just once.
Can I limit that in someway ? I couldn't find a Post for that here.
I tried this:
function loremIpsum() {
var executed = false;
return function() {
if (!executed) {
executed = true;
$('.element').fadeOut()
}
}
}
But that didn't work
You can use sessionStorage to keep track of executed boolean value for the session.
function loremIpsum() {
sessionStorage.setItem('executed', true);
$('.element').fadeIn();
}
And in the caller, you can check:
if (!(sessionStorage.getItem('executed') === "true")) { //since sessionStorage stores items as strings
loremIpsum();
}
function loremIpsum() {
loremIpsum= function() {};
$('.element').fadeIn()
}
Did work - nevermind then.
So I'm Trying to make a "soft auto scrolling" function for and elements scroll-bar, basically its meant to be called then scroll an element for you. However as you can see in the code below I've encapsulated the function in a self-invoking function which holds the element in "element". However when it first reads the variable from the returned function in the line else if (pos>element.scrollHeight) element is returned as null
'use strict';
window.onload=(function()
{
window.scrollTo=(function()
{
var element=document.getElementById('more_info'),
ease=function(pos)
{
var diff=(pos-element.scrollTop).
increment=(diff / Math.abs(diff));
if (Math.abs(diff)<1)
{
element.scrollTop=pos;
return;
}
else if (Math.abs(diff)<30)
{
element.scrollTop+=1-(increment / diff);
}
else
{
element.scrollTop+=increment*10;
}
window.setTimeout(ease,1000 / 10);
};
return function(pos)
{
if (pos<0) {pos=0;}
else if (pos>element.scrollHeight) {pos=element.scrollHeight;}
ease(pos);
};
}());
}());
I Updated the code so that its called after the document loads, however my debugger seems to show that var element is still called before window.onload is
You're executing the onload function immediately.
window.onload = (function () {
...
}());
That makes the function execute then assign the returned value as the onload handler. This means it's executing before the DOM's ready. Change it to remove the self-execution.
window.onload = function () {
...
};
I'm relatively new to coding in JavaScript, and I've came across a problem. I like to nest functions to keep things orderly, but how would I exit from a parent function from inside a child function?
example:
function foo1() {
function foo2() {
//return foo1() and foo2()?
}
foo2();
}
See update under the fold
You can't. You can only return from the child function, and then return from the parent function.
I should note that in your example, nothing ever calls foo2 (As of your edit, something does). Let's look at a more real example (and one that comes up a lot): Let's say we want know if an array contains an entry matching some criterion. A first stab might be:
function doesArrayContainEntry(someArray) {
someArray.forEach(function(entry) {
if (entryMatchesCondition(entry)) {
return true; // Yes it does <-- This is wrong
}
});
return false; // No it doesn't
}
You can't directly do that. Instead, you have to return from your anonymous iterator function in a way to stop the forEach loop. Since forEach doesn't offer a way to do that, you use some, which does:
function doesArrayContainEntry(someArray) {
return someArray.some(function(entry) {
if (entryMatchesCondition(entry)) {
return true; // Yes it does
}
});
}
some returns true (and stops looping) if any call to the iterator function returns true; it returns false if no call to the iterator returned true.
Again, that's just one common example.
You've referred to setInterval below, which tells me that you're almost certainly doing this in a browser environment.
If so, your play function almost certainly has already returned by the time you want to do what you're talking about, assuming the game has any interaction with the user other than alert and confirm. This is because of the asynchronous nature of the environment.
For example:
function play() {
var health = 100;
function handleEvent() {
// Handle the event, impacting health
if (health < 0 {
// Here's where you probably wanted to call die()
}
}
hookUpSomeEvent(handleEvent);
}
The thing is, that play will run and return almost immediately. Then the browser waits for the event you hooked up to occur, and if it does, it triggers the code in handleEvent. But play has long-since returned.
Make a note whether the parent function should also return.
function foo1() {
bool shouldReturn = false;
function foo2() {
shouldReturn = true; // put some logic here to tell if foo1() should also return
return;
}
if (shouldReturn) {
return;
} else {
// continue
}
}
It only says that you can't return the parent function in the child function, but we can do a callback and make it happen.
function foo1(cb = () => null) {
function foo2() {
cb();
}
foo2();
}
foo1(() => {
// do something
});
We can use Promises for this:
const fun1 = async () => {
const shouldReturn = await new Promise((resolve, reject) => {
// in-game logic...
resolve(true)
})
if(shouldReturn) return;
}
if you wanna return from the parent function, then just resolve with true
Based on your comment, something like this might work as a main game loop.
function play() {
var stillPlaying = true;
while(stillPlaying) {
... play game ...
stillPlaying = false; // set this when some condition has determined you are done
}
}
I am working on a js file that makes use of JScroll. The callback for the jsp-scroll-y event is defined in the following function
function initWall() {
//callback from jqueryscrollpane
Scroll_TimeLine_Instance = function (event, scrollPositionY, isAtTop, isAtBottom){
//get more content
if (isAtBottom) {
GetMoreDate(objid, vwrsid, secid, orgid, incflwr, qty, mintlid, maxtlid, successGetTimeLineCallback, failureGetTimeLineCallback);
}
}();
}
Another function is defined that then binds this callback to the jsScroll
function reapplyScroll() {
Utilities.DestroyScrollBar($(_target).closest('.widgetBody'));
Utilities.ApplyScrollBar($(_target).closest('.widgetBody'), false, Scroll_TimeLine_Instance);
}
Utilities.ApplyScrollBar = function (element, showScrollBar, scrollCallback) {
$(element).jScrollPane({
horizontalGutter: 5,
verticalGutter: 5,
'showArrows': false
}).bind('jsp-scroll-y', scrollCallback);
if (!showScrollBar) {
$(element).find('.jspDrag').hide();
}
}
The callback was never called, and I found this was because it was undefined. If I remove the Immediate object initialization (); from after the creation of the function everything works fine.
Can anyone explain this? I don't understand why it was being called immediate anyway, so i assume this is an error on the part of whoever created it, and I have no idea why it would cause this variable to be undefined?
It is undefined because the function (that is immediately called) does not return any value
So it seems indeed that this is a bug of the library..
Either remove the (); at the end, or if you want to call it right there as well just invoke it in the following line
function initWall() {
//callback from jqueryscrollpane
Scroll_TimeLine_Instance = function (event, scrollPositionY, isAtTop, isAtBottom){
//get more content
if (isAtBottom) {
GetMoreDate(objid, vwrsid, secid, orgid, incflwr, qty, mintlid, maxtlid, successGetTimeLineCallback, failureGetTimeLineCallback);
}
}; /// this is assigning the function to our variable
Scroll_TimeLine_Instance (); // this is the invokation
}
I made code like this, to easier connecting callbacks on events:
dojo.ready(function() {
for(var action in page.actions) {
for(var key in page.actions[action]) {
(function() {
dojo.query(key).connect(action, function(evt) {
if(page.actions[action][key]() == false)
dojo.stopEvent(evt);
});
})();
}
}
});
page = {
actions :
{
onclick :
{
"#page-action-one" : function()
{
alert("Action 1");
return false;
},
"#page-action-two" : function()
{
alert("Action 2");
return false;
}
}
}
};
But click on "#page-action-one" an "#page-action-two" make the same alert("Action 2"). I tried to use cloer, but without effect. I now, I can make it different way, but I would like to now, why is this happening.
You're trying to fix the closure issue by wrapping your event handler assignment in an anonymous function. But the key to that trick is that you have to pass in the looping variable (or variables) as an argument to the anonymous function - otherwise the anonymous function wrapper does nothing. Try:
dojo.ready(function() {
for(var action in page.actions) {
for(var key in page.actions[action]) {
(function(action, key) {
dojo.query(key).connect(action, function(evt) {
if(page.actions[action][key]() == false)
dojo.stopEvent(evt);
});
})(action, key);
}
}
});
This "fixes" the value of action and key at the time the anonymous function is called, so within the anonymous function those variable names only apply to the passed arguments, not to the named variables in the outer scope, which will update on the next loop iteration.