bacon.js: Strange behaviour with holdWhen and onValue - javascript

Take a look at the working CodePen here: http://codepen.io/djskinner/pen/JdpwyY
// Animation start events push here
var startBus = new Bacon.Bus();
// Animation end events push here
var endBus = new Bacon.Bus();
// Balance updates push here
var balanceBus = new Bacon.Bus();
// A Property that determines if animating or not
var isAnimating = Bacon.update(false,
[startBus], function() { return true; },
[endBus], function() { return false; }
);
// Only update the displayBalance when not animating
var displayBalance = Bacon.update(0,
[balanceBus.holdWhen(isAnimating)], function(previous, x) {
return x;
}
);
setTimeout(function() {
var streamTemplate = Bacon.combineTemplate({
balance: displayBalance
});
// Uncommenting this block changes the way the system behaves
// streamTemplate.onValue(function(initialState) {
// console.log(initialState);
//})();
// Print the displayBalance
streamTemplate.onValue(function(v) {
console.log(v.balance);
});
});
Pressing the balance button generates a new random number. A Property is created that uses holdWhen to restrict balance updates coming through until the isAnimating Property becomes false.
If I was interested in getting the initial state of streamTemplate, I might get the value and immediately unsubscribe:
streamTemplate.onValue(function(initialState) {
console.log(initialState);
})();
However, once I do this the displayBalance Property behaves differently and I no longer receive updates.
Why would this seemingly inert change make such a drastic different to the system? Surely the behaviour of the system shouldn't be dependent on whether someone has subscribe and unsubscribed to the streamTemplate at some point in the past?

This behaviour has been confirmed as a bug that has been fixed in 0.7.67.
See here for details.

Related

Meteor inserting objects from one collection to another bug

By click I add record from one collection to another. But a lot of times it adds an empty object. Here I tried to add some timeout and check if there is an _id attribute, and the amount of empty objects has reduced, but there are still some empty objects, when click too often. Is this a known bug? Is there any way to get around this?
timeout = false;
Template.clients.events({
'click': function() {
if(typeof this._id !== 'undefined' && !timeout) {
timeout = true;
TempCol.insert(this, function() {
var tmt = 300 + parseInt(Math.floor(Math.random() * (300 + 1)));
setTimeout(function() { timeout = false; }, tmt);
});
}
}
});
UPD: Actually, it gets the job done, but the question is still open: is that a bug or what?

Knockout computed and input validation

I am fairly new to knockout and am trying to figure out how to put two pieces that I understand together.
I need:
Items that are dependent on each other.
Input value validation on the items.
Example:
I have startTime in seconds, duration in seconds, and stopTime that is calculated from startTime + duration
startTime cannot be changed
duration and stopTime are tied to input fields
stopTime is displayed and entered in HH:MM:SS format
If the user changes stopTime, duration should be calculated and automatically updated
If the user changes duration, stopTime should be calculated and automatically updated
I can make them update each other (assume Sec2HMS and HMS2Sec are defined elsewhere, and convert between HH:MM:SS and seconds):
this.startTime = 120; // Start at 120 seconds
this.duration = ko.observable(0);
// This dependency works by itself.
this.stopTimeFormatted = ko.computed({
read: function () {
return Sec2HMS(this.startTime + parseInt(this.duration()), true);
},
write: function (value) {
var stopTimeSeconds = HMS2Sec(value);
if (!isNaN(stopTimeSeconds)) {
this.duration(stopTimeSeconds - this.startTime);
} else {
this.duration(0);
}
},
owner: this
});
Or, I can use extenders or fn to validate the input as is shown in the knockout docs:
ko.subscribable.fn.HMSValidate = function (errorMessage) {
//add some sub-observables to our observable
var observable = this;
observable.hasError = ko.observable();
observable.errorMessage = ko.observable();
function validate(newValue) {
var isInvalid = isNaN(HMS2Sec(newValue));
observable.hasError(isInvalid ? true : false);
observable.errorMessage(isInvalid ? errorMessage : null);
}
//initial validation
validate(observable());
//validate whenever the value changes
observable.subscribe(validate);
//return the original observable
return observable;
};
this.startTime = 120; // Start at 120 seconds
this.duration = ko.observable(0);
this.stopTimeHMS = ko.observable("00:00:00").HMSValidate("HH:MM:SS please");
But how do I get them working together? If I add the HMSValidate to the computed in the first block it doesn't work because by the time HMSValidate's validate function gets the value it's already been changed.
I have made it work in the first block by adding another observable that keeps track of the "raw" value passed into the computed and then adding another computed that uses that value to decide if it's an error state or not, but that doesn't feel very elegant.
Is there a better way?
http://jsfiddle.net/cygnl7/njNaS/2/
I came back to this after a week of wrapping up issues that I didn't have a workaround for (code cleanup time!), and this is what I have.
I ended up with the idea that I mentioned in the end of the question, but encapsulating it in the fn itself.
ko.subscribable.fn.hmsValidate = function (errorMessage) {
var origObservable = this;
var rawValue = ko.observable(origObservable()); // Used for error checking without changing our main observable.
if (!origObservable.hmsFormatValidator) {
// Handy place to store the validator observable
origObservable.hmsFormatValidator = ko.computed({
read: function () {
// Something else could have updated our observable, so keep our rawValue in sync.
rawValue(origObservable());
return origObservable();
},
write: function (newValue) {
rawValue(newValue);
if (newValue != origObservable() && !isNaN(HMS2Sec(newValue))) {
origObservable(newValue);
}
}
});
origObservable.hmsFormatValidator.hasError = ko.computed(function () {
return isNaN(HMS2Sec(rawValue()));
}, this);
origObservable.hmsFormatValidator.errorMessage = ko.computed(function () {
return errorMessage;
}, this);
}
return origObservable.hmsFormatValidator;
};
What this does is creates another computed observable that acts as a front/filter to the original observable. That observable has some other sub-observables, hasError and errorMessage, attached to it for the error states. The rawValue keeps track of the value as it was entered so that we can detect whether it was a good value or not. This handles the validation half of my requirements.
As for making two values dependent on each other, the original code in my question works. To make it validated, I add hmsValidate to it, like so:
this.stopTimeFormatted = ko.computed({
read: function () {
return Sec2HMS(this.startTime + parseInt(this.duration()), true);
},
write: function (value) {
this.duration(HMS2Sec(value) - this.startTime);
},
owner: this
}).hmsValidate("HH:MM:SS please");
See it in action here: http://jsfiddle.net/cygnl7/tNV5S/1/
It's worth noting that the validation inside of write is no longer necessary since the value will only ever be written by hmsValidate if it validated properly.
This still feels a little inelegant to me since I'm checking isNaN a couple of times and having to track the original value (especially in the read()), so if someone comes up with another way to do this, I'm all ears.

