I'm trying to figure out why the text entry field is not active when the checkbox changes?
<form data-bind="foreach: editables">
<input type="checkbox" name="edit" data-bind=" checked: active" />
<input type="text" name="edit" data-bind="value: name, disable: !active" />
<br/>
</form>
var viewModel = function () {
this.editables = ko.observableArray(
[{
active: true,
name: "mi"
}, {
active: false,
name: "yo"
}, {
active: true,
name: "cel"
}]);
};
ko.applyBindings(new viewModel());
http://jsfiddle.net/legolito/2FAJN/2/
I hope that someone can helpme. (english isn't my native language, so i'm sorry if something is bad with my grammar )
Have you considered making the active property an observable?
http://jsfiddle.net/tzG3t/
var viewModel = function () {
this.editables = ko.observableArray(
[{
active: ko.observable(true),
name: "mi"
}, {
active: ko.observable(false),
name: "yo"
}, {
active: ko.observable(true),
name: "cel"
}]);
};
ko.applyBindings(new viewModel());
That because you are not using ko.observable in ko.observableArray
See the knockout documentation on observableArrays
Key point: An observableArray tracks which objects are in the array, not the state of those objects
Simply putting an object into an observableArray doesn’t make all of
that object’s properties themselves observable. Of course, you can
make those properties observable if you wish, but that’s an
independent choice. An observableArray just tracks which objects it
holds, and notifies listeners when objects are added or removed.
So make it observable and problem solved.
Fiddle: http://jsfiddle.net/2FAJN/4/
Related
Here is the fiddle:https: https://jsfiddle.net/t5v7fmoq/1/
What I want to achieve:
I want to be able to update checkbox view automatically depending on the recieved state variable (which can have true or false value)
state variables (with initial states) for three checkboxes are:
self.state1 = ko.observable(true);
self.state2 = ko.observable(false);
self.state3 = ko.observable(true);
In the init function I populate observablearray:
self.init = function() {
self.availableItems([
new Item(1, "item1", self.state1(), self.onItemStateChange),
new Item(2, "item2", self.state2(), self.onItemStateChange),
new Item(3, "item3", self.state3(), self.onItemStateChange)
]);
In Item function I set the observable properties and onChnage method:
function Item(id, name, state, onChange) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.state = ko.observable(state);
self.state.subscribe(function(newValue) {
onChange(self, newValue);
});
}
With setTimeout I fake an one-time ajax call, which sets new states:
setTimeout(()=>{
self.state1(false)
self.state2(true)
self.state3(false)
self.availableItems()[0].state(self.state1())
self.availableItems()[1].state(self.state2())
self.availableItems()[2].state(self.state3())
},1000)
But, what I want to achieve, is that I want to avoid typing the following:
self.availableItems()[0].state(self.state1())
self.availableItems()[1].state(self.state2())
self.availableItems()[2].state(self.state3())
I want to code this behaviour and track this statuses using common practice and optimal coding...
I don't have the idea how to approach this problem differently.
I tried using arrays like this (so that later I can use forach and indexing):
setTimeout(()=>{
self.state1(false)
self.state2(true)
self.state3(false)
self.availableItems()[0].state(self.itemStatus()[0])
self.availableItems()[1].state(self.itemStatus()[1])
self.availableItems()[2].state(self.itemStatus()[2])
},1000)
But this does not work as expected.
In Short I would like to learn what coding approach to take to code the behaviour, so that when a new state is recieved from server, the proper state is applied to proper checkbox, and proper checkbox view is updated properly.
General truth: If you create numbered variables (item1, item2, item3), you are doing something wrong. Use arrays.
Depending on how you're getting state updates from the server, the implementation of updateState needs to be changed. My implementation below assumes you're getting an array of Boolean values, e.g. [true, true, false].
It's a good idea to make viewmodels that accept a params object and initialize themselves with it, so that's what the code below does.
function Item(params) {
var self = this;
self.id = ko.observable(params.id);
self.name = ko.observable(params.name);
self.state = ko.observable(params.state);
}
function ItemList(params) {
var self = this;
self.items = ko.observableArray(params.items.map(item => new Item(item)));
self.updateState = function () {
var items = self.items(),
randomStates = items.map(item => Math.random() < 0.5);
randomStates.forEach((state, i) => items[i].state(state));
};
}
var viewModel = new ItemList({
items: [
{id: "item1", name: "Item 1", state: false},
{id: "item2", name: "Item 2", state: false},
{id: "item3", name: "Item 3", state: true},
]
});
ko.applyBindings(viewModel);
.switchName {
font-weight: bold;
}
pre {
position: absolute;
right: 0;
top: 0;
left: 50%;
font-size: smaller;
]
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: items">
<div class="switchBox">
<input type="checkbox" data-bind="checked: state, attr: {id: id}">
<label class="switchName" data-bind="text: name, attr: {for: id}"></label>
</div>
</div>
<button data-bind="click: updateState">Simulate Random Update</button>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
I'm having two form elements, both 2-way-databinded via backbone.stickit.
The second form element (#input) is just cosmetics - there for showing it's actually working.
The idea is that my View gets (re)rendered,every time the option inside the dropdown (#select) menu gets changed.
I'm trying to achieve that by catching the the 'changed' event of #select and call this.render() to (re)render the view.
Apparently that doesn't work. The selected option doesn't get saved back into the model and I fail to understand why.
I'm not looking for a solution, rather than an explanation, why the following code doesn't work. The solution (as in: works for me) is part of the fiddle - commented out.
HTML:
<script type="text/template" id="tpl">
<h1>Hello <%= select %></h1>
<select id="select">
</select>
<p>Select:
<%= select %>
</p>
<hr>
<input type="text" id="input">
<p>Input:
<%= input %>
</p>
</script>
<div id="ctr"></div>
JavaScript:
Foo = Backbone.Model.extend({
defaults: {
select: "",
input: "",
}
});
FooView = Backbone.View.extend({
el: '#ctr',
template: _.template($('#tpl').html()),
initialize() {
this.model.bind('change', function() {
console.log("model change:");
console.log(this.model.get('select'));
console.log(this.model.get('input'));
}, this);
//this.model.bind('change:select', function() { this.render(); }, this); // <--------------------- WORKS
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.stickit();
return this;
},
events: {
'change #select': function(ev) {
console.log('change event triggered:');
console.log(this.model.get('select'));
console.log(this.model.get('input'));
this.render(); // <--------------------- DOES NOT WORK - WHY?
},
/* 'click #render': function(ev) {
console.log('render event triggered:');
console.log(this.model.get('select'));
console.log(this.model.get('input'));
this.render();
} */
},
bindings: {
'#input': 'input',
'#select': {
observe: 'select',
selectOptions: {
collection: function() {
return [{
value: '1',
label: 'Foo'
}, {
value: '2',
label: 'Bar'
}, {
value: '3',
label: 'Blub'
}]
}
}
},
},
});
new FooView({
model: new Foo()
}).render();
https://jsfiddle.net/r7vL9u07/9/
The reason it does not work to call this.render() from within your change #select event handler is because you are disrupting the two-way data binding that Backbone.stickit is providing you. The flow goes something like the following:
User changes the value of '#select'.
Your change #select handler fires and calls this.render().
render repopulates #ctr with a new select menu with no selected option.
Backbone.stickit responds to the change to #select.
Backbone.stickit tries to obtain the value of #select, but since it contains no selected option the value is undefined.
Backbone.sticket sets the model's select attribute to undefined.
The reason it works if you move the this.render() call to within the model's change:select handler is because Backbone.stickit is able to correctly update the model without the DOM changing before it gets the chance.
So I have a Select that has its options from a computed. I want to select a default every time the selects options change.
I have tried several different ways of doing it:
subscribe to list - is called before list has returned so changes the value of the observable alright but it dosnt render right because the list changes AFTER.
afterRender - Does not work with this type of binding.
OptionsafterRender - works, as in the fiddle below, HOWEVER its called for every individual item rather then just once on the whole render so strikes me as the Wrong Way to do this.
var rawData = [{
Type: "1",
Color: "Blue",
Name: "Blue Car"
}, {
Type: "2",
Color: "Blue",
Name: "Blue House"
}, {
Type: "1",
Color: "Red",
Name: "Red Car"
}, {
Type: "2",
Color: "Red",
Name: "Red House"
}];
var viewModel = {
FirstSelectedOption: ko.observable(),
SecondSelectOptions: null,
SecondSelectedOption: ko.observable(),
Load: function() {
var self = viewModel;
self.SecondSelectOptions = ko.computed(function() {
var selected = self.FirstSelectedOption();
var returnValue = new Array({
Type: "*",
Color: "All",
Name: "All"
});
var filteredlist = ko.utils.arrayFilter(rawData, function(item) {
return item.Type == selected;
});
returnValue = returnValue.concat(filteredlist);
return returnValue;
}, self);
self.SecondSelectedOption.SetDefault = function() {
// we want the default to always be blue instead 'all', blue might not be the last option
var self = viewModel;
var defaultoption = ko.utils.arrayFirst(self.SecondSelectOptions(), function(item) {
return item.Color == "Blue";
});
self.SecondSelectedOption(defaultoption);
};
}
};
viewModel.Load();
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="value: FirstSelectedOption">
<option value="1">Car</option>
<option value="2">House</option>
</select>
<br/>
<select data-bind="options: SecondSelectOptions,
optionsText: 'Name',
value: SecondSelectedOption,
optionsAfterRender: SecondSelectedOption.SetDefault"></select>
http://jsfiddle.net/dt627rkp/
The only way I can think off off the top of my head is a custom binding...and im not even sure that would really be possible without reimplemnting the entire options binding.
I can't be the first one to want this, is there a best practice/way that I'm missing?
The optionsAfterRender callback passes 2 parameters: option (element), and item (data bound to the option). The callback already loops over the options, so no need to reiterate:
self.SecondSelectedOption.SetDefault = function (option, item) {
var self = viewModel;
if (item.Color === 'Blue')
self.SecondSelectedOption(item);
};
Updated fiddle
Ref: from the docs
EDIT: That being said, if you don't want the options to re-evaluate every time,
you could also simply bind the change event with the setDefault method on the first <select>. If I were faced with this code 'issue', I would probably preprocess the data into separate arrays, like in this fiddle
I have an Ember component that takes in an array as it's content and for each item renders a checkbox if it's an object or a new nested instance if it's an array.
The component needs to be able to bind data both to the first set of items in the root array and all the nested items. I though of using computed properties and observers but as Ember states You cannot use nested forms like todos.#each.owner.name or todos.#each.owner.#each.name.
Any idea how this can be achieved?
Component:
<dl class="selection-list">
{{#each content}}
<dd>
{{#isArray this}}
{{selection-list content=this}}
{{else}}
<label class="checkbox">
{{input type="checkbox" checked=selected disabled=disabled}}
{{label}}
</label>
{{#isArray children}}
{{selection-list content=children}}
{{/isArray}}
{{/isArray}}
</dd>
{{/each}}
</dl>
Model:
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return {
items: [
{
label: '1'
},
{
label: '2',
children: [
{
label: '2.1'
},
{
label: '2.2',
children: [
{
label: '2.2.1'
},
{
label: '2.2.2'
},
{
label: '2.2.3'
}
]
}
]
},
{
label: '3'
},
{
label: '4'
}
]
};
}
});
Use of binding:
App.ApplicationController = Ember.ObjectController.extend({
selectionCount: function () {
return this.get('items').filterBy('selected').length;
}.property('items.#each.selected')
});
Example:
http://jsbin.com/yipuw/1/edit?html,js,output
I solved this using views. The key points are:
Changed the component to a view. We want to keep the same context for N levels of nesting.
Used Ember.Checkbox and handled the change event in there so we can modify selectCount as clicks happen instead of iterating through the list of checkboxes every time one of them changes.
Check it out: http://jsbin.com/nuzex/1/edit?html,js,console,output
I really don't feel like we've arrived at 'the' solution for the nested array binding issue as of yet. I've extended your bins with my own current working solution. The key to this is a pseudo-property whose only mission in life is to notify parents that something changed somewhere down the family tree.
http://jsbin.com/yivif/2/edit?html,js,output
Actually, here's a slightly tighter version
http://jsbin.com/yivif/10/edit?html,js,output
There is an example in documetation. The code is here.
The documentation talks that isCompleted controller's computed property will be called with argument wich value is either true or false depends on checked input property's value.
How does controller automatically knows that is checked property of input is changed? I mean it's so unobvious that controller's cumputed property will be called when checked input property's state changes. How it works? Where the documentation describes this behavior?
Big thanks.
The controller doesn't know, the input helper binds the checkbox to the passed in argument checked.
{{input type="checkbox" checked=isCompleted class="toggle"}}
http://emberjs.com/guides/templates/input-helpers/
http://emberjs.com/api/classes/Ember.Checkbox.html
Ember.Checkbox = Ember.View.extend({
classNames: ['ember-checkbox'],
tagName: 'input',
attributeBindings: ['type', 'checked', 'indeterminate', 'disabled', 'tabindex', 'name'],
type: "checkbox",
checked: false,
disabled: false,
indeterminate: false,
init: function() {
this._super();
this.on("change", this, this._updateElementValue);
},
didInsertElement: function() {
this._super();
this.get('element').indeterminate = !!this.get('indeterminate');
},
_updateElementValue: function() {
set(this, 'checked', this.$().prop('checked'));
}
});