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.
Related
I am currently trying to implement an autosave feature in a Knockout JS applcation using extenders. I want to call an autosave function when users stop typing in a field, not just when they tab out of it.
This is the logChange method I want to call when an observable gets updated.
//KO Extender for logging changes and calling the autosave function
ko.extenders.logChange = function (target, precision) {
//create a writable computed observable to intercept writes to our observable
var result = ko.pureComputed({
read: target, //always return the original observables value
write: function (newValue) {
debugger;
var current = target(),
valueToWrite = newValue,
attName = precision;
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
//self.autoSave(attName, target());
} else {
//if the rounded value is the same, but a different value was written, force a notification for the current field
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
};
This is how I am setting up the observable in my viewmodel.
self.controlCenter = ko.observable().extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" }, logChange: "ControlCenter" });
And this is my html markup for that observable
<div class="pure-u-1-2 pure-u-md-1-4 pure-u-lg-1-8">
<label for="ddlControlCenter">Jurisdiction</label>
<input type="text" class="pure-input-1 full-text" list="controlCenterList" data-bind="textInput: controlCenter" />
<datalist id="controlCenterList" data-bind="foreach: controlCenters">
<option data-bind="value: $data"></option>
</datalist>
</div>
The logChange method gets called, but it doesn't look like the rateLimit is being applied as the logChange gets called immediately on keypress.
Update:
I've updated the fiddle below. The mistake you were making was that you were calling a function inside the logChange function, right after updating the new value. But the concept of rateLimit is that the notification that the observable value was changed is sent after a delay to all its subscribers.
In other words, only the observable's subscribers are affected by the rateLimit, not anything inside the logChange function. So the correct way to do it would be to call your autoSave function inside the subscriber.
function ViewModel() {
var self = this;
self.autoSave = function(attName, value){
console.log(attName + " is now = " + value);
}
self.precision = ko.observable();
ko.extenders.logChange = function (target, precision) {
//create a writable computed observable to intercept writes to our observable
var result = ko.pureComputed({
read: target, //always return the original observables value
write: function (newValue) {
var current = target(),
valueToWrite = newValue;//,
self.precision(precision);
//attName = precision;
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
//self.autoSave(attName, target());
} else {
//if the rounded value is the same, but a different value was written, force a notification for the current field
if (newValue !== current) {
target.notifySubscribers(valueToWrite);
}
}
}
}).extend({ notify: 'always' });
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
};
self.controlCenter = ko.observable().extend({ rateLimit: { timeout: 500, method: "notifyWhenChangesStop" }, logChange: "ControlCenter" });
self.controlCenters = ko.observableArray([]);
this.controlCenter.subscribe(function (val) {
if (val !== '')
this.controlCenters.push(val);
self.autoSave(self.precision(), val);
}, this);
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="pure-u-1-2 pure-u-md-1-4 pure-u-lg-1-8">
<label for="ddlControlCenter">Jurisdiction</label>
<input type="text" class="pure-input-1 full-text" list="controlCenterList" data-bind="textInput: controlCenter" />
<datalist id="controlCenterList" data-bind="foreach: controlCenters">
<option data-bind="value: $data"></option>
</datalist>
</div>
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
I need to do following things: When user checks the checkbox, some function is called.
<input type="checkbox" data-bind="what to write here?" />
and in model:
var viewModel = {
this.someFunction = function() {
console.log("1");
}
};
I have not found anything about this is documentation here.
What you need is the click binding:
<input type="checkbox" data-bind="click: someFunction" />
And in your view model:
var ViewModel = function(data, event) {
this.someFunction = function() {
console.log(event.target.checked); // log out the current state
console.log("1");
return true; // to trigger the browser default behavior
}
};
Demo JSFiddle.
Or if you want to you use the checked binding you can subscribe on the change event of your property:
<input type="checkbox" data-bind="checked: isChecked" />
And in your viewmodel:
var ViewModel = function() {
this.isChecked = ko.observable();
this.isChecked.subscribe(function(newValue){
this.someFunction(newValue);
}, this);
this.someFunction = function(value) {
console.log(value); // log out the current state
console.log("1");
}
};
Demo JSFiddle.
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.
I'd like to validate a form using the jquery validate plugin, but I'm unable to use the 'name' value within the html - as this is a field also used by the server app.
Specifically, I need to limit the number of checkboxes checked from a group. (Maximum of 3.) All of the examples I have seen, use the name attribute of each element. What I'd like to do is use the class instead, and then declare a rule for that.
html
This works:
<input class="checkBox" type="checkbox" id="i0000zxthy" name="salutation" value="1" />
This doesn't work, but is what I'm aiming for:
<input class="checkBox" type="checkbox" id="i0000zxthy" name="i0000zxthy" value="1" />
javascript:
var validator = $(".formToValidate").validate({
rules:{
"salutation":{
required:true,
},
"checkBox":{
required:true,
minlength:3 }
}
});
Is it possible to do this - is there a way of targeting the class instead of the name within the rules options? Or do I have to add a custom method?
Cheers,
Matt
You can add the rules based on that selector using .rules("add", options), just remove any rules you want class based out of your validate options, and after calling $(".formToValidate").validate({... });, do this:
$(".checkBox").rules("add", {
required:true,
minlength:3
});
Another way you can do it, is using addClassRules.
It's specific for classes, while the option using selector and .rules is more a generic way.
Before calling
$(form).validate()
Use like this:
jQuery.validator.addClassRules('myClassName', {
required: true /*,
other rules */
});
Ref: http://docs.jquery.com/Plugins/Validation/Validator/addClassRules#namerules
I prefer this syntax for a case like this.
I know this is an old question. But I too needed the same one recently, and I got this question from stackoverflow + another answer from this blog. The answer which was in the blog was more straight forward as it focuses specially for this kind of a validation. Here is how to do it.
$.validator.addClassRules("price", {
required: true,
minlength: 2
});
This method does not require you to have validate method above this call.
Hope this will help someone in the future too. Source here.
Here's the solution using jQuery:
$().ready(function () {
$(".formToValidate").validate();
$(".checkBox").each(function (item) {
$(this).rules("add", {
required: true,
minlength:3
});
});
});
Here's my solution (requires no jQuery... just JavaScript):
function argsToArray(args) {
var r = []; for (var i = 0; i < args.length; i++)
r.push(args[i]);
return r;
}
function bind() {
var initArgs = argsToArray(arguments);
var fx = initArgs.shift();
var tObj = initArgs.shift();
var args = initArgs;
return function() {
return fx.apply(tObj, args.concat(argsToArray(arguments)));
};
}
var salutation = argsToArray(document.getElementsByClassName('salutation'));
salutation.forEach(function(checkbox) {
checkbox.addEventListener('change', bind(function(checkbox, salutation) {
var numChecked = salutation.filter(function(checkbox) { return checkbox.checked; }).length;
if (numChecked >= 4)
checkbox.checked = false;
}, null, checkbox, salutation), false);
});
Put this in a script block at the end of <body> and the snippet will do its magic, limiting the number of checkboxes checked in maximum to three (or whatever number you specify).
Here, I'll even give you a test page (paste it into a file and try it):
<!DOCTYPE html><html><body>
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<input type="checkbox" class="salutation">
<script>
function argsToArray(args) {
var r = []; for (var i = 0; i < args.length; i++)
r.push(args[i]);
return r;
}
function bind() {
var initArgs = argsToArray(arguments);
var fx = initArgs.shift();
var tObj = initArgs.shift();
var args = initArgs;
return function() {
return fx.apply(tObj, args.concat(argsToArray(arguments)));
};
}
var salutation = argsToArray(document.getElementsByClassName('salutation'));
salutation.forEach(function(checkbox) {
checkbox.addEventListener('change', bind(function(checkbox, salutation) {
var numChecked = salutation.filter(function(checkbox) { return checkbox.checked; }).length;
if (numChecked >= 3)
checkbox.checked = false;
}, null, checkbox, salutation), false);
});
</script></body></html>
Since for me, some elements are created on page load, and some are dynamically added by the user; I used this to make sure everything stayed DRY.
On submit, find everything with class x, remove class x, add rule x.
$('#form').on('submit', function(e) {
$('.alphanumeric_dash').each(function() {
var $this = $(this);
$this.removeClass('alphanumeric_dash');
$(this).rules('add', {
alphanumeric_dash: true
});
});
});
If you want add Custom method you can do it
(in this case, at least one checkbox selected)
<input class="checkBox" type="checkbox" id="i0000zxthy" name="i0000zxthy" value="1" onclick="test($(this))"/>
in Javascript
var tags = 0;
$(document).ready(function() {
$.validator.addMethod('arrayminimo', function(value) {
return tags > 0
}, 'Selezionare almeno un Opzione');
$.validator.addClassRules('check_secondario', {
arrayminimo: true,
});
validaFormRichiesta();
});
function validaFormRichiesta() {
$("#form").validate({
......
});
}
function test(n) {
if (n.prop("checked")) {
tags++;
} else {
tags--;
}
}
If you need to set up multpile class rules you can do it like this:
jQuery.validator.addClassRules({
name: {
required: true,
minlength: 2
},
zip: {
required: true,
digits: true,
minlength: 5,
maxlength: 5
}
});
source: https://jqueryvalidation.org/jQuery.validator.addClassRules/
Disclaimer: Yes, I know it's 2021 and you shouldn't be using jQuery but, sometimes we have to. This information was really useful to me, so I hope to help some eventual random stranger who has to maintain some legacy system somewhere.
$(".ClassName").each(function (item) {
$(this).rules("add", {
required: true,
});
});