EmberJSBin here
I have a component which needs to perform a given operation when any of its values are changed, but the observer I created does not fire:
export default Ember.Component.extend({
taxes: null,
NOAYears: 2,
NOAValues: Ember.computed("NOAYears", function() {
var NOAs = [],
lastYear = new Date().getFullYear() - 1;
for (var year = 0; year < this.get("NOAYears"); year++) {
NOAs.push({year: lastYear - year, value: 0});
}
return NOAs;
}),
NOAYearsChanged: function() {
// does not fire when any of the values change
}.observes("NOAValues.#each.value")
});
In the component template, I am binding via the {{#each}} iterator:
{{#each NOAValues as |year|}}
<label for="{{year.year}}-value">{{year.year}}</label>
{{input min="0" step="any" value=year.value required=true placeholder="Yearly Income"}}
{{/each}}
How can I get my observer to fire when any of the value properties in NOAValues is changed?
This issue has been verified as a bug, caused by legacy code, which interprets any property name beginning with a capital letter (i.e. PascalCase) as either a global or a Class name reference... rendering the property unobservable.
Source: https://github.com/emberjs/ember.js/issues/10414
It seems like efforts will be made to fix it in some upcoming releases.
In order to observe property changes, you need to use a setter for a given property. I think you introduce a NOA model that extends Ember.Object it should be sufficient. For example:
// file app/model/noa.js
export default Ember.Object.extend({
year: undefined,
value: undefined
});
and then replace this:
for (var year = 0; year < this.get("NOAYears"); year++) {
NOAs.push({year: lastYear - year, value: 0});
}
with
for (var year = 0; year < this.get("NOAYears"); year++) {
NOAs.push(app.model.noa.create({year: lastYear - year, value: 0}));
}
You should see some property changes.
Related
Update:
I checked if setDate() is called from ngOnInit: it is.
Also I specified the variables: rentalStartInput is an string Array and contains the real dates with I get from the other component (see picture below).
rentalStart is a Date, right now it is hard-coded, which I want to change. rentalStart should countain the start day of a range which I got from rentalStartInput. Current I write it manually, to see if highlighting dates works. (it does)
Update end
I'm a beginner with typescript/angular and I'm having the problem of not being able to access variables.
I have some rentals which have a start and end date. I want to display those in a calendar like the following:
Currently I get it out that the range is displayed as in the picture, but only hard-coded.
TypeScript:
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
// #Input() rentalStartInput: String[]; //error: Property 'rentalStartInput' has no initializer and is not definitely assigned in the constructor.
#Input() rentalStartInput: String[] = [];
#Input() rentalEndInput: String[] = [];
rentalStart: Date = new Date(2023-1-1);
rentalEnd: Date = new Date(2023-1-1);
getDaysFromDate(month: number, year: number) {
const startDate = moment(`${year}/${month}/01`)
const endDate = startDate.clone().endOf('month')
this.dateSelect = startDate;
const diffDays = endDate.diff(startDate, 'days', true)
const numberDays = Math.round(diffDays);
this.monthSelect = Object.keys([...Array(numberDays)]).map((a: any) => {
a = parseInt(a) + 1;
const dayObject = moment(`${year}-${month}-${a}`);
return {
name: dayObject.format("dddd"),
value: a,
indexWeek: dayObject.isoWeekday(),
selected: this.isInRange(a, month, year),
};
});
}
isInRange(day: any, month: any, year: any) {
let rangeDay = new Date(year, month - 1, day);
this.rentalStart = new Date(2023, 0, 6); //hard-coded, what I want to change
this.rentalEnd = new Date(2023, 0, 17)
let rentalRange = moment.range(this.rentalStart, this.rentalEnd);
return rentalRange.contains(rangeDay); //true if day is in range -> highlighted in ui
}
}
When I try to get access to variables rentalStartInput and rentalEndInput it doesnt work. I want to replace rentalStartand rentalEnd with them. I also don't want to do the assignment within the method, because then it's done 31 times. But when I change it to a new method, rentalStart and rentalEnd are undefined.
How can I assign both variables and use them in isInRange?
#Component({
selector: 'app-calendar',
templateUrl: './calendar.component.html',
styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
#Input() rentalStartInput: String[] = [];
#Input() rentalEndInput: String[] = [];
rentalStart: Date = new Date(2023-1-1);
rentalEnd: Date = new Date(2023-1-1);
ngOnInit(): void {
this.setDate();
}
setDate(){
this.rentalStart = new Date(2023, 0, 6);
this.rentalEnd = new Date(2023, 0, 17);
console.log('setDate'); //check if it is ever called from ngOnInit (it is)
}
isInRange(day: any, month: any, year: any) {
console.log(this.rentalStart); // undefined
(...)
}
}
I get the real dates from another component via #Input(). They are in separate variables: rentalStartInput and rentalEndInput
In console they look like this:
What i'm trying to do is to generate range objects to check if a day is included.
First of all, I fail getting access to the dates.
console.log(this.rentalStartInput) // see picture
console.log(JSON.stringify(this.rentalStartInput)) // []
console.log(this.rentalStartInput[0]) // undefined
console.log(Object.keys(this.rentalStartInput).length) // 0
console.log(this.rentalStartInput.length) // 0
console.log(this.rentalStartInput['1']) // undefined
What possibilities are there to access the data? I cant do a for loop unless I know the length of the array.
I hope my problem is understandable. I can provide more code if necessary.
Thank you very much for any kind of help!
I'm creating a booking application in Vue CLI. I decided to use vue-ctk-date-time-picker for choosing date and time. I'm planning to disable some times, depending on the date, but i'm running to a problem. My code only disables the times of last date specified in array and ignores the rest.
I've logged the times array to console depending on the date and it prints correct values. Other than that console displays no errors.
<VueCtkDateTimePicker only-date v-model="date"/>
<VueCtkDateTimePicker only-time :disabled-hours="disabledHours"/>
date: null,
disabledHours: [],
testArray: [
{
date: "2019-05-28",
times: ["10", "11"]
},
{
date: "2019-05-29",
times: ["10", "11", "12"]
}
]
watch: {
date(newVal, oldVal) {
for (let i = 0; i < this.testArray.length; i++) {
if (newVal == this.testArray[i].date) {
for (let j = 0; j < this.testArray[i].times.length; j++) {
this.disabledHours.push(this.testArray[i].times[j]);
}
} else {
this.defaultHours();
}
}
}
},
created() {
this.defaultHours();
}
defaultHours() {
this.disabledHours = ["00","01","02","03"]
}
If date is "2019-05-28", then i expect disabled hours to be 10 and 11.
If date is "2019-05-29", then i expect disabled hours to be 10, 11 and 12 etc.
But what happen is, that it takes the last date specified in the array and only disables its hours.
The code you've posted will always loop through all entries in testArray and take some action for each entry. I think the behavior you want is for the code to only take action on an entry that matches, and default if no entry matches. There are many ways to achieve that behavior but one way is the following
date(newValue) {
const matched = testArray.find(entry => entry.date === newValue);
if (matched) {
this.disabledHours = matched.times;
} else {
this.defaultHours();
}
}
I'm building a simple weekly calendar component for my app and I'm struggling to find a way for creating the weeks navigation. Here's what I have so far:
/week-view/component.js
import Ember from 'ember';
export default Ember.Component.extend({
firstDay: moment().startOf('week'),
days: Ember.computed('firstDay', function() {
let firstDay = this.get('firstDay').subtract(1, 'days');
let week = [];
for (var i = 1; i <= 7; i++) {
var day = firstDay.add(1, 'days').format('MM-DD-YYYY');
week.push(day);
}
return week;
}),
actions: {
currentWeek() {
this.set('firstDay', moment().startOf('week'));
},
previousWeek() {
this.set('firstDay', moment().startOf('week').subtract(7, 'days'));
},
nextWeek() {
this.set('firstDay', moment().startOf('week').add(7, 'days'));
}
}
});
/week-view/template.hbs
<button {{action "previousWeek"}}>previous</button>
<button {{action "currentWeek"}}>current week</button>
<button {{action "nextWeek"}}>next</button>
<ul>
{{#each days as |day|}}
<li>{{day}}</li>
{{/each}}
</ul>
At the moment it works to navigate one week before and one after the current week only. Any idea on how to make this work for an unlimited number of weeks is greatly appreciated. Thanks in advance.
I think you shouldn't change your firstDay property while prepairing week array (in the computed function). It overrides momentjs state. Computed property in this case should just read firstDay property without affecting changes to it.
Also in your previous and next week actions you don't have to create new momentjs objects. You can easily operate on previously created firstDay property, for example.
this.get('firstDay').subtract(7, 'days');
But in this case, state of momentjs have changed, but emberjs doesn't see any changes. That is because your firstDay property doesn't really changed (and computed property is set to check only firstDay, it doesn't work deeply). In fact firstDay property is just reference to momentjs object, and that object has been changed, not the reference. But luckily you can manually force emberjs to reload any computed properties based on any property in this way:
this.notifyPropertyChange('firstDay');
So little bit refactored working example can looks like:
import Ember from 'ember';
export default Ember.Component.extend({
selectedWeek: moment().startOf('week'),
days: Ember.computed('selectedWeek', function() {
let printDay = this.get('selectedWeek').clone();
let week = [];
for (var i = 1; i <= 7; i++) {
week.push(printDay.format('MM-DD-YYYY'));
printDay.add(1, 'days');
}
return week;
}),
actions: {
currentWeek() {
this.set('selectedWeek', moment().startOf('week'));
},
previousWeek() {
this.get('selectedWeek').subtract(7, 'days');
this.notifyPropertyChange('selectedWeek');
},
nextWeek() {
this.get('selectedWeek').add(7, 'days');
this.notifyPropertyChange('selectedWeek');
}
}
});
I've got a vue component that will show a calendar week. The component is meant to be modular so it will not know what days are populated with what dates until it's parent component (the month) passes in the data.
My template looks like this:
<div class="cs-week">
<div class="day" v-for="n in 7">
<!-- I'm still building it out, so for now I jsut want to show the date -->
{{ dayLabels[n] }}
</div>
</div>
The Vue Component looks like this:
module.exports = {
props:[
'events',
'weekdata',
'weeknumber'
],
data: function (){
return {
// initializing each of the day label slots so the view doesn't blow up for not having indexed data when rendered
dayLabels: [
null,
null,
null,
null,
null,
null,
null
]
}
},
methods:{
loadWeek: function (){
for(
var i = this.weekdata.days[0],
j = this.weekdata.dates[0];
i <= this.weekdata.days[1];
i++, j++
){
this.dayLabels[i] = j;
}
},
test: function (){
this.loadWeek();
}
}
}
The data being passed to the component from the parent tells it the range of the days to fill and the dates to use:
weekdata: {
days: [3,6], // start on wednesday, end on saturday
dates: [1,3] // use the 1st through the 3rd for the labels
}
When I fire this method, the data updates, but the bound elements never update:
The thing is, if I hard code an update to the labels array before I iterate through the loop...
loadWeek: function (){
debugger;
this.dayLabels = [1,2,3,3,2,1]; // arbitrary array data assigned
for(
var i = this.weekdata.days[0],
j = this.weekdata.dates[0];
i <= this.weekdata.days[1];
i++, j++
){
this.dayLabels[i] = j;
}
},
... the bound elements will update:
Is there a reason why it won't work without the arbitrary assignment before the loop??
When you change an array by setting a value in it, Vuejs cannot detect the change and wont fire any on-change methods. See here: http://vuejs.org/guide/list.html#Caveats
You can use the $set() method to change an object in an array, and that will force Vue to see the change. So in your for-loop
this.dayLabels.$set(i, j);
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