I have buttons to set different dates (one for tomorrow and one for in a week) and have made a function for only calculating business days. So the 'tomorrow' button should show either 'Tomorrow' or 'Monday' and the 'week' button should either show 'in 7 days', 'in 8 days' or 'in 9 days' depending on what day of the week it currently is.
These are my two buttons:
<v-btn
:value="dates[0]"
>
{{ buttonText }}
</v-btn>
<v-btn
:value="dates[1]"
>
{{ buttonText }}
</v-btn>
This is my computed for the different dates to postpone and this works just fine:
postponeDays(): DateTime[] {
const today = DateTime.local()
return [
onlyBusinessDays(today, 1),
onlyBusinessDays(today, 7),
]
},
The onlyBusinessDays function looks like this:
export const onlyBusinessDays = (date: DateTime, nrOfDays: number): DateTime => {
const d = date.startOf('day').plus({ days: nrOfDays })
const daysToAdd = d.weekday === 6 ? 2 : d.weekday === 7 ? 1 : 0
return d.plus({ days: daysToAdd })
}
And here is my computed for getting the button text that is not working and I need help with:
buttonText(): DateTime | string {
if (!this.dates[1]) {
if (DateTime.local().plus({ days: 7 }).hasSame(this.dates[1], 'day')) {
return 'in 7 days'
}
if (DateTime.local().plus({ days: 8 }).hasSame(this.dates[1], 'day')) {
return 'in 8 days'
} else return 'in 9 days'
} else {
if (DateTime.local().plus({ days: 1 }).hasSame(this.dates[0], 'day')) {
return 'Tomorrow'
} else return 'Monday'
}
},
I've tried doing the computed for the button text in many different ways but I always end up getting the exact same text for both buttons which feels weird since the postpone function for actually getting the correct dates is working perfectly fine. Anyone that knows what I'm doing wrong? :)
Make buttonText a method instead, so you can put {{ buttonText(dates[0]) }} and {{ buttonText(dates[0]) }}.
As in the Vue docs on computed properties:
Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed.
Your two v-btn elements aren't evaluated with different scopes, so there's no reason for Vue to believe that buttonText will have different values in both cases. What you want conceptually is for buttonText to return different values based on the date you pass in, which fits much closer to a method as far as Vue is concerned.
As on the methods page of the guide:
It is also possible to call a method directly from a template. As we'll see shortly, it's usually better to use a computed property instead. However, using a method can be useful in scenarios where computed properties aren't a viable option.
Related
I am using a calendar application that shows the available timeslots for a group. For example if the team has 2 members, and if each member has their own workingHours and own existing bookings; to show the team calendar where the array output is only giving all slots that are commonly (collectively) open among all the members. Code below is incorrectly providing returns that are not consistent with the common open slots.
userSchedule has the working hours and busy times of the 2 members of the team
userSchedule = [{"workingHours":[{"days":[1,2,3,4,5],"startTime":60,"endTime":540}],"busy":[]},{"workingHours":[{"days":[2,3],"startTime":60,"endTime":540},{"days":[5],"startTime":60,"endTime":450},{"days":[1],"startTime":180,"endTime":540}],"busy":[]}]
From this I want to get the working hours of the combined team when both are available first. Then i want to removed the blocked times and only show in team calendar (in an array) the actual days and slots that the combined team is available.
The below logic is not working and showing output where the workingHours are not all days that both are available
const workingHours = userSchedules?.reduce(
(currentValue: ValuesType<typeof userSchedules>["workingHours"], s) => {
// Collective needs to be exclusive of overlap throughout - others inclusive.
if (eventType.schedulingType === SchedulingType.COLLECTIVE) {
// taking the first item as a base
if (!currentValue.length) {
currentValue.push(...s.workingHours);
return currentValue;
}
// the remaining logic subtracts
return s.workingHours.reduce((compare, workingHour) => {
return compare.map((c) => {
const intersect = workingHour.days.filter((day) => c.days.includes(day));
return intersect.length
? {
days: intersect,
startTime: Math.max(workingHour.startTime, c.startTime),
endTime: Math.min(workingHour.endTime, c.endTime),
}
: c;
});
}, currentValue);
} else {
// flatMap for ROUND_ROBIN and individuals
currentValue.push(...s.workingHours);
}
return currentValue;
},
[]
);
EDIT AND SOLVED:
I've found the origin of the problem:
I thought arr.slice returned the new array but, actually, it returns the deleted element. See the documentation
The solution is simply to put the slice function before the bracket and not in it:
remove (index) {
const arrayCopy = this.liens.slice()
arrayCopy.splice(index, 1)
this.$emit('update:liens', arrayCopy)
}
Thank you kissu for your time!
I was previously using nuxt 2.xx and since switching to nuxt-edge the vue behavior changed with array.slice. Let me explain :
My code is very basic and is used to add/remove items of an array in a child component "stockLiens". This array is passed to a parent component "currentEtabCommentUser" via .sync
Everything was working fine before but since I'm with nuxt-edge, if I have - for instance - an array of 3 items and I want to delete the 2nd one, it is the only item left!
My stockLiens component Javascript is the following:
props: {
liens: {
type: Array,
default: () => []
}
},
methods: {
// I used concat() and slice() to avoid mutating a prop in the component
add () {
const arrayConcat = this.liens.concat([{ url: '', description: '' }])
this.$emit('update:liens', arrayConcat)
},
remove (index) {
const arrayCopy = this.liens.slice()
console.log(this.liens) // line 108
console.log(arrayCopy) // line 109
this.$emit('update:liens', arrayCopy.splice(index, 1))
console.log(arrayCopy) // line 111
}
}
When I run the script, the text in the firefox console is the following:
On the picture, you can see that :
stockLiens:108 is an array of 3 items (it makes sense since it is this.liens)
stockLiens:109 is an array of 2 items! (strange since it's a simple array.slice() of this.liens) The 2 items are the old index 0 and the old index 2 (I've clicked on old index 1)
stockLiens:111 is the same array that on line 109. (strange again because it should have been spliced in line 110)
currentEtabCommentUser:186 is an array of 1 item : the old index 1 (since line 186 is just a listener on this.liens, it should be the same array that on stockLiens:111. I really don't understand anything!)
My currentEtabCommentUsercomponent:
<stockLiens
:liens.sync="liens"
/>
<script>
watch: {
liens () {
console.log(this.liens) // line 186
}
}
</script>
I've tried to change .sync, to copy the array with for(i...), to get() / set(v) the liens property, ... I've looked on many forum during hours (2 days now) but I can't understand the problem.
Can someone help me please ?
Thank you in advance!
My stockLiens component HTML:
<v-card
v-for="(url, i) in liens"
:key="i"
>
<v-text-field
v-model="url.url"
label="Url"
/>
<v-text-field
v-model="url.description"
label="Description"
/>
<v-divider vertical />
<v-btn
icon
#click="remove(i)"
>
<v-icon>mdi-delete-forever-outline</v-icon>
</v-btn>
</v-card>
I currently have the following code that lists a list of years. I feel that all this code may be very unnecessary and perhaps a computed property for validYears, would help me make this code more optimal and get rid of the unnecessary watchers. My issue is converting this to a computed property as I'm failing to grasp the correct logic to achieve this. I'd appreciate if someone can offer an example of how I can set a computed property for valid years and still return the same result.
onBeforeMount(calculateDateRange)
watch(() => props.earliestDate, (newValue, prevValue) => {
calculateDateRange();
});
// If there is a new value passed from the parent, the dropdown should display that new value.
watch(() => props.latestDate, (newValue, prevValue) => {
calculateDateRange()
});
const validYears = ref([])
function calculateDateRange () {
for(let year = props.latestDate; year >= props.earliestDate; year--){
validYears.value.push(year)
}
}
I didn't provide the rest of the code not to clutter the question, but as one can see in this component I have a set of props that determine the values in my for loop.
You could optimize it as follows :
const validYears = computed(()=>{
let _years=[]
for(let year = props.latestDate; year >= props.earliestDate; year--){
_years.push(year)
}
return _years;
})
Is there a sort by date in the React data grid? If so, how can this be called? In the examples, all sorting works only on the line:
Example 1
Example 2
In example 2, there are columns with dates, but they are sorted as strings.
i have a solution that is really good but not great. really good in that it sorts perfectly but it isn't good if you also want to filter on the column as well.
Basically you are displaying the formated date with a custom formatter and the value you are passing to sort on is the the number of seconds from 1/1/1970 for each date so it will sort right.
And yes, i use functions instead of components for the formatter and headerRenderer values all the time and it works fine.
i work an an intranet so i can't just copy/paste my code so this is just hand typed but hopefully you get the idea.
class GridUtil {
static basicTextCell = (props)=>{
return (<span>{props.value? props.value : ""}</span>)
}
}
class AGridWrapper extends React.Component {
///////////////
constructor(props){
super(props);
this.state={...}
this.columnHeaderData = [
{
key: "dateCompleted",
name: "Date Completed",
formatter: (data)=>GridUtil.basicTextCell({value: data.dependentValues.dateCompletedFormatted}),
getRowMetaData: (data)=>(data),
sortable: true
}
];
}//end constructor
///////////////////////////////////
//Note that DateUtil.getDisplayableDate() is our own class to format to MM/DD/YYYY
formatGridData = !props.inData? "Loading..." : props.inData.map(function(obj,index){
return {
dateCompleted: rowData.compDate? new Date(rowData.compDate).getTime() : "";
dateCompletedFormatted: rowData.compDate? DateUtil.getDisplayableDate(rowData.compDate) : "";
}
});
//////////
rowGetter = rowNumber=>formatGridData[rowNumber];
/////////
render(){
return (
<ReactDataGrid
columns={this.columnHeaderData}
rowGetter={this.rowGetter}
rowsCount={this.formatGridData.length}
...
/>
}
I use react-table I was having the same issue, the solution in case, was to convert the date to a javaScript Date, and also return this as a JSon format, I have to format how the date render in the component. here is a link that shows how I did this.
codesandbox sample
I'm trying to display a list of items on an event's agenda.
The event has a start_date end each item on the agenda has a duration in minutes, for example:
event:{
start_date: '2017-03-01 14:00:00',
agendas:[
{id:1,duration:10},
{id:2,duration:15},
{id:3,duration:25},
{id:4,duration:10}
]
}
Now, in my event component, I load agendas with a v-for:
<agenda v-for="(agenda,index) in event.agendas"
:key="agenda.id"
:index="index"
:agenda="agenda">
In agenda component, I want to increment the time at which each item starts:
<div class="agenda">
//adding minutes to start_date with momentJs library
{{ moment(event.start_date).add(agenda.duration,'m') }} //this should increment, not add to the fixed event start date
</div>
Currently it only adds to the fixed event start_date... I would like to show the times 14:00 for event 1, 14:10 for event 2, 14:25 for event 3 and 14:50 for event 4.
How can I increment the value in a v-for directive in Vue.js 2.0?
It looks like you already got an answer that works for you but I'll post this here in case anyone is looking for an alternate solution. The accepted answer might be good for an initial render of the agendas but will start to break if the agendas array is mutated or anything causes the list to re-render because the start time calculation is based on a stored value that gets incremented every iteration.
The code below adds a computed property (based on event.agendas) that returns a new array of of agenda objects, each with an added start property.
Vue.component('agenda', {
template: '<div>Agenda {{ agenda.id }} — {{ agenda.duration }} min — {{ agenda.start }}</div>',
props: {
agenda: Object
}
});
new Vue({
el: "#root",
data: {...},
computed: {
agendas_with_start() {
const result = [];
let start = moment(this.event.start_date);
for(let agenda of this.event.agendas) {
result.push({
id: agenda.id,
duration: agenda.duration,
start: start.format('HH:mm')
});
start = start.add(agenda.duration, 'm');
}
return result;
}
}
});
Then, in the template, the agendas_with_start computed property is used in the v-for:
<div id="root">
<h3>Event Start: {{ event.start_date }}</h3>
<agenda v-for="agenda in agendas_with_start"
:key="agenda.id"
:agenda="agenda"></agenda>
</div>
Here's a working codepen. The benefit of this approach is that if the underlying agendas array is mutated or re-ordered or the event start time changes or anything causes Vue to re-render the DOM, this computed property will be re-evaluated and the start times will be re-calculated correctly.
Vue.js will actually let you bind prop values to methods on the parent's scope, so the easiest way will be to do something like this:
<agenda class="agenda" v-for="(agenda,index) in event.agendas"
:key="agenda.id"
:index="index"
:agenda="agenda"
:start-time="get_start_time(agenda.duration)">
</agenda>
And then the get_start_time() is a method within the parent's scope:
new Vue({
el: '#app',
data: {
time_of_day: '',
event:{
// ...
},
},
methods: {
get_start_time(duration) {
if (this.next_event_start === '') {
this.next_event_start = moment(this.event.start_date);
}
this.event_start = this.next_event_start.format('h:mm a');
this.next_event_start.add(duration, 'minutes');
return this.event_start;
},
},
});
I've made a quick CodePen as a basic example to show it in action, but you'll need to update the actual code to compensate for multiple days.