stopping dynamically generated setInterval

I am generating multiple charts each with their own setInterval to refresh the data. I have it set to clearInterval when the dynamically generated container is removed - but if I reload and it has the same id the old setInterval continues to run. Is there a way to set a dynamically named setInterval that can be stopped when the replacement is generated?
Right now I'm using:
function generateChart(data, location){
var chart = new Highcharts.Chart({
// blah blah blah
}, function(chart){
setInterval(function(){
if($('#'+location).length){
// I'm doing stuff every minute
}else{
clearInterval();
}
},60000);
});
}
What happens is, the location is a randomly generated string that becomes the element ID for the container for the Highchart and if they user saves the chart it becomes the unique identifier. If the user updates the chart that's saved and reloads the chart, the old one gets .removed() and the new one generated in its place. Now the new one has the same element ID as the old one and since the old interval finds the container it wants it attempts to continue updating - which is can't since its chart went poof.
is there a way to set a dynamic variable I can use for setInterval so that I can clearInterval on it?
var blob+location = setInterval(function(){ ...
and then
clearInterval(blob+location);
You can just use an object:
var myObj = {};
var location = "somevalue";
myObj[location] = setInterval(...
clearInterval(myObj[location]);
ok - since I couldn't seem to wrap my head around some of your answers I decided to go low tech.
function genToken(){
var num = Math.floor(Math.random() * 10000);
var token = 't-' + num;
return token;
}
function genLocation(){
var chartToken = genToken();
var newChart = '<div id="'+location+'" data-token="'+chartToken+'"></div>';
$('#chartHome').append(newChart);
}
// inside my chart function
var token = $('#'+location).data('token');
setInterval(function(){
if( $('[data-token="'+token+'"]').length ){
// still there - keep going
}else{
// all gone - time to stop
clearInterval();
}
},60000);
now when I do:
$('#'+location).remove();
the token also vanishes and won't be the same if I generate a new chart with the same location id.
Stop using setInterval, use setTimeout instead (How do I execute a piece of code no more than every X minutes?):
function generateChart(data, location) {
var element = $('#'+location);
var chart = new Highcharts.Chart({
// blah blah blah
}, foo);
var foo = function() {
if(element){
// I'm doing stuff every minute
setTimeout(foo, 6000);
}
};
}
To stop it, just avoid the setTimeout or make element = null.
Maybe my code is a little bit wrong (I'm getting sleep right now), but the thing is to use setTimeout and closures.
If inside foo, something longs more than 6 seconds you will be in troubles since setTimeinterval will call it again, please watch http://www.youtube.com/watch?feature=player_detailpage&v=i_qE1iAmjFg#t=462s , so, this way you ensure that this will run 6 seconds after the last completed stuff.
I'll let this example here to posterity:
http://jsfiddle.net/coma/vECyv/2/
var closure = function(id) {
var n = 0;
var go = true;
$('#' + id).one('click', function(event) {
go = false;
});
var foo = function() {
if(go) {
console.log(id, n++);
setTimeout(foo, 1000);
}
};
foo();
};
closure('a');
closure('b');
Not sure if anyone is still looking for this solution but I ran into this problem and chose the following approach.
For anyone dynamically creating private/anonymous intervals that need to be stopped based on some event. You can simply save the interval in a variable, then transfer that variable into a data property in your html element.
// Outer scope
let pos = 1
let interval = setInterval(() => {
if (pos < 700) {
pos++;
}
htmlEl.style.top = pos + "px";
});
htmlEl.setAttribute("data-interval", interval)
This will save the numeric identifier of your interval, providing that html element is somewhere in your DOM.
Then, later you can simply extract this data attribute and use it to cancel an interval.
let intervalId = document.querySelector("#someElement").dataset.interval;
clearInterval(intervalId);

Open social viewer state (isOwner)

We are creating a gadget for the opensocial API 0.7.
In some functions we have to decide, if the viewer is the owner.
We couldn't use the usual function for this purpose:
return gadgets.util.getUrlParameters().viewer == gadgets.util.getUrlParameters().owner;
so we had to create a workaround and get the information via a DataRequest.
The DataRequest calls a callback function and has no useable return value.
We tried a quick hack by using global variables to set the corresponding value.
The issue at this point is, that the function does not 'wait' for the callback-function to be finished. We know this is no good code/style at all, but we tried to force a timeout for debug reasons.
Handling all the code within the callback-function (as suggested in the examples of the opensocial docs) is not possible.
We are looking for something like a real 'sleep()' in JavaScript to wait for the callback-function to complete or another alternative to get the owner information about the viewer.
globalWorkaroundIsOwner = false;
function show_teaser(){
if (current_user_is_owner()){
// ...
}
// ...
}
function current_user_is_owner() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), 'viewer');
// This will set the the correct value
req.send( user_is_owner_workaround );
// This is an attempt to delay the return of the value.
// An alert() at this point delays the return as wanted.
window.setTimeout("empty()", 2000);
// This return seems to be called too early (the variable is false)
return globalWorkaroundIsOwner;
}
function user_is_owner_workaround(dataResponse) {
var viewer = dataResponse.get('viewer').getData();
globalWorkaroundIsOwner = viewer.isOwner();
// value is correct at this point
}
Can you use an additional flag in order to indicate whether the remote query has already returned the required value?
var globalWorkaroundIsOwner = false;
var workaroundStarted = false, workAroundComplete = false;
var checker;
function show_teaser(){
if (!workaroundStarted) {
workaroundStarted = true;
current_user_is_owner();
}
if (workaroundComplete) {
if (globalWorkaroundIsOwner){
// ...
}
// ...
if (checker) {
clearInterval(checker);
}
}
}
function current_user_is_owner() {
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest(opensocial.DataRequest.PersonId.VIEWER), 'viewer');
checker = setInterval("show_teaser()", 1000);
// This will set the the correct value
req.send( user_is_owner_workaround );
}
function user_is_owner_workaround(dataResponse) {
var viewer = dataResponse.get('viewer').getData();
globalWorkaroundIsOwner = viewer.isOwner();
workAroundComplete = true;
// value is correct at this point
}

