AngularJS Countdown within an ng-repeat - javascript

I have a small AngularJS app which searches and retrieves a listing of users and their next scheduled meeting (assuming one is scheduled in the next 8 hours) brought back from the server using JSON with the time format in UTC for ease of calculation to local times. Each user could have a different status (busy until x time, free until x time).
What I would like to accomplish is to be able to update the DOM with time remaining until the meeting scheduled has completed or time left until the meeting starts. I have some code working sort of, but because I am apparently not doing this correctly with only a few entries in the result set it brings the browser to a near standstill. Any suggestions would be appreciated!
My current code consists of the following snippets:
[Main Page]
<tr ng-repeat="item in pagedItems[currentPage-1] | orderBy:sortingOrder:reverse" ng-class="{success:item._freeBusy=='Free', error:item._freeBusy=='Busy'}">
<td>{{item._firstName}}</td>
<td>{{item._lastName}}</td>
<td>{{item._facilityName}}</td>
<td>{{item._extension}}</td>
<td>{{item._description}}</td>
<td><a ng-hide="!item._extension" ng-click="dial(item)">Dial</a></td>
<td><button class="btn btn-primary" ng-click="openDetails(item)">Details</button></td>
<td>{{item._freeBusy}} {{item._timeLeft}} {{calculateTime(item._freeBusyTime,$index)}}</td>
</tr>
[Controller]
$scope.timeUntil = function(s) {
function isoToObj(s) {
var b = s.split(/[-T+:]/i);
return new Date(Date.UTC(b[0], --b[1], b[2], b[3], b[4], b[5]));
}
// Utility to add leading zero
function z(n) {
return (n < 10 ? '0' : '') + n;
}
// Convert string to date object
var d = isoToObj(s);
var diff = d - new Date();
// Allow for previous times
var sign = diff < 0 ? '-' : '';
diff = Math.abs(diff);
// Get time components
var hours = diff / 3.6e6 | 0;
var mins = diff % 3.6e6 / 6e4 | 0;
var secs = Math.round(diff % 6e4 / 1e3);
// Return formatted string
return sign + z(hours) + ' Hours ' + z(mins) + ' Min' + ':' + z(secs);// + ':' + z(secs)
}
$scope.calculateTime = function(s, idx) {
timeoutID = $timeout(function() {
$scope.items[idx]._timeLeft = $scope.timeUntil(s);
$scope.calculateTime(s, idx);
}, 1000);
};
EDIT
I understand the issues as mentioned below, what I am struggling with is how to register this corretly. As it could be up to 15+ separate times updating to a single tick that's where I am getting lost.

You are registering way more timeouts than you think. Every time angular renders your view, you register new timeout handlers. Add a counter of your callback and watch the count go:
$scope.called = 0;
$scope.calculateTime = function(s, idx) {
timeoutID = $timeout(function() {
$scope.items[idx]._timeLeft = $scope.timeUntil(s);
$scope.calculateTime(s, idx);
console.log(++$scope.called);
}, 1000);
};
See this plunk where I reproduced the bug: http://plnkr.co/edit/qJ4zDl6gc5C7Edg0T0gB. Just run it and watch the counter.
Why do you want to update your _timeLeft in the rendering cycle? Why not do this:
$scope.called = 0;
setInterval(function() {
angular.forEach($scope.items, function function_name(item) {
item._timeLeft = $scope.timeUntil(item._freeBusyTime);
});
console.log(++$scope.called);
$scope.$apply();
}, 1000);
See new plunk: http://plnkr.co/edit/rVJ6p4VXDQvt7rjT6eka

You are calling $scope.calculateTime recursively! And you are modifying the list of items during the ng-repeat, which also causes and endless loop.
How about this: http://plnkr.co/edit/0JqK96irV4ETdWZYxO3P?p=preview
changed the html to refer to a separate array that doesn't affect ng-repeat:
<td>in {{_timeLeft[$index]}}</td>
which is updated as follows:
$scope._timeLeft = [];
var intervalID = window.setInterval(function() {
for (var i=0; i<$scope.items.length; i++) {
$scope._timeLeft[i] = $scope.timeUntil($scope.items[i]._freeBusyTime);
}
$scope.$apply();
}, 1000);
Note that $scope.$apply() is required to let Angular know that '_timeLeft' has been modified, which updates all references to it.

I do not think you should call $timeout every view update.
Instead, you can count down time within your controller and let view show the timer.
http://plnkr.co/edit/OUVdJyllzmVmPC0FhqYF?p=preview

Related

Determining time remaining until bus departs

