Vue2: Moment JS countdown timer - methods

I have a problem which I don't know how to solve.
I have a component with an end-date, and I want to show a countdown timer with the remaining seconds.
I use moment JS for this, but I don't know how to implement this in Vue2.
Should I use a computed method?
computed: {
timer: function() {
var now = moment();
var then = moment().add(180, 'seconds');
return moment().to(then);
(function timerLoop() {
this.timer = countdown(then).toString();
requestAnimationFrame(timerLoop);
})();
},
Problem is that I have to return the value before vue2 shows it. But I also have to use requestAnimationFrame to update this every second.
Can anyone help me out? And what is the best way to use this? setInterval or requestAnimationFrame? I think the latter, because there will be 100+ timers on 1 page, so performance is necessary.
So long story, short:
Momentjs and countdown timer
How can I create an Vue2 function/method/mixin of this? And which updates every second?
Thanks

Instead of having a time-loop for each timer, I suggest having a single interval updating a value on the model every second and using Vue's reactivity to trigger updates to the computed properties.
I've created a pen where you easily can play around with number active timers to see how it impacts performance.
data() {
return {
interval: null,
now: new Date(),
dates: [], // Your dates here
}
},
computed() {
timers() {
return this.dates.map(then => moment(this.now).to(then))
},
},
mounted() {
this.interval = setInterval(() => {
this.now = new Date()
}, 1000)
}

Related

JasmineJS + AngularJS: How to mock the delay inside Spy#callFake()

Let's assume I have a service function that returns me the current location. And the function has callbacks to return the location. We can easily mock the function like as follows. But I wanted to introduce some delay (let's say 1 sec) before the callFake() invokes the successHandler(location).
Is there a way to achieve that?
xxxSpec.js
spyOn(LocationService, 'getLocation').and.callFake(function(successHandler, errorHandler) {
//TODO: introduce some delay here
const location = {...};
successHandler(location);
}
LocationService.js
function getLocation(successCallback, errorCallback) {
let location = {...};
successCallback(location);
}
Introducing delay in Javascript is easily done with the setTimeout API, details here. You haven't specified if you are using a framework such as Angular, so your code may differ slightly from what I have below.
It does not appear that you are using Observables or Promises for easier handling of asynchronous code. Jasmine 2 does have the 'done' callback that can be useful for this. Something like this could work:
it( "my test", function(done) {
let successHandler = jasmine.createSpy();
spyOn(LocationService, 'getLocation').and.callFake(function(successHandler, errorHandler) {
setTimeout(function() {
const location = {...};
successHandler(location);
}, 1000); // wait for 1 second
})
// Now invoke the function under test
functionUnderTest(/* location data */);
// To test we have to wait until it's completed before expecting...
setTimeout(function(){
// check what you want to check in the test ...
expect(successHandler).toHaveBeenCalled();
// Let Jasmine know the test is done.
done();
}, 1500); // wait for longer than one second to test results
});
However, it is not clear to me why adding the timeouts would be valuable to your testing. :)
I hope this helps.

Inside a React single-page app, what is a useful way to measure the time for a page to appear visually complete?

Or, if exact measurement is difficult, is there a measurement that will respond somewhat proportionally to front-end improvements? We'd like to fire an event when that happens (for Real User Monitoring).
Here is an idea that may give you what you need. I tried it and it does work, but I am not sure if this will be accurate enough for your needs.
I should also point out that I did not try this with nested components, but rather only regarding a single component with all of its markup. This will be clear from my code.
Some points must be prefaced before the approach. We know that in the react lifecycle the constructoris the first to fire and that componentDidMount fires right after the rendering of the component. This makes me think that the time it takes between the constructor to fire and componentDidMount to fire is the time it takes for the rendering to complete.
Another important point is the way setTimeout works. We know that the amount of time that gets passed to the setTimeout function is not the actual amount that it will take till the function will fire, but rather it is the minimum amount until the function will fire. This is because of the event loop and is beyond the scope here.
With the above in mind here is my code.
export default class App extends React.Component {
constructor() {
super();
this.timer();
}
timer() {
let date = new Date();
console.log(date.toLocaleTimeString())
setTimeout(() => {
myTimer();
}, 0)
function myTimer() {
let date = new Date();
console.log(date.toLocaleTimeString())
}
}
createItems() {
let items = [];
for (let i = 0; i < 100000; i++) {
items.push(<h3 key={i}>{i}</h3>)
}
return items;
}
render() {
const items = this.createItems();
return (
<div>
{items}
</div>
)
}
}
The idea here is that since the constructor fires first I can call my timer function from there and get the starting time. Then in my timer function I call a setTimeout and pass it 0 as the delay parameter. Because JavaScript is single threaded the callback in setTimeout will only fire once the rendering is complete, therefor I pass it no delay to ensure that it does in fact fire right after the rendering is complete. The callback function then logs the current time again, and I can now see the difference between start and end time of rendering.
Hope this helps.