How to lock AJAX functions from overlapping?

I've got one function checkEvery15Seconds that runs every 15 seconds. It checks to see if new comments have been added to a page.
I've got a form that submits a new comment once the submit button is pressed, then displays the new comment on the page.
In the process of adding a new comment checkEvery15Seconds is querying the database at the same time, so I end up with duplicate comments on the page (not in the database though, this is purely a JavaScript issue).
How can I get my "submitComment" function to stop checkEvery15Seconds and restart it after the "submitComment" function has finished executing?
add a boolean called somewhat suspend15sCheck in a scope which is accessible by both functions. enable it while adding the comment and afterwards set it to false again.
in your 15sCheck-function you first have to check if you are allowed to check :-)
var suspend15sCheck = false;
function addComment()
{
suspend15sCheck = true;
// add comment on base of form data
suspend15sCheck = false;
}
function myTimer()
{
if(suspend15sCheck === false)
{
// add comments via ajax request
// remember to check if the comments who will be added already exist :-)
}
}
Simplest solution: use a flagging variable that you turn on and off. The first line of your "checkEvery15Seconds" function reads: if (!global_checkingEvery15Seconds) return;
Just set that variable (whatever you name it, global or object-bound) to true when you want the checking turned on, and off when you don't.
You'll need a status variable to indicate the current state of the comment ajax request
var requestComments = false;
if(requestComments === false) {
requestComments = true;
// make ajax request
// on ajax success/fail
requestComments = false;
}
Wrap it up in an object that allows other functions to set start/stop flags on it.
function My15SecondsObj() {
var objSelf = this;
//
this.run();
}
My15SecondsObj.Paused = false;
My15SecondsObj.prototype.run= function() {
if (!Paused)
{
// Do your call here
}
var _this = this;
setTimeout(function() { _this.run(); }, 15000);
}
Now when you want to use this object, just do
var myObj = new My15SecondsObj();
and when you want to pause it,
myObj.Paused = true;
and start it again by doing:
myObj.Paused = false;
Add some events if you want to get really crazy, so that other objects can subscribe to notifications about when the database updates have succeeded, etc...

Categories

Resources