I am getting this warning [Vue warn]: You may have an infinite update loop in a component render function. and I think I know why it's happening.
My Use Case :
I have a loop where I am passing an object with index to a vue method. Since computed cannot have arguments so I cannot use it. The remaining option is method.
I am trying to increase the date by one week after the loop reaches to a odd number. Only 1 and 2 is an exception.
Here is my code I have been trying to use
getDate(date, i){
var currentDate=date.startingDate
var res=currentDate
if(i>2){
// change the date
if(i%2==0){
//use previous dates
res=date.startingDate
}else{
// increase the date by one week from the last week
var currDate = new Date(currentDate)
var d=currDate.setDate(currDate.getDate() + 7);
var d=new Date(d)
var newDate=d.toISOString()
var dateArr=newDate.split("T")
var res=dateArr[0]
console.log('ok')
date.startingDate=res // this line is the problem and causes the infinite loop problem.
}
}
return res
}
Here date.startingDate is always a fixed date.
Sample Input
date.startingDate='2018-05-11'
so when i=1, and 2 (i always starts from 1)
out put is date.startingDate='2018-05-11'
when i=3,4 date should increase by one week so expected out put is 2018-05-17
The problem is date.startingDate=res I cannot reset the date to this one. But I have to reset it to the increased new date in order to be able to add new date when i=5,6 or bla bla.
Any suggested solutions in other ways or may be this code can be made better in a different ways? Any help would be highly appreciated.
Thank you so much for the time.
Edit
<div class="row _6-2-1__Tou align-items-center team" v-for="(m,j) in data.row" :key="j">
<div class="col-auto _6-2-1__Tou-one pad-0" >
<ul class="_6-2-1__Tou-text">
<li>{{getDate(m.date,j+1)}}</li>
</ul>
</div>
Ok it seems impossible to avoid this problem. So I changed my approach. I didn't update the date while populating the doom, instead, I used vuex store and getters. When returning from getters, I changed the values there and once all date changes are done, I simply returned the array. It worked nicely. Here is my code
This code is a bit changed in terms of logic as I added dynamic conditions. But concept is same.
fixture(state){
// we can actually update here
for(var i in state.fixture){
var details=state.fixture[i].row
for(var j in details){
//console.log(details[j].date.startingDate)
if(state.counter==state.groupCount){
// increase the date
if(state.date){
var currDate = new Date(state.date)
}else{
var currDate = new Date(details[j].date.startingDate)
}
var d=currDate.setDate(currDate.getDate() + 7);
var d=new Date(d)
var newDate=d.toISOString()
var dateArr=newDate.split("T")
var res=dateArr[0]
details[j].date.startingDate=res
state.date=details[j].date.startingDate
state.counter=1
}else{
if(state.date){
details[j].date.startingDate=state.date
}
state.counter++
}
}
state.counter=0
state.date=''
}
return state.fixture
},
Hope this approach helps others.
Related
I have a table that contains a date column. This column has dates from the past to the future including the current time with intervals of approximately 7 min (it may change).
I need to color the row which has present value with green and to move green to the next row when the next time is reached.
Is it possible to listen to the time value and change style over the rows?
I could color the present value but when time goes on, the style doesn't move to the next row since the code executed once on the page load.
Here is my code attempt:
I am using vue so in the HTML table part:
<tbody v-for='row in rows' :key='row.id'>
<tr :style="{'background-color': dateFormat(row.date) ?'green' :'' }">
...
</tr>
</tbody>
and in the methods:
dateFormat(d) {
const time = new Date(d);
const cc = new Date();
if (time.getMinutes() === cc.getMinutes()) return true;
return false;
}
Any help will be appreciated!
Thanks in advance.
There's not too much information of what kind of a table you want to integrate the action to. You can apply the following instructions to achieve what you want.
Make a "model" of the task, at first, read the dates when the active row should change to a JS array (data in the example code). It's handy to also include the table rows in that array, that saves some time when you don't have to query the DOM to find a row to highlight. Then create some variables to store some information of the state of the task (current, next). This information is used to control the state. Finally, create a timer, which runs when ever there's a next date to await. Calculate the delay based on the values you've stored in the model. Something like this:
// Fill the dates (for the example only)
const rows = Array.from(document.querySelector('#traced').rows);
fillDates(new Date(), 5); // constant 5 = increase time [second]
// A date can be passed to Date constructor as an acceptable string too
// Creates the timer
(function() {
const tbody = document.querySelector('#traced'),
rows = Array.from(tbody.rows),
data = rows.map(row => {
const time = new Date(row.cells[1].textContent).getTime();
// The constant 1 above is the date column number of the table
return {row, time};
});
let now = Date.now(),
last = data.length - 1,
next = data.findIndex(row => row.time > now),
current = Math.max(-1, next - 1);
if (now > data[last].time) {
// All the dates are in the past, no need for a timer
data[last].row.classList.add('active');
return;
}
// Updates row highlighting and counters, the timed function
function activateRow() {
// Update highlights
if (current > 0) {
// Remove the current highlight
data[current - 1].row.classList.remove('active');
}
if (current > -1 && next) {
// Highlight the current row
data[current].row.classList.add('active');
}
// Set the timer if needed
if (next > last) {return;} // Quit, no more dates to await
const delay = data[next].time - Date.now();
window.setTimeout(activateRow, delay);
// Update counters
current += 1;
next += 1;
}
activateRow();
}());
// Emulates the server-side dynamic table filling (for the example only)
function fillDates(base, gap = 15000) {
if (gap < 1000) {
gap *= 1000;
}
gap += Math.floor(Math.random() * 3000);
const zone = new Date().getTimezoneOffset(),
date = new Date(base).getTime() - zone * 60000;
rows.forEach((row, index) => {
const dte = new Date(date + gap * index).toISOString(),
end = dte.length - 5;
row.lastElementChild.textContent = dte.substring(0, end);
});
}
.active {
background: green;
}
<table>
<tbody id="traced">
<tr><td>Date 1</td><td></td></tr>
<tr><td>Date 2</td><td></td></tr>
<tr><td>Date 3</td><td></td></tr>
<tr><td>Date 4</td><td></td></tr>
<tr><td>Date 5</td><td></td></tr>
<tr><td>Date 6</td><td></td></tr>
</tbody>
</table>
When you're integrating the example to your own code, include only the IIFE to your JS code, the other parts of the snippet are there to make it possible to run the code reasonably in StackSnippet nevertheless visitor's timezone. Of course you've to define active class in your CSS too. You should also not include the style of the active row on the server, the JS snippet takes care of the highlighting.
Depending on the date format in the table, you might also need to edit the code according to the used format, and even make changes to the actual dates, because depending on visitor's timezone, the dates you add on the server could be badly off on some other timezone, and the automatic highlighter won't work.
There's also a jsFiddle to play with.
I have an array that contains dates. and for some reason I can't get it to show on my screen I've been debugging for a few days now and I've tracked it down to a single line, but the line has worked before and I can't figure out what the issue might be.
The array looks like this:
var selectItems =
[ "05-26-2017", "06-02-2017", "06-09-2017",
"06-16-2017", "06-23-2017", "06-30-2017", "07-07-2017", "07-14-2017",
"07-21-2017", "07-28-2017"...];
It's passed as an argument from another function, but that's how it's showing in console.log().
I might be going about this the wrong way, maybe even a lot further around then I need to but this is what I've come up with:
1. function setTHead(selectItems) {
2 var formatString;
3. for (var x = 0; x < 12; x++) {
4. formatString = selectItems[x].replace(/[^0-9/-]/g, "").toString();
5. console.log(selectItems);
6. $('#datTab').append("<div id='col" + x + "' class='column'>'" + formatString + "'</div>");
7. }
8. }
the array up top is what's showing from the console.log 5 lines down.
the sixth line is what is seeming to give me issues. Nothing is put on the page at all.
I'm getting a console error saying:
jQuery.Deferred exception: selectItems is undefined setTHead#http://localhost/mySite/script.js:136:9
startUp2#http://localhost/mySite/script.js:146:5
#http://localhost/mySite/table.php:19:9
mightThrow#http://localhost/mySite/lib/jquery.js:3586:52
resolve/</process<#http://localhost/mySite/lib/jquery.js:3654:49
setTimeout handler*resolve/<#http://localhost/mySite/lib/jquery.js:3692:37
fire#http://localhost/mySite/lib/jquery.js:3320:30
fireWith#http://localhost/mySite/lib/jquery.js:3450:29
fire#http://localhost/mySite/lib/jquery.js:3458:21
fire#http://localhost/mySite/lib/jquery.js:3320:30
fireWith#http://localhost/mySite/lib/jquery.js:3450:29
ready#http://localhost/mySite/lib/jquery.js:3923:13
completed#http://localhost/mySite/lib/jquery.js:3933:9
EventListener.handleEvent*#http://localhost/mySite/lib/jquery.js:3949:9
#http://localhost/mySite/lib/jquery.js:39:9
#http://localhost/mySite/lib/jquery.js:17:3
undefined
followed by:
TypeError: selectItems is undefined
and thats pointing to line 6.
if anyone has any advice I would be very much appreciative. Thank you in advance.
EDIT: A little more code:
function startTblView(defSel) {
if (defSel === true) {
setCookie('defSel', true, 7);
} else{
setCookie('defSel', false, 7);
}
saveSelected();
window.open('table.php', '_self');
defSel = getCookie('defSel');
if (defSel) {
selectItems = getDefDates();
}else {
selectItems = reGetSelected();
}
setTHead(selectItems);
}
defSel, is a boolean passed from my last page stating whether I'm doing a default view or a custom view, the custom view is passed from saveSelected();
saveSelected is a function for just saving the selected global value as a cookie so I can pull it out on the next page.
getDefDates pulls the default values for the array
reGetSelected, gets the selected array from the cookie.
I apologize for wonky naming conventions. I'm the only one working on this site and I'm just making sure the names don't overlap.
You can do this :
HTML code
<div id="datTab"></div>
JS code
var selectItems =
[ "05-26-2017", "06-02-2017", "06-09-2017",
"06-16-2017", "06-23-2017", "06-30-2017", "07-07-2017", "07-14-2017",
"07-21-2017", "07-28-2017"];
function setTHead(selectItems) {
var formatString;
$.each( selectItems, function( index, value ){
formatString = value.replace(/[^0-9/-]/g, "").toString();
$('#datTab').append("<div id='col" + index + "' class='column'>'" + value + "'</div>");
});
};
You can use $.each, its better than 'for' with javascript.
The .each() method is designed to make DOM looping constructs concise
and less error-prone. When called it iterates over the DOM elements
that are part of the jQuery object. Each time the callback runs, it is
passed the current loop iteration, beginning from 0. More importantly,
the callback is fired in the context of the current DOM element, so
the keyword this refers to the element.
I did a JsFiddle
Here.
long time lurker, first time poster. Really love the community you have over here :)
Ok right to the problem. The goal is filtering the ng-repeat by passing 2 arguments to the filter.
I have a nested ng-repeat and on the second repeat i need to filter the entries depending on whether their lifespan in in the current month or not.
I have done this before with ng-if, but since i use different CSS for odd and even rows, that produced unwanted results.
the relevant HTML part:
<div class="col-md-12" ng-repeat="month in monthTable">
<div class="col-md-12">
<h2>
<span>{{ month.start | amDateFormat:"MMMM" | uppercase}}</span>
</h2>
</div>
<div class="col-md-12">
<div ng-class-even="'calendar_row_even'" ng-class-odd="'calendar_row_odd'"
ng-repeat="bed in syncData.beds | filter: filterListItems(month, bed) |
orderBy:'timeFrom'">
// --> displays the unfiltered results for each month at the moment...
</div>
</div>
</div>
The monthTable is used to determine the start and endpoints of a month + to generate names is appropriate locale, it's filled like this:
for ( var i = 0; i < 12; i++) {
$scope.monthTable.push({
start: new Date(Date.UTC($scope.currentYear, i, 1)).getTime(),
end: new Date(Date.UTC($scope.currentYear, i+1, 1)).getTime()
})
};
Pretty self-explanatory so far.
Now here is the function that "should do" the filtering:
$scope.filterListItems = function (month, bed) {
console.log(month);
console.log(bed);
return true;
/*return (bed.timeFrom < month.end && bed.timeUntil >= month.start);
--> this should be the code.*/
};
The problem is i can't get the filter to recieve 2 arguments.
If i write is this way:
"filter: filterListItems(month, bed)"
the month gets passed on, but the bed is undefined
If i write it this way:
"filter: filterListItems:month:bed"
only the bed gets passed on, but the month is undefined
I have no idea what i'm doing wrong, would really appreciate it if any1 can point me in the right direction.
You should declare your method as filter :
angular.module('yourApp')
.filter('filterListItems', filterListItems);
And adapt your method like follows :
function filterListItems() {
return function(items, month) {
// 'items' represent the objects listed by your ng-repeat
// Now, filter the items to return only items that respect your condition.
return items.filter(function(item) {
return (item.timeFrom < month.end && item.timeUntil >= month.start);
});
}
}
And use it like :
ng-repeat="bed in syncData.beds | filterListItems:month
I'm working on a todo list that groups tasks by week (based on added and completion dates) and groups tasks by the days of the week. The database structure looks like this:
users
userA
tasks
taskobject1
taskobject2
..
userB
tasks
taskobject1
task object2
I'm using an ng-repeat to display all the tasks to the view for each user. I would like to be able to sort them first by which week they fall into and then like this:
#week1
--monday--
task a
task b
--tuesday--
task c
task d
..
#week2
--monday--
task a
..
Even a conceptual answer would be helpful. I don't know where to start exactly.
Thanks.
See also this post, which provides a directive to do this grouping: Is it possible to .sort(compare) and .reverse an array in angularfire?
There are a few approaches you could take here.
Data Structured by Date
If the records will always be fetched by this structure, you can just store them that way;
/users/usera/tasks/week1/monday/taska/...
Then you can simply fetch them at the tasks level as an object, and you will obtain a pre-sorted JSON object with values nested at the appropriate levels.
This approach is not highly compatible with AngularFire, which is intended for binding objects or collections, not nested trees of data, but should work with some care.
Using Priorities
A second approach would be to add priorities on to the tasks, which would be an epoch timestamp. Now when you want to fetch them, you can startAt/endAt specific points in the tree, and get the records. Each will have a timestamp on it you can use to identify the week and day.
var ref = new Firebase(ref).startAt(twoWeeksAgo).endAt(nextThursday);
$scope.list = $fireabse(ref).$asArray();
However, this does not segment the entries. You would need to either examine each entry in the ng-repeat and add headers on the fly:
// in the controller
var last = null;
$scope.priorityChanged(priority) {
/**
* here we would use a library like moment.js to parse the priority as a date
* and then do a comparison to see if two elements are for the same day, such as
**/
var current = moment(priority).startOf('day');
var changed = last === null || !last.isSame(current);
last = current;
return changed;
};
$scope.getDayName = function($priority) {
return moment($priority).format('dddd');
};
<!-- in the view -->
<li ng-repeat="item in list" ng-init="changed = priorityChanged(item.$priority)">
<h3 ng-show="changed">{{getDayName(item.$priority)}}</h3>
{{item|json}}
</li>
This approach is readily compatible with AngularFire.
Roll Your Own List
A last approach would be to cut out AngularFire and roll your own. For example, if we have the week and weekday stored on each task, we could do the following:
app.service('DatedList', function($timeout) {
return function(pathToList) {
var list = {};
pathToList.on('child_added', function(snap) {
$timeout(function() { // force Angular to run $digest when changes occur
var data = snap.val();
var week_number = data.week;
var week_day = data.day;
list[week_number][week_day] = data;
});
});
//todo: write similar processing for child_changed and child_removed
return list;
}
});
app.controller('ctrl', function($scope, DatedList) {
var listRef = new Firebase(URL).limit(500);
$scope.weeks = DatedList(listRef);
});
<div controller="ctrl">
<div ng-repeat="(week, days) in weeks">
<h1>{{week}}</h1>
<div ng-repeat="(day, items) in days">
<h2>{{day}}</h2>
<div ng-repeat="item in items">
{{item|json}}
</div>
</div>
</div>
</div>
I have view model with 3 fields
dateStart = ko.observable();
dateEnd = ko.observable();
days = ko.observable();
assuming startDate is selected, whenever endDate is selected days field needs to be updated (days = endDate - startDate).
Also when days field is updated i need to calculate endDate (endDate = startDate + days).
how can this be done with knockoutjs ?
Thank You!
I've tried
http://jsfiddle.net/NfG4C/6/, but my js always throws too musch recursion exception.
From what I understand, you basically need 2 things.
You want to calculate the "days" field whenever someone selects the
"endDate" [assuming they have selected the "startDate" ofcourse]
You want to recalculate the "endDate" field whenever someone changes the
"days" field
One way to solve this would be to use a "writeable" computed Observable [http://knockoutjs.com/documentation/computedObservables.html]. Please go through the link for details, but in general term, a 'writeable computed observable' is something whose value is 'computed' based on some 'other' observable(s) and vice-versa.
I took the liberty to modify your fiddle and change the "days" as a computed observable. Please take a look: http://jsfiddle.net/dJQnu/5/
this.days = ko.computed({
read: function () {
//debugger;
// here we simply need to calculate the days as => (days = endDate - startDate)
if (that.dateStart() && that.dateEnd()) {
var vacDayCounter = 0;
for (var curDate = new Date(that.dateStart()); curDate <= that.dateEnd(); curDate = curDate.addDays(1)) {
if (isDateCountsAsVacation(curDate)) {
vacDayCounter++;
}
}
//that.days(vacDayCounter);
return vacDayCounter;
}
},
write: function (newDays) {
if (newDays && !isNaN(newDays) && that.dateStart()) {
var tmpEndDate = new Date(that.dateStart())
appliedDays = 0;
while (appliedDays < newDays) {
if (isDateCountsAsVacation(tmpEndDate)) {
appliedDays++;
}
tmpEndDate = tmpEndDate.addDays(1);
}
if (tmpEndDate) {
that.dateEnd(tmpEndDate);
}
}
}
});
If you notice, I simply reused your code (logic) for the read and write part. During read, we are "computing" the value for the observable itself, in this case the "days" and during write (which fires anytime the user changes the actual "days" input value) we are recalculating the "dateEnd" field.
Please let me know if you have any other question.
Hope this helps.
Thanks.
You get a recusive problem because when updating the observables from a subscription will also trigger that observables subscribe method.
You need to add a fourth member, updatedFromSubscriber
set it to false from tbe beginning, in each subscribe method add
if(this.updatedFromSubscriber)
return;
and just before updating the observable do
this.updatedFromSubscriber = true
set it to false after updating the observable