For our digital signage system, I'd like to show how long until the next bus departs. I've built the array that holds all the times and successfully (maybe not elegantly or efficiently) gotten it to change all that to show how much time is remaining (positive or negative) until each listed departure.
I need a nudge in the right direction as to how to determine which bus is next based on the current time. If there is a bus in 7 minutes, I only need to display that one, not the next one that leaves in 20 minutes.
I was thinking perhaps a for loop that looks at the array of remaining times and stops the first time it gets to a positive value. I'm concerned that may cause issues that I'm not considering.
Any assistance would be greatly appreciated.
UPDATE: Unfortunately, all the solutions provided were throwing errors on our signage system. I suspect it is running some limited version of Javascript, but thats beyond me. However, the different solutions were extremely helpful just in getting me to think of another approach. I think I've finally come on one, as this seems to be working. I'm going to let it run over the holiday and check it on Monday. Thanks again!
var shuttleOrange = ["09:01", "09:37", "10:03", "10:29", "10:55", "11:21", "11:47", "12:13", "12:39", "13:05", "13:31", "13:57", "14:23", "14:49", "15:25", "15:51", "16:17", "16:57", "17:37", "18:17"];
var hFirst = shuttleOrange[0].slice(0,2);
var mFirst = shuttleOrange[0].slice(3,5);
var hLast = shuttleOrange[shuttleOrange.length-1].slice(0,2);
var mLast = shuttleOrange[shuttleOrange.length-1].slice(3,5);
var theTime = new Date();
var runFirst = new Date();
var runLast = new Date();
runFirst.setHours(hFirst,mFirst,0);
runLast.setHours(hLast,mLast,0);
if ((runFirst - theTime) >= (30*60*1000)) {
return "The first Orange Shuttle will depart PCN at " + shuttleOrange[0] + "."
} else if (theTime >= runLast) {
return "Orange Shuttle Service has ended for the day."
} else {
for(var i=0, l=shuttleOrange.length; i<l; i++)
{
var h = shuttleOrange[i].slice(0,2);
var m = shuttleOrange[i].slice(3,5);
var departPCN = new Date();
departPCN.setHours(h,m,0);
shuttleOrange[i] = departPCN;
}
for(var i=shuttleOrange.length-1; i--;)
{
//var theTime = new Date();
if (shuttleOrange[i] < theTime) shuttleOrange.splice(i,1)
}
var timeRem = Math.floor((shuttleOrange[0] - theTime)/1000/60);
if (timeRem >= 2) {
return "Departing in " + timeRem + " minutes."
} else if (timeRem > 0 && timeRem < 2) {
return "Departing in " + timeRem + " minute."
} else {
return "Departing now."
}
}
You only need to search once to find the index of the next scheduled time. Then as each time elapses, increment the index to get the next time. Once you're at the end of the array, start again.
A sample is below, most code is setup and helpers. It creates a dummy schedule for every two minutes from 5 minutes ago, then updates the message. Of course you can get a lot more sophisticated, e.g. show a warning when it's in the last few minutes, etc. But this shows the general idea.
window.addEventListener('DOMContentLoaded', function() {
// Return time formatted as HH:mm
function getHHmm(d) {
return `${('0'+d.getHours()).slice(-2)}:${('0'+d.getMinutes()).slice(-2)}`;
}
var sched = ["09:01", "09:37", "10:03", "10:29", "10:55", "11:21", "11:47",
"12:13", "12:39", "13:05", "13:31", "13:57", "14:23", "14:49",
"15:25", "15:51", "16:17", "16:57", "17:37", "18:17","21:09"];
var msg = '';
var msgEl = document.getElementById('alertInfo');
var time = getHHmm(new Date());
var index = 0;
// Set index to next scheduled time, stop if reach end of schedule
while (time.localeCompare(sched[index]) > 0 && index < sched.length) {
++index;
}
function showNextBus(){
var time = getHHmm(new Date());
var schedTime;
// If run out of times, next scheduled time must be the first one tomorrow
if (index == sched.length && time.localeCompare(sched[index - 1]) > 0) {
msg = `Current time: ${time} - Next bus: ${sched[0]} tomorrow`;
// Otherwise, show next scheduled time today
} else {
// Fix index if rolled over a day
index = index % sched.length;
schedTime = sched[index];
msg = `Current time: ${time} - Next bus: ${schedTime}`;
if (schedTime == time) msg += ' DEPARTING!!';
// Increment index if gone past this scheduled time
index += time.localeCompare(schedTime) > 0? 1 : 0;
}
msgEl.textContent = msg;
// Update message each second
// The could be smarter, using setInterval to schedule running at say 95%
// of the time to the next sched time, but never more than twice a second
setInterval(showNextBus, 1000);
}
showNextBus();
}, false);
<div id="alertInfo"></div>
Edit
You're right, I didn't allow for the case where the current time is after all the scheduled times on the first running. Fixed. I also changed all the string comparisons to use localeCompare, which I think is more robust. Hopefully the comments are sufficient.
I have used filter for all shuttle left after the right time and calculated how much time left for the first one.
var shuttleOrange = ["09:01", "09:37", "10:03", "10:29", "10:55", "11:21", "11:47", "12:13", "12:39", "13:05", "13:31", "13:57", "14:23", "14:49", "15:25", "15:51", "16:17", "16:57", "17:37", "18:17"];
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var remainShuttle = shuttleOrange.filter(bus => bus.substring(0,2) > h || (bus.substring(0,2) == h && bus.substring(3,5) > m));
var leftMinutes = (parseInt(remainShuttle[0].substring(0,2))*60 + parseInt(remainShuttle[0].substring(3,5)) - (parseInt(h) *60 + parseInt(m)));
console.log(parseInt(leftMinutes / 60) + " hours and " + leftMinutes % 60 +" minutes left for next shuttle");

