Issues in Implementing a Drop Down with Knockout - javascript

I am trying to replace a text box in the existing website with a dropdown menu with few options in it. Everything is working fine expect the value is not being stored/registered when the person hits register. But with the text box it works fine. Please see the code below that i have made for the drop down:
</label>
<label class="label-4 lcol1" for="d_name-suffix"><small>(optional)</small>
<select id="d_name-suffix" style="width:auto; height:auto" data-bind="options: $root.nameSuffix, value: nameSuffix, optionsText: 'options1'" />
</label>
JS:
self.nameSuffix = ko.observable([
{ options1: "Mr" },
{ options1: "Mrs" },
{ options1: "Miss" }
]).extend({ pattern: NineElevenRegistries.inputValidation.name });
And here is the code that was implemented for the textbox:
self.nameSuffix = ko.observable().extend({
maxLength: NineElevenRegistries.inputValidation.nameSuffixMaxLength,
pattern: NineElevenRegistries.inputValidation.name
});

You need to use an array for the values and store the value in an observable.
self.nameSuffixes = ko.observableArray([
{ options1: "Mr" },
{ options1: "Mrs" },
{ options1: "Miss" }
]);
self.nameSuffix = ko.observable();
And in your view -
<select id="d_name-suffix" style="width:auto; height:auto" data-bind="options: $root.nameSuffixes, value: nameSuffix, optionsText: 'options1'" />

So actually i figured out the solution to this problem. Instead of making Mr, Mrs etc as objects, i created string using the following array:
self.nameSuffixes = ko.observableArray([ "","Mr.","Mrs","Miss"]);
self.nameSuffix = ko.observable();

Related

Dropdown menus in AngularJS

I have two dropdown menus in my angular app. When I select an option in the 1st dropdown menu, I want that choice to effect the 2nd dropdown menu. Example: 1st menu will have a list of status messages. When I select, let's say, "Need Maintenance" It will change the 2nd menu to the departments relevant to the Maintenance. Or if I choose the status "Lost", it will change the 2nd menu to the departments relevant to the Lost status. Here is my code and setup:
.controller('HomeCtrl', function($scope, $rootScope, $http, $ionicPlatform) {
// This disables the user from being able to go back to the previous view
$ionicPlatform.registerBackButtonAction(function (event) {
event.preventDefault();
}, 100);
// This function only gets called when the submit button is hit on the messaging screen. It posts to server for sending of message
$scope.submit = function() {
$http.post('http://localhost:8000/', {
messageText: $scope.messageText,
name: window.localStorage.getItem("username")
});
$scope.messageText = ""; //clearing the message box
}
})
<div class="form1" ng-controller="HomeCtrl">
<form class="form">
<select placeholder="Select a Subject" ng-model="selectSubject" data-ng-options="option.subject for option in subject"></select>
<select placeholder="Select a Department" ng-model="selectDept" data-ng-options="option.dept for option in dept"></select>
<input type="text" class="messageText" placeholder="Message" ng-model="messageText">
<button class="button" ng-click="submit()"><span>Submit</span></button>
</form>
</div>
That is my controller relevant to the html that is also posted.
I know how to get the information I need from my node server which will be housing the information. All I need help figuring out is changing the options within the 2nd menu when a option is clicked in the 1st menu.
I am thinking just having a http.get call when a option is clicked which will then populate the 2nd menu. The first menu will be static, not changing.
I've just started messing around with Angular and a database and and was able to dynamically populate one select based on the choice in another.
Below is the HTML for my two select boxes (my second select is a multiple, but you can obviously remove that attribute):
<label>Select a Table</label>
<select name="tableDropDown" id="tableDropDown"
ng-options="table.name for table in data.availableTables"
ng-model="data.selectedTable"
ng-change="getTableColumns()">
</select>
<label>Select Columns</label>
<select name="columnMultiple" id="columnMultiple"
ng-options="column.name for column in data.availableColumns"
ng-model="data.selectedColumns"
multiple>
</select>
And I have the controller below. The init function clears all of the data sources for the two select's, retrieves the list of tables (for the first select), sets the selected table to the first table in the list, and then calls the second function.
The second function (which is also called whenever the selected table changes, thanks to the ng-change directive) retrieves the metadata for the selected table and uses it to populate the second select with the list of available columns.
.controller('SimpleController', function($scope, $http) {
init();
function init() {
$scope.data = {
availableTables: [],
availableColumns: [],
selectedTable: {}
};
$http.get("http://some.server.address")
.then(function (response) {
$scope.data.availableTables = response.data.value;
$scope.data.selectedTable = $scope.data.availableTables[0];
$scope.getTableColumns();
});
}
$scope.getTableColumns = function () {
$scope.data.selectedColumns = [];
table = $scope.data.selectedTable.url;
if (table != "") {
$http.get("http://some.server.address/" + table + "/$metadata?#json")
.then(function (response) {
$scope.data.availableColumns = response.data.value;
});
}
}
...
});
maybe you can use the ng-change directive on the 1st select and using the callback function to populate the 2nd select in the way you prefer( http call or local data).
if your Departments objects has a references to the Subject object, something like a subject_id, you can just do a filter:
Example:
<div ng-controller="MyCtrl">
subjects
<select placeholder="Select a Subject" ng-model="selectSubject" ng-options="subject.name for subject in subjects"></select>
departmets
<select placeholder="Select a Department" ng-model="selectDept" ng-options="dept.name for dept in depts | filter:{ subject_id : selectSubject.id }"></select>
</div>
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.subjects = [
{
id: 1,
name: "sub 1",
},
{
id: 2,
name: "sub 2",
}
];
$scope.depts = [
{
id: 1,
name: "Dep 1",
subject_id: 1
},
{
id: 2,
name: "Dep 2",
subject_id: 1
},
{
id: 3,
name: "Dep 3",
subject_id: 2
},
{
id: 4,
name: "Dep 4",
subject_id: 2
}
]
}

Data-Binding with Knockout JS

I am having a problem not being able to bind my data and then display it using cshtml. I have tried different methods of making observable arrays and I am thinking, my main problem comes from trying to utilize my supposedly "bounded-data"...The following is my cshtml(c#-html) code, and then my js code.
<!--*****Unfinished*****-->
<td>
<label class="element-label">Continuous (Vibratory) Acceleration</label>
<select class="form-control device-family-selector" , data-bind="options: changeAuxFlange.availableVForces, optionsText: 'forceName', value: changeAuxFlange.selectedForces, optionCaption: 'Choose a force...'"></select>
</td>
<td>
<input style="width:50px; text-align:right;" , data-bind="text: changeAuxFlange.selectedForces" />
</td>
</tr>
<tr>
<td>
<label class="element-label">Maximum (Shock) Acceleration</label>
<select class="form-control device-family-selector" , data-bind="options: changeAuxFlange.availableSForces, optionsText: 'forceName', value: changeAuxFlange.selectedForces, optionCaption: 'Choose a force...'"></select>
</td>
<td>
<input style="width:50px; text-align:right;" , data-bind="value: changeAuxFlange.selectedForces" />
</td>
<!--**********-->
view model:
"use strict";
function ViewModel()
{
// it would make more sense with the current setup to make the ViewModel be the Application, but I have set it up like this in case some day it is desired that this tool creates multiple applications in one session
this.application = ko.observable(new Application('New Application', this));
this.requestSearchMode = ko.observable(false);
}
function Application(name, parentViewModel)
{....
this.sections =
{
gForceSection: initGforceSection(this),
pumpSection: initPumpSection(this),
calcLoadsSection: initCalcLoadsSection(this)
}....
}
function initGforceSection(application)
{
var data = ko.observableArray();
var gForceSection = new Section('G-Forces', data, application);
var self = this;
var Force = function (name, value) {
this.forceName = name;
this.forceValue = value;
};
var vibForce = {
availableVForces: ko.observableArray([
{ vForce: "Skid steer loader", value: 4 },
{ vForce: "Trencher (rubber tires)", value: 3 },
{ vForce: "Asphalt paver", value: 2 },
{ vForce: "Windrower", value: 2 },
{ vForce: "Aerial lift", value: 1.5 },
{ vForce: "Turf care vehicle", value: 1.5 },
{ vForce: "Vibratory roller", value: 6 }
]),
selectedForces: ko.observable()
};
var shockForce = {
availableSForces: ko.observableArray([
{ sForce: "Skid steer loader", value: 10 },
{ sForce: "Trencher (rubber tires)", value: 8 },
{ sForce: "Asphalt paver", value: 6 },
{ sForce: "Windrower", value: 5 },
{ sForce: "Aerial lift", value: 4 },
{ sForce: "Turf care vehicle", value: 4 },
{ sForce: "Vibratory roller", value: 10 }
]),
selectedForces: ko.observable()
};
gForceSection.families = ko.observableArray();
productData.getPumpFamilies(function (data) {
gForceSection.families(data);
addPump(application);
});
gForceSection.tbxNumberofPumps = ko.computed(function () { return gForceSection.data().length });
return gForceSection;
}
//CREATE VIEWMODEL
var viewModel = new ViewModel;
ko.applyBindings(viewModel);
/******/
The viewModels is a series of nested objects which makes references quite complicated. I can see you're trying to logically structure the data but it makes it difficult to help. Knockout has a context for binding which starts with the bound viewmodel. You can change the context for an element/section using the with binding.
Otherwise you have to give Knockout a full path, e.g. data-bind="value: app.gforcesection.someitem.someProperty - this can be cause errors if an item in the path is undefined.
I've removed a lot of the structure to make it a working sample to try to help:
http://jsfiddle.net/Quango/3y9qhnv9/
The new viewModel is now a 'flat' object with all the properties on it directly. I wasn't sure why you bound the input boxes to the force so I amended those to bind to the value property of each one. Hope this helps you in the right direction.

Dependent combo box populating in Dojo Dijit?

I have a dojo combobox which when the options are changed i need to populate a related second combo box
which is co dependent on the value of the first combo box. How can i achieve this. The code i have tried so far is below
html code:
<div class="gis_SearchDijit">
<div class="formContainer">
<div data-dojo-type="dijit.form.Form" data-dojo-attach-point="searchFormDijit">
<table cellspacing="5" style="width:100%; height: 49px;">
<tr>
<td>
<label for="Customer">Customer:</label>
<div data-dojo-type="dijit.form.ComboBox" id="Customer"></div> <br />
<label for="Attributes">Search Attribute:</label>
<div data-dojo-type="dijit.form.ComboBox" id="Attributes"></div>
<br />
Enter Attribute Value:<input id="searchText" type="text" data-dojo-type="dijit.form.ValidationTextBox" data-dojo-props="name:'searchText',trim:true,required:true,style:'width:100%;'"
/>
</td>
</tr>
</table>
</div>
</div>
<div class="buttonActionBar">
<div data-dojo-type="dijit.form.Button" data-dojo-props="busyLabel:'searching',iconClass:'searchIcon'" data-dojo-attach-event="click:search">
Search
</div>
</div>
postCreate: function () {
this.inherited(arguments);
this.populateCmbCustomer(Memory);
var comboBoxDigit = registry.byId("Customer");
comboBoxDigit.on('change', function (evt) {
alert('on change'); //This alert is triggerd
this.populateCmbCustomerAttribute(Memory);
});
populateCmbCustomer: function (Memory) {
var stateStore = new Memory({
data: [
{ name: "Cust Data", id: "CUST" },
{ name: "Owner Data", id: "Owner" },
{ name: "Applicant", id: "Applicant" }
]
});
var comboBoxDigit = registry.byId("Customer");
//console.log("COMBO: ", comboBoxDigit);
comboBoxDigit.set("store", stateStore);
},
populateCmbCustomerAttribute: function (Memory) {
var custAttribute = new Memory({
data: [
{ name: "Custid", id: "Cust" },
{ name: "Applicant Id", id: "Applicant" },
{ name: "Owner_Id", id: "Owner" }
]
});
var CmbCustomerAttribute = registry.byId("Attributes");
CmbCustomerAttribute.set("store", custAttribute);
},
Note: the above is just part of the code of my widjet.js.
I am able to load the first combobox with the options. So now when i chose 'Cust Data' in my first combobox then custid should be the only option in second combo box. In the same way Owner Data in first then Owner_id in second...But so far i am not able to populate the second combo-box.. Can some one please guide me
Than You in advance
you should use the query property of the second combo it should look something like this
comboBoxDigit.on('change', function (evt) {
var CmbCustomerAttribute = registry.byId("Attributes");
CmbCustomerAttribute.set("query", {filteringProperty: this.get('value')}); //if you dont understand this check out the stores tutorials
});
EDIT: tutorials referenced in above code snippet helps clear up the ambiguity of "filteringProperty" quite a bit.
I would also recomend to populate your sencond combo from the beggining with something like this
var CmbCustomerAttribute = registry.byId("Attributes");
CmbCustomerAttribute.set("store", store);
CmbCustomerAttribute.set("query", {id:'nonExistantId'}); //so nothing shows in the combo
I think somthing like this is what you are seraching for, any doubts just ask. as cant say mucho more whitout a fiddle or actual code

Knockout two-way bindings not working on checkbox

I have a form which generates a list options for the user, each option has a check-box, a label and an input field. The input field should only be shown whilst the check-box is ticked. The options are generated through a JSON call.
However, knockout doesn't seem to be doing what I would have expected when using the visible binding. When I check a row, the text box is correctly shown but when I uncheck it, the text box stays shown.
I suspect this is something to-do with the observable "selected" being overridden or something like that but I am stuck for ideas.
Here is a fiddle showing the issue: http://jsfiddle.net/qccQs/2/
Here is the HTML I am using in the fiddle:
<div data-bind="template: { name: 'reason-template', foreach: reasonList }"></div>
<script type="text/html" id="reason-template">
<div>
<input type="checkbox" data-bind="value: selected" />
<span data-bind="text: name"></span>
<input type="text" class="datepicker" data-bind="value: date, visible: selected" />
</div>
</script>
Here is the javascript that I am using in the fiddle:
function ReasonItem(name) {
this.name = ko.observable(name);
this.date = ko.observable(null);
this.selected = ko.observable(false);
};
function MyViewModel() {
var self = this;
self.reasonList = ko.observableArray([ ])
};
var vm = new MyViewModel();
new Request.JSON({
url: '/echo/json/',
data: {
json: JSON.encode({
data: [
{ name: "Reason 1", selected: false, date: null },
{ name: "Reason 2", selected: false, date: null },
{ name: "Reason 3", selected: false, date: null }
]
}),
delay: 0
},
onSuccess: function(response) {
$.each(response.data, function(index, reason) {
vm.reasonList.push(new ReasonItem(reason.name));
});
}
}).send();
ko.applyBindings(vm);
Any ideas on how I can get this to behave like I expected it to?
For inputs of checkbox type you need to use checked instead of value:
<input type="checkbox" data-bind="checked: selected" />
See Knockout Documentation.

knockout.js - nested array data and cascading pre-populated dropdown lists binding

I'm fairly new to knockout.js, however, I've been happily using it in my ASP.NET MVC 4 project, until I ran into this obstacle which has been bothering me for a while, can't seem to put my finger on it.
The scenario which I'm working on requires several combinations of location data (region, country, city), i.e. cascading dropdown lists, which isn't a problem to do when inputting fresh data, but I ran into problem(s) when trying to edit the saved data.
Data is in JSON format, with nested arrays, looks like this (shortened for illustration purposes):
var newData =
[
{
"ID":1,
"Name":"Australia and New Zealand",
"Countries":[
{
"ID":13,
"Name":"Australia",
"Cities":[
{
"ID":19,
"Name":"Brisbane"
},
{
"ID":28,
"Name":"Cairns"
},
...
I suspect I can't load the data (or more clearly, to bind it) properly since I'm having trouble accessing the Region sub-array (which contains Region's Countries) and the Countries sub-array (which contains Countries' Cities).
Then there's the matter of having prepopulated options, which works partially, the viewmodel loads the number of lines, but doesn't select anything.
Here's the VM:
var existingRows = [
{
"Region": 1,
"Country": 13,
"City": 19
},
{
"Region": 1,
"Country": 158,
"City": 3
}];
var Location = function (region, country, city) {
var self = this;
self.region = ko.observable(region);
self.country = ko.observable(country);
self.city = ko.observable(city);
// Whenever the region changes, reset the country selection
self.region.subscribe(function () {
self.country(undefined);
});
// Whenever the country changes, reset the city selection
self.country.subscribe(function () {
self.city(undefined);
});
};
var LocationViewModel = function (data) {
var self = this;
self.lines = ko.observableArray(ko.utils.arrayMap(data, function (row)
{
var rowRegion = ko.utils.arrayFirst(newData, function (region)
{
return region.ID == row.Region;
});
var rowCountry = ko.utils.arrayFirst(rowRegion.Countries, function (country) {
return country.ID == row.Country;
});
var rowCity = ko.utils.arrayFirst(rowCountry.Cities, function (city) {
return city.ID == row.City;
});
return new Location(rowRegion, rowCountry, rowCity);
}));
// Operations
self.addLine = function () {
self.lines.push(new Location())
};
self.removeLine = function (line) {
self.lines.remove(line)
};
};
var lvm = new LocationViewModel(existingRows);
$(function () {
ko.applyBindings(lvm);
});
HTML code:
<tbody data-bind="foreach: lines">
<tr>
<td><select data-bind="options: newData, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a region...', attr: { name: 'SubRegionIndex' + '['+$index()+']' }, value: region"></select></td>
<td><select data-bind="options: Countries, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a country...', attr: { name: 'CountryIndex' + '['+$index()+']' }, value: country"></select></td>
<td><select data-bind="options: Cities, optionsText: 'Name', optionsValue: 'ID', optionsCaption: 'Select a city...', attr: { name: 'CityIndex' + '['+$index()+']' }, value: city"></select></td>
<td><a href='#' data-bind='click: $parent.removeLine'>Remove</a></td>
</tr>
</tbody>
I tried to modify the Cart editor example from the knockout.js website with prepopulated data, but haven't really made much progress, I seem to be missing something. Didn't really find anything with nested arrays so I'm stuck here...
I've put up the full code on JSFiddle here:
http://jsfiddle.net/fgXA2/1/
Any help would be appreciated.
The problem is the way in which you are binding to the selected item in your select lists:
<select data-bind="
options: newData,
optionsText: 'Name',
optionsValue: 'ID',
value: region">
</select>
Here you are binding the ID property from your JSON data to the region property on your view model.
This means that when you bind your second select list:
<td data-bind="with: region">
<select data-bind="
options: Countries,
optionsText: 'Name',
optionsValue: 'ID',
value: $parent.country">
</select>
</td>
You attempt to bind to region.Countries. However, region simply contains the selected region ID. In this case the console is your friend:
Uncaught Error: Unable to parse bindings. Message: ReferenceError:
Countries is not defined;
The same problem is true of your third select list for Cities since you are now attempting to bind to country.Cities where country is also just the ID.
There are two options available here. The first is to remove the optionsValue parameters, thus binding the actual JSON objects to your view model properties. That and a binding error on your Cities select box (you were binding to CityName instead of Name) were the only problems:
http://jsfiddle.net/benfosterdev/wHtRZ/
As you can see from the example I've used the ko.toJSON utility to output your view model's object graph. This can be very useful in resolving problems (in your case you would have seen that the region property was just an number).
The downside of the above approach is that you end up storing a copy of all of the countries, and their cities for the selected country in your view model.
A better solution if dealing with large data sets would be to only store the selected identifier (which I believe you were attempting to do originally) and then define computed properties that filter your single data set for the required values.
An example of this can be seen at http://jsfiddle.net/benfosterdev/Bbbt3, using the following computed properties:
var getById = function (items, id) {
return ko.utils.arrayFirst(items, function (item) {
return item.ID === id;
});
};
this.countries = ko.computed(function () {
var region = getById(this.selectedRegion.regions, this.selectedRegion());
return region ? ko.utils.arrayMap(region.Countries, function (item) {
return {
ID: item.ID,
Name: item.Name
};
}) : [];
}, this);
this.cities = ko.computed(function () {
var region = getById(this.selectedRegion.regions, this.selectedRegion());
if (region) {
var country = getById(region.Countries, this.selectedCountry());
if (country) {
return country.Cities;
}
}
}, this);
You can see from the rendered object graph that only the currently selected countries and cities are copied to the view model.

Categories