Angular.js select looses focues option when $scope.$apply() - html

I have a quite straightforward select element like that:
<select id="selectCurrency" selectmodel="currencyPair" class="select"
ng-model="currencyPair" ng-options="ft.id as ft.name for ft in contentChart"
ng-change="currencyPairChange(currencyPair)">
</select>
When I drop down the select, focus any option other then the first one and then the controller calls $scope.$apply(), focus is going back to the first option automatically.
I have no clue how to avoid such behaviour.
Any help strongly appreciated.

Got it working. I got the idea from this question. Here is the fiddle.
<div ng-app>
<div ng-controller="SelectCtrl">
{{rate}}
<select id="selectRate" ng-model="rate" data-ng-click="suspendDigest()">
<option ng-repeat="ft in rateChart" value="{{ft.id}}">{{ft.name}}</option>
</select>
</div>
JS:
function SelectCtrl($scope,$interval) {
//$scope.rate='S';
$scope.rateChart = [
{id: 'S', name: 'buy rate'},
{id: 'K', name: 'sell rate'},
{id: 'R', name: 'average rate'}
];
setInterval(function(){
$scope.$apply();
}, 2000);
}

Related

Knockout Select2 set initial value by object

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)
);
}

How to get alert when Ichange dropdown options using angularjs

This is my dropddown menu, when I change the option will get alert its working, but when i select again on the same option i didnt get alert.How it posiible
index.html
<body ng-app="demoApp">
<div ng-controller="DemoController">
<div>
<select ng-model="currentlySelected" ng-options="opt as opt.label for opt in options" ng-change="logResult()">
</select>
The value selected is {{ currentlySelected.value }}.
</div>
</div>
</body>
app.js
angular.module('demoApp', []).controller('DemoController', function($scope) {
$scope.options = [
{ label: 'one', value: 1 },
{ label: 'two', value: 2 }
{ label: 'three', value: 3 }
{ label: 'four', value: 4 }
{ label: 'five', value: 5 }
];
$scope.currentlySelected = $scope.options[1];
$scope.logResult = function() {
alert($scope.currentlySelected);
}
});
What I expect is if I select "two" twice i need to alert twice. Is this possible or should I be using a different directive for this?
No, it's not possible. onchange event is fired when selection changes, not when you make a selection. So unless you choose different option, it's not going to fire.
If you need this behaviour I would suggest using one of the custom select solutions, even custom CSS based select would work. Then you could bind to onclick event instead of onchange.

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

How to change appearance of the state type drop down when disabled mode

We have two drop downs. Based on the first dropdown event changed the second drop down values are getting populated.
How to change appearance of the 2nd type drop down such that it should be different for both enabled and disabled state?
In both drop down they look like enabled but until we select the country the states not loaded any way .. The select CSS was behaving as excepted in IE but not in Chrome. It was looking as enabled in Chrome.
//***********//
button,
input,
optgroup,
select,
textarea {
margin: 0;
font: inherit;
color: inherit;
}
When I removed the Color inherit from boot strap It makes the difference.How to override the style sheet which effecs the color to the state dropdown in the customized CSS
<div class="item1" id="countries">
<div class="selectbox">
<select data-bind="options: Countries,
optionsText: $data,
optionsValue: $data,
value: SelectedCountry,
event: { change: $parent.CountryActionChanged }">
</select>
</div>
</div>
<div class="item2" id="states">
<div class="selectbox">
<select data-bind="options: States,
optionsText: 'Name',
optionsValue: 'Id',
value: SelectedStateType,
event: { change: $parent.StateActionChanged },
enable: $parent.IsStateType"
>
</select>
</div>
</div>
You haven't provided a full repro. When I tried to stub out the things you omitted, I ran in to various problems. So I've decided to tell you how you could do things slightly different, in a way that will also solve the enable binding issue. Here it is:
var ViewModel = function() {
var self = this;
self.countries = [
{ name: "US", states: ["NY", "CA", "etc"] },
{ name: "Monaco", states: [] },
{ name: "Vatican", states: [] }
// etc
];
self.selectedCountry = ko.observable();
self.selectedState = ko.observable();
self.availableStates = ko.computed(function() {
if (!!self.selectedCountry()) {
return self.selectedCountry().states;
}
return [];
});
self.selectedCountryHasStates = ko.computed(function() {
return self.availableStates().length > 0;
});
};
ko.applyBindings(new ViewModel());
select { min-width: 15em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="item1" id="countries">
<div class="selectbox">
<select data-bind="
options: countries,
optionsText: 'name',
value: selectedCountry">
</select>
</div>
</div>
<div class="item2" id="states">
<div class="selectbox">
<select data-bind="
options: availableStates,
value: selectedState,
enable: selectedCountryHasStates">
</select>
</div>
</div>
Things to note:
Don't use the event binding unless you really, really have to. Instead, use computed functions so your view models change dynamically based on the selections made by users.
(Subjective) Reserve PascalCase for constructor functions, and use camelCase for members and variables.
Base the enable status on whether there are states or not. Do this in a seperate computed so you can unit test its logic.
Try to bind dropdowns to complex object (e.g. countries themselves), this makes your view models use the OO features of constructor functions / javascript in a way that makes your code a lot more readable. It also slims down your data-bind attributes a lot.

Knockoutjs Options Binding with JSON data?

I am trying to list the options for a select tag from server with the help of knockout options binding. I have a PHP page which returns the JSON data which is pushed to a knockout observable array which is binded to the select tag. But somehow it is not working, please refer to the following code for reference:
HTML:
<div class="form-group">
<select class="form-control">
<option data-bind="options: Country_Names, optionsText: 'country_name'"></option>
</select>
</div>
JavaScript:
$(document).ready(function(){
function appModel(session_info){
/* Session Info related bindings are commented as they are working fine */
var self = this;
this.Country_Names = ko.observableArray();
// Bindings related to the batch processing
$(function(){
$.ajax({
url:"../api/master_list.php",
type:"get",
data:{mastertype: '1'},
cache:false,
success:function(country_list){
ko.mapping.fromJSON(country_list, {}, self.Country_Names);
}
});
});
};
$.ajax({
url:"../api/sessions.php",
type:"get",
data: {rqtype: '1'},
cache:false,
success:function(session_info){
var data = $.parseJSON(session_info);
if (data.status == 'Invalid_id'){
window.location.replace('../files/main.html');
} else {
ko.applyBindings(new appModel(session_info));
}
}
});
});
Sample JSON:
[{"country_name":"Albania"},{"country_name":"Chile"},{"country_name":"Cuba"}]
Question, Why are the options not listed in the select tag? Am i missing something obvious?
You are data-binding the option element, whereas you want to put the bindings on the select, like this:
<select class="form-control" data-bind="options: Country_Names, optionsText: 'country_name'">
</select>
The <option...> element is gone; the options will be generated by Knockout.
Note that the relevant documentation is very clear on how all this works. If you haven't already, I suggest (re-)reading it.
Or, see this full demo:
var data = [{"country_name":"Albania"},{"country_name":"Chile"},{"country_name":"Cuba"}];
ko.applyBindings({ Country_Names: data });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<select data-bind="options: Country_Names, optionsText: 'country_name'"></select>