Multiple Select List and KnockoutJS - javascript

I have a multi-select list that I've implemented following the instructions on the KO site. The important portions of my code currently look like this (removed unnecessary code):
function Attribute(data) {
var self = this;
self.Id = data.Id;
self.Name = data.Name;
}
// Individual Row in Table
function Criteria(data) {
var self = this;
self.Attributes = data.Attributes;
}
// Represent the ViewModel for attributes.
function CriteriaViewModel() {
var self = this;
// Catalog Data
self.availableAttributes = window.ko.observableArray([]);
$.getJSON(window.attributeListUrl, function(availableData) {
self.availableAttributes($.map(availableData.Attributes, function(item) { return new Attribute(item); }));
});
// Editable Data
self.criterion = window.ko.observableArray([]);
// Load initial state from server
$.getJSON(window.criteriaListUrl, function (availableData) {
self.criterion($.map(availableData.Criterion, function (item) { return new Criteria(item); }));
});
}
Then, in my HTML, I bind it all together (or, I at least try to):
<tbody data-bind="foreach: criterion">
<tr>
<td>
<select class="selectedAttributes"
data-bind="options: $root.availableAttributes, selectedOptions: Attributes, optionsText: 'Name', optionsValue: 'Id'"
multiple
size="6">
</select>
</td>
</tr>
</tbody>
The possible options display correctly. However, there is no apparent binding between the criteria's attributes against the possible options. From reading the guide, it seems as though KO should be able to bind objects directly. Can anybody provide guidance here?
I forgot to mention that everything works except the actual binding of the multi-select list. I am applying my bindings appropriately in general - just not with the multi-select list.

The attributes property on the Criteria object needs to be an observableArray. Here is a Jsfiddle demonstrating
function Criteria(data) {
var self = this;
self.Attributes = ko.observableArray(data.Attributes);
}

var x= $('#select1 option:selected');
if(x.length>0){
x.each(function(){
alert($(this).text());
self.selectedCategory.push(new categoryModel($(this).text()));
$('#select1 option:selected').remove();
});
}
refer http://jsfiddle.net/deepakpandey1234/wse4gdLq/

Related

How to programmatically select ng-option value?

I have a view that is filled with dropdownlists to filter a report. I also have a view of saved filters that are displayed as links. When a user clicks on their saved filters, I want the appropriate values of the dropdownlists to be selected. The drop downs are being populated properly. On the saved filter link there is an ng-click that will call a function that iterates through the collection of saved filter values and automatically selects the correct one. I cannot figure out how to programmatically set the selected option. Any help is much appreciated!
<select uid="locSelect"
class="span12"
ng-model="reportDetail.selectedLoc"
ng-options="loc.dbid as loc.serviceName for loc in reportDetail.locList | orderBy:'name'">
<option uid="unselectedLocOption" value="">-- Select One --</option>
</select>
Here is the list of saved filters:
<div class=" well fixed-search" style="overflow-x: hidden;overflow-y: auto;">
<div class="well-header">
Saved Filters
</div>
<div ng-if="!hasSavedFilters">
<span>No saved filters</span>
</div>
<ul ng-if="hasSavedFilters" class="nav nav-list dashboard-list">
<li ng-repeat="filter in reportDetail.savedFilters">
<a uid="savedFilter" href="" ng-click="reportDetail.loadSavedFilters(filter.filters)">
<span ng-bind="filter.title"></span>
</a>
</li>
</ul>
And here is my controller
(function(){
'use strict';
var ReportDetailController = function(ReportsService, $scope){
var _locList = {};
var _hospitalStatusList = {};
var _providerStatusList = {};
var _savedFilters = [];
var _sourceTypeList = {};
var _dateRangeList = {};
var _init = function(){
ReportsService.getCurrentReportSavedFilters().then(function(data){
$scope.reportDetail.savedFilters =data;
$scope.hasSavedFilters = ReportsService.hasSavedFilters();
});
ReportsService.getLOCListForDDL().then(function(data){
$scope.reportDetail.locList = data;
//$scope.reportDetail.selectedLoc = $scope.reportDetail.locList[0];
});
ReportsService.getSelectListData()
.then(function(data){
$scope.reportDetail.sourceTypeList = data.CONNECTION_TARGET_STATUS;
$scope.reportDetail.hospitalStatusList = data.CONNECTION_SOURCE_STATUS;
});
ReportsService.getDateRangesForDDL()
.then(function(data){
$scope.reportDetail.dateRangeList = data;
});
$scope.reportDetail.providerStatusList = ReportsService.getProviderStatusForDDL();
};
var _loadSavedFilters = function(filters){
for(var i = 0, l = $scope.reportDetail.locList.length; i<l; i++){
if($scope.reportDetail.locList[i].serviceName == filters.levelOfCare){
$scope.reportDetail.selectedLoc = $scope.reportDetail.locList[i];
console.log($scope.reportDetail.selectedLoc);
}
}
}
var _isActive = function(filter){
for(var i = 0, l = $scope.reportDetail.savedFilters.length; i<l; i++){
if(filter.title == $scope.reportDetail.savedFilters[i].title){
return true;
}
return false;
}
}
var _generateReport = function(){
return ReportsService.generateReport();
};
$scope.reportDetail = {
init: _init,
selectedLoc: null,
isActive: _isActive,
locList: _locList,
selectedHospitalStatus: 'NOTIFIED',
hospitalStatusList: _hospitalStatusList,
selectedProviderStatus: 'NEW',
providerStatusList: _providerStatusList,
selectedSourceType: 'CONNECTED',
sourceTypeList: _sourceTypeList,
selectedDateRange: '',
dateRangeList: _dateRangeList,
savedFilters: _savedFilters,
loadSavedFilters: _loadSavedFilters,
generateReport: _generateReport
};
$scope.reportDetail.init();
};
app.controller('ReportDetailController', ['ReportsService', '$scope', ReportDetailController]);
})();
You just need to set the ng-model to whatever it should be, so in this case you would set reportDetail.selectedLoc to whatever loc.dbid it should be.
For example: http://jsfiddle.net/uWLua/1/
Note: Make sure they have the same type, so in your example make sure they are either both integers, or both strings, it will not know they are the same if you have one as 5073 and one as "5073"
I updated the fiddle to show that the string and number do not do the same thing.
The ng-model and the expression feeding ng-options -must- match in order for Angular to compare values and see what option is 'selected'. Just as 'dave' indicated.
Due to time constraints I ended up going a different route. I created an event bus of sorts in my service layer and subscribe to the even in my controller, updating the model, and used ng-repeat with ng-selected.
I'm still interested to understand why this was not working with ng-options. The model and ng-options types matched, and everything appeared to be wired up correctly. When I have more time i'll re-address the original issue. Thanks for all who responded!
You need custom directive, or something similar to this two approaches
<div ng-controller="MyCtrl">
<h1>Approach 1</h1>
<span ng-repeat="val in dbs">
<input type="checkbox" ng-model="val.checked">{{val.name}}
</span>
<hr/>
<h1>Approach 1</h1>
<select multiple>
<option ng-repeat="val in dbs" name="val.name" value="val.name" ng-selected="val.checked">{{val.name}}</option>
</select>
<h4>Source (note scope changes)</h4>
{{dbs}}
</div>
also you can use ng-change to do some complex ops
If I understand, in summary, you have a select filled with a list, and you want to programmatically set one of those to be selected, type it as the default right?
If so, you can easily solve this with ng-options, just associate your controller instance with scope and assign the position of the list you want to the model of select, for example:
Select HTML
<select ng-model="vm.aluno_id" name="aluno_id" ng-options="aluno.nome for aluno in alunos">
Controller
app.controller("auxiliarController", function( $scope){
//instancia controller;(Controller instance;)
var vm = this;
$scope.vm = vm;
//carregando lista no scope, que serĂ¡ utilizado pelo angular no select
//Loading list in scope, which will be used by angular in select
$scope.alunos = [{id: 1, nome: "aa"}, {id: 2, nome: "bb"}];
//setando item default no select
$scope.vm.aluno_id = $scope.alunos[0];
});
I hope I have helped

Options Binding overrides initial View Model value

I tried to follow the other similar questions on Stack Overflow but thus far have been unsuccessful in fixing the problem.
I am using jQuery AJAX to retrieve several items: a contact and its associated information, all available salutation types, all available email types and all available phone types.
I have successfully bound the options to the select boxes. However, it appears to overwrite the 'value' binding that holds the initial view model value.
Could any of you help me solve this? Please let me know if you have any questions for clarification.
Please see the code below:
View Model:
function contactPageViewModel() {
var self = this;
self.contact = ko.observable();
self.availableSalutations = ko.observableArray();
self.availableEmailTypes = ko.observableArray();
self.availablePhoneTypes = ko.observableArray();
self.availableAddressTypes = ko.observableArray();
}
where contact is an object coming from the server, which includes the element contact.salutation.
The json coming back for contact is:
{
//...
"createdBy":null,
"createdOn":1392848929000,
"updatedBy":null,
"updatedOn":1392848929000,
"contactId":305,
"salutation":{"salutationId":102,"salutation":"Mrs."},
"firstName":"Laura",
"middleInitial":"K",
"lastName":"Ritchey"
//...
}
the json coming back from availableSalutations (which is a property of a json object wrapper 'listObject') is:
[{"salutationId":41,"salutation":"Ms."},
{"salutationId":101,"salutation":"Mr."},
{"salutationId":66,"salutation":"CDR"},
{"salutationId":81,"salutation":"LCDR"},
{"salutationId":102,"salutation":"Mrs."},
{"salutationId":121,"salutation":"Mr."},
{"salutationId":64,"salutation":"LTC"}]
The code to map the JSON result to the knockout observables:
contactPageViewModel.contact = ko.mapping.fromJS(data.listObject[0]);
contactPageViewModel.availableEmailTypes = ko.mapping
.fromJS(data.listObject[1]);
contactPageViewModel.availableSalutations = ko.mapping
.fromJS(data.listObject[2]);
....
applyBindings();
The HTML:
<label for="rank"> Rank / Title: </label>
<select data-bind="optionsText: 'salutation',
options: availableSalutations,
value: contactPageViewModel.contact.salutation"
class="rankList"
name="Rank"
id="rankSelect">
</select>
Try value: $root.contact().salutation instead of value: contactPageViewModel.contact.salutation.
Or:
<label for="rank"> Rank / Title: </label>
<!-- ko with: contact -->
<select data-bind="options: $root.availableSalutations, optionsText: 'salutation', value: salutation" class="rankList" name="Rank" id="rankSelect">
</select>
<!-- /ko -->
Update:
You could look at this Fiddle. May be it contains a lot of excess code and you can simplify it, but the main things is to separate initial and selected salutations and add optionsCaption to select bindings:
var initialSalutation = new salutationViewModel(data.salutation);
And:
self.salutation = ko.observable();
self.displayedSalutation = ko.computed(function () {
if (self.salutation()) {
return self.salutation();
} else {
return initialSalutation;
}
})
Update 2:
Look at this Fiddle. I've added optionsValue: 'salutationId' to select bindings and move displayedSalutation to contactPageViewModel.
I think problem was with matching objects (select item and salutation from contact). When value of select is salutationId and contact salutation also salutationId (number value, not object) all working good.

Knockout - strategies to get and set selectbox values in templates, when sharing an observableArray?

I have just started experimenting with JavaScript, JQuery and Knockout.js (I'm normally working on the server-side in Java) and it's beyond brilliant - however I have hit a wall, and I'm extremely doubtful that I'm doing, whatever I'm trying to do, 'by the book'.
I have created a fiddle for it here: http://jsfiddle.net/kcyhw/
My mission:
I need to create a template that can be re-used all over the website. Fx. a template that contains the possibility to create, edit and delete users - which would make it easy to create users in a "User configuration"-window, or as a part of a wizard. Either way, the logic controlling everything should be the same. However, besides sharing the same arrayObservable for data, the selection choices should of course not observe each other. Right now, it's entirely a select box.
I'm using JQuery.serialize to convert an entire form to key-value, to be sent to a server, so it's important that I not only get the value, but also have it "saved" in the value attribute on the select box.
My problem:
I simply can't figure out how Knockout.js and the select box is connected. All the objects are displayed fine by their respective values (id and fullname), both in the select box, and in the properties section.
When using serialize with jQuery it just prints: "perselect="... so it doesn't get a value.
I tried the following:
Using optionValue in the data-bind - it works, and it binds to the "value", however, I can see that it "takes over" my binding, and kills the text I retrieve from the object. I removed it, and continued...
Computed values, however it didn't work out since the template wants (after my knowledge) a literal object, and functions can't reference other properties in such an object.
Created my own binding, so I could get a reference to both the element (the select box) and all the other binding values. In this function I tried, using jQuery, to set the attribute 'value' on the element which was passed in, however, it doesn't work.
I can also see, that the binding gets called 4 times (that probably because it calls init and then update for each template I created which contains the select box).
To me, it looks like I have created a friggin' mess, and I would really appreciate if some smart people could point me into the right direction of how to solve this. Resources, code-snippets, advice... whatever you got.
The code:
<html>
<head>
<script src="javascript/jquery-1.10.2/jquery-1.10.2.js"></script>
<script src="javascript/knockout-2.3.0/knockout-2.3.0.js"></script>
<script src="javascript/knockout.mapping-master-2.0/knockout.mapping-latest.js"></script>
<script type="text/javascript" src="javascript/json2-2.0/json2.js"></script>
<title>A Knockout Demo</title>
<script>
/**
* JQuery Function
*/
$(document).ready(function() {
// Domain Object
var Person = function(id, fullname) {
var self = this;
self.id = id;
self.fullname = fullname;
};
// Knockout Model
var KoModel = function() {
var self = this;
// Declare observables
self.persons = ko.observableArray();
// Allows observables to share an array without observing each other
self.createPersonSelector = function(namevalue) {
var person = new Object();
person.selectedPerson = ko.observable();
person.name = namevalue;
return person;
}
// Prints a serialized string which could be sent to the server
self.printFormElements = function(formElements) {
alert($(formElements).serialize());
}
// Will change the person select value, to a real value
self.changePersonSelectValue = function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var value = valueAccessor(), allBindings = allBindingsAccessor();
// Next, whether or not the supplied model property is observable, get its current value
var valueUnwrapped = ko.unwrap(value);
// Now manipulate the DOM element
var $eleme = $(element);
if ($eleme == null) {
return;
}
// Change to item number two in the list *doesn't work*.
$eleme.val(2);
};
// Person selectbox value binding
ko.bindingHandlers.personSelect = {
init : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
self.changePersonSelectValue(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update : function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
self.changePersonSelectValue(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
}
};
// Put some test-data into the array
self.persons.push(new Person(1, 'Martin Smith'));
self.persons.push(new Person(2, 'Andy Gregersen'));
self.persons.push(new Person(3, 'Thomas Peep'));
};
// Apply bindings
ko.applyBindings(new KoModel());
});
</script>
<script type="text/html" id="person-template">
<span>Choose ID: </span><select data-bind="options: $root.persons, optionsText: 'id', personSelect: true, value:selectedPerson, attr: {'name': name, 'id': name}"></select></br>
<span>ID:</span> <span data-bind="text: selectedPerson().id"></span></br>
<span>Full Name: </span> <span data-bind="text: selectedPerson().fullname"></span></br>
</script>
<body>
<h1>Person Select One</h1>
<form data-bind="submit: printFormElements">
<div
data-bind="template: { name: 'person-template', data:createPersonSelector('personselect')}"></div>
<button type="submit">Submit</button>
</br>
</form>
<h1>Person Select Two</h1>
<form data-bind="submit: printFormElements">
<div
data-bind="template: { name: 'person-template', data:createPersonSelector('personselecttwo')}"></div>
<button type="submit">Submit</button>
</br>
</form>
</body>
</html>
The easiest way for me to answer is by changing quite a few things that I might do differently. The main issue that's holding you back is that you're using jQuery for things that can be handled by KO in a much easier way.
Below are the things I'd change, to see the full result have a look at this fiddle (which doesn't use jQuery at all).
Simplify your model to something like this:
var KoModel = function() {
var self = this;
// Declare observables
self.persons = ko.observableArray();
// Prints a serialized string which could be sent to the server
self.printFormElements = function() {
alert(ko.mapping.toJSON(self));
}
// Hold selected person
self.selectedPersons = ko.observableArray();
};
A few things to note:
The "print" function now uses the mapping plugin, which is great for serializing view models;
It's much, much shorter. The "CreatePersonSelector" and "changePersonSelectValue" functions won't be needed anymore, nor do the custom bindings;
The selectedPersons is now an observable, and an array at that because the view could potentially be a multi-select;
On a side note, I've placed adding the test values to outside the ViewModel;
This corresponds to the following View for starting off a template:
<div data-bind="template: { name: 'person-template' }"></div>
I've removed the data bit for now. This means each instance of this code would bind to (the same) $root view model. If you don't want that I'd suggest creating a container view model to hold several KoModels.
The template looks like this:
<span>Choose ID: </span>
<select data-bind="options: persons, optionsText: 'fullname', selectedOptions: selectedPersons"></select><br />
<!-- ko foreach: selectedPersons -->
<span>ID:</span> <span data-bind="text: id"></span><br />
<span>Full Name: </span> <span data-bind="text: fullname"></span><br />
<!-- /ko -->
Here's the jist:
The data-bind is much simpler. You don't need to fiddle with value attributes because Knockout will bind each option to a specific item in your array;
This leaves you free to use the fullname for the text;
The selectedOptions bit tells Knockout where to store selected items in your view model;
The selected options are shown in a foreach because the select could potentially be multiple select.
Now the ko.mapping.toJSON(self) call in the view model will generate something like this:
{
"persons": [{
"id": 1,
"fullname": "Martin Smith"
}, {
"id": 2,
"fullname": "Andy Gregersen"
}, {
"id": 3,
"fullname": "Thomas Peep"
}],
"selectedPersons": [{
"id": 2,
"fullname": "Andy Gregersen"
}]
}
As you can see the list of selected persons is there, to be sent to the server. The rest is there by default, but the mapping plugin can be configured to great detail.
Hope this helps and solves your problem!

Knockout.js: Getting computed observable to return array for use with select options

Basically, what I am trying to achieve is to populate a dropdown based on the value of another dropdown in Knockout.js
My view code(stripped obviously):
<div data-bind="with: chosenMailData">
<table id="tabl-1234" class="mails">
<thead><tr><th>Destination</th><th>Hotel</th></tr></thead>
<tbody data-bind="template: { name: 'iti-template', foreach: objects, afterRender: $root.myPostProcessingLogic }">
</table>
</div>
<script type="text/html" id="iti-template">
<tr>
<td><select class="desti" data-bind="options: $root.destinations, value: destination.name"></select></td>
<td><select data-bind="options: $root.generall(), value: $root.generall()"></select></td>
</tr>
</script>
My View-Model(again stripped-down):
self.destinations=['Kerela', 'Shoghi, Himachal Pradesh', 'Kasauli, Himachal Pradesh'];
self.myPostProcessingLogic = function(elements) {
this.generall = ko.computed(function() {
$('.desti').each(function(i, obj) {
stayOptions = [];
$.getJSON("http://127.0.0.1:8000/api/hotel?format=json&location__name="+obj.value, function(data) {
$.each(data.objects, function(i, item){
stayOptions.push(item.name);
});
});
});
return stayOptions;
}, this);
}
Inserting alert() in this.generall() shows that stayOptions does get populated with the values I want in it. It is getting that array to get displayed in select options for corresponding row in Hotels column which is the problem.
Probably I am making a really dumb mistake but I have been looking at the code for a long time now and nothing comes to mind. Please advise.
EDIT: I am doing this too at the beginning of my view-model:
self.generall = ko.observableArray();
When calling methods, the this variable gets assigned on invocation.
var f = $root.myPostProcessingLogic;
f(); // this will not set 'this' to '$root'
The above is essentially what knockout is doing, and this makes this be bound to something else inside myPostProcessingLogic(). You have already defined the scoped self variable, so this is easy to fix.
Another problem is that, reassigning observables won't preserve any subscribers, and any dependant observables won't update.
self.generall = ko.observableArray();
self.myPostProcessingLogic = function(elements) {
self.generall.removeAll();
$('.desti').each(function(i, obj) {
$.getJSON("http://127.0.0.1:8000/api/hotel?format=json&location__name="+obj.value, function(data) {
$.each(data.objects, function(i, item){
self.generall.push(item.name);
});
});
});
}

ObservableArray not reflecting data update

I'm creating an app using the very slick KnockoutJS library, but I've run into a snag. On the html page, I have a plain <select> control that I want to load with JSON data returned from a web service.
I define the observable array as follows:
var laborRow = function () {
this.positions = ko.observableArray([]);
};
When the page loads, the ajax call is made and the data is returned. In the callback, I do the following:
success: function (msg) {
laborRow.positions = msg;
}
based on the KO docs, I would expect that I would set the result like this:
laborRow.positions(msg);
However, that just throws an error stating that "laborRow.positions in not a function"
The template in the html is as follows:
<tbody data-bind='template: {name: "laborRowTemplate", foreach: laborLine}'> </tbody>
</div>
<script type="text/html" id="laborRowTemplate">
<tr>
<td><select data-bind='options: positions, optionsText: "Title", optionsCaption: "select", value: selectedPosition '></select></td>
</tr>
</script>
The laborRow object is a property on the ViewModel which is bound to the page. For whatever reason, this does not work. To add another wrinkle, if I add code to peek into the observableArray and print out some piece of data, the data is in there. So it is being loaded successfully.
Any thoughts would be greatly appreciated.
The full code for my example case:
var laborRow = function () {
this.positions = ko.observableArray([]);
};
var projectEstimate = function () {
this.laborLine = ko.observableArray([new laborRow()]);
};
var projectViewModel = new projectEstimate();
ko.applyBindings(projectViewModel);
//and the code in the callback function on ajax success
success: function (msg) {
laborRow.positions = msg;
//laborRow.positions(msg); **this does not work - error is laborRow.positions is not a function**
},
And the html:
<tbody data-bind='template: {name: "laborRowTemplate", foreach:
laborLine}'> </tbody>
<script type="text/html" id="laborRowTemplate">
<tr>
<td><select data-bind='options: positions, optionsText:
"Title", optionsCaption: "select", value: selectedPosition '></
select></td>
</tr>
</script>
Finally, thanks to Sean's comments below, I was able to get it working by modifying the code in the callback as follows:
success: function (msg) {
projectViewModel.laborLine()[(projectViewModel.laborLine().length-1)].positionList(msg);
}
The problem is that you haven't actually created your model:
var laborRow = function () {
this.positions = ko.observableArray([]);
// will only be called if you call var some_var = new laborRow()
};
Change your function to a bare object (as shown in the Knockout docs):
var laborRow = {
positions: ko.observableArray([])
};
And you'll be able to call laborRow.positions(msg); and have it work.
EDIT
Based on the new code, laborRow is still not instantiated -- if you are setting var laborRow somewhere else in your code (around the ajax request, perhaps) then you'll want to make sure that your call stack looks like this:
projectViewModel.laborLine()[0].positions()
// This will return the array you're looking for.
// The key is that laborLine is a `getter` not an attribute
I've been bitten by the "ko variables are getters not attributes" bug on several occasions ... might that be happening with your code?

Categories