Knockout Select2 set initial value by object - html

I have a Knockout.js web application where I have a select2 dropdown. I want to bind both the id and text values to a variable instead of just the id. Here's my data:
var cars = [{id: 1, name: 'Honda'}, {id: 2, name: 'Toyota'}, {id: 3, name: 'Dodge'}];
var selectedCar = ko.observable();
Here's my html:
<select data-bind="value: selectedCar, optionsCaption: 'Select', optionsText: 'name', options: cars"></select>
So now, whenever I select something in the dropdown my variable contains the entire object like so:
selectedCar = {id: 1, name: 'Honda'};
The only problem occurs when you load the page and want the dropdown to be set to a specific value. Even though before rendering the html the selectedCar variable is set to {id: 1, name: 'Honda'} when the page renders the dropdown is not set to anything, it just is set to the placeholder 'Select'.
What am I doing wrong?

If you want to bind both the id and text value to a variable, you need to use the optionsValue binding and bind the entire context to it ($data);
<select data-bind="value: selectedCar,
optionsCaption: 'Select',
optionsText: 'name',
options: cars,
optionsValue: $data"></select>
Normally we would specify the id or whatever to get an initial value selected. So it would make sense like in your question to supply the entire object {id: 1, name: 'Honda'} as the initial value to selectedCar if wet set optionsValue: $data and not $optionsValue: 'id'.
(IMPORTANT) But turns out this doesn't work, because we're creating a new object and so Knockout's equality test will fail when it compares the objects of cars with the object inside selectedCar. The correct way to set the initial value is cars[0].
I'm sure this is a typo, but I'll specify anyway: when you create any variable that needs to be accessed by the HTML you need to bind it to this, which would be a reference to the viewModel.
this.cars = [{id: 1, name: 'Honda'}, {id: 2, name: 'Toyota'}, {id: 3, name: 'Dodge'}];
this.selectedCar = ko.observable();
Let's test all this with a fiddle:
var viewModel = function(){
var self = this;
self.cars = [{id: 1, name: 'Honda'}, {id: 2, name: 'Toyota'}, {id: 3, name: 'Dodge'}];
self.selectedCar = ko.observable(self.cars[0]);
};
ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<select data-bind="value: selectedCar, optionsCaption: 'Select', optionsText: 'name', optionsValue:$data, options: cars"></select>
<!-- to verify that we are getting the entire object -->
<p data-bind="text: ko.toJSON(selectedCar)"></p>

The value of a select box will become the object that corresponds to the selected item or the property set in the optionsValue parameter. So in the case of objects, the selected value you set must be the same instance that exists in the array. Having a different instance of an object happens to be structurally equivalent is not enough.
For situations like this, I find it easier to bind the value of the select box to a unique id for the object instead. Then you could map out that id to the actual instance that you want through a computed value.
function ViewModel(data) {
this.cars = data.cars;
this.selectedCarId = ko.observable(data.selectedCarId);
this.selectedCar = ko.computed(() => {
let selectedCarId = this.selectedCarId();
return this.cars.find(c => c.id === selectedCarId);
});
}
let model = {
cars: [
{ id: 1, name: 'Honda' },
{ id: 2, name: 'Toyota' },
{ id: 3, name: 'Dodge' }
],
selectedCarId: 2
};
ko.applyBindings(new ViewModel(model), document.getElementById('content'));
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.min.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div id="content">
<select data-bind="value: selectedCarId,
optionsCaption: 'Select',
optionsText: 'name',
optionsValue: 'id',
options: cars">
</select>
<p>selectedCarId: <span data-bind="text: selectedCarId"></span></p>
<p>selectedCar: <span data-bind="text: ko.toJSON(selectedCar)"></span></p>
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>
</div>
If you'd rather not use a separate computed property, you would still need to pass in an index, but the value you set, must be obtained from the array.
function ViewModel(data) {
this.cars = data.cars;
this.selectedCar = ko.observable(
data.cars.find(c => c.id === data.selectedCarId)
);
}

Related

Apply the styles to selected values in the multi-select dropdown in Angular

