Computed observable in knockoutjs doesn't update on change? - javascript

I'm currently studying knockoutjs and I was just following the tutorial on list and collection on the official website of knockoutjs, currently what I have is a dropdown that lists the items I have and then adjacent to it is a text that displays the text(price), now what I want to happen is that the text displayed should change based from the selected item of the dropdown.
Here is a jsfiddle of my code: http://jsfiddle.net/UQskS/
Also, if you notice something wrong with my code aside from what I mention above please do advise me, for best practice or for correction.
// Class to represent a row in the seat reservations grid
function SeatReservation(name, initialMeal) {
//var self = this;
//self.name = ko.observable(name);
this.name = name;
this.meal = ko.observable(initialMeal);
this.formattedPrice = ko.computed(function () {
var price = this.meal().price;
return price ? "$" + price.toFixed(2) : "None";
}, this);
}
function ReservationsViewModel(name, meal) {
//var self = this;
// Non-editable catalog data - would come from the server
this.availableMeals = [
{ mealId: 1, mealName: "Standard (sandwich)", price: 47.55 },
{ mealId: 2, mealName: "Premium (lobster)", price: 34.95 },
{ mealId: 3, mealName: "Ultimate (whole zebra)", price: 290.123 }
];
//editable data
this.seats = ko.observableArray([
new SeatReservation("Randel", this.availableMeals[2]),
new SeatReservation("Knockout", this.availableMeals[1])
]);
//operations
this.addSeat = function () {
this.seats.push(new SeatReservation("", this.availableMeals[0]));
};
this.removeSeat = function (seat) {
this.seats.remove(seat);
;}
}
ko.applyBindings(new ReservationsViewModel());
Sir/Ma'am, your answers would be of great help. Thank you++

The only thing you really need to change from how you currently have it coded is the options binding parameters.
You are using optionsValue and then trying to update the id on the meal property. This is just going to overwrite the mealId property without changing the object it is referring to.
This jsFiddle demonstrates what is going on. Notice that you have to click the refresh link in order to force the UI to update since mealId is not an observable.
To fix this, you just need to bind the value directly to the selected object instead.
<select data-bind="
options: $root.availableMeals,
value: meal,
optionsText: 'mealName',
optionsCaption: 'Choose...'">
</select>
Here is the updated (and working) fiddle: http://jsfiddle.net/jwcarroll/nrHcs
Update:
If you want to use the mealId because you will be pulling these values from the database, then at some point you will have to do a lookup to get the other values. Either you pay the price up front, or when you are saving.
Here is an updated fiddle that shows one way of how you might accomplish this.
http://jsfiddle.net/jwcarroll/YAMS5/

Related

Reloading w2ui's grid.columns.editable.items if the field is a combo box

I'm trying to alter the "items" array attached to a combo box inside an editable field of w2ui's grid after the grid has been initially rendered.
To demonstrate my issue I've set up a jsfiddle taken from the "grid inline editing" demo here:
http://jsfiddle.net/8dkdoc4p/5/
and since some box appeared saying I must include code (why?) here's conceptually what I am trying to do. Note that this won't make too much sense unless you've seen this grid demo: http://w2ui.com/web/demos/#!grid/grid-21
function alterComboBox() {
people.push({ id: myid, text: "ID " + myid});
myid++;
w2ui['grid'].refresh();
}
The idea is to add another item for the combo box at run time and have the grid actually display the new item as another option.
Thanks in advance!
You have to re-assign the global record "people" to the w2ui grid columns after altering the record.
In case of your "select" field, you also have to call the render() method.
http://jsfiddle.net/8dkdoc4p/8/
var myid = 22;
function alterComboBox() {
people.push({ id: myid, text: "ID " + myid});
myid++;
w2ui['grid'].getColumn('list').editable.items = people;
w2ui['grid'].getColumn('combo').editable.items = people;
w2ui['grid'].getColumn('select').editable.items = people;
w2ui['grid'].getColumn('select').render();
//w2ui['grid'].refresh(); // no need!
}

How to use bootstrap-select or any other js control with Aurelia?

Not sure what I am missing here, probably something silly, but I am unable to find anything regarding, let's say how to use bootstrap-select control in Aurelia views. Can someone point me to the right article please?
PS: I am not looking to create another custom control out of bootstrap-select but use as it as.
Request for your help.
https://silviomoreto.github.io/bootstrap-select/
You can create a custom attribute that adds the bootstrap-select behavior to the <select> element. Here's an example:
http://plnkr.co/edit/So23Hm?p=preview
bootstrap-select.js
import {inject} from 'aurelia-framework';
const defaultOptions = {
style: 'btn-info',
size: 4
};
#inject(Element)
export class BootstrapSelectCustomAttribute {
constructor(element) {
this.element = element;
}
attached() {
let options = Object.assign({}, defaultOptions, this.value || {});
$(this.element).selectpicker(options);
}
detached() {
$(this.element).selectpicker('destroy');
}
}
app.html:
<template>
<require from="./bootstrap-select"></require>
<select value.bind="selectedPlanet" bootstrap-select>
<option model.bind="null">Select a planet</option>
<option repeat.for="planet of planets" model.bind="planet">${planet.name}</option>
</select>
</template>
app.js:
export class App {
selectedPlanet = null;
planets = [
{ name: 'Mercury', diameter: 3032 },
{ name: 'Venus', diameter: 7521 },
{ name: 'Earth', diameter: 7926 },
{ name: 'Mars', diameter: 4222 },
{ name: 'Jupiter', diameter: 88846 },
{ name: 'Saturn', diameter: 74898 },
{ name: 'Uranus', diameter: 31763 },
{ name: 'Neptune', diameter: 30778 }];
}
Pass options to the selectpicker call like this:
<select bootstrap-select.bind="{ size: 4 }">
Or like this:
<select bootstrap-select.bind="myOptions"> <!-- assumes there's a myOptions property on your view-model -->
You need to fit it into the Aurelia lifecycle, but other than that, you're free to do as you like.
If you look here you will find a couple of examples of using jQuery plugins in Aurelia. A relatively complex example being the Autocomplete widget. This is a custom control wrapping the jQuery plugin, which I think you don't want to do, but it should still give you an idea of how to implement it.
Extending Jeremy's answer and partially solving the refresh question, you could add something like this.
I couldn't find any legal way to get an event when the source of the repeater changed in a custom attribute. If anyone knows a better/elegant/decent way, please share.
Based on this answer Two way binding not working on bootstrap-select with aurelia, you could add a "task" that queries the length of the select and if the length changes, call the refresh.
With a little variation from the original, I decided to abort the task when the elements length changes the first time. In my case, they will always change only once, when updated after getting them from the database.
bind() {
var _this = this;
var sel = this.element;
var prevLen = sel.options.length || 0;
var addOpt = setInterval(function () {
var len = sel.options.length || 0;
if (len != prevLen || len > 0) {
clearTimeout(addOpt); //abort the task
$(_this.element).selectpicker("refresh");
}
}, 250);
};
As a way of disclaimer, I'm against writing code like this. It wastes resources when there should be a better way to solve this problem.
Finally, I started saying that it partially solved the refresh problem. Since it aborts the execution after the first change (assuming that no more changes are going to happen), if the items change more than once, this task would need to run forever.

Saving user's selection when refreshing the page

I currently have a page that displays data for different teams.
I have some data that the user can click on to make it an "on" or "off" state, showing a different icon for each. It's basically like a checklist, just without the physical checkboxes.
I would like to remember which of the "checkboxes" have been ticked, even after the user refreshes the page or closes the browser and returns later.
I have heard that localStorage is a good option, but I'm not sure how to use it in a situation like mine.
Currently I have this code:
team1 = {
"information1": {
"name": "tom",
"age": "34"
},
"information2": {
"name": "bob",
"age": "20"
},
};
team2 = {
"information1": {
"name": "betsy",
"age": "27"
},
"information2": {
"name": "brian",
"age": "10"
},
};
$(document).ready(function() {
$("#displayObject1").on("click", function() {
switchData(team1);
});
$("#displayObject2").on("click", function() {
switchData(team2);
});
$("#table").on("click", ".on", function() {
$(this).removeClass("on");
$(this).addClass("off");
});
$("#table").on("click", ".off", function() {
$(this).addClass("on");
$(this).removeClass("off");
});
});
function switchData(object) {
$("#table").contents("div").remove();
if (!('rows' in object)) {
var rows = [];
Object.keys(object).forEach(function (key) {
if (key != 'rows') {
rows.push($('<div class="row on">' + object[key].name + '</div>'));
}
});
object.rows = rows;
}
object.rows.forEach(function (row) {
$('#table').append(row);
});
}
This makes rows to represent a team. The rows are retained with their color highlighting when the user looks at different teams during a browser session.
This is my HTML:
<div id="displayObject1">
<span>Display object 1</span>
</div>
<div><hr></div>
<div id="displayObject2">
<span>Display object 2</span>
</div>
<div id="table">
</div>
And some CSS to show which list items are "on" and "off".
.on {
background-color: green;
}
.off {
background-color: red;
}
How can the page remember the color highlighting?
If you want to use local storage to make the state of the team listings persist across browser sessions, you have to maintain a logical representation of the state and save it as a string whenever the state changes.
JSON.stringify lets you encode a JavaScript object in a JSON string. For example, you can call a function like the following whenever you modify a global object named pageState:
function savePageState() {
localStorage.setItem('pageState', JSON.stringify(pageState));
}
To retrieve the page state on page load, you can do something like this:
pageState = JSON.parse(localStorage.getItem('pageState'));
if (pageState === null) {
pageState = {
teams: teams
};
savePageState();
} else {
teams = pageState.teams;
}
If pageState wasn't saved in a previous session, it is now created and saved to local storage. Otherwise, we consult pageState for data that we can use to restore the previous appearance of the team listings.
This code sample works on the assumption that the global object teams contains information relevant to the page state. You can add further properties to the page-state object to store more information. For example, to remember which team is currently displayed, you could do:
pageState.showTeam = teamName;
Then you can consult pageState.showTeam when you're initializing the page contents, perhaps like this:
if (teamName == pageState.showTeam) {
showTeam(teamName);
$(label).addClass('selected');
}
I have made a page that demonstrates this approach. I can't include it in my answer as a snippet because localStorage is sandboxed, but you can access the page here:
http://michaellaszlo.com/so/click-rows/
I've reorganized your team data to enable dynamic page initialization. Now each team object contains an array of person objects.
When the user clicks on a team member's name, the CSS class selected is toggled on the HTML element and the corresponding person object is updated by toggling its selected property:
function memberClick() {
$(this).toggleClass('selected');
this.person.selected = (this.person.selected ? false : true);
savePageState();
};
The showTeam function checks a person's selected property when it's building its HTML representation, and adds the CSS class selected if appropriate. This is what makes it possible to restore the visual appearance of the page from the last session.
you can set a flag in localStorage for each option using:
localStorage.setItem('item1',true) //set the value to the checkbox's value
and access it using:
flagValue = localStorage.item1
use these values similarly in your code once the document is ready to set the initial values of the checkboxes accordingly

Master / Detail Functionality in kogrid

I want to implement Master / Detail Functionality in kogrid. So that when I select any field of first grid table all the other details will be shown in next detail grid.
Can anybody have any hint how to implement this.
A create a master/detail grid set you need to use a ko.computed to select/filter the detail.
You need 3 observable arrays and 1 computed.
var model = {
parent: ko.observableArray(),
details: ko.observableArray(),
selectedParents: ko.observableArray()
};
model.selectedDetails = ko.computed( function() {
var selectedParent = this.selectedParents()[0];
if ( !selectedParent ) {
return [];
}
return this.details().filter( function(item) {
// define filter comparison here
} );
}, model );
In your markup, define 2 koGrids
<div data-bind="koGrid: {
data: parent,
multiSelect: false,
selectedItems: selectParents }"></div>
<div data-bind="koGrid: { data: selectedDetails } "></div>
koGrid will populate the selectedItems observableArray with Zero or One item. The computed will execute a filter over the children which the second grid is bound to.
I have omitted the columnDef data bind part from breivity.
If you have an AJAX query to run for the details array, then use a subscription against the "parent" observable, change selectDetails to a straight observableArray and set its contents in the AJAX callback
See this jsFiddle for a basic example

Display data, one object element at a time in knockout

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.

Categories