redux-observable: Mapping to an action as soon as another was triggered at least once

I have an SPA that is loading some global/shared data (let's call this APP_LOAD_OK) and page-specific data (DASHBOARD_LOAD_OK) from the server. I want to show a loading animation until both APP_LOAD_OK and DASHBOARD_LOAD_OK are dispatched.
Now I have a problem with expressing this in RxJS. What I need is to trigger an action after each DASHBOARD_LOAD_OK, as long as there had been at least one APP_LOAD_OK. Something like this:
action$
.ofType(DASHBOARD_LOAD_OK)
.waitUntil(action$.ofType(APP_LOAD_OK).first())
.mapTo(...)
Does anybody know, how I can express it in valid RxJS?
You can use withLatestFrom since it will wait until both sources emit at least once before emitting. If you use the DASHBOARD_LOAD_OK as the primary source:
action$.ofType(DASHBOARD_LOAD_OK)
.withLatestFrom(action$.ofType(APP_LOAD_OK) /*Optionally*/.take(1))
.mapTo(/*...*/);
This allows you to keep emitting in the case that DASHBOARD_LOAD_OK fires more than once.
I wanted to avoid implementing a new operator, because I thought my RxJS knowledge was not good enough for that, but it turned out to be easier than I thought. I am keeping this open in case somebody has a nicer solution. Below you can find the code.
Observable.prototype.waitUntil = function(trigger) {
const source = this;
let buffer = [];
let completed = false;
return Observable.create(observer => {
trigger.subscribe(
undefined,
undefined,
() => {
buffer.forEach(data => observer.next(data));
buffer = undefined;
completed = true;
});
source.subscribe(
data => {
if (completed) {
observer.next(data);
} else {
buffer.push(data);
}
},
observer.error.bind(observer),
observer.complete.bind(observer)
);
});
};
If you want to receive every DASHBOARD_LOAD_OK after the first APP_LOAD_OK You can simply use skipUntil:
action$ .ofType(DASHBOARD_LOAD_OK)
.skipUntil(action$.ofType(APP_LOAD_OK).Take(1))
.mapTo(...)
This would only start emitting DASHBOARD_LOAD_OK actions after the first APP_LOAD_OK, all actions before are ignored.

Implement repeat interval of more than a day for parse.com background job

I have created a background job like this:
Parse.Cloud.job("ResetLeaderboard",
function(request, response)
{
Parse.Cloud.useMasterKey();
var query = new Parse.Query("Leaderboard");
query.find(
{
success: function(results)
{
response.success("Success!");
},
error: function(error)
{
response.error(error);
}
})
.then(
function(results)
{
return Parse.Object.destroyAll(results);
});
});
I want to run this job every 15 days. But there is no option available at www.parse.com to set time interval for more than a day.
I think I need to use a time stamp and compare that value with current time. Can somebody show me the standard way to do this?
You're right that the job scheduling UI is constrained to a single day. The way to solve the problem is to have the job run daily, but to have it do nothing on 14 out of 15 runs. Those do-nothing runs will be wasteful, but microscopically so, and parse is paying the bills anyway.
The specifics of the solution depend on specific requirements. If you require maximum control, like exactly 15 days down to the millisecond, starting at a millisecond-specific time, you'd need to create some scratch space in the database where state (in particular, date) from the prior run is kept.
But the job looks like a cleanup task, where the requirement of "very nearly 15 days, beginning within 15 days" is sufficient. With that simpler requirement, your intuition is correct that simple date arithmetic will work.
Also, importantly, it looks to me like your intention is to find several objects in need of deletion, then delete them. The posted logic doesn't quite do that. I've repaired the logic error and cleaned up the promise handling as well...
// Schedule this to run daily using the web UI
Parse.Cloud.job("ResetLeaderboard", function(request, response) {
if (dayOfYear() % 15 === 0) {
var query = new Parse.Query("Leaderboard");
query.find().then(function(results) {
Parse.Cloud.useMasterKey();
return Parse.Object.destroyAll(results);
}).then(function() {
response.success("Success!");
}, function(error) {
response.error(error);
});
} else {
response.success("Successfully did nothing");
}
});
function dayOfYear() {
var now = new Date();
var start = new Date(now.getFullYear(), 0, 0);
var diff = now - start;
var oneDay = 1000 * 60 * 60 * 24;
return Math.floor(diff / oneDay);
}
The dayOfYear function is thanks to Alex Turpin, here

Backbone.js : change not firing on model.change()

I'm facing a "change event not firing" issue on Backbone.js =/
Here my view of User model :
window.UserView = Backbone.View.extend({
...
initialize: function()
{
this.model.on('destroy', this.remove, this);
this.model.on('change', function()
{
console.log('foo');
});
},
render: function(selected)
{
var view = this.template(this.model.toJSON());
$(this.el).html(view);
return this;
},
transfer: function(e)
{
var cas = listofcas;
var transferTo = Users.getByCid('c1');
var transferToCas = transferTo.get('cas');
this.model.set('cas', cas);
console.log('current model');
console.log(this.model);
//this.model.change();
this.model.trigger("change:cas");
console.log('trigger change');
transferTo.set('cas', transferToCas);
console.log('transferto model');
console.log(transferTo);
//transferTo.change();
transferTo.trigger("change:cas");
console.log('trigger change');
}
});
Here, the User model :
window.User = Backbone.Model.extend({
urlRoot: $('#pilote-manager-app').attr('data-src'),
initialize: function()
{
this.set('rand', 1);
this.set('specialite', this.get('sfGuardUser').specialite);
this.set('name', this.get('sfGuardUser').first_name + ' ' + this.get('sfGuardUser').last_name);
this.set('userid', this.get('sfGuardUser').id);
this.set('avatarsrc', this.get('sfGuardUser').avatarsrc);
this.set('cas', new Array());
if (undefined != this.get('sfGuardUser').SignalisationBouclePorteur) {
var cas = new Array();
_.each(this.get('sfGuardUser').SignalisationBouclePorteur, function(value)
{
cas.push(value.Signalisation);
});
this.set('cas', cas);
}
}
});
In User model, there is "cas" attribute, which is an array of objects.
I read in others topics that change events are not fire on model.set if attributes are not a value.
So, I try to trigger directly the change event with model.change() method.
But, I have no "foo" log in my console ...
I'm pretty new to backbone and I was having this same problem.
After doing some research, I found a few posts that shed a little bit more light on why this was happening, and eventually things started to make sense:
Question 1
Question 2
The core reason has to do with the notion of reference equality versus set/member equality. It appears that to a large extent, reference equality is one of the primary techniques backbone uses to figure out when an attribute has changed.
I find that if I use techniques that generate a new reference like Array.slice() or _.clone(), the change event is recognized.
So for example, the following code does not trigger the event because I'm altering the same array reference:
this.collection.each(function (caseFileModel) {
var labelArray = caseFileModel.get("labels");
labelArray.push({ Key: 1, DisplayValue: messageData });
caseFileModel.set({ "labels": labelArray });
});
While this code does trigger the event:
this.collection.each(function (caseFileModel) {
var labelArray = _.clone(caseFileModel.get("labels")); // The clone() call ensures we get a new array reference - a requirement for the change event
labelArray.push({ Key: 1, DisplayValue: messageData });
caseFileModel.set({ "labels": labelArray });
});
NOTE: According to the Underscore API, _.clone() copies certain nested items by reference. The root/parent object is cloned though, so it will work fine for backbone. That is, if your array is very simple and does not have nested structures e.g. [1, 2, 3].
While my improved code above triggered the change event, the following did not because my array contained nested objects:
var labelArray = _.clone(this.model.get("labels"));
_.each(labelArray, function (label) {
label.isSelected = (_.isEqual(label, selectedLabel));
});
this.model.set({ "labels": labelArray });
Now why does this matter? After debugging very carefully, I noticed that in my iterator I was referencing the same object reference backbone was storing. In other words, I had inadvertently reached into the innards of my model and flipped a bit. When I called setLabels(), backbone correctly recognized that nothing changed because it already knew I flipped that bit.
After looking around some more, people seem to generally say that deep copy operations in javascript are a real pain - nothing built-in to do it. So I did this, which worked fine for me - general applicability may vary:
var labelArray = JSON.parse(JSON.stringify(this.model.get("labels")));
_.each(labelArray, function (label) {
label.isSelected = (_.isEqual(label, selectedLabel));
});
this.model.set({ "labels": labelArray });
Interesting. I would have thought that .set({cas:someArray}) would have fired off a change event. Like you said, it doesn't seem to, and I can't get it to fire with .change() BUT, I can get the events to work if I just do model.trigger('change') or model.trigger('change:attribute')
This would allow you to trigger the change event without that random attribute hack.
If someone could explain what is going on with events, Backbone, and this code, that would help me learn something too... Here is some code.
Ship = Backbone.Model.extend({
defaults: {
name:'titanic',
cas: new Array()
},
initialize: function() {
this.on('change:cas', this.notify, this);
this.on('change', this.notifyGeneral, this);
},
notify: function() {
console.log('cas changed');
},
notifyGeneral: function() {
console.log('general change');
}
});
myShip = new Ship();
myShip.set('cas',new Array());
// No event fired off
myShip.set({cas: [1,2,3]}); // <- Why? Compared to next "Why?", why does this work?
// cas changed
// general change
myArray = new Array();
myArray.push(4,5,6);
myShip.set({cas:myArray}); // <- Why?
// No event fired off
myShip.toJSON();
// Array[3] is definitely there
myShip.change();
// No event fired off
The interesting part that might help you:
myShip.trigger('change');
// general change
myShip.trigger('change:cas');
// cas changed
I find this interesting and I hope this answer will also spawn some insightful explanation in comments which I don't have.

Resources