I had a list array of ng-model on my options. Each ng-model assign with different values depends on the user inputs. It works for me when I tried to select the options box and the result produced what I want. But when I retrieved new data from API and parse the value into that options box. Is not working.
HTML
<div ng-repeat="($a_choice_index, c) in menu.additionalchoice">
<label for="max_select" class="rl-label required">Do you want maximum select?
<label for="max_select" class="select-block">
<select id="max_select" name="max_select" ng-model="c[$a_choice_index].maxSelect" ng-change="selectMaxSelect(c[$a_choice_index].maxSelect, $a_choice_index)" ng-options="o.name for o in maxSelectOptions"></select>
<svg class="svg-arrow">
<use xlink:href="#svg-arrow"></use>
</svg>
</label>
</label>
</div>
Controller
$scope.menu = {};
$scope.maxSelectOptions = [
{
name: "No",
value: 0
},
{
name: "Yes",
value: 1
}
];
$scope.selectMaxSelect = function(v, index){
$scope.menu.mealchoices[index].max_select = v;
};
MyService.GetData(function(menu){
$scope.menu = menu; // menu.additionalchoice is added into menu
});
API Data
Each menu.additionalchoice produces
{"1":{"title":"Add favorite oils","maxSelect":{"name":"No","value":0}},"title":"Add favorite oils","max_select":4,"additional_choice":1,"maxSelect":1,"meals":...
When I tried to:
1) ng-init="c[$a_choice_index].maxSelect = c.maxSelect"
It shows empty.
2) ng-init="selectMaxSelect(c.maxSelect, $a_choice_index)"
It shows some values but the value is incorrect. c[$a_choice_index].maxSelect returns {"name": "No", "value": 0} when the c.maxSelect == 1 which is YES.
You can bind the select option by value only by using ... as ... for ... in ... syntax, which is a lot easier compare to binding using object.
ng-options="o.value as o.name for o in maxSelectOptions"
The result in ng-model should now be 0 or 1 instead of an object.
Related
I'm trying to calculate a rate automatically when a user gives the inputs.
A user can select one of the 3 items in the "types" list, and it should input the result of multiplying the multiplier (input), and the chosen object.
I tried a bunch of things (including interpolating on the controller), but can't seem to retrieve the rate in the object whose name is the same as the ng-model.
What can I do here?
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<label>Input a multiplier</label></br>
<input ng-model="h" /></br>
<label>Which object do you want?</label></br>
<select ng-model="objectType" ng-options="j for j in types"></select></br>
<h1> {{ h * rates.objectType }} </h1>
</div>
Angularjs:
var app = angular.module("myApp", []);
app.controller("myCtrl", function($scope) {
$scope.types = ["obj1", "obj2", "obj3"];
$scope.rates = {
obj1: 3,
obj2: 5,
obj3: 7
}
});
You can do it like this
{{h * rates[objectType] || 0}}
Use [] object notation for variable property names
{{ h * rates[objectType] || 0 }}
The || 0 is an assumption that you want to show zero when either of the other values is undefined
What would be the best way to create a multidimensional associative array from form inputs?
The form looks like this:
<div id="items">
<h4>Engraving Text</h4>
<div class="item" data-position="1">
<h4 id="engraving-item">Item 1</h4>
<label>Engraving Line 1: </label>
<input type="text" class="engraving-input engraving-line1" name="trophy" id="item1-line1">
<br />
<label>Engraving Line 2: </label>
<input type="text" class="engraving-input engraving-line2" name="trophy" id="item1-line2">
<br />
<label>Engraving Line 3: </label>
<input type="text" class="engraving-input engraving-line3" name="trophy" id="item1-line3">
<br />
</div>
</div>
If the user enters that they want multiple items - additional inputs are dynamically added to the form using these first 3 as a template.
I'm looking to create this sort of array (for example if the user added 2 items):
var myArray = {
item1 :
[
{
engraving-line1 : "user entered data",
engraving-line2 : "more user data",
engraving-line3 : "yep, more data"
}
],
item2 :
[
{
engraving-line1 : "user entered data",
engraving-line2 : "more user data",
engraving-line3 : "yep, more data"
}
]
};
I had written this but I think I am headed in the wrong direction with it or at the very least - writing it poorly.
var totalItems = $("#quantity_wanted").val();
jsonObj = [];
i=1;
while (i < totalItems){
items = {};
$('.item[data-position="'+i+'"] :input').each(function(){
var name = $(this).attr("name");
var engraving = $(this).val();
item = {}
item ["name"] = name;
item ["engraving"] = engraving;
items.push(item);
});
jsonObj.push(items)
i++;
}
Just looking for help writing the javascript that will help me to iterate through the inputs on the screen and push them into a multidimentional associative array like the one I listed.
Your code could be much simplified.
data-position attribute in jquery selector doesn't make sense
since you don't actually use its value. You just need to select all
input group containers by their shared class (.item), then, for
each container, select all descendant inputs.
Your code building item element is redundant. You can use inline
object literal/initializer ({...}) instead.
Furthermore, as #Andy noted, items array should be initialized by array literal ([]), not object ({}).
So the code should look like this:
var jsonObj = [];
$('div.item').each(function(){
var items = [];
$(this).find('input').each(function() {
items.push({
name: $(this).attr("name"), engraving: $(this).val()
});
});
jsonObj.push(items)
});
In our application we have a series of select filters that must be populated dynamically based on the context of the situation. On first load, the default options are inserted into the array via AJAX and it appears on the UI as expected. However, when the select list refreshes, the UI does not reflect the changes even though if you inspect the code the array appears to contain the new values.
I have written the same code for two filters but for some strange reason it only works in one of the situations, I have tried the following to resolve this to no avail:
Populating the array manually using arbitrary data
Forcing knockout to update with array.valueHasMutated()
Using the two different types of array clearing functions array.removeAll and array([])
Using push.apply and push
Saving the result to a variable and then assigning that to the array
Making the values inside the array observable
This first instance of the code works as expected when the options change:
success: function (data) {
self.filtersModel.values2.removeAll();
var serverData = $.map(data, function (value, i) {
return new SelectBoxOption(value.Description, value.Id);
});
serverData.forEach(function (value) {
self.filtersModel.values2.push(value);
});
}
This is the second function that does NOT work:
success: function (data) {
self.filtersModel.values.removeAll();
var other;
var serverData = $.map(data, function (value, i) {
// If the option is "other" save it as a variable and add to array later
if (value.Code === "OTHR") {
other = new SelectBoxOption(value.Description, value.Id);
self.filtersModel.othersValue(value);
}
else if (value.Code == "EQTY") {
var equity = new SelectBoxOption(value.Description, value.Id);
self.filtersModel.equityValue(value);
return equity;
}
else
return new SelectBoxOption(value.Description, value.Id);
});
serverData.forEach(function (value) {
self.filtersModel.values.push(value);
});
// Add "other" option to bottom of the array
if (other)
self.filtersModel.values.push(other);
}
Any help would be greatly appreciated.
UPDATE
HTML to populate the select list occurs like this:
<div class="form-group">
<label for="value">Value</label>
<select id="value" class="form-control" data-bind="value: selectedValue, options: values, optionsCaption: '-- ' + 'Select Value' + ' --', optionsValue: 'optionId', optionsText: 'optionName'"></select>
</div>
<div class="form-group">
<label for="value2">Value 2</label>
<select id="value2" class="form-control" data-bind="enable: $parent.valueIsAuthorisedAndvalueIsEquityOrOther, value: selectedValue2, options: values2, optionsCaption: '-- ' + 'Select Value 2' + ' --', optionsValue: 'optionId', optionsText: 'optionName'"></select>
</div>
Example data is returned in this format:
data = [
{optionId: 1, optionName: "Value 1"},
{optionId: 2, optionName: "Value 2"},
{optionId: 3, optionName: "Value 3"},
{optionId: 4, optionName: "Value 4"}
];
If I understand your question, the second code doesn't work because your are updating the self.filtersModel.othersValue and self.filtersModel.equityValue in every iteration.
I rewrote your code, hope it helps:
self.filtersModel.values.removeAll();
var normals = [];
var others = [];
var equities = [];
ko.utils.arrayForEach(data, function (d) {
if (d.Code === 'OTHR')
others.push(new SelectBoxOption(d.Description, d.Id);
else if (d.Code === 'EQTY')
equities.push(new SelectBoxOption(d.Description, d.Id);
else
normals.push(new SelectBoxOption(d.Description, d.Id);
});
self.filtersModel.values(normals.concat(equities).concat(others));
After much deliberation, I found that the issue was not related to the ajax function at all but rather a buggy method call I defined before the changing the select list:
self.clearFilters = function() {
self.filtersModel = new createFiltersModel();
}
function createFiltersModel() {
return {
values: ko.observableArray([])
}
};
This created a new instance of the filtersModel every time the select list changed, so any changes to the model were lost when the ajax call was made. Doh!
I have a really simple CRUD app for managing music albums. Just two fields are tracked, title and artist.
In this example, the dropdown shows a list of albums, and if I fill out the form and click Save it will be added to the list.
In the second example, selecting an album will populate the form so it can be edited and updated.
My question is, is there a way to get both functionality in the same form? Sure I could create two identical forms and have them do slightly different things, but given they're operating on the same data, it would be nice if when a current_album is selected, it updates, and when "New album..." is selected, it creates.
The major roadblock seems to be value vs ng-model. I can set the value so it populates when I pick an item from the <select> OR I can set an ng-model="newAlbum", but not both to my knowledge.
You shouldn't be using the value attribute with ng-model. This is a very bad practice.
What I would suggest is to use ng-change on your list and keep a cloned object with the editing value.
$scope.onChange = function() {
if ($scope.currentAlbum) {
$scope.editing.title = $scope.currentAlbum.title;
$scope.editing.artist = $scope.currentAlbum.artist;
} else {
$scope.editing = {};
}
};
The all you need to do when saving is checking is it a new object or not:
$scope.addOrSaveAlbum = function() {
if ($scope.currentAlbum) {
$scope.currentAlbum.title = $scope.editing.title;
$scope.currentAlbum.artist = $scope.editing.artist;
} else {
$scope.albums.push({ title: $scope.editing.title, artist: $scope.editing.artist });
}
$scope.editing = {};
};
See http://jsfiddle.net/4Zeuk/12/
(thank you to Wawy to point out ng-change instead of $scope.$watch)
You can indeed get both functionality without the need of two different forms, but you can't use the same object in the scope in ng-model for both select and form fields.
But what you can do is have two different objects in the scope, one that contains the value of the selected item and the other will contain either a new instance of an album or a copy of the selected one. Then when you click the save/update button, based on the id of the object in the form you can decide if you need to save or modify the album collection.
Here is one way of doing what I've just explained:
<div ng-app="albumShelf">
<div ng-controller="MainCtrl">
<div style="float:left;">
<select ng-options="b.title for b in albums" ng-model="current_album" ng-change=updateForm()>
<option value="">Choose album...</option>
</select>
</div>
<div style="float:left;">
<form>
<input type="text" ng-model="newAlbum.title">
<br>
<input type="text" ng-model="newAlbum.artist">
<br>
<input type="submit" value="{{ current_album ? 'Update' : 'Save' }}" ng-click="modifyAlbumCollection()">
</form>
</div>
</div>
var albumShelf = angular.module('albumShelf', [/*'ngResource'*/])
.controller('MainCtrl', ['$scope', function($scope/*, albumFactory*/) {
//$scope.albums = albumFactory.query();
$scope.albums = [
{ id: 1, title: 'Disorganized Fun', artist: 'Ronald Jenkees' },
{ id: 2, title: 'Secondhand Rapture', artist: 'MS MR' }
];
$scope.modifyAlbumCollection = function() {
if ($scope.newAlbum.id !== null) {
//We are modifying an existing album
var i, found = false;
for (i = 0; i<$scope.albums.length && !found; i++) {
if ($scope.albums[i].id === $scope.newAlbum.id) {
$scope.albums[i] = angular.copy($scope.newAlbum);
found = true;
}
}
} else {
//We are adding a new album to the collection
$scope.newAlbum.id = $scope.albums.length;
$scope.albums.push(angular.copy($scope.newAlbum));
$scope.newAlbum = { id: null, title: '', artist: '' };
}
};
$scope.updateForm = function() {
if ($scope.current_album) {
//Copy the information from the selected album into the form.
$scope.newAlbum = angular.copy($scope.current_album);
} else {
//Clear previous album info.
$scope.newAlbum = { id: null, title: '', artist: '' };
}
};
}])
//.factory('albumFactory', function($resource) {
// return $resource('/albums/:albumId.json', { albumId: '#id' }
// );
//});
Here is the jsfiddle
In my opinion it's more clear if you use a ng-change in the select rather than a $watch on the ng-model value. Because what you want is update the form when a new value is selected from the dropdown rather than watching for changes on the object in the $scope.
This puts a checkbox next to each item of a list where changing the checked status adds/removes that value from the SelectedItems array:
<script type="text/x-jquery-tmpl" id="tmpl">
<input name="cSelect"
type="checkbox"
value="${ ID }"
data-bind="checked: VM.SelectedItems" />
<!-- Add other item content here -->
</script>
VM.SelectedItems = ko.observeableArray([]);
At any point, SelectedItems contains the ids of the checked items.
What if I wanted the checkbox to add and remove an object to SelectedItems? For example, I want to store an actual object of { id : 3, checked : true } instead of serializing it for the value attribute?
When using the checked binding against an array, it will only work with an array of primitives.
One option is to create a computed observable that builds your array of objects from the selected ids.
var ViewModel = function() {
var self = this;
this.items = [{id: 1, name: "one"}, {id: 2, name: "two"}, {id: 3, name: "three"}];
this.selectedIds = ko.observableArray();
this.selectedItems = ko.computed(function() {
return ko.utils.arrayMap(self.selectedIds(), function(id) {
return ko.utils.arrayFirst(self.items, function(item) {
return item.id == id; //selected id will be a string
});
});
});
};
ko.applyBindings(new ViewModel());
If you are dealing with a large number of items, then you might want to build an object that is an index of the items by key, so that you can loop through selectedIds and directly grab each object to reduce the amount of looping.
Here is a sample: http://jsfiddle.net/rniemeyer/pQQsY/
With KnockoutJS 3.0.0 you can use the checkedValue parameter:
<input name="cSelect" type="checkbox" value="${ ID }" data-bind="checkedValue: $data, checked: VM.SelectedItems" />
If your binding also includes checkedValue, this defines the value
used by the checked binding instead of the element’s value attribute.
This is useful if you want the value to be something other than a
string (such as an integer or object), or you want the value set
dynamically.
More details in the documentation
We can use like
<input type="checkbox" data-bind="attr: {value: JSON.parse($data).Id}, checked: $root.selectedIds"/>
and write a click event in the checkbox to get a selected data or subscripe method for selectedIds and get the selected id entire details as a JSON and we have to use JSON.parse to get the data.
but I don't how to store entire object with out JSON.