I don't know if what I'm experiencing is a bug, but I can't seem to reset a select box in Angular JS 1.0.2 (also tested with 1.1.5) where there is only one option. This is for a iPad app wrapped in Phonegap. I've tested in the browser (Safari, Chrome) though and the issue is still there.
I'm working on an app that has many products that are in different categories and sub-categories. When you select a category the route changes and normally resets the select box. And it looks like so:
However, if you were to choose an option and then decide to choose another sub-category when there is only one option in the select box for a sub-category (when the user clicks one of the images where it says "Other Products") the select box doesn't properly reset. The userthen can't advance from this point to the next select box. It looks like this:
I've almost gotten it to work by coming up with this function from the various resources out there, but seems Angular is being quirky. It looks like this with where I've got so far:
The problem is that I want the blank space to be before the option, not after. Then the user has to click the second blank option and then click the option again in order to activate the second select box.
Here is the JS:
$scope.reset = function() {
$scope.variants.selectedIndex = 0;
};
Here is the JSON. Notice that these set of variants have the same size:
1: {
name: 'Super Awesome Product',
description: 'Cool description',
category: 'viewers',
variants: {
1: {
color: 'Gold',
size: '55-62mm',
productCode: 'FCSTVG',
price: 0,
image: [path + 'FCSTVG-T.png', path + 'FCSTVG.png']
},
2: {
color: 'Silver',
size: '55-62mm',
productCode: 'FCSTVS',
price: 0,
image: [path + 'FCSTVS-t.png', path + 'FCSTVS.png']
}
}
}
};
And the HTML for the select box:
<select ng-model="selectedVariant" ng-show="variants != null">
<option ng-repeat="size in variants" value="{{size}}">
{{size[0].size}}
</option>
</select>
And the HTML for the where my reset() is clicked. Like I had said, these are the "Other Products" of images below:
<div class="other-products">
<h2>Other products</h2>
<div class="slide-container">
<ul ng-show="products != null" style="width: {{products.length * 112}}px">
<li ng-repeat="p in products" ng-show="findDefaultImage(p) != null">
<a href="#" eat-click ng-click="selectProduct(p.id);reset()">
<img ng-src="{{findDefaultImage(p)}}" alt="" />
</a>
</li>
</ul>
</div>
</div>
I've tried everything, like adding different values to this line $scope.variants.selectedIndex = 0; like -1 or 1.
Any help would be appreciated!
UPDATE: I solved the issue by hardcoding it. Didn't know why I didn't do it before, but I'm still endorsing #Shawn Balestracci answer as it answers the question.
Angular has a tendency to empty out the "index 0" of a select box pushing all of the options back 1 in the select box, but in actuality the option that a user selects is actually the next option in the drop down list. I don't know if this is a bug or a feature.
Here's how I hardcoded the HTML:
<select ng-model="selectedVariant" required="required" ng-show="variants != null">
<option style="display:none" value="">PICK ONE:</option>
<option ng-repeat="size in variants" value="{{size}}">
{{size[0].size}}
</option>
</select>
This stops Angular from pushing back the options in the drop down.
In your reset function just change $scope.selectedVariant to a value that isn't in the list. It's the value stored in that model that will determine which option is selected (and vice-versa.)
You should also be able to take advantage of the ngOptions directive isntead of creating them via ng-repeat.
http://docs.angularjs.org/api/ng.directive:select
$scope.reset = function() {
$scope.selectedVariant = {};
};
So if there is only one option, why use a select box? Or should the second option be "none".
In anycase, if you could put together a simple jsfiddle, that would help folks better see the problem and be able to play with it and hopefully find you an answer.
I disagree with resetting the select model to an empty object. That will blank out the list of options you may have there. Instead you can set the selected option to zero using the model:
$scope.reset = function() {
$scope.selectedVariant = 0;
};
Related
How to set default value in Select?
I am trying to create time- dropdown select with multiple options.
At present. the selectedTimeOption correctly identifies if we choose an option from dropdown, but can't detect a value that I am trying to display initially from the Typescript.
I want to first display a predefined value in select box even before selection (it basically comes from another page)
How do I implement it?
Is there a problem with TS component or the html? If someone can explain with small working example on stackblitz.com , that would be helpful.
HTML:
<select
[value]="selectedTimeOption"
(valueChange)="setTime($event)"
>
<option
"let option of timeOfOptions"
[value]="option"
>
{{ option.label }}
</option>
</select>
TS:
timeOptions:TimeItems[] =[
{ label: '0:00', value: '0' },
{ label: '1:00', value: '1' },
{ label: '2:00', value: '2' },
];
You can have a look at he stackblitz example here,
Where, you can initialize the value to be displayed first(as you said from another component) in the example it is shown as,
firstSelectValue = 'one';
and then in your template, in your element, you can have a 'getter', in the given example, it is,
*ngFor="let opt of firstSelectOptions"
Your <option tag should be like this:
<option [value]="option.value">
And your selectedTimeOption can be set to:
selectedTimeOption = '1'
There are syntax issue and coding conventions you need to follow. please rename those option variable while iterating over array, something like below.
ex: *ngFor = "let time of timeSource"
Here is the stackblitz example
I am working on an AngularJS 1.0 application and facing an issue with Angular Select.
Just Copying a dummy code:
<select ng-model ="data.state" ng-options="data.states">
Select
Here, it's listing states of a country and passing a default data as ng-model.
I need to show a text "Select" if default is not matching with the data source.
But it's not working as expected and it's showing an extra empty space instead of select.
Thanks
"I need to show a text "Select" if default is not matching with the data source." In this case your data.state may be empty (ie "") case. You may be expecting some logic like this:
<label>States : </label>
<select ng-model ="data.state" ng-options="data for data in data.states" >
<option value="" selected hidden >SELECT</option>
<!--Removes the Empty space and shows "SELECT" as pre-populated by default in the drop-down. -->
</select>
<br>Selected State : {{data.state}}
Empty space appears as a placeholder offered by Angular to hold text like "Select an option" or "Choose a best answer". You can override this using the <option value="" selected hidden >Select</option> which won't appear in the selected drop down options list as it is hidden.
If you wish to have the select appear as an option for user to choose in order if the field can be remained empty and is not mandatory you can use the following code instead:
<option value="" selected >SELECT</option>
In controller expecting the data as:
// $scope.data.state = something that is returned from some API response
$scope.data = {
states: [
"Kerala",
"Karnataka",
"Andhra",
"Haryana"
]
}
Checkout the JS Fiddle here for working sample.
<select ng-model ="data.state" ng-options="data.states">
make use of track by in ng-repeat. track by $index or id if any
A view of my AngularJS app makes heavy use of ng-repeat directive. It is done like this:
<div ng-repeat="branches in company">
<p>{{branches.name}}</p>
<p>{{branches.location}}</p>
<div>
<select ng-model="branches.officeInformationType">
<option ng-repeat="offices in branches">{{offices.type}}</option>
</select>
<select ng-model="branches.officeInformationMeters">
<option ng-repeat="offices in branches">{{offices.meters}}</option>
</select>
<select ng-model="branches.officeInformationColor">
<option ng-repeat="offices in branches">{{offices.color}}</option>
</select>
</div>
</div>
The fact is, the second ng-repeat and and the others after it (offices in branches) are actually the same everytime, so it wouldn't need to be recalculated for every branch. It would need to be binded to the row it belonges to, for saving it later, so the branches.officeInformation model should still be watched by angular, but I would like to make the whole program more performant.
I am using angular-ui-router and when I change the view between my "Choose your office" view and any other, the lag is tremendous, almost at a minute of wait time when you leave the "Choose your office" page. It renders fast enough, 2 seconds for the whole rendering, but when I leave the page it takes a ton of time to change to the other view.
Any ideas, taking into consideration that the ng-model binding "branches.officeInformation.." is of importance?
EDIT: I have tried remove the nested ng-repeats and for each ng-repeat that I removed, the transition between states got faster and faster. When I removed all the nested ng-repeats the transition became instantaneous, hence why I believe it has to do with the ng-repeats.
The ng-repeats are tracked by $index and where possible I used :: for one time binding.
Thanks.
We can lazy load a dropdown's options right before the user interacts with it.
First, we initialize each dropdown with only the selected option, so you can see it when the dropdown is closed.
Then we attach an ng-focus directive to each dropdown. When our callback fires we can:
fully populate the options for that dropdown
remove all but the selected option from the previously active dropdown
I wasn't entirely sure of the structure of your data (it looks like some arrays have additional properties on them). So I chose to create "view model" objects that represent the UI. You can adapt this to your own structure.
Controller:
// Set up some test office options (null for no selection)
var allOffices = [null];
for (var i = 0; i < 50; i++) {
allOffices.push(i);
}
// activeDropdown holds the dropdown that is currently populated with the full list
// of options. All other dropdowns are only populated with the selected option so
// that it shows when the dropdown is closed.
var activeDropdown;
$scope.company = [
// Branch 1
[
// These objects represent each dropdown
{
// Just the selected option until the user interacts with it
options: ["0"],
selected: "0"
}, {
// Just the selected option until the user interacts with it
options: ["1"],
selected: "1"
}, {
// Just the selected option until the user interacts with it
options: [null],
selected: null
}
],
// Branch 2
[
// These objects represent each dropdown
{
// Just the selected option until the user interacts with it
options: ["2"],
selected: "2"
}, {
// Just the selected option until the user interacts with it
options: ["3"],
selected: "3"
}, {
// Just the selected option until the user interacts with it
options: [null],
selected: null
}
]
];
// When the user interacts with a dropdown:
// - fully populate the array of options for that dropdown
// - remove all but the selected option from the previously active dropdown's
// options so that it still shows when the dropdown is closed
$scope.loadOffices = function (dropdown) {
if (activeDropdown === dropdown) {
return;
}
dropdown.options = allOffices;
if (activeDropdown) {
activeDropdown.options = [activeDropdown.selected];
}
activeDropdown = dropdown;
};
Template:
<div ng-repeat="branch in company">
<div ng-repeat="dropdown in branch">
Selected: {{ dropdown.selected }}
<select ng-focus="loadOffices(dropdown)" ng-model="dropdown.selected">
<option ng-repeat="o in dropdown.options">{{ o }}</option>
</select>
</div>
</div>
Note that ng-focus was the only directive I needed to apply to each dropdown when I tested this. But you may need to add ng-keydown, ng-mouseover, ng-click, or others to get it to work in all scenarios including mobile.
I also noticed a potential styling issue. When you focus on a dropdown, we load all of the options for that dropdown. This may cause the width of the dropdown to change, so if you can set the same width for all of them you should be good.
If the number of options in each dropdown is huge, we may be able to optimize even further by writing some custom directives that interact and allow the actual DOM element options to be shared. But I suspect we won't have to go that far for this example.
Have you tried 'track by $index' ? it will reduce angular watches overhead.
something like that:
div ng-repeat="branches in company track by $index">
<p>{{branches.name}}</p>
<p>{{branches.location}}</p>
<div>
<select ng-model="branches.officeInformationType">
<option ng-repeat="offices in branches track by $index">{{offices.type}}</option>
</select>
<select ng-model="branches.officeInformationMeters">
<option ng-repeat="offices in branches track by $index">{{offices.meters}}</option>
</select>
<select ng-model="branches.officeInformationColor">
<option ng-repeat="offices in branches track by $index">{{offices.color}}</option>
</select>
</div>
</div>
First and foremost, thanks to those that helped me find the answer.
The problem was that I nested too many ng-repeats with too many event handlers attached to each repeated element. ng-models, ng-changes and ng-clicks were really heavy, but the number of elements was also out of control.
I solved this by using a single select without any nested ng-repeats, this select (and the options) are in a modal view, so a different controller. From that controller I return the select results, having only one select for all the elements in the page. When the data is returned from the modal, I use it from the main controller of the view.
Thanks again.
I am trying to achieve a databinding for multiple selection with optgroup using knockoutJS. In addition we would like to use select2 for its search and display capabilities.
Here is the fiddle sample.
Everything works well when the items are added directly using the html control. You may pickup some countries in the example above and click the view button to see that the code of the countries are well retrieved. However, I would like to populate the items another way. Precisely, I created a command to flush the observable array containing the selected items and force the first item in the list of available options to be selected (which is the country Laos in our example). This command is executed when clicking the second button.
After clicking this latter button, you can check that the observable selectedCountries contains the expected values by clicking the first button. Unfortunately, the UI control is not refreshed, do you have an idea how to do that? The html databiding for my view looks like
<select class="multi-select" data-bind="foreach: availableCountries,selectedOptions:selectedCountries" multiple="multiple">
<optgroup data-bind="attr: {label: label}, foreach: children">
<option data-bind="text: display, value: code"></option>
</optgroup>
</select>
The short answer is that Select2 doesn't know about changes you make to the underlying model.
I was able to make your sample work using a hack, see here: http://jsfiddle.net/bXPM6/
The changes made are:
<select id="foo" class="multi-select" data-bind="foreach: availableCountries, selectedOptions:selectedCountries" multiple="multiple">
(Note the added id=foo).
And I added a subscription to the observable:
function MyViewModel(){
var self = this;
self.availableCountries = ko.observableArray(app.availableCountries());
self.selectedCountries = ko.observableArray([]);
// added this bit
self.selectedCountries.subscribe(function (newValue) {
$('#foo').select2("val", newValue);
});
}
The better option is to make a custom knockout binding that can keep Select2 updated with changes to your model.
Something like this:
HTML:
<select class="multi-select" data-bind="foreach: availableCountries, selectedOptions:selectedCountries, select2: selectedCountries" multiple="multiple">
JavaScript:
ko.bindingHandlers.select2 = {
update: function (element, valueAccessor) {
$(element).select2("val", ko.unwrap(valueAccessor()) || "");
}
};
Hope this is of some help.
I need to add a "fake" <option> to a <select>, that will be shown as the selected one, but that can't be selected in the dropdownmenu if the user want to change item.
For example: I have a page that displays 10 fruits. Each fruit has his weight. Using the select, I can filter the fruits by weight:
<select id="fruits">
<option>1 kg</option>
<option>2 kg</option>
<option>3 kg</option>
</select>
if I select the 2nd option (2kg), the page will remove every fruit that has weight != 2kg, and obviously show only those with weight == 2kg.
Now I need to add a "weight range", for example "show fruits whose weight is between 1 and 2 kg". I don't want to add a new option to the , I just want to filter the table showing fruits with weight between the selected range (1 and 2kg), and show this range as the selected value in the select. So my CLOSED dropdown menu will have value = "1-2kg", but if I click on the select, I will not find "1-2kg" option (that's why I wrote "fake" option in the title).
In a nutshell I just want to edit the selected-shown select text, not his options... Something like, using JS
var select = document.getElementById("fruit-select");
select.value = "1-2kg";
obviously this is not working because the option "1-2kg" does not exists..
Is this possible? I hope I was clear enough.. Thanks in advance for any helps, best regards
Solution (not working on Safari...)
Thanks to Ricardo I came to this solution: http://jsfiddle.net/4suwY/5/
HTML:
<select id="asd">
<option>hello</option>
<option>I'M THE CHOSEN ONE</option>
<option>asd</option>
<option>wer</option>
<option>qwe</option>
</select>
JS:
var sel = document.getElementById("asd");
var optnz = sel.getElementsByTagName("option")[1];
sel.value = optnz.value;
optnz.style.display = "none";
the "I'M THE CHOSEN ONE" option is displayed as selected, but is not clickable (not even visible in the options list)
Thanks guys!
I think what you need to do here, is actually have a visible select with all those values, 1kg, 2kg, etc and then you have a hidden select, that will contains those ranges, like 1-2kg, etc (if the ranges are fixed). Everytime you select something from the visible dropdown you change the selected item of the hidden dropdown to the desired range.
It sounds like you want to include a range of weights. You may want to consider a different type of input, such as a range slider. Here's an example from the jQuery UI project. I'm not suggesting you use jQuery UI per se, just showing you one way it can be implemented.
You could use <select multiple> and whenever someone selects more than one option you get the maximum and minimum and show that interval.
try this:
http://jsfiddle.net/4suwY/4/
the only problem I see is losing the value on select click on this function:
$("#asd").mousedown(function () {
$("#asd option[value='1-2kg']").remove().change();
});