I am using the same observable for display and edit views as follows. I would like a default text ('-') to appear in the display view whenever the return value is null, but I do not want that in the edit view. How could that be achieved?
function ViewModel() {
var self = this;
self.txt1 = ko.observable();
self.txt2 = ko.observable();
}
// View Mode
<span data-bind="text:txt1"></span>
<span data-bind="text:txt2"></span>
// Edit Mode
<input type="text" data-bind="value:txt1" />
<input type="text" data-bind="value:txt2" />
You can use a computed observable to return a value for the view mode, using it to check the value currently stored and return - if necessary:
function ViewModel() {
var self = this;
self.txt1 = ko.observable();
self.txt2 = ko.observable();
self.viewtxt1 = ko.computed(function() {
if (self.txt1() == null)
return '-';
return self.txt1();
});
self.viewtxt2 = ko.computed(function() {
//txt2 is checked for null or blank, use whichever is appropriate
if (self.txt2() == null || self.txt2() == '')
return '-';
return self.txt2();
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
// View Mode<br />
<span data-bind="text:viewtxt1"></span><br />
<span data-bind="text:viewtxt2"></span><br />
<br />
// Edit Mode<br />
<input type="text" data-bind="value:txt1" /><br />
<input type="text" data-bind="value:txt2" /><br />
In the above there are two different checks. txt1 is checked just for null, and so if you edit it and delete all characters, the blank value will persist in the view. txt2 is checked for both null and blank, so removing all characters will reset the view back to -. Use whichever one is appropriate to you.
This might not be the most paradigmatic Knockout.js solution but it's working well and it's very quick.
Instead of the <span> you could use an HTML <input />, with placeholder="-" readonly="readonly". You may want also to style it so that you hide borders.
<input class="readonly" data-bind="value:txt1" readonly placeholder="-" />
<input class="readonly" data-bind="value:txt2" readonly placeholder="-" />
.readonly {
border: 0;
}
http://jsfiddle.net/Lbf3uxqe/
An alternative approach to computeds as per my other answer is to write an entire new custom binding handler. This means you only have the one observable on your view model, with the logic contained within the binding handler instead. Note that you need to change the view to use this binding handler, rather than the text binding it was using:
ko.bindingHandlers.textdefault = {
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var v = valueAccessor();
var d = allBindings.get('defaultIfBlank') || '-';
element.innerText = (v() == null || v() == '') ? d : v();
}
};
function ViewModel() {
var self = this;
self.txt1 = ko.observable();
self.txt2 = ko.observable();
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
// View Mode<br />
<span data-bind="textdefault:txt1"></span><br />
<span data-bind="textdefault:txt2, defaultIfBlank: '$'"></span><br />
<br />
// Edit Mode<br />
<input type="text" data-bind="value:txt1" /><br />
<input type="text" data-bind="value:txt2" /><br />
Note that in this approach, we can also customise the handler through the allBindings variable - in the above I have added a parameter to allow customising of which character is displayed if the entry is blank.
Related
I have two checkboxes that I would like to implement following logic:
if both are OFF, that is allowed, if one checkbox is ON, if the other is to be ON (checked) then the first one must toggle to OFF and vice versa
How can I do this with html and ko.js?
This is the code that i got for toggling:
var viewmodel = function(){
var self = this;
self.checkA = ko.observable(true);
self.checkB = ko.pureComputed({
read: function(){
return !self.checkA()
},
write: function(value){
value? self.checkA(false) : self.checkA(true);
},
owner: self
});
};
ko.applyBindings(new viewmodel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
A
<input type="checkbox" data-bind="checked: checkA" />
B
<input type="checkbox" data-bind="checked: checkB" />
Since the value of B isn't merely a function of the value of A (both can be false) you'll have to have a second observable to store its state independently. Then for your logic I recommend using ko.subscribe instead of a computed function to react to the changes to either value.
var viewmodel = function() {
var self = this;
self.checkA = ko.observable(true);
self.checkB = ko.observable(false);
self.checkA.subscribe(function(value) {
if (value) self.checkB(false); //also clear B
});
self.checkB.subscribe(function(value) {
if (value) self.checkA(false); //also clear A
});
};
ko.applyBindings(new viewmodel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
A <input type="checkbox" data-bind="checked: checkA" />
B <input type="checkbox" data-bind="checked: checkB" />
I am trying to add a simple functionality in my program and Im having a little trouble figuring out how to do something I wanted.
Here's what I got:
My input textbox, with a link beside it to disable/enable readonly property on that input textbox.
<div>
<input type="text" data-bind="attr: { 'readonly': getreadonlyState() }" value="420" />
Edit
</div>
Here's my knockout script for it:
var ViewModel = function() {
var self = this;
self.getreadonlyState = ko.observable('readonly');
self.readonly = function() {
if (self.getreadonlyState()) {
self.getreadonlyState(undefined);
}
else self.getreadonlyState('readonly');
}
}
ko.applyBindings(new ViewModel());
This works great, but what I wanted is when I click the edit link, it will change the text of the link to something like: "Stop Editing" so when I click "Stop Editing" the readonly property is enabled again.
Here's a fiddle of what Im working on.
Any help will be greatly appreciated, thank you!
Here's an alternative to #thangcao's answer. I'm not saying this is any better or worse, simply an alternative which uses a subscribe handler instead of a computedObservable.
<div>
<input type="text" data-bind="attr: { 'readonly': getreadonlyState() }" value="420" />
</div>
var ViewModel = function() {
var self = this;
self.getreadonlyState = ko.observable('readonly');
self.getreadonlyState.subscribe(function(val) {
self.linkText(val === "readonly" ? "Edit" : "Stop editing");
});
self.readonly = function() {
if (self.getreadonlyState()) {
self.getreadonlyState(undefined);
}
else self.getreadonlyState('readonly');
}
self.linkText = ko.observable("Edit");
}
ko.applyBindings(new ViewModel());
Notice that there's no need for the additional <span> in #thangcao's answer.
Also, why is the "edit"/"stop editing" element an anchor tag? Why not just make it a <span> and do away with the need for the additional inline JavaScript (which you can anyway replace with a return false; inside the readonly function).
http://jsfiddle.net/ajameson/eeTjS/87/
I have updated your Fiddle and hope that it meets your need:
<div>
<input type="text" data-bind="attr: { 'readonly': getreadonlyState() }" value="420" />
<span data-bind="text:linkText"></span>
</div>
var ViewModel = function() {
var self = this;
self.getreadonlyState = ko.observable('readonly');
self.readonly = function() {
if (self.getreadonlyState()) {
self.getreadonlyState(undefined);
}
else {
self.getreadonlyState('readonly');
}
}
self.linkText = ko.computed(function(){
return self.getreadonlyState() == 'readonly' ? "Stopping edit" : "Edit";
}, self);
}
ko.applyBindings(new ViewModel());
You can use this binginHandlers :
ko.bindingHandlers.readOnly = {
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
element.setAttribute("disabled", true);
} else {
element.removeAttribute("disabled");
}
}
};
In my html :
<input type="text" id="create-finess" class="form-control" data-bind="readOnly: _locked" />
Finaly in my JS :
//Constructor of my view model
function ViewModel(resx) {
this._locked = ko.observable();
}
// on init of the page i lock the input
this._load = function () {
this._locked(true);
}
thank you for looking into this.
I have the following example built: http://jsfiddle.net/zm381qjx/5/
This is a menu list builder. When you add a menu, an edit form pops up. Using protectedObservable so that i can either commit or reset (as per code). One functionality, which i am having problems with, is there is radio button list (for TypeId), and depending on the value (10 = Url, 20 = Category, 30 = Page), you set the respective properties (10 = Url, 20 = CategoryId, 30 = PageId).
Flicking through the radio buttons, if Url is selected, another textbox should show (based on urlVisible) so user can enter the Url. I have added a span with text: TypeId.temp so i can see the temporary value. This is very irregular. Try to flick through several times.
Any help will be greatly appreciated.
My HTML
<a class="btn btn-primary" data-bind="click: addMenu">Add Menu</a>
<ul data-bind="foreach: Menus">
<li></li>
</ul>
<div class="panel panel-default" data-bind="slideIn: editMenuItem, with: editMenuItem">
<div class="panel-body">
<div class="form-group">
<label for="MenuName">Name: </label>
<input type="text" id="MenuName" data-bind="value: Name" class="form-control" />
</div>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="10" data-bind="checked: TypeId" /> Url
</label>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="20" data-bind="checked: TypeId" /> Category
</label>
<label class="radio-inline">
<input type="radio" name="MenuTypeId" value="30" data-bind="checked: TypeId" /> Page
</label>
<div class="form-group" data-bind="visible: urlVisible">
<label for="MenuUrl">Url: </label>
<input type="text" id="MenuUrl" data-bind="value: Url" class="form-control" />
</div>
<br />
<p>TypeId.temp = <span data-bind="text: TypeId.temp"></span></p>
<br /><br />
<input type="button" class="btn btn-success" value="Update" data-bind="click: commit" /> or
Cancel
</div>
</div>
My JS:
var vm = null;
//wrapper for an observable that protects value until committed
ko.protectedObservable = function (initialValue) {
//private variables
var _temp = ko.observable(initialValue);
var _actual = ko.observable(initialValue);
var result = ko.dependentObservable({
read: function () {
return _actual();
},
write: function (newValue) {
_temp(newValue);
}
});
//commit the temporary value to our observable, if it is different
result.commit = function () {
var temp = _temp();
if (temp !== _actual()) {
_actual(temp);
}
};
//notify subscribers to update their value with the original
result.reset = function () {
_actual.valueHasMutated();
_temp(_actual());
};
result.temp = _temp;
return result;
};
ko.bindingHandlers.slideIn = {
init: function (element) {
$(element).hide();
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).stop().hide().slideDown('fast');
} else {
$(element).stop().slideUp('fast');
}
}
};
var Menu = function (Id, Name, TypeId, CategoryId, PageId, Url) {
var self = this;
/* Core Properties */
self.Id = ko.observable(Id);
self.Name = ko.protectedObservable(Name);
self.TypeId = ko.protectedObservable(TypeId);
self.CategoryId = ko.protectedObservable(CategoryId);
self.PageId = ko.protectedObservable(PageId);
self.Url = ko.protectedObservable(Url);
/* Virtual Properties */
self.urlVisible = ko.computed(function () {
return self.TypeId.temp() == "10";
}, self);
/* Virtual Functions */
self.editMenu = function (data) {
if(vm.editMenuItem()) {
vm.editMenuItem(null);
}
vm.editMenuItem(data);
};
/* Core Functions */
self.commit = function () {
if (self.Name.temp() == '' || self.Name.temp() == null) {
alert('Please enter a name.'); return;
}
self.Name.commit();
self.TypeId.commit();
self.CategoryId.commit();
self.PageId.commit();
self.Url.commit();
vm.editMenuItem(null);
};
self.reset = function () {
self.Name.reset();
self.TypeId.reset();
self.CategoryId.reset();
self.PageId.reset();
self.Url.reset();
vm.editMenuItem(null);
};
};
var ViewModel = function() {
var self = this;
/* Core Properties */
self.Menus = ko.observableArray([]);
/* Virtual Properties */
self.editMenuItem = ko.observable(null);
self.addMenu = function(){
var menu = new Menu(0, "New Menu", "10", 0, 0, "");
self.Menus.push(menu);
self.editMenuItem(menu);
};
};
$(function () {
vm = new ViewModel();
ko.applyBindings(vm);
});
If you change your radio button binding to
<input type="radio" name="MenuTypeId" value="10" data-bind="checked: TypeId.temp" />
The temp id will be changed accordingly and radio button behaviour is consistent, but not with TypeId as value.
also the protectedObservable binding the radio button value is not playing nice
When you manually click the radio the TypeId value is never changed (as you are not committing the value) and I guess that as the radio button value never changes from 10 , it is not recognizing the subsequent manual clicks on Url radio button.
I updated the value using a button and it is changing accordingly; but then it will not move the value from that TypeId on subsequent radio button clicks
And the problem is still appearing for protectedObservable binding but not with a simple observable.
Code which explores this idea further: http://jsfiddle.net/zm381qjx/101/
I have a simple html page with value input and save button.
I want the save will be enabled only if the value is changed (somtimes is initialized and somtimes not.
I've tryied few things without any success
HTML
<input type="text"
placeholder="type here"
data-bind="value: rate,"/>
<button data-bind="click: save">Save</button>
JS
var viewmodel = function () {
this.rate = ko.observable('88').extend(required: true);
};
viewmodel.prototype.save = function () {
alert('save should be possible only if rate is changed);
};
Also on jsfiddle
Should be able to achieve this with a computed observable and the enable binding.
See http://jsfiddle.net/brendonparker/xhLrB/1/
Javascript:
var ctor = function () {
var self = this;
self.originalRate = '88';
self.rate = ko.observable('');
self.canSave = ko.computed(function(){
return self.originalRate == self.rate();
});
};
ctor.prototype.save = function () {
alert('save should be possible only if rate is changed');
};
ko.applyBindings(new ctor());
HTML:
<input type="text" placeholder="type here" data-bind="value: rate, valueUpdate: 'afterkeydown'"/>
<button data-bind="click: save, enable: canSave">Save</button>
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.