Creating an alias in Knockout? - javascript

At times I have elements from a view model that I am accessing multiple times in the view and I would like to be able to assign an alias from the view context to shorten things up.
I'm looking for something that would be similar to the foreach as alias or with, but that I am able to use on a single arbitrary access.
As an example, in the following I might want to alias $root.form().budget.budgetEndDate to endDate
<input id="foo" type="text" data-bind="dateTimePicker: $root.form().budget.budgetEndDate" />
Then I could rewrite the binding as
<input id="foo" type="text" data-bind="dateTimePicker: endDate" />

Two options.
First, for demo purposes only but not quite recommended, you ould pollute the global namespace:
function Root() {
this.form = ko.observable(new Form());
}
function Form() {
this.budget = {
budgetEndDate: ko.observable("dummy budget end date")
};
window["endDate"] = this.budget.budgetEndDate;
}
ko.applyBindings(new Root());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div>
endDate outside form: <b data-bind="text: endDate"></b>
</div>
<div data-bind="with: form">
<div>inside form: <b data-bind="text: endDate"></b></div>
<div data-bind="with: budget">
<div>inside form.budget: <b data-bind="text: endDate"></b></div>
</div>
</div>
Second, you could place it on $root:
function Root() {
this.form = ko.observable(new Form());
this.endDate = this.form().budget.budgetEndDate;
}
function Form() {
this.budget = {
budgetEndDate: ko.observable("dummy budget end date")
};
}
ko.applyBindings(new Root());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div>
endDate outside form: <b data-bind="text: $root.endDate"></b>
</div>
<div data-bind="with: form">
<div>inside form: <b data-bind="text: $root.endDate"></b></div>
<div data-bind="with: budget">
<div>inside form.budget: <b data-bind="text: $root.endDate"></b></div>
</div>
</div>
However, on a subjective note, I'd consider this a design smell and/or an XY-problem. The place you bind to budgetEndDate should probably be the right context / view to access it directly. You should consider moving that observable to another part of your view model.

Related

How do I get the value of a control inside of a knockoutjs foreach

I have a knockout foreach section and I am trying to get the values of the generated controls inside of a function bound to a click event for each section. For each section that is generated, when I click on the button I want to know what it's comment is.
//HTML
<div data-bind="foreach: areas">
<button data-bind="click: $parent.saveComment()" />
<input type="text" data-bind="text: comment" />
</div>
//KnockoutJS function
saveComment: function(){
console.log([value of the comment textbox]);
}
Typically for an <input> you'll want to use the value binding. You can pass $data but it is not a requirement as that value will be passed automatically for you.
<div data-bind="foreach: areas">
<button data-bind="click: $parent.saveComment">Save</button>
<input type="text" data-bind="value: comment"></input>
<br/>
</div>
Do not include any () on the saveComment binding and declare the function with a parameter in your model. The function will receive the value that represents the equivalent of $data.
saveComment: function (data) {
console.log(data.comment());
}
Or if you prefer, you can simply use this inside of saveComment to refer to the "current" context.
saveComment: function () {
console.log(this.comment());
}
Check out my example fiddle that contains both usages.
HTML:
<div data-bind="foreach: areas">
<button data-bind="click: $parent.saveComment($data)" />
<input type="text" data-bind="text: comment" />
</div>
Javascript:
saveComment: function(area){
console.log(rea);
}
See:
What is the origin and purpose of the variable $data in KnockoutJS?

Update ViewModel using Custom Bindings KnockoutJS

So I have this SPA developed using this sample.
The sample shows the list of Todo in a table something like this
<section id="lists" data-bind="foreach: todoLists, visible: todoLists().length > 0">
<table width="100%" style="margin-top: 20px;" class="table-main">
<thead>
<tr class="b-table-line">
<th>Select</th>
<th>Title</th>
<th>Artist</th>
</tr>
</thead>
<tbody data-bind="foreach: todos">
<tr>
<td>
<input type="checkbox" data-bind="checked: isDone" /></td>
<td>
<input class="todoItemInput" type="text"
data-bind="value: title,
disable: isDone,
blurOnEnter: true,
updateOnTitle:true,
click: $root.clearErrorMessage" />
</td>
<td>
<input class="todoItemInput" type="text"
data-bind="value: artist,
click: $root.clearErrorMessage" />
</td>
</tr>
</tbody>
</table>
Now what I am trying to do here is as soon as I change the Title text, I try to change Artist text as well, for that I have created a custom binding updateOnTitle and associated it with the textbox as shown in the table. Its definition looks something like this:
ko.bindingHandlers.updateOnTitle = {
init:function(element,valueAccessor,allBindings,viewModel,bindingContext)
{
$(element).blur(function (evt) {
//Here I am trying to update the artist property based on title
bindingContext.$data.title("Title goes here");
bindingContext.$data.artist("New Artist Name Here");
}
}
The changes are not reflected in the table above. Both these properties are observable.
I would like to know what exactly am I missing here?
I may as well turn my comment into an answer. I think binding handlers are better suited as a general approach to solve problems, and it pays off to avoid having a binding rely on a specific ViewModel (your binding refers to "artist" and "title"). A writeable computed observable may be more suited for the task.
Suppose the following view:
<h3>Inputs</h3>
Title: <input type="text" data-bind="value: title, valueUpdate: 'afterkeydown'" /><br />
Artist: <input type="text" data-bind="value: artist, valueUpdate: 'afterkeydown'" />
<hr />
<h3>Read only version</h3>
Title: <span data-bind="text: title"></span><br />
Artist: <span data-bind="text: artist"></span>
Notice the first input is bound to titleEditing, the computed observable. The ViewModel could be defined with these properties:
function ViewModel() {
var self = this;
var _title = ko.observable('my title');
self.title = ko.computed({
read: _title,
write: function(newval) {
_title(newval);
self.artist('New Artist Name Here');
}
});
self.artist = ko.observable('john doe');
};
Now, if you update the first input, title will change and artist will reset.
See this fiddle for a demo.

Knockout.js misterious data binding

I am trying to wireup knockout.js inside some existing application.
Binding works in some mysterious ways. If I use p tag it completelly messes up the binding.
If I use the label tag it also messes up the binding.
For example:
<p data-bind="foreach: currentFields">
<p data-bind="foreach: props">
<span data-bind="text: type"></span>
</p>
</p>
This is not gonna work. If I change to span it works.
But then I have the problem with a label tag. If I delete label the binding works.
It seems autobinding is completely fucked up if I use anything except plain span.
Is there a proper javascript binding library? Is Angular.js any better in this regard?
Because rewriting all the existing HTML including CSS just to please knockout.js is really no fun at all :)
Sample code:
<p>
<span data-bind="foreach: currentFields">
<span data-bind="text: value"></span>
<span data-bind="text: type"></span>
<span data-bind="text: selected"></span>
<span data-bind="text: props"></span>
<span data-bind="foreach: props, visible: selected">
<li>
<label>Labela:</label>
<span>
<span data-bind="text: type"></span>
<input type="text" data-bind="value: type, valueUpdate: 'afterkeydown'" required="required" maxlength="140" ></input>
</span>
</li>
</span>
</span>
</p>
var Property = function(type) {
this.type = ko.observable(type);
}
var Field = function(value) {
this.props = ko.observableArray([new Property("a"), new Property("b")]);
this.type = ko.observable('text');
this.value = ko.observable(value);
this.selected = ko.observable(false);
};
var myViewModel = function () {
var self = this;
this.currentFields = ko.observableArray([new Field("a"), new Field("b")]);
this.currentField = ko.observable();
this.addField = function() {
self.currentFields.push(new Field("xyz"));
};
this.selectField = function(field) {
ko.utils.arrayForEach(self.currentFields(), function(item) {
item.selected(false);
});
field.selected(true);
self.currentField(field);
};
};
$(document).ready(function() {
ko.applyBindings(new myViewModel());
});
Could you havea look at this fiddle and tell what is not working as expected ?
<span data-bind="foreach: currentFields">
<span data-bind="text: value"></span>
<span data-bind="text: type"></span>
<span data-bind="text: selected"></span>
<span data-bind="text: ko.toJSON(props)"></span>
<span data-bind="foreach: props, visible: selected">
<li>
<label>Labela:</label>
<span>
<span data-bind="text: type"></span>
<input type="text" data-bind="value: type, valueUpdate: 'afterkeydown'" required="required" maxlength="140"></input>
</span>
</li>
</span>
</span>
I changed this binding :
<span data-bind="text: props"></span>
into :
<span data-bind="text: ko.toJSON(props)"></span>
If you have invalid HTML, the browser reformats it before Knockout has a chance to parse it. What were nested elements in your original HTML become de-nested in the browser.
The <p> element has specific limitations--it cannot contain other paragraphs or lists or basically any type of block element.
See http://www.w3.org/TR/html5/grouping-content.html#the-p-element

Knockout.js input focus after click

I am trying to set focus on an input with knockout after the click event is fired but couldn't find a clean way to handle it without coupling with the DOM. Here is the JS code I have:
(function() {
var vm = {
text: ko.observable(),
items: ko.observableArray([])
}
vm.addItem = function() {
vm.items.push(vm.text());
vm.text(null);
}
ko.applyBindings(vm);
}());
This is my DOM:
<input type="text" data-bind="value: text" />
Send
<ul data-bind="foreach: items">
<li data-bind="text: $data"></li>
</ul>
Here is the JsFiddle sample: http://jsfiddle.net/srJUa/1/
What I want it to set focus on the input after the vm.addItem is completed. Any idea how this can be done cleanly, for example with a custom knockout binding?
Knockout has a built-in binding for manipulating the focus: The "hasfocus" binding.
So you just need to create a boolean property on your viewmodel and bind it on your input and set the property to true if you want to focus the input.
Or in your case you can binding directly to your text property, so when it is does not have any text it should has the focus:
<input type="text" data-bind="value: text, hasfocus: !text()" />
Demo JSFiddle.
OK, I have solved the issue by leveraging the hasfocus binding:
(function() {
var vm = {
text: ko.observable(),
items: ko.observableArray([]),
isFocused: ko.observable()
}
vm.addItem = function() {
vm.items.push(vm.text());
vm.text(null);
vm.isFocused(true);
}
ko.applyBindings(vm);
}());
HTML:
<input type="text" data-bind="value: text, hasfocus: isFocused" />
Send
<ul data-bind="foreach: items">
<li data-bind="text: $data"></li>
</ul>
Working sample: http://jsfiddle.net/srJUa/2/
Not sure if this is the best way, though.

Knockout.js how do i bind to a sub property

I know how to bind to a property, but how do i bind to a property like:
Parent.Child
Using the hello world example on Knockout JS.com:
Html:
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
<h2>ChildProperty: <span data-bind="text: parentProperty.childProperty"> </span>!</h2>
Javascript:
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.parentProperty = ko.observable(
{
childProperty: "I am a child Property"
});
this.fullName = ko.computed(function() {
// Knockout tracks dependencies automatically. It knows that fullName depends on firstName and lastName, because these get called when evaluating fullName.
return this.firstName() + " " + this.lastName();
}, this);
};
ko.applyBindings(new ViewModel("Planet", "Earth"));
I would like to create a binding to the childProperty.
I created a jsfiddle here
Thanks
So so very close!
You want
<span data-bind="text: parentProperty().childProperty"> </span>
Your updated fiddle http://jsfiddle.net/szk2e/2/
Adding an answer here as this is the best fit to my particular situation...
There is a situation where Tim's answer won't work. This is when the parent property can be undefined.
For example, if you're using the common pattern of itemsSource and selectedItem (where the user selects a single item from a list) selectedItem will be undefined on first evaluation, and whenever the user has undone their selection. Using the binding text:selectedItem().SomeProperty will "break" knockout, preventing bindings from being evaluated. Note, trying to short circuit this using the visible binding (e.g., text:selectedItem().SomeProperty, visible:selectedItem) will NOT work.
In this case, you have to use the with binding to switch the binding context to the value of the property. So, using OP's example...
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2>Hello, <span data-bind="text: fullName"> </span>!</h2>
<h2 data-bind="with:parentProperty">ChildProperty:
<span data-bind="text: childProperty"></span>!
</h2>
Note that the behavior for this binding is (from the docs)
The with binding will dynamically add or remove descendant elements depending on whether the associated value is null/undefined or not
If you also need to hide the container depending on whether the property is undefined or not, then you should use the <!-- ko --> virtual element to surround the container. More information can be found here.

Categories

Resources