I have a few questions I cant seem to figure out. I tried to make a fiddle to show my problem but I cant seem to get it to work although it is working in my app.(https://jsfiddle.net/6dfyx2og/1/#&togetherjs=KxPTHsH0Pu). Values show up on screen and can be manipulated by the input boxes. But now $scope.booking.nights wont update if a user changes the value. (In my app when a user fills out both the arrival date and departure date it calls the function calNights that gives us the number of nights).
can anyone see my error?
Since I was not able to get the fiddle working I will post code here
controller:
angular.module('squawsomeApp')
.controller('BookingCtrl', function($scope){
$scope.values = [1,2,3,4,5];
// var self = this;
function parseDate(str) {
var mdy = str.split('/')
return new Date(mdy[2], mdy[0]-1, mdy[1]);
}
function daydiff(first, second) {
return (second-first)/(1000*60*60*24);
}
var calCheckIn = new Pikaday({
field: document.getElementById('checkin'),
format: 'MM/DD/YYYY',
onSelect: function() {
$scope.booking.checkin = null;
$scope.booking.checkin = this.toString('MM/DD/YYYY');
calNights();
}
});
var calCheckOut = new Pikaday({
field: document.getElementById('checkout'),
minDate: $scope.checkin,
format: 'DD/MM/YYYY',
onSelect: function() {
$scope.booking.checkout = this.toString('MM/DD/YYYY');
calNights();
}
});
var calNights = function(){
console.log('outside')
$scope.booking.nights = null;
console.log($scope.booking.nights)
if ($scope.booking.checkin && $scope.booking.checkout) {
console.log('inside')
$scope.booking.nights = daydiff(parseDate($scope.booking.checkin), parseDate($scope.booking.checkout));
// return daydiff(parseDate(self.booking.checkin), parseDate(self.booking.checkout))
console.log($scope.booking.nights)
}
};
var calCost = function(){
if ($scope.booking.nights < 7) {
$scope.booking.cost = 145;
} else if ($scope.booking.nights >= 7 && $scope.booking.nights <= 29){
$scope.cost = 135;
} else if ($scope.booking.nights >= 29){
$scope.booking.cost = 120;
}
};
$scope.booking = {
checkin: null,
checkout: null,
guests: null,
nights: null, //daydiff(parseDate(self.booking.checkin), parseDate(self.booking.checkout))
cost: null,
pretotal: this.nights * this.cost,
tax: 0,
total: this.pretotal + this.tax
};
});
html:
<div controller="BookingCtrl">
<form name="bookingForm" ng-submit="">
<div class="form-fields">
<label for="checkin">Check In</label>
<input id="checkin" name="checkin" placeholder="dd-mm-yyyy" type="text" ng-model="booking.checkin">
<label for="checkout">Check Out</label>
<input id="checkout" name="checkout" placeholder="dd-mm-yyyy" type="text" ng-model="booking.checkout">
<label for="guests">Guests</label>
<select ng-model="booking.guests" ng-options="value for value in values"></select>
<ul>
<li>{{booking.checkin}}</li>
<li>{{booking.checkout}}</li>
<li>{{booking.nights}}</li>
<li>{{booking.guests}}</li>
<li>{{booking.cost}} x {{booking.nights}}</li>
<li>Tax</li>
<li>Total</li>
</ul>
<ul>
<li>{{booking.pretotal}}CND</li>
<li>{{tax}}CND</li>
<li>{{booking.total}}</li>
</ul>
<input type="submit" value="Submit" ng-disabled="bookingForm.$invalid">
</div>
</form>
</div>
as an aside I initially I wanted to use the this/self syntax inside the controller instead of using $scope. This worked well but I couldn't get the values to display in the view. (the ones bound to inputs would show up, but that was just due to data binding and they would not show the inital values. If some one could explain why this is please let me know.
ie why this in the controller did not show the values in the view
var self = this;
self.booking = {
checkin: null,
checkout: null,
guests: null,
nights: null,
cost: null,
pretotal: this.nights * this.cost,
tax: 0,
total: this.pretotal + this.tax
};
but this did:
$scope.booking = {
checkin: null,
checkout: null,
guests: null,
nights: null,
cost: null,
pretotal: this.nights * this.cost,
tax: 0,
total: this.pretotal + this.tax
};
The scope object ($scope) is what enables the controller and the view to communicate with each other. If you put your properties on self instead of the scope object, there is no way for the view to use them, because they are properties of the controller itself, not the scope object. When you data-bind input elements in a view, Angular looks for the bound properties in the scope object that it created and passed to the controller. If it doesn't find them, it will create properties in the scope object and bind the inputs to them. On the other hand, if you have a property of the scope object that you have given a value in your controller, then Angular will bind that property to the input that you have data-bound to the property. Therefore, the input will have an initial value on the page that is whatever your controller set it to.
You can use the this / self syntax - but you should be using ControllerAs Syntax.
<div ng-controller="BookingCtrl as book">
<p>{{ book.booking.checkin }}</p>
</div>
Read all bout it in the docs :)
https://docs.angularjs.org/api/ng/directive/ngController
Related
I want to bind two functions here into one HTML element. Bellow the code of Vue function
var table_content = new Vue({
el: '#table_content',
data: {
arr: [],
_arr_original: [],
price: 0,
},
created(){
// some stuff here
},
methods:{
change(index,index1){
arr = this.arr[index]
arr.total = arr.data[index1].Koef * arr.data[index1].harga_satuan
Vue.set(this.arr,index,arr)
}
},
computed:{
modify_price:{
get(){
return this.price.toLocaleString()
},
set(value){
var v = parseInt(value.replace(/,/g,''))
isNaN(v) ? "" : this.price = v;
}
},
}
})
HTML element
<table class="tableizer-table table-hover" id="table_content">
<thead>
<td>
<div class="handsontableInputHolder">
<textarea tabindex="-1" name="uom" class="handsontableInput area-custom val2 text-center" style="" v-model="data.harga_satuan modify_price" v-on:keyup="change(0,index)"></textarea>
</div>
{{-- <input name="txtEmmail" class="val2"/> --}}
</td>
<td>
<div class="handsontableInputHolder">
<textarea tabindex="-1" name="total" class="handsontableInput area-custom multTotal text-center" disabled style="">#{{ data.Koef * data.harga_satuan }}</textarea>
</div>
</td>
//..
The idea is I want to bind function change and modify_price. So here are the detail
Function `change` will handle any input from user and count a total on HTML DOM `total`
Function `modify_price` will `get` input from user (number) and auto number formating with comma that input. In other hands function set will convert text with comma and turns into number.
So how I can running that code simmulteanously and binding both of function? I have check from this github issue that
Component v-model is designed for single value input components that attend to similar use cases for native input elements.
For a complex component that manages the synchronization of more than
one values, explicit prop/event pairs is the proper solution. In this
particular case I don't think the saved keystrokes are worth the added
complexity of additional syntax.
Any idea? Thank you.
After a day doing some research, I found a solution to have 2 v-model in one HTML DOM. Here are my solution
First, we need vue.component with v-model binded to computed parameter
Vue.component('number-input', {
props: ["value"],
template: `
<div>
<input type="textarea" class="val2 bhnPrice alatPrice handsontableInput area-custom text-center text-bold" v-model="displayValue" #blur="isInputActive = false" #focus="isInputActive = true"/>
</div>`,
data: function() {
return {
isInputActive: false
}
},
computed: {
displayValue: {
get: function() {
if (this.isInputActive) {
return this.value.toString()
} else {
return this.value.toFixed(0).replace(/(\d)(?=(\d{3})+(?:\.\d+)?$)/g, "$1,")
}
},
set: function(modifiedValue) {
let newValue = parseFloat(modifiedValue.replace(/[^\d\.]/g, ""))
if (isNaN(newValue)) {
newValue = 0
}
this.$emit('input', newValue)
}
}
}
});
Second, change HTML DOM as bellow
<div class="handsontableInputHolder">
<number-input style="" v-model="data.harga_satuan" v-on:keyup="change(2,index)"></number-input>
</div>
Let say i have a object called,
scope.rec = {a: 2, b: 3, name: a,b};
And i split the "name" key like scope.x = scope.rec.name.split(","); then scope.x will become an array.
Now i need to iterate over "scope.x" in the view and get the value associated with the matching property name on scope.rec. I only want to iterate over the valid property names, so I will need to use a filter on scope.x, but it is not working as I would expect.
Once I get the first part working, I will also need to add functionality to multiply the values of the scope.rec properties together - in the example above, it is only 2 numbers (a,b), but it could be more than 2.
Below is the code that I tried.
scope.x =
scope.rec.name.split(",");
scope.myFilter = function(y) {
if(!scope.rec.hasOwnProperty(y)) return false;
scope.ys = Number(scope.rec[y]);
return scope.ys;
};
html:
<div ng-repeat="y in x | filter:myFilter">
<label for="{{y}}">{{y}}</label>
<input type="number" id="{{y}}" value={{ys}}>
</div>
<div><input id="calc" type="number" ng-model="calc()" disabled></div>
Now the ys in the input is same for both inputs, and the calc() function does not calculate the values correctly.
Appreciate your help in advance.
your filter (at least how you use it in your view) will receive an array with all elements, not just one. So you need to return a complete array
angular.module('myApp').filter('myFilter', function() {
return function(arrayOfYs, recFromScope) {
var filtered = [];
arrayOfYs.forEach(function(y){
if(!recFromScope.hasOwnProperty(y)) return;
// if the value in the object is already a number, it is not necessary to use Number. If it is not the case, add it
filtered.push(scope.rec[y]);
});
return filtered;
}
});
and return the filtered data.
According to your view, you need to use angular filters.
for you input you should use this
<input type="number" id="{{y}}" value={{y}}>
although I would remove that id - ids needs to be unique and probably there are values repeated.
for your calc() function you can use reduce to multiply them
$scope.calc = function(){
return $scope.filteredItems.reduce(function(prev, current) {
return prev * current;
}, 1);
};
and to get a reference to $scope.filteredItems use this in your view
<div ng-repeat="y in (filteredItems = (x | filter:myFilter:rec))">
You could do something like this:
angular.module('myApp', [])
.controller('MyController', MyController);
function MyController($scope) {
$scope.result = 1;
$scope.recObj = {};
$scope.rec = {
a: 2,
b: 3,
c: 5,
name: 'a,b,c,d'
};
function initData() {
var dataKeysArr = $scope.rec.name.split(",");
for (var dataKey of dataKeysArr) {
if ($scope.rec.hasOwnProperty(dataKey)) {
$scope.recObj[dataKey] = $scope.rec[dataKey];
}
}
};
initData();
// Watch
$scope.$watchCollection(
"recObj",
function() {
$scope.result = 1;
for (var dataKey in $scope.recObj) {
$scope.result *= $scope.recObj[dataKey];
};
}
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="MyController">
<div ng-repeat="(key, val) in recObj">
<label for="{{key}}">{{key}}</label>
<input type="number" id="{{key}}" ng-model="recObj[key]">
</div>
<div>
<input id="calc" type="number" ng-model="result" disabled>
</div>
</div>
</div>
I am having two issue in this page
https://plnkr.co/edit/c7r4CAR1WJrOzQUybip2?p=preview
When the USD amount is 3, it gives 2.219999 value in EUR. I have tried using .toFixed(2) but it's not working.
Another problem is I am calling ng-model="curr.to()". Even though it outputs the result correctly, there is a console error.
angular.js:13920 Error: [ngModel:nonassign] Expression 'curr.to()' is non-assignable. Element: <input type="number" ng-model="curr.to()" class="ng-pristine ng-untouched ng-valid">
Any help would be appreciated.
1. You probably got an error, because toFixed converts the number to a string, and you can't bind it to your input. A simple Number(watever.toFixed()) would do.
2. You can't bind a function expression to ngModel since it performs two-way data binding. You can alternatively use watch instead.
Note: I am personally against your 1st method. The controller should not care how the data be displayed on the view. You should consider another approach by using filter, etc..
However, here's a sample of your working version.
(function(angular) {
'use strict';
angular.module('finance', []);
angular.module('finance').factory('currencyConverter', function() {
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {
USD: 1,
EUR: 0.74,
CNY: 6.09
};
var convert = function(amount, inCurr, outCurr) {
var res = amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr]
return Number(res.toFixed(2));
};
return {
currencies: currencies,
convert: convert
}
});
angular.module('currencyConvert', ['finance']);
angular.module('currencyConvert').controller('currencyCnvtCtrl', ['$scope', 'currencyConverter',
function InvoiceController($scope, currencyConverter) {
this.from = 1;
this.inCurr = 'USD';
this.outCurr = 'CNY';
this.currencies = currencyConverter.currencies;
this.to = 0;
var w1 = $scope.$watch('curr.from', function() {
$scope.curr.to = currencyConverter.convert($scope.curr.from, $scope.curr.inCurr, $scope.curr.outCurr);
});
var w2 = $scope.$watch('curr.inCurr', function() {
$scope.curr.to = currencyConverter.convert($scope.curr.from, $scope.curr.inCurr, $scope.curr.outCurr);
});
var w3 = $scope.$watch('curr.outCurr', function() {
$scope.curr.to = currencyConverter.convert($scope.curr.from, $scope.curr.inCurr, $scope.curr.outCurr);
});
var w4 = $scope.$watch('curr.to', function() {
$scope.curr.from = currencyConverter.convert($scope.curr.to, $scope.curr.outCurr, $scope.curr.inCurr);
});
$scope.$on('$destroy', function() {
w1();
w2();
w3();
w4();
});
}
]);
})(window.angular);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="currencyConvert">
<h1>Currency Converter</h1>
<section class="currency-converter" ng-controller="currencyCnvtCtrl as curr">
<h4>Type in amount and select currency</h4>
<input type="number" ng-model="curr.from">
<select ng-model="curr.inCurr">
<option ng-repeat="c in curr.currencies">{{c}}</option>
</select>
<br>
<input type="number" ng-model="curr.to">
<select ng-model="curr.outCurr">
<option ng-repeat='c in curr.currencies'>{{c}}</option>
</select>
</section>
</div>
I am actually working with knockout and want to know if there is a way in which i can inverse the knockout property. I have a function IsArchived and want to create a inverse of that, named NotArchived. But I am having issues with it.
The main issue is that I am not seeing any difference in my output. for example there's total of 2000 account in my system out of which its showing 1500 accounts as archived and 2000 account as non archived. Instead of that it should show only 500 non archived accounts.
<li>
<label id="isArchived">
<input type="checkbox" data-bind="checked: isArchived" /><span>Archived</span>
</label>
</li>
<li>
<label id="NotArchived">
<input type="checkbox" data-bind="checked: NotArchived" /><span>Not Archived</span>
</label>
</li
JavaScript:
function WorkersViewModel() {
var self = this;
var initialized = false;
self.isArchived = ko.observable(false);
self.NotArchived = ko.observable(true);
};
self.navigateToSearch = function(uriArray) {
if (self.isArchived()) {
uriArray.push({
isArchived: true
});
}
if (self.NotArchived()) {
uriArray.push({
NotArchived: false
});
}
self.runSearch = function() {
var parameters = {
IsArchived: self.isArchived() ? true : null,
NotArchived: self.isArchived() ? false : null,
};
You can do it by using a computed.
function WorkersViewModel() {
var self = this;
var initialized = false;
self.isArchived = ko.observable(false);
self.NotArchived = ko.computed({
read: function(){ return !self.isArchived() },
write : function(value) { self.isArchived(!value); }
});
};
Depending from the evaluation sequence you need, you may use:
computed observable
subscription
because the solution with a computed observable has been already posted, here is a snippet which uses a subscription:
self.isArchived = ko.observable(false);
self.isNotArchived = ko.observable(true);
self.isArchived.subscribe(function(newValue) {
self.isNotArchived(!newValue);
});
Anozher difference is that the computed observable will be evaluated also for the first time when the view model is instantiated, whereby by using the subscription you should provide to both observables the correct initial value.
Looking for a good example of how to set up child models in knockoutjs. This includes binding to child events such as property updates which I haven't been able to get working yet.
Also, it would be better to bind to a single child in this case instead of an array but I don't know how to set it up in the html without the foreach template.
http://jsfiddle.net/mathewvance/mfYNq/
Thanks.
<div class="editor-row">
<label>Price</label>
<input name="Price" data-bind="value: price"/>
</div>
<div class="editor-row">
<label>Child</label>
<div data-bind="foreach: childObjects">
<div><input type="checkbox" data-bind="checked: yearRound" /> Year Round</div>
<div><input type="checkbox" data-bind="checked: fromNow" /> From Now</div>
<div>
<input data-bind="value: startDate" class="date-picker"/> to
<input data-bind="value: endDate" class="date-picker"/>
</div>
</div>
</div>
var ChildModel= function (yearRound, fromNow, startDate, endDate) {
var self = this;
this.yearRound = ko.observable(yearRound);
this.fromNow = ko.observable(fromNow);
this.startDate = ko.observable(startDate);
this.endDate = ko.observable(endDate);
this.yearRound.subscribe = function (val) {
alert('message from child model property subscribe\n\nwhy does this only happen once?');
//if(val){
// self.startDate('undefined');
// self.endDate('undefined');
//}
};
}
var ParentModel = function () {
var self = this;
this.price = ko.observable(1.99);
this.childObjects = ko.observableArray([ new ChildModel(true, false) ]);
};
var viewModel = new ParentModel ();
ko.applyBindings(viewModel);
Try it with the following:
this.yearRound.subscribe(function (val) {
alert('value change');
});
If you want to have the subscriber also being called while loading the page do something like this:
var ChildModel= function (yearRound, fromNow, startDate, endDate) {
var self = this;
this.yearRound = ko.observable();
this.fromNow = ko.observable(fromNow);
this.startDate = ko.observable(startDate);
this.endDate = ko.observable(endDate);
this.yearRound.subscribe(function (val) {
alert('value change');
});
this.yearRound(yearRound);
}
http://jsfiddle.net/azQxx/1/ - this works for me with Chrome 16 and Firefox 10
Every time the checked button changes its value the callback fires.
The observableArray is fine in my opinion if you may have more than one child model associated to the parent.