I'm trying to change an input field's value when a video loads. Here's the relevant code:
This works:
$scope.stopRecording = function () {
// value of the input changes as expected
$scope.videoEndPos = 10;
};
This doesn't
$scope.stopRecording = function () {
video.onloadedmetadata = function() {
$scope.videoEndPos = 10;
// the value of the input does not change, but this still correctly outputs 10
console.log($scope.videoEndPos);
};
};
In an effort to keep this short I left out some crucial video stuff there, but that part is working and the onloadedmetadata is firing properly, so its something funky with angular and the input. However, let me know if you suspect I've left out some relevant code.
The video.stopRecording happens outside of the Angular universe, so it does not know about the change. What you need to use is $scope.$apply, which allows you to execute changes to the scope from outside of Angular.
$scope.stopRecording = function () {
video.onloadedmetadata = function() {
$scope.$apply(function(){
$scope.videoEndPos = 10;
});
// the value of the input does not change, but this still correctly outputs 10
console.log($scope.videoEndPos);
};
};
The video.onloadedmetadata is probably an asynchronous call that doesn't return until after the digest loop. Change it to this:
$scope.stopRecording = function () {
video.onloadedmetadata = function() {
$scope.$apply(function() {
$scope.videoEndPos = 10;
});
};
};
Related
I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});
I'm trying to cancel a requestAnimationFrame loop, but I can't do it because each time requestAnimationFrame is called, a new timer ID is returned, but I only have access to the return value of the first call to requestAnimationFrame.
Specifically, my code is like this, which I don't think is entirely uncommon:
function animate(elem) {
var step = function (timestamp) {
//Do some stuff here.
if (progressedTime < totalTime) {
return requestAnimationFrame(step); //This return value seems useless.
}
};
return requestAnimationFrame(step);
}
//Elsewhere in the code, not in the global namespace.
var timerId = animate(elem);
//A second or two later, before the animation is over.
cancelAnimationFrame(timerId); //Doesn't work!
Because all subsequent calls to requestAnimationFrame are within the step function, I don't have access to the returned timer ID in the event that I want to call cancelAnimationFrame.
Looking at the way Mozilla (and apparently others do it), it looks like they declare a global variable in their code (myReq in the Mozilla code), and then assign the return value of each call to requestAnimationFrame to that variable so that it can be used any time for cancelAnimationFrame.
Is there any way to do this without declaring a global variable?
Thank you.
It doesn't need to be a global variable; it just needs to have scope such that both animate and cancel can access it. I.e. you can encapsulate it. For example, something like this:
var Animation = function(elem) {
var timerID;
var step = function() {
// ...
timerID = requestAnimationFrame(step);
};
return {
start: function() {
timerID = requestAnimationFrame(step);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation = new Animation(elem);
animation.start();
animation.cancel();
timerID; // error, not global.
EDIT: You don't need to code it every time - that's why we are doing programming, after all, to abstract stuff that repeats so we don't need to do it ourselves. :)
var Animation = function(step) {
var timerID;
var innerStep = function(timestamp) {
step(timestamp);
timerID = requestAnimationFrame(innerStep);
};
return {
start: function() {
timerID = requestAnimationFrame(innerStep);
}
cancel: function() {
cancelAnimationFrame(timerID);
}
};
})();
var animation1 = new Animation(function(timestamp) {
// do something with elem1
});
var animation2 = new Animation(function(timestamp) {
// do something with elem2
});
I have the following code working:
$wmdInput.on('keyup', function () {
var rawContent = $wmdInput.val();
scope.$apply(function () {
ngModel.$setViewValue(rawContent);
});
});
However it seems to slow down my entering of characters. Is there some way I could put a timeout on this so that all the data is saved but it just does not do it more than for example once every two seconds?
Pure AngularJS approach
var promise;
$wmdInput.on('keyup', function () {
$timeout.cancel(promise);
promise = $timeout(function() {
var rawContent = $wmdInput.val();
ngModel.$setViewValue(rawContent);
}, 2000);
});
If you can use lodash (which you should) just wrap the function in _.debounce:
$wmdInput.on('keyup', _.debounce(function () {
var rawContent = $wmdInput.val();
scope.$apply(function () {
ngModel.$setViewValue(rawContent);
});
}, 300));
This will cause the function to be called only when the user has stopped typing for 300 ms -- obviously you should tweak the wait to whatever works best for you.
IMO debouncing is more appropriate than throttling in this case.
Reactive Extensions for JavaScript might be an option for you to use. You can setup the keyup event as an event source and throttle the events. In fact the README has an example that seems similar to what you want...
var $input = $('#input'),
$results = $('#results');
/* Only get the value from each key up */
var keyups = Rx.Observable.fromEvent(input, 'keyup')
.map(function (e) {
return e.target.value;
})
.filter(function (text) {
return text.length > 2;
});
/* Now throttle/debounce the input for 500ms */
var throttled = keyups
.throttle(500 /* ms */);
/* Now get only distinct values, so we eliminate
the arrows and other control characters */
var distinct = throttled
.distinctUntilChanged();
In your case you may not want the filter for length > 2, so just remove that expression. Regardless, you can just tack on a subscribe at the end to process your event
distinct.subscribe(function(value) {
scope.$apply(function () {
ngModel.$setViewValue(value);
});
});
And there are bindings for AngularJS for you as well.
in the function above, add two lines to:
remove keyup event from wmdInput.
set timer to add keyup event to
wmdInput after 2 seconds.
OK, I am missing something fundamental here I am sure! But for the life of me can not work it out.
Scenario
It's a simple hide show menu;
// Setup hover
var fadeDuration = 200;
var setDelay;
$level1Item.hover(function () {
$(this).addClass('hover').find('.level-2').fadeIn(fadeDuration);
}, function () {
$(this).removeClass('hover').find('.level-2').fadeOut(fadeDuration);
});
And it works fine... but the drop down is rather LARGE and is irritating when it pops up all be it very sexily when you mouse moves from top to bottom of screen.
So I want to set a timeout and clear it on mouse out...
// Setup hover
var fadeDuration = 200;
var setDelay;
$level1Item.hover(function () {
setDelay = setTimeout("$(this).addClass('hover').find('.level-2').fadeIn(200)", 500);
//$(this).addClass('hover').find('.level-2').fadeIn(fadeDuration);
}, function () {
clearTimeout(setDelay);
$(this).removeClass('hover').find('.level-2').fadeOut(fadeDuration);
});
ABSOLUTELY NOTHING HAPPENS!! I have tried alerts in the timeout function and they work... originally the variable fadeDuration was undefined but a number stops the console error.
Try amending the setTimeout call to use an anonymous function:
// Setup hover
var fadeDuration = 200;
var setDelay;
var $item;
$level1Item.hover(function () {
$item = $(this);
setDelay = setTimeout(function() {
$item.addClass('hover').find('.level-2').fadeIn(200)
}, 500);
},
function () {
clearTimeout(setDelay);
$(this).removeClass('hover').find('.level-2').fadeOut(fadeDuration);
});
When the string you pass to setTimeout is eval()ed, this is window and not whatever object you are expecting.
Don't pass strings to setTimeout, and be careful to preserve the value of this.
var self = this;
setDelay = setTimeout(function () {
$(self).addClass('hover').find('.level-2').fadeIn(200);
}, 500);
You can't use this in the setTimeout-code, since this depends on the context. So when the timeout fires, the this is a different this... bad English, but hopefully it makes sense.
Also, avoid using strings in timers; use functions instead. While you can use a string, which is then evaluated as JavaScript, it's generally bad form compared to simply wrapping the same code in a function
var fadeDuration = 200;
var setDelay;
$level1Item.hover(function () {
var element = $(this);
setDelay = setTimeout(function() {
element.addClass('hover').find('.level-2').fadeIn(fadeDuration);
}, 500);
}, function () {
clearTimeout(setDelay);
$(this).removeClass('hover').find('.level-2').fadeOut(fadeDuration);
});
I'd like to write a Chrome extension that works with a particular JS-based chat application. It needs to be made aware every time the chat receives a message.
Now, I can obviously do this easily by setting up a timer and checking to see if $("chat-messages").childElements().length has increased, but I'd rather go with the more elegant method of setting up an event handler of some sort to fire every time appendChatMessage() is invoked. Is there a way to do this?
var oldfunc = appendChatMessage;
appendChatMessage = function() { eval(oldfunc); myChatMessageReceivedHandler(); }
Doesn't seem to be working.
If there is a method appendChatMessage that is called every time a new message arrives, you could do like this
var old = appendChatMessage;
appendChatMessage = function() {
// call the initial method and store the result
var result = old.apply( this, arguments );
// do your stuff here
// return the initial result
return result;
};
You have to do oldfunc(). Besides that I'd create an event to to that
var oldfunc = appendChatMessage;
appendChatMessage = function() { oldfunc(); $(document).trigger("msg_received"); }
$(document).bind("msg_received", function(params){
//do your logic when message arrives
});
You should decide which element to attach the event into and its params.
Hope this helps. Cheers
var oldfunc = appendChatMessage;
appendChatMessage = function() { eval(oldfunc(); myChatMessageReceivedHandler(); }
Should work, depending on the context.
var f = function () { console.log('foo'); }
var f2 = f;
f = function () { f2(); console.log('bar'); }
This should print:
foo
bar