I am new to angular. As part of my baby steps , I have a ng-select(multiple)component.
this is my component.html
<label>Multiselect with custom bindings</label>
<ng-select [multiple]=true>
<ng-option *ngFor= "let city of cities" [value]="city.id"> {{city.name}}</ng-option>
</ng-select>
component.ts
cities = [
{ id: 1, name: 'Vilnius' },
{ id: 2, name: 'Kaunas' },
{ id: 3, name: 'Pavilnys' },
{ id: 4, name: 'PabradÄ—' },
{ id: 5, name: 'KlaipÄ—da' },
];
I have custom component called tags which styles the selected values.
<tags [(ng-model)]="values" </tags>
My query is I need to pass these selected values to this component and display the styled selected values in the same place . Please help!. Thanks in advance.
Passing the selected values to the component:
component.ts:
selectedValues = [];
component.html:
add the following to your ng-select:
[(ngModel)]="selectedValues"
It will automatically update this array.
Then you can display the array data again where you need them.
Not sure if this answers your question. What do you mean by 'display the styled selected values in the same place'?

md-chips with md-select in multi select mode

When I am trying to generate md-chips on selecting multiple values from md-select, It is not working. Does md-chips works only with autocomplete analyser and input field?
<md-chips ng-model="launchAPIQueryParams.type">
<md-select name="launchCalType" ng-model="launchAPIQueryParams.type"
multiple="true" placeholder="Launch Type"
md-on-close='applylaunchFilter("type")'>
<md-option ng-repeat="typeOption in launchTypeOptions" ng-value="typeOption[1]">
{{typeOption[0]}}
</md-option>
</md-select>
</md-chips>
The short answer: No.
<md-chips> component will only takes <input> or <md-autocomplete> into its transcluded context.
However, the same thing can be achieved with md-autocompelet.
The key is set md-min-length on <md-autocomplete> to 0 so it will auto show the menu just like what a <md-select> menu would be.
Here's an example:
// controller.js
angular
.moduel('mdChipsDemo', [])
.controller('MdChipsDemoCtrl', function() {
var vm = this;
vm.selectedOption = '';
vm.searchText = '';
vm.launchAPIQueryParams = {
types: [],
};
vm.launchTypeOptions = [
{name: 'Op1', value: 1},
{name: 'Op2', value: 2},
{name: 'Op3', value: 3},
{name: 'Op4', value: 4},
];
});
// template.html
<div ng-app="mdChipsDemo" ng-controller="MdChipsDemoCtrl as vm">
<md-chips ng-model="vm.launchAPIQueryParams.types">
<md-autocomplete
md-selected-item="vm.selectedOption"
md-search-text="vm.searchText"
md-items="typeOption in vm.launchTypeOptions"
md-item-text="typeOption.name"
md-min-length="0"
placeholder="Search for a launchTypeOptions">
<span md-highlight-text="vm.searchText">{{typeOption.name}}</span>
</md-autocomplete>
<md-chip-template>
<span>{{$chip.name}}</span>
</md-chip-template>
</md-chips>
</div>
If your ultimate goal is to have multiple select ability, <md-autocomplete> also expose <md-item-template> where you can put your <md-select> in. Check the doc for md-autocomplete and you will see.
Or if you really insist on using <select>, there's an 3rd-party component on npm calls md-chips-select which does what you want.
https://www.npmjs.com/package/md-chips-select

Update md-select programmatically

I have the following HTML
<md-select id="testSelect"
ng-model="objElements" ng-change="onElementChange()"
name="objElements" placeholder="Select Stuff">
<md-option id="testOption"
ng-repeat="ele in Elements"
ng-if="ele.Completed==false"
ng-value=ele.ID
ng-selected="$first">
{{ele.Name}}
</md-option>
Elements is populated using a $http.get request and is displaying correctly - I am able to select an element and ng-change fires correctly.
If I add a new element using a $http.post, then pushing the new object onto Elements[] using Elements.push($scope.NewElement), is there a way I can set the new object as the 'selected' element?
I can see the new element has been added correctly to the Elements[] but cannot figure out how to set ng-selected=NewElement.
Yes, simply set objElements to your new element from the response. Should do the trick.
There's a good example here: https://material.angularjs.org/latest/api/directive/mdSelect (code below).
<div ng-controller="MyCtrl">
<md-select ng-model="selectedUser">
<md-option ng-value="user" ng-repeat="user in users">{{ user.name }}</md-option>
</md-select>
</div>
angular.controller('MyCtrl', function($scope) {
$scope.users = [
{ id: 1, name: 'Bob' },
{ id: 2, name: 'Alice' },
{ id: 3, name: 'Steve' }
];
$scope.selectedUser = { id: 1, name: 'Bob' };
});

Autofill html select AngularJS

I have a little issue with a HTML select with AngularJS. When I do a petition to my API I get one of the values as an integer, but when I try to autofill a select with it I can't set de "value" correctly.
In this picture you can se what the HTML is receiving and the values that I want to set
Are there any way to cast this value?
Thanks in advance :)
EDITED:
The controller to get customer data and fill the form
.controller('CustomerDetailCtrl', ['Customer', '$scope', '$sessionStorage', '$stateParams', '$ionicPopup', function (Customer, $scope, $sessionStorage, $stateParams, $ionicPopup) {
if ($sessionStorage.auth) {
Customer.get({data: $stateParams.customerId + '_' + $sessionStorage.user_id}).$promise.then(function (data) {
if (data.response && $sessionStorage.role === 1) {
$scope.customer = data.response[0];
if (data.history) {
$scope.histories = data.history;
}
} else {
console.log('Error de accesso...');
}
})
}
$scope.addTask = function (customer) {
alert('add task!');
}
$scope.deleteTask = function (customer, history) {
alert('delete task!');
}
}])
The form:
<label class="item item-input item-select">
<div class="input-label">
Cliente avisado?
</div>
<select name="informed" ng-model="customer.informed" required>
<option value="0">NO</option>
<option value="1">SI</option>
</select>
</label>
And here a picture of the data from de API:
I know that you've already received an answer on this, but I wanted to show you one other potential option that doesn't involve having to change your data from an int to string. If you define the options for your select in your controller (or in a service if this will be used in multiple different places throughout your app) then you can take advantage of ng-options and its ability to use a value other than a string.
Here's an example (obviously I've hardcoded some things and put this all in a single module - not something you'd do in a real app).
JS:
angular.module('app', [])
.controller('ctrl', function($scope){
// select options (if these are common maybe store them in a service
// so you can share them in many controllers without duplicating the code)
$scope.selectOptions = [
{
text: 'NO',
value: 0
},
{
text: 'SI',
value: 1
}];
// sample data
$scope.customer = {
address: 'San Rosendo 11',
date: '2016-03-16T16:19:13+0100',
email: 'Montes',
equipment: 'PC',
id: 262,
informed: 1,
lastName: 'Montes',
location: 'Tienda',
name: 'Juanma',
notes: '',
pass: 'no tiene',
phone: '900112233',
price: '0',
status: 'Pendiente',
tasks: 'dfsdf'
};
});
HTML:
<div ng-app='app' ng-controller='ctrl'>
<select ng-model='customer.informed' ng-options='option.value as option.text for option in selectOptions'></select>
</div>
jsFiddle
Define a default value somewhere in your controller: $scope.customer.informed = "NO";

Dropdownlist binding to an object not returning selected object

I am binding an object to a dropdownlist using Knockout 2.2.1. The binding is working for putting the correct items in the list but when I try to get the OBJECT selected it is not working. I have a JSFiddle showing this problem; http://jsfiddle.net/CTBSTerry/g4Gex/
Html
<div style="margin-bottom: 15px;">
Your Choices:
<select data-bind="options: choicelists[0].options, optionsText: 'otext', optionsValue: 'oprice', value: selOpt1, optionsCaption: 'Choose...'"></select>
</div>
<div data-bind="visible: selOpt1" style="margin-bottom: 15px;"> <!-- Appears when you select something -->
You have chosen<br>
From Object:
<span data-bind="text: selOpt1() ? selOpt1().otext : 'unknown'"></span>
<br>From Value:
<span data-bind="text: selOpt1() ? selOpt1() : 'unknown'"></span>
</div>
JavaScript:
var mychoice = function (txt, price) {
this.otext = txt;
this.oprice = price;
}
var viewModel = {
prodphoto: "",
prodname: "",
prodDesc: "",
baseprice: "",
choicelists: [
{ 'listlabel': 'Size List',
'options': ko.observableArray([
new mychoice('Small', 'Small|$|0.00'),
new mychoice('Medium', 'Medium|$|0.00'),
new mychoice('Large', 'Large|$|0.00'),
new mychoice('X Large + 2.00', 'X Large|$|2.00'),
])
}],
textlists: [],
selOpt1: ko.observable()
}
ko.applyBindings(viewModel);
When you click the dropdown to make a choice I have 2 spans that attempt to show the selected value which I want as the object selected not just the specific value field. The object notation returns nothing but does not error. The second span shows the selected value but since it is not the selected object I would have to iterate through the object to get the related object. The Knockout documentation shows a very similar sample but I need a bit more complex view model. Can someone help me and point out why this is not working?
Thanks,
Terry
If you remove optionsValue from your binding, then Knockout will use the actual object rather than a property on it.
So, you would want to remove optionsValue: 'oprice' from the binding, then selOpt1 will be populated with the actual object.
Sample: http://jsfiddle.net/rniemeyer/g4Gex/1/