Convert value into lower case before knockout binding - javascript

Demo Here
I bound a label with knockoutjs. The value bound always should be in lower case. While it will remain in uppercase in js model. How to do this ?
Javascript
var model = {
name:ko.observable("Test")
}
ko.applyBindings(model);
HTML
<label data-bind="text:name">

you just need to use toLowerCase in the view
view :
<div class='liveExample'>
<p> name: <label data-bind='text: name().toLowerCase()'></label></p>
</div>
<b>Original Value:
<pre data-bind="text:ko.toJSON($data,null,2)"></pre>
sample working fiddle here

It's unclear what you want to do, in particular when the value is coming from the textarea, but you can probably do whatever it is using a writable computed:
model.lowerName = ko.computed({
read: function() {
return model.name().toLowerCase();
},
write: function(newValue) {
// ...save this however it is you want to save it...
}
});
HTML:
<input data-bind="value:lowerName">
Re your updated question: Your update completely changes the question. If you don't need updates from the element and are only showing what's in name, you have two options:
A read-only computed:
model.lowerName = ko.pureComputed(function() { return model.name().toLowerCase(); });
HTML:
<label data-bind="text:lowerName"></label>
Just do it in the binding:
<label data-bind="text:name().toLowerCase()"></label>

Related

Does a writable computed observable really need an extra internal observable?

I am trying to use a writable computed observable, but do not really get it why I cannot get some data in the observable by just typing in it. I found that I need an extra observable to copy content from the write: to the read:, which seems weird.
self.fullName = ko.pureComputed({
read: function () {
return ...data from other observables to show in the observable;
},
write: function (value) {
// value is the content in the input
// can be sent to other observables
},
owner: self
});
I found that in the above model, what you type in the observable is not really inside.
In the complete example below (including test output and comments) I use an extra observable to copy data from write: to read:. Crucial in my project is the checkbox to decide if you get two observables filled identically by typing in one of them, or differently by typing in both of them.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Writable computed observables</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src='https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.1/knockout-min.js'></script>
</head>
<body>
<h1>Writable computed observables</h1>
<p>See: Knockout Doc</p>
<h2>Original example</h2>
<div>
First name: <input data-bind="textInput: firstName" />
<span data-bind="text: firstName"></span>
</div>
<div>
Last name: <input data-bind="textInput: lastName" />
<span data-bind="text: lastName"></span>
</div>
<div class="heading">
Hello, <input data-bind="textInput: fullName" />
<span data-bind="text: fullName"></span>
</div>
<h2>My example</h2>
<div>
Name: <input data-bind="textInput: Name" />
<span data-bind="text: Name"></span>
</div>
<div>
Mirror first name? <input type="checkbox" data-bind="checked: cbMirror" />
<span data-bind="text: cbMirror"></span>
</div>
<script>
function MyViewModel() {
var self = this;
// example from knockout site:
self.firstName = ko.observable('Planet');
self.lastName = ko.observable('Earth');
self.fullName = ko.pureComputed({
read: function () {
//return;
return self.firstName() + " " + self.lastName();
},
write: function (value) {
// value is the content in the input field, visible on form,
// but apparently not yet in the observable.
// now copy this value to first/last-name observables,
// that in turn copy it to the read-function,
// that returns it to the observable.
var lastSpacePos = value.lastIndexOf(" ");
if (lastSpacePos > 0) { // Ignore values with no space character
self.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
self.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
}
},
owner: self
});
// checkbox whether or not to mirror between two fields
self.cbMirror = ko.observable(false);
// this observable is to help the writable computed observable to copy input from write() to read()
self.tmpName = ko.observable();
// the writable computed observable that may mirror another field, depending on the checkbox
self.Name = ko.pureComputed({
read: function () {
return self.cbMirror() ? self.firstName() : self.tmpName();
},
write: function (value) {
//if (self.cbMirror()){
// self.firstName(value);
//}else{
self.tmpName(value);
//}
},
owner: self
});
}
ko.applyBindings(new MyViewModel());
</script>
</body>
</html>
The question, hence: is there really no better way, to directly get some content from write: to read: without the extra observable self.tmpName?
Update:
With the understanding gained from the Answer below, I could simplify the write: part of my example code, see the unneeded code that I commented-out.
Yes you need an observable if you want to store the user input. A computed doesn't store information it only modifies it. It's like the difference between a variable and a function. Functions don't store their values for later viewing it's only modifying an input and giving an output.
the writable computed observable doesn't store data. It passes the data to the backing observable. That's the entire point of the write portion is to take the value and store it somewhere. If you add another span to look at the value of tmpName you'll see that it's storing whatever you type unless cbMirror is checked.