How do I get daily step count in a gear fit 2 pro web based watchface?

I am building a html/js powered watchface for the gear fit 2 pro and I'm having trouble accomplishing what seems like a simple task: getting the daily step count.
I have poured over the documentation, but it only describes how to count either steps since the watchface has started, or steps since the device has been booted. Other watchfaces immediately detect the system-wide step count and display it, but I don't see how this is possible!
Does anyone have an example of how to do this? I suspect the stepdifference or readrecorderdata functions might be involved, but the first is impossible to use due to inadequate documentation and the second does not seem to actually be present in the device.
You can setAccumulativePedometerListener() for the time period sensor data required. In you case you can reset the listener at end of the day. I've written a pseudo_code for you to show daily step count.
var sensor_data = document.getElementById("sensor-data");
var step_count=0,
offset=0, // to reduce yesterday's data
currentDailyStep=0;
function updateTime() {
var datetime = tizen.time.getCurrentDateTime(),
hour = datetime.getHours(),
minute = datetime.getMinutes(),
second = datetime.getSeconds();
if(hour === 23 && minute === 59 && second === 59){ // at the end of the day
tizen.humanactivitymonitor.unsetAccumulativePedometerListener();
tizen.humanactivitymonitor.stop("PEDOMETER");
offset = step_count; // store today's count
pedometer_init(); //reset
}
/*
* Other Codes
* ............
* .........
*/
}
function onchangedCB(pedometerInfo) {
step_count = pedometerInfo.accumulativeTotalStepCount;
currentDailyStep = step_count - offset; // totl count - total count till yesterday
sensor_data.innerHTML = currentDailyStep;
}
function pedometer_init(){
tizen.humanactivitymonitor.start("PEDOMETER");
tizen.humanactivitymonitor.setAccumulativePedometerListener(onchangedCB);
}
function init(){
pedometer_init();
}
window.onload = init();
You need to reduce offset manually as stop() function don't reset the count. Store the daily step data If you are interested to show statistics.
In addition, In Tizen Developers API References there's a Code Sample using HumanActivityRecorder to record Step count daily, Please Check If it helps:
function onerror(error){
console.log(error.name + ": " + error.message);
}
function onread(data){
for (var idx = 0; idx < data.length; ++idx)
{
console.log("*** " + idx);
console.log('totalStepCount: ' + data[idx].totalStepCount);
}
}
var type = 'PEDOMETER';
var now = new Date();
var startTime = now.setDate(now.getDate() - 7);
var anchorTime = (new Date(2000, 1, 2, 6)).getTime();
var query ={
startTime: startTime / 1000,
anchorTime: anchorTime / 1000,
interval: 1440 /* 1 day */
};
try{
tizen.humanactivitymonitor.readRecorderData(type, query, onread, onerror);
}
catch (err){
console.log(err.name + ': ' + err.message);
}

How to manage multiple timers in javascript?

Below is the coding I use for one count up timer:
var sec = 0;
function pad ( val ) { return val > 9 ? val : "0" + val; }
function setTime()
{
document.getElementById("seconds0").innerHTML=pad(++sec%60);
document.getElementById("minutes0").innerHTML=pad(parseInt(sec/60,10));
}
var timer = setInterval(setTime, 1000);
If I have two timers, I write like this:
var sec = 0;
var sec1 = 0;
function pad ( val ) { return val > 9 ? val : "0" + val; }
function setTime()
{
document.getElementById("seconds0").innerHTML=pad(++sec%60);
document.getElementById("minutes0").innerHTML=pad(parseInt(sec/60,10));
}
function setTime1()
{
document.getElementById("seconds1").innerHTML=pad(++sec1%60);
document.getElementById("minutes1").innerHTML=pad(parseInt(sec1/60,10));
}
var timer = setInterval(setTime, 1000);
var timer1 = setInterval(setTime1, 1000);
Actually the timer I use is to show waiting time of people. The number of people is a unknown variable. Means that it can be from 1 - 100. So, one people is one timer. Below is the function I written.
showWait = function(){
var html = "";
for (var i = 0; i < total; i++)
{
html += '<div id="numbers" class="col-sm-3 col-xs-6">'+i+'</div>';
html += '<div id="qtime" class="col-sm-3 col-xs-6"></span><div><span class="glyphicon glyphicon-time"></span> Waiting</div><div id="waittime"><label id="minutes'+i+'">00</label>:<label id="seconds'+i+'">00</label></div></div>';
html += '</div>';
}
$('#waitnumber').html(html);
}
So, I don't think the way to create more timer is to keep repeating same function over and over again, right? It can't be if 100 people then there is 100 timers, right? Is there any simpler way to manage multiple timers?
i guess that every interval has 1000 ms waiting time, so you dont need multiple timers just one and in this one timer do what you need for every person
var sec = [time0, time1, time2 ....];
function pad ( val ) { return val > 9 ? val : "0" + val; }
function setTime()
{
for(person = 0; person < numberOfPeople; person++)
{
document.getElementById("seconds" + person).innerHTML=pad(++sec[person]%60);
document.getElementById("minutes" + person).innerHTML=pad(parseInt(sec[person]/60,10));
}
}
var timer = setInterval(setTime, 1000);
IF your time interval for each person is going to be fixed then I would suggest you should run only one timer of 1000ms and define certain variables for each person i.e something like that
var persons = [
{id : 0, min : 0, sec : 0, othercounts: 0},
{id : 1, min : 0, sec : 0, othercounts: 0}
]
and on the execution of timer function, just iterate through the array or (any data structure that you feel comfortable with) and increment the time counter variables for every person and refresh the dom.
your timer function will be like:
function setTime(){
persons.forEach(function(p){
p.min ++;
// your logic
document.getElementById("seconds"+ e.id).innerHTML=pad(++sec%60);
document.getElementById("minutes" + e.id).innerHTML=pad(parseInt(sec/60,10));
});
}
and register the interval only once i.e on document load event or on your custom event
var timer = setInterval(setTime, 1000);
on arrival of new person, just push the person object into the persons array.
This is just one way, there can be more better solution by rendering the html only once at the end of the loop.

Create countdown in rowview (Titanium)

I am a beginner in Appcelerator Titanium APP development. From the inspiration of this link I am trying to create a countdown timer to be work in TableRowView as each row have its own time set. And I customize this class to show Hours with minutes and seconds.
I created the following code in each TableRowView to execute countdown in list on the fly.
Code 1
my_timer[timer_index] = new countDown(parseInt(timer_index), parseInt(15), parseInt(50),
function() {
remainingTime.text = ''+my_timer[timer_index].time.h + " : " + my_timer[timer_index].time.m + " : " + my_timer [timer_index].time.s;
}, function() {
//alert("The time is up!");
}
);
my_timer[timer_index++].start();
my_time used to push all the instances of countdown timer for each row.
The data is coming from XHR, therefore I created an array literal to hold all instances like in the snippet of code.
Problem: when I try to run my app with this code, it shows me an exception saying something like "time.h is undefined". However, I defined time.h as you can see in code.
Furthermore, I can use this class for multiple countdowns by using single array
for example:
my_timer[0] = new countDown(2,5,5,function(){
somelabel1.text = my_timer[0].time.h+":"+my_timer[0].time.m+":"+my_timer[0].time.s;
})
my_timer[1] = new countDown(2,5,5,function(){
somelabel1.text = my_timer[1].time.h+":"+my_timer[1].time.m+":"+my_timer[1].time.s;
})
the above code works perfectly and it has no error. But if I try to use this class in loop and pass index number rather than hard-coded values like in Code 1, it shows exception as I stated above.
Any help will be highly appreciable.
Thank you in advance.
Well, if I had to guess, which I do have to guess because you didn't give us a complete example or even a description of your problem...
Gut instinct is you're looping through to create the rows, referencing a mutable variable (remainingTime) as you do so in a nested function. But when you move on to the next item in the loop, remainingTime changes. So when the nested function references it, it's not the same as what you originally specified, so only the last timer is updating.
This is demonstrated by the following code, which alerts "3" three times.
for (var i = 0; i < 3; i++) {
setTimeout(function() {
alert(i);
}, 100);
}
If you don't know why, or how to fix it, then I suggest you spend some more time cuddled up by a fire with a cup of joe and a good book on JavaScript.
THank you for your time and answers. I just solved this problem by customizing CountDown Class
var countDown = function(h, m, s, _instance_index, fn_tick, fn_end) {
return {
total_sec : h * 60 * 60 + m * 60 + s,
timer : this.timer,
instance_index : _instance_index,
set : function(h, m, s) {
this.total_sec = parseInt(heart) * 60 * 60 + parseInt(e) * 60 + parseInt(s);
this.time = {
h : h,
m : m,
s : s
};
return this;
},
start : function() {
var self = this;
this.timer = setInterval(function() {
///alert('running');
if (self.total_sec) {
self.total_sec--;
var hour = parseInt(self.total_sec / (60 * 60));
var min = (self.total_sec - (parseInt(hour * (60 * 60))) - (self.total_sec % 60)) / 60;
self.time = {
h : parseInt(self.total_sec / (60 * 60)),
m : parseInt(min),
s : (self.total_sec % 60)
};
fn_tick(self.time.h + ":" + self.time.m + ":" + self.time.s, self.instance_index);
} else {
self.stop();
fn_end();
}
}, 1000);
return this;
},
stop : function() {
clearInterval(this.timer);
this.time = {
h : 0,
m : 0,
s : 0
};
this.total_sec = 0;
return this;
}
};
};
And call this class by using the following code:
my_timer[items_json.Record.NEW[i].ASSIGN_QUEST_ID] = new countDown(parseInt(n[0]), parseInt(n[1]), parseInt(n[2]), items_json.Record.NEW[i].ASSIGN_QUEST_ID, function(curr_time, instance_index) {
questTime[instance_index].text = 'TIME LEFT ' + curr_time;
}, function() {
//alert("The time is up!");
});
my_timer[items_json.Record.NEW[i].ASSIGN_QUEST_ID].start();

Incrementing a number smoothly with a variable time period in JS

I have a really simple JS counter which I display on a dashboard like screen which does the following:
Every 5 minutes it makes an jsonp call and retrieves a "total" number
It then displays this number to the screen by incrementing the last total displayed till it is equal to the new total. (the number can only ever increase)
I'm having some trouble with making the number increment smoothly. What I would like to do is find a delta (i.e. New total - old total) and increment the number gradually over the 5 minutes till the next call so it looks like a nice smooth transition.
Any ideas on how I can do this?
Currently some of my code looks like this (This block get's called every 5mins. And yes, it's in dire need of a refactor...)
var LAST_NUMBER_OF_SESSIONS = null;
var five_minutes_in_seconds = 300;
var new_number_of_sessions;
$.getJSON('http://blah.com/live_stats/default_jsonp.aspx?callback=?', function(data) {
if(LAST_NUMBER_OF_SESSIONS === null){
LAST_NUMBER_OF_SESSIONS = data.total_sessions;
}
new_number_of_sessions = data.total_sessions;
var delta = Math.floor(new_number_of_sessions - LAST_NUMBER_OF_SESSIONS);
var time_interval = (five_minutes_in_seconds / delta) * 1000;
var old_value = LAST_NUMBER_OF_SESSIONS;
var new_value = null;
sessions_interval = setInterval(function (){
new_value = parseInt(old_value, 10) + 1;
$('#stats').text(new_value);
old_value = new_value;
if(new_value >= new_number_of_sessions){
clearInterval(sessions_interval);
}
}, time_interval);
LAST_NUMBER_OF_SESSIONS = new_value;
});
}
This code it seems to increment the number very quickly at the start of the 5min period and then stop so it's not exactly right...
Try this:
var total = 0,
delta = 0,
stats = $('#stats').text( total );
function increment() {
var v = +stats.text();
if ( v < total ) {
stats.text( v + 1 );
} else {
$.getJSON('http://...', function(data) { // added data here
delta = Math.floor( 300000 / ( data.total_sessions - total ) );
total = data.total_sessions;
});
}
setTimeout(increment, delta);
}
Update:
In order to test my code, I had to simulate the JSON reponse - I used an array of numbers. See here: http://jsfiddle.net/simevidas/MwQKM/
(In the demo, I use an interval of 5 seconds instead of 5 minutes.)
I am not exactly sure why your code doesn't work as expected, although I suspect that it has to do with line LAST_NUMBER_OF_SESSIONS = new_value;. I wrote something similar and it works fine. It's not that different from what you have, minus that last line of code.

Categories

Resources