In a basic table structure, I want to be able to display a set of data from an array of objects one at a time. Clicking on a button or something similar would display the next object in the array.
The trick is, I don't want to use the visible tag and just hide the extra data.
simply you can just specify property that indicate the current element you want to display and index of that element inside your observableArray .. i have made simple demo check it out.
<div id="persons"> <span data-bind="text: selectedPerson().name"></span>
<br/>
<button data-bind="click: showNext" id="btnShowNext">Show Next</button>
<br/>
</div>
//here is the JS code
function ViewModel() {
people = ko.observableArray([{
name: "Bungle"
}, {
name: "George"
}, {
name: "Zippy"
}]);
showNext = function (person) {
selectedIndex(selectedIndex() + 1);
};
selectedIndex = ko.observable(0);
selectedPerson = ko.computed(function () {
return people()[selectedIndex()];
});
}
ko.applyBindings(new ViewModel());
kindly check this jsfiddle
Create observable property for a single object, then when clicking next just set that property to other object and UI will be updated.
Related
This is a follow-up question to this one:
As explained in the above-linked answer:
When you provide an expression for a binding value rather than just a
reference to an observable, KO effectively wraps that expression in a
computed when applying the bindings.
Thus, I expected that when providing the changeCity as a binding expression (it is a function and not an observable), then changing the value on the input box would fire the changeCity function.
However, as you can see on the first snippet, it doesn't (Nor when binding it as changeCity()), but If changeCity is declared as a ko.computed, it does fire - see the second snippet.
Does it mean that a bounded function and a bounded computed are not completely the same with regard to dependency tracking?
First snippet - bounded function:
var handlerVM = function () {
var self = this;
self.city = ko.observable("London");
self.country = ko.observable("England");
self.changeCity = function () {
if (self.country() == "England") {
self.city("London");
} else {
self.city("NYC");
}
}
}
ko.applyBindings(new handlerVM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind="text: city"> </h1>
<span data-bind="text: 'change the country, get out of focus, and nothing will happen...'"></span>
<br/>
<input data-bind="value: country" />
Second snippet - bounded computed:
var handlerVM = function () {
var self = this;
self.city = ko.observable("London");
self.country = ko.observable("England");
self.changeCity = ko.computed(function () {
if (self.country() == "England") {
self.city("London");
} else {
self.city("NYC")
}
});
}
ko.applyBindings(new handlerVM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind="text: city"> </h1>
<span data-bind="text: 'change the country, get out of focus, and behold:'"> </span>
<br/>
<input data-bind="value: country" />
I take it that you're not just trying to solve a practical problem, but that you're mostly interested in the "theoretical difference" between passing a computed or a plain function to a binding. I'll try to explain the differences/similarities.
Let's start with an example
const someObs = ko.observable(10);
const someFn = () => someObs() + 1;
const someComp = ko.computed(someFn);
const dec = () => someObs(someObs() - 1);
ko.applyBindings({ someObs, someFn, someComp, dec });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div>Obs. value: <span data-bind="text: someObs"></span></div>
<div>Computed created in binding: <span data-bind="text: someFn()"></span></div>
<div>Computed created in vm: <span data-bind="text: someComp"></span></div>
<button data-bind="click: dec">-1</button>
The example above shows that both someFn and someComp do the same thing. By referencing someFn() in a binding handler's value, you've essentially created a computed with a dependency to someObs.
Why this doesn't work in your first example
You never referenced your changeCity method in any knockout related code, which means there'll never be the chance to create a dependency. Of course, you can force one, but it's kind of weird:
var handlerVM = function () {
var self = this;
self.city = ko.observable("London");
self.country = ko.observable("England");
self.changeCity = function () {
if (self.country() == "England") {
self.city("London");
} else {
self.city("NYC");
}
}
}
ko.applyBindings(new handlerVM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind="text: city"> </h1>
<span data-bind="html: 'change the country, get out of focus, and <strike>nothing</strike> <strong>something</strong> will happen...'"></span>
<br/>
<input data-bind="value: (changeCity(), country)" />
Why a regular computed does work
In your second example, you use a ko.computed. Upon instantiating a ko.computed, the passed function is evaluated once (immediately) and dependencies to all used observables are created.
If you were to change the ko.computed to a ko.pureComputed, you'll see your second example will also stop working. A pureComputed only evaluates once its return value is actually used and won't create dependencies until then.
The internals
Knockout wraps your binding's value in a function as a string. You can read more about this in an answer I wrote earlier.
We also know that any observable that is called inside a binding-handler's init method, creates a dependency that calls the binding's update method when a change happens.
So, in the example I gave, this is what happens:
The text binding is parsed
The function function() { return someFn(); } is passed as a value accessor to the text binding's init method.
The value accessor is called to initialize the text field
someObs is asked for its value and a dependency is created
The correct value is rendered to the DOM
Then, upon pressing the button and changing someObs:
someObs is changed, triggering the text binding's update method
The update method calls the valueAccessor, re-evaluating someObs and correctly updating its text.
Practical advice
To wrap up, some practical advice:
Use a ko.pureComputed when you create a new value out of one or more observable values. (your example)
self.city = ko.pureComputed(
() => self.country() === "england"
? "london"
: "nyc"
);
Use a subscribe if you want to create side effects based on an observable value changing. E.g.: a console.log of a new value or a reset of a timer.
Use a ko.computed when you want to create side effects based on a change in any of several observables.
the expected behavior in both snippets is that once the text in the input box is changed (and the focus is out), changeCity is fired (Happens on the 2nd, not on the 1st).
Ahhh, now I understand. You are describing what a subscription does.
First off, rid your mind of DOM events. The <input> field does not exist. All there is is your viewmodel. (*)
With this mind-set it's clear what to do: React to changes in your country property, via .subscribe(). The following does what you have in mind.
var handlerVM = function () {
var self = this;
self.city = ko.observable("London");
self.country = ko.observable("England");
self.country.subscribe(function (newValue) {
switch (newValue.toLowerCase()) {
case "england":
self.city("London");
break;
case "usa":
self.city("NYC");
break;
default:
self.city("(unknown)");
}
});
}
ko.applyBindings(new handlerVM());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<h3 data-bind="text: city"></h3>
<input data-bind="value: country" />
(*) Of course the <input> field still exists. But it helps to imagine the view (your HTML) as 100% dependent on your viewmodel. Knockout does all the viewmodel-view interaction for you. It takes care of displaying changes in the viewmodel data, and it takes care of feeding back user interactions into your viewmodel. All you should pay attention to is changes in your viewmodel.
Whenever you feel that you need to listen to a basic DOM event like "click", chances are that you are doing something wrong, i.e. chances are you are missing an observable, or a custom binding.
I have (n) check boxes and a button in my angular2 view . When I click on one of them a function is called. When I click on the button every checkbox must be unchecked. How to do it?? (n) may vary dynamically.
enter image description here
I will give you an example from a table, since I have no idea what your code actually looks like, but it should work for what you need.
You need some object defined for all of your checkboxes. They likely all have certain properties in common, like labels. Here is an example of such an object:
myData = {
content: [
{
some_string: '',
some_number: 0,
type: '',
selected: false
}
]
};
With this object you can create checkbox instances and push each one to an array, which will hold all of your checkbox objects.
Create your checkboxes in your html in a loop using the objects you have defined above. In your html have your checkboxes call a function. In the case below the checkToggle() function is called.
<input id='{{row.id}}' class='bx--checkbox bx--checkbox--svg'
type='checkbox' name='checkbox' (change)="checkToggle($event,
row.id)" [checked]="row.selected">
checkToggle() has been defined as follows:
//select or deselect this check box
checkToggle(event, nodeId) {
const id = this.findNode(nodeId);
this.myData.content[id].selected = !this.myData[id].selected;
}
Your button should end up calling a function to check all of the boxes
<button (click)="checkToggleAll($event)">Your Button Title</button>
Finally, have your checkToggleAll() function go through the entire array of checkboxes and set them. Here is an example:
//select or deselect all the check boxes
checkToggleAll(event) {
for (let i = 0; i < this.myData.content.length; i++) {
if (this.controls[this.myData.content[i].type]) {
this.myData.content[i].selected = event.target.checked;
}
}
}
This is not something you can plug into your code but it should give you some idea of how to accomplish what you're after.
I would like to re-use an "object", however, one of the object's properties values should be recalculated every time the object is accessed.
In my code I have a library which can basically make a list of card views from a data url. This list of card views is added to a page. There are two types of lists: Active Buildings list and Archived Buildings list. Switching between these two lists is done by pressing a button, which triggers the "rerender" function of the repeater shown below.
Archived Buildings should not be clickable. I pass along some configuration options to my library where I handle the relevant parts. However, because of the way I invoke the card view library, the value of the enableClick configuration option is always set to what the state was like at the load of the page.
Example of how the code looks:
$(function () {
var buildingsContainer = $('#buildings');
buildingsContainer.repeater({
url: function () {
var activeFilter = buildingFilter.find('.btn-primary').data('status');
return '/Building/All?status=' + activeFilter;
},
renderItem: cardTemplates(buildingsContainer).building({
activateBuildingUrl: '#(Url.Action("ActivateBuilding", "Building"))/{Id}',
editUrl: '#(Url.Action("Edit", "Building"))/{Id}',
deleteBuildingUrl: '#(Url.Action("SoftDeleteBuilding", "Building"))/{Id}',
enableClick: getActiveFilter() === 'Active'
})
})
});
function getActiveFilter() {
var buildingFilter = $('#buildingFilter');
return buildingFilter.find('.btn-primary').data('status');
}
No matter what the currently pressed button is, enableClick is always set to what it was when the page opened.
To better demonstrate my problem, I have created a JSFiddle:
https://jsfiddle.net/e3xnbxov/
In this JSFiddle, you see I have a options object with a value property. In the button's click listeners I print this value. However, it always remains on Active, even though I switch between Active and Archived. How can I make it so the value of the property is recalculated?
I think you have 2 options here.
1) Set the property as a function, and evaluate it:
$(function() {
var options = {
value: ()=>$('#container').find('.btn-primary').data('status')
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value());
});
});
jsfiddle: https://jsfiddle.net/mw8kuq6L/
2) Just use "this" to directly access the data value you want to check:
$(function() {
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log($(this).data('status'));
});
});
The problem is that the object (options) is created once, and the property is set once.
At the moment that the creation (and property setting) occurs, the 'active' button matches the jQuery selector ($('#container').find('.btn-primary')).
Javascript, like many languages, uses references. When you set the object's property, it received a reference to the result of the jQuery selector, not the selector (as a method) itself.
You could change it to behave more as you're expecting by creating a method on your object:
$(function() {
var options = {
value: function () {
return $('#container').find('.btn-primary').data('status')
}
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value());
});
});
Thus your options object now has a callable method which dynamically returns what you were expecting.
Otherwise I'd update the property when the selected button changes:
$(function() {
var options = {
value: $('#container').find('.btn-primary').data('status')
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
options.value = $('#container').find('.btn-primary').data('status');
console.log(options.value);
});
});
This is just meant to be an addition to lpg's answer.
Another way would be to use a getter function which behaves like lpg's value function but can be used like a normal property:
$(function() {
var options = {
// define a getter for the property 'value'
get value () {
return $('#container').find('.btn-primary').data('status');
}
};
var container = $('#container');
container.find('.btn').click(function() {
container.find('.btn').removeClass('btn-primary').addClass('btn-default');
$(this).addClass('btn-primary');
console.log(options.value); // use the property for the property 'value'
});
});
<link href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<div id="container">
<button class="btn btn-sm btn-primary" data-status="Active">Active</button>
<button class="btn btn-sm btn-default" data-status="Archived">Archived</button>
</div>
I'm using Rivets.js to draw some components within an rv-each-* block
They all render fine when the page loads.
When I Splice the first element of the model, the model correctly removes the first element but the rv-each-* block removes the last element.
The component is just made up of a button that contains the text of the objects value it is bound to. Next to each component call I print some text that is also bound to each objects value. The two should be the same, but after the Splice they are not.
var model2 = [{ "value":"1"},{"value":"2"},{"value":"3"}];
rivets.components['control'] = {
template: function() {
return '<button>{data.option.value}</button>';
},
initialize: function(el, attributes) {
return new controlviewmodel(attributes);
}
};
function doSplice()
{
model2.splice(0,1);
}
<table id="view">
<tr rv-each-option="model">
<td>
<control option="option"/>
</td>
<td>{option.value}</td>
</tr>
</table>
I've created a fiddle to try and show my problem: http://jsfiddle.net/dko9b9k4/
Am I doing something wrong or is this a bug?
Edit: this example can be fixed by changing
return new controlviewmodel(attributes);
to return attributes;
I have some problems with nested view models in knockout using the mapping plugin. I'm able to recreate the problem, and I have created a fiddle for it here: Fiddle
I have stripped down the actual view and viewmodel, so don't expect the output to look nice, but it will get the message accros. This is my view:
<div data-bind="foreach: $root.selectedArmy().Units">
<div class="unitoverview">
<!-- ko foreach: UnitMembers-->
<div class="member">
<div>
<span class="name" data-bind="text: Name, click: $parent.RemoveTest"></span>
</div>
<div data-bind="foreach: test">
<span data-bind="text:$data, click: $parent.RemoveTest"></span>
</div>
<h1 data-bind="text: test2"></h1>
</div>
<!-- /ko -->
</div>
</div>
<span data-bind="click:AddUnit">CLICK TO ADD UNIT</span>
And this is my model:
var armymaker = armymaker || {};
var unitMapping = {
'UnitMembers': {
create: function (options) {
return new UnitMemberViewModel(options.data);
}
}
};
var UnitViewModel = function (unit) {
var self = this;
self.Name = ko.observable("unitname");
self.UnitDefinitionId = ko.observable(unit.Id);
ko.mapping.fromJS(unit, {}, self);
};
var UnitMemberViewModel = function (unitmemberdefinition) {
var self = this;
self.test = ko.observableArray([ko.observable('TEST'), ko.observable('TEST2')]);
self.test2 = ko.observable('TEST1');
self.RemoveTest = function () {
self.test.splice(0,1);
self.Name('BUGFACE');
self.test2('OKI!!');
};
ko.mapping.fromJS(unitmemberdefinition, {}, self);
};
var ViewModel = function () {
var self = this;
self.showLoader = ko.observable(false);
self.newArmy = ko.observable({});
self.unitToAdd = ko.observable(null);
self.selectedArmy = ko.observable({ Template: ko.observable(''), Units: ko.observableArray() });
self.AddUnit = function () {
var data = {'Name': 'My name', 'UnitMembers': [
{ 'Name': 'Unitname1' }
] };
self.unitToAdd(new UnitViewModel((ko.mapping.fromJS(data, unitMapping))));
self.selectedArmy().Units.push(self.unitToAdd());
self.unitToAdd(null);
};
};
armymaker.viewmodel = new ViewModel();
ko.applyBindings(armymaker.viewmodel);
What happens is the following:
I click the link CLICK TO ADD UNIT, and that created a UnitViewModel, and for each element in the UnitMember array it will use the UnitMemberViewModel because of the custom binder (unitMapper) that I am using.
This all seems to work fine. However in the innermost view model, I add some field to the datamodel. I have called them test that is an observableArray, and test2 that is an ordinary observable. I have also created a method called RemoveTest that is bound in the view to both the span that represent test2, and the span in the foreach that represent each element of the array test.
However when I invoke the method, the change to the observable is reflected in the view, but no changes to the observableArray is visible in the view. Check the fiddle for details.
Are there any reasons why changes to an obsArray will not be visible in the view, but changes to an ordinary observable will?
I have made some observations:
The click event on the observable does not work, only the click event on the elements on the observableArray.
It seems that self inside the click event does not match the actual viewmodel. If I go self.test.splice(0,1) nothing happens in the view, but self.test.splice only contains one element after that command. However if I traverse the base viewmodel (armymaker.viewmodel.Units()[0].UnitMembers()[0].test) is still contains two elements.
Calling splice on the traversed viewmodel (armymaker.viewmodel.Units()[0].UnitMembers()[0].test.splice(0,1)) removes the element from the view, so it seems in some way that the element referenced by self is not the same object as what is linked inside the view. But then, why does it work for the observable that is not an array?
There is probably a flaw with my model, but I can't see it so I would appreciate some help here.
You are basically "double mapping".
First with
self.unitToAdd(new UnitViewModel((ko.mapping.fromJS(data, unitMapping))));
and the second time inside the UnitViewModel:
ko.mapping.fromJS(unit, {}, self);
where the unit is already an ko.mapping created complete "UnitViewModel", this double mapping leads to all of your problems.
To fix it you just need to remove the first mapping:
self.unitToAdd(new UnitViewModel(data));
self.selectedArmy().Units.push(self.unitToAdd());
self.unitToAdd(null);
and use the mapping option inside the UnitViewModel:
var UnitViewModel = function (unit) {
var self = this;
self.Name = ko.observable("unitname");
self.UnitDefinitionId = ko.observable(unit.Id);
ko.mapping.fromJS(unit, unitMapping, self);
};
Demo JSFiddle.
SideNote to fix the "The click event on the observable does not work" problem you just need to remove the $parent:
<span class="name" data-bind="text: Name, click: RemoveTest"></span>
because you are already in the context of one UnitMemberViewModel.