cant make an object I added to a knockout model observable

Here is the fiddle demonstrating the problem http://jsfiddle.net/LkqTU/31955/
I made a representation of my actual problem in the fiddle. I am loading an object via web api 2 and ajax and inserting it into my knockout model. however when I do this it appears the attributes are no longer observable. I'm not sure how to make them observable. in the example you will see that the text box and span load with the original value however updating the textbox does not update the value.
here is the javascript.
function model() {
var self = this;
this.emp = ko.observable('');
this.loademp = function() {
self.emp({
name: 'Bryan'
});
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
here is the html
<button data-bind="click: loademp">
load emp
</button>
<div data-bind="with: emp">
<input data-bind="value: name" />
<span data-bind="text: name"></span>
</div>
You need to make name property observable:
this.loademp = function(){
self.emp({name: ko.observable('Bryan')});
}

Number input box in Knockout JS

I'm trying to create a number input box which will accept numbers only.
My initial value approach was to replace value and set it again to itself.
Subscribe approach
function vm(){
var self = this;
self.num = ko.observable();
self.num.subscribe(function(newValue){
var numReg = /^[0-9]$/;
var nonNumChar = /[^0-9]/g;
if(!numReg.test(newValue)){
self.num(newValue.toString().replace(nonNumChar, ''));
}
})
}
ko.applyBindings(new vm())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="textInput: num" />
Now this approach works but will add another cycle of subscribe event, so I tried to use a custom binding so that I can return updated value only. New to it, I tried something but not sure how to do it. Following is my attempt but its not working. Its not even updating the observable.
Custom Binding attempt
ko.bindingHandlers.numeric_value = {
update: function(element, valueAccessor, allBindingsAccessor) {
console.log(element, valueAccessor, allBindingsAccessor())
ko.bindingHandlers.value.update(element, function() {
var value = ko.utils.unwrapObservable(valueAccessor());
return value.replace(/[^0-9]/g, '')
});
},
};
function vm() {
this.num = ko.observable(0);
this.num.subscribe(function(n) {
console.log(n);
})
}
ko.applyBindings(new vm())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
<input type="text" data-bind="number_value: num, valueUpdate:'keyup'">
<span data-bind="text: num"></span>
</div>
So my question is, Can we do this using custom bindings and is it better approach than subscribe one?
Edit 1:
As per #user3297291's answer, ko.extenders looks more like a generic way for my subscribe approach. I'm looking for an approach (if possible in Knockout), which would clean value before it is set to observable.
I have taken reference from following articles:
How to update/filter the underlying observable value using a custom binding?
How can i update a observable in custom bindings?
Note: In the first example, they are using jQuery to set the value. I would like to avoid it and do it using knockout only
I´m on favor of use extender as user3297291's aswer.
Extenders are a flexible way to format or validate observables, and more reusable.
Here is my implementation for numeric extender
//Extender
ko.extenders.numeric = function(target, options) {
//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 newValueAsNum = options.decimals ? parseFloat(newValue) : parseInt(newValue);
var valueToWrite = isNaN(newValueAsNum) ? options.defaultValue : newValueAsNum;
target(valueToWrite);
}
}).extend({
notify: 'always'
});
//initialize with current value to make sure it is rounded appropriately
result(target());
//return the new computed observable
return result;
};
//View Model
var vm = {
Product: ko.observable(),
Price: ko.observable().extend({
numeric: {
decimals: 2,
defaultValue: undefined
}
}),
Quantity: ko.observable().extend({
numeric: {
decimals: 0,
defaultValue: 0
}
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Edit
I get your point, what about and regular expression custom binding to make it more reusable?
Something like this.
function regExReplace(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var observable = valueAccessor();
var textToReplace = allBindingsAccessor().textToReplace || '';
var pattern = allBindingsAccessor().pattern || '';
var flags = allBindingsAccessor().flags;
var text = ko.utils.unwrapObservable(valueAccessor());
if (!text) return;
var textReplaced = text.replace(new RegExp(pattern, flags), textToReplace);
observable(textReplaced);
}
ko.bindingHandlers.regExReplace = {
init: regExReplace,
update: regExReplace
}
ko.applyBindings({
name: ko.observable(),
num: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="textInput : name, regExReplace:name, pattern:'(^[^a-zA-Z]*)|(\\W)',flags:'g'" placeholder="Enter a valid name" />
<span data-bind="text : name"></span>
<br/>
<input class=" form-control " type="text " data-bind="textInput : num, regExReplace:num, pattern: '[^0-9]',flags: 'g' " placeholder="Enter a number " />
<span data-bind="text : num"></span>
I think you can divide the problem in to two parts:
Making sure the user can only input numbers, or
Making sure your viewmodel value is a number rather than a string.
If you only need part 1, I'd advice you to use default HTML(5) features:
<input type="number" step="1" />
<input type="text" pattern="\d*" />
If you want to make sure the user cannot enter any other than a number, and want to use the value in your viewmodel as well, I'd use an extender. By extending the observable, you can change its value before any subscriptions are fired.
The knockout docs provide an excelent example on their documentation page:
Note that when you use the extender, you don't need to worry about the pattern or type attribute anymore; knockout modifies the value instantly as soon as it's set.
ko.extenders.numeric = 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(),
roundingMultiplier = Math.pow(10, precision),
newValueAsNum = isNaN(newValue) ? 0 : +newValue,
valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;
//only write if it changed
if (valueToWrite !== current) {
target(valueToWrite);
} 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;
};
var vm = {
changes: ko.observable(0),
myNumber: ko.observable(0).extend({
numeric: 1
})
};
vm.myNumber.subscribe(function() {
vm.changes(vm.changes() + 1);
});
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="value: myNumber">
<div>2 times <span data-bind="text: myNumber"></span> is <span data-bind="text: myNumber() * 2"></span>.</div>
<div> Changes: <span data-bind="text: changes"></span></div>
Using Knockout Validation | LIVE PEN:
<input data-bind="value: Number">
ko.validation.init();
function VM() {
var self = this;
self.Number = ko.observable().extend({
required: true,
pattern: {
message: 'Invalid number.',
params: /\d$/
}
});
}
ko.applyBindings(window.v=new VM());
Following is a mimic of knockout's textInput binding, but with custom parsing. Note, I know, this has added few extra lines of duplicate code but I guess its worth.
I thought of creating my custom code, but reinventing the wheel will have lots of issues, hence appreciating Knockout teams effort and copying it.
Numeric Only - JSFiddle.
Characters Only - JSFiddle
I have updated code in following
updateModel: To fetch only parsed value from element. This will prevent updating incorrect value.
updateView: To check if user have entered incorrect value. If yes, replace previous value, else update previous value as current value and proceed.
Usability
I have tried to increase scope of this binding beyond this question. I have added a special data attributes (data-pattern and data-flag) to create regex will parse accordingly.

knockout.js checkboxes used to control enabled/disabled state of input fields

I am trying to bind a checkbox to each line in a list of objects, in a very similar fashion to a question asked/answered here: Binding a list of objects to a list of checkboxes
Essentially, as follows:
<ul data-bind="foreach: phones">
<li>
<input type='text' data-bind="attr: {value:phone}, disable: $root.selectedPhones"/>
<input type="checkbox" data-bind="attr: {value:id}, checked: $root.selectedPhones" />
</li>
</ul>
<hr/> selected phones:
<div data-bind="text: ko.toJSON($root.selectedPhones)"></div>
<hr/> phones:
<div data-bind="text: ko.toJSON($root.phones)"></div>
with js as follows:
function Phone(id,phone) {
this.id = id;
this.phone = phone;
}
var phones_list = [
new Phone(1, '11111'),
new Phone(2, '22222'),
new Phone(3, '33333')
];
var viewModel = {
phones: ko.observableArray(phones_list),
selectedPhones: ko.observableArray()
};
ko.applyBindings(viewModel);
The idea being that in the initial state, all of the input boxes are disabled and that clicking a checkbox will enable the input box in that row.
The data is coming from a fairly deeply nested object from the server-side so I'd like to avoid 'padding' the data with an additional boolean ie avoiding new Phone(1,'xx', false)
(a) because it's probably unnecessary (b) because the structure is almost certainly going to change...
Can the selectedPhones observable be used by the enable/disable functionality to control the status of fields in that 'row'?
Hope someone can help....
I have a jsfiddle here
You can create a small helper function which checks that a given id appers in the selectedPhones:
var viewModel = {
phones: ko.observableArray(phones_list),
selectedPhones: ko.observableArray(),
enableEdit: function(id) {
return ko.utils.arrayFirst(viewModel.selectedPhones(),
function(p) { return p == id })
}
};
Then you can use this helper function in your enable binding:
<input type='text' data-bind="attr: {value:phone}, disable: $root.enableEdit(id)"/>
Demo JSFiddle.

How to get selected checkboxes on button click in angularjs

I want to do something like this
<input type="checkbox" ng-model="first" ng-click="chkSelect()"/><label>First</label>
<input type="checkbox" ng-model="second" ng-click="chkSelect()"/><label>Second</label>
<input type="checkbox" ng-model="third" ng-click="chkSelect()"/><label>Third</label>
<input type="checkbox" ng-model="forth" ng-click="chkSelect()"/><label>Forth</label>
<button>Selected</button>
On button click I want to display selected checkbox labelname.
$scope.chkSelect = function (value) {
console.log(value);
};
Because the checkboxes are mapped, you can reference $scope.first, $scope.second, etc in your chkSelect() function. It's also possible to have a set of checkboxes mapped as a single array of data instead of having to give each checkbox a name. This is handy if you are generating the checkboxes, perhaps from a set of data.
I agree with Bublebee Mans solution. You've left out a lot of detail on why you're trying to get the label. In any case if you REALLY want to get it you can do this:
$scope.chkSelect = function (value) {
for(var key in $scope){
var inputs = document.querySelectorAll("input[ng-model='" + key + "']");
if(inputs.length){
var selectedInput = inputs[0];
var label = selectedInput.nextSibling;
console.log(label.innerHTML);
}
};
};
You can mess around with it to see if it's indeed selected.
fiddle: http://jsfiddle.net/pzz6s/
Side note, for anybody who knows angular please forgive me.
If you are dealing with server data, you might need isolated html block and deal with data in controller only.
You can do it by creating array in controller, maybe your data from response, and use ngRepeat directive to deal independently in html code.
Here is what I am telling you:
HTML:
<form ng-controller="MyCtrl">
<label ng-repeat="name in names" for="{{name}}">
{{name}}
<input type="checkbox"
ng-model="my[name]"
id="{{name}}"
name="favorite" />
</label>
<div>You chose <label ng-repeat="(key, value) in my">
<span ng-show="value == true">{{key}}<span>
</label>
</div>
</form>
Javascript
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.names = ['pizza', 'unicorns', 'robots'];
$scope.my = { };
}
You want to have something like the following in your controller (untested, working from memory):
$scope.checkBoxModels = [ { name: 'first', checked: false }, { name: 'second', checked: false }, { name: 'third', checked: false }, { name: 'fourth', checked: false } ];
Then in your view:
<input ng-repeat"checkboxModel in CheckBoxModels" ng-model="checkBoxModel.checked" ng-click="chkSelect(checkBoxModel)" /><label>{{checkBoxModel.name}}</label>
Then update your function:
$scope.chkSelect = function (checkBoxModel) {
console.log(checkBoxModel.name);
};

Categories

Resources