I trying to develop a little app.
I have made a login view and it works, I am able to bind the data written by the user. After this, I show two select box. The first is binded with a list (correctly) and the second has to be bind with a list populated after a value is selected from the first.
I'm not able to read the value selected from the first list.
I have this html:
<div class="dx-fieldset">
<div class="dx-field">
<div class="dx-field-label">Rete</div>
<div class="dx-field-value"
data-bind="dxLookup: { dataSource: is_retistiSource, value: rete, displayExpr: 'NOME', title: 'Retisti associati', placeholder: 'Selezionare rete',
onSelectionChanged:setRete }" />
</div>
<div class="dx-field">
<div class="dx-field-label">Impianto</div>
<div class="dx-field-value"
data-bind="dxLookup: { dataSource: is_impiantiSource, value: impianto, displayExpr: 'NOME', title: 'Impianti associati', placeholder: 'Selezionare impianto' }" />
</div>
</div>
and this javascript:
OverviewAPP.afterLogin = function (params) {
var isReady = $.Deferred();
var viewModel = {
rete: ko.observable(""),
impianto: ko.observable(""),
is_retistiSource: OverviewAPP.listaReti,
is_impiantiSource: OverviewAPP.listaImpianti,
setRete: function () {
console.log(viewModel.rete);
var nRetisti = OverviewAPP.listaRetiImpianti.length;
for (i = 0; i < nRetisti; i++) {
if (OverviewAPP.listaRetiImpianti[i]["retista"]["NOME"] == this.rete)
{
OverviewAPP.listaImpianti = listaRetiImpianti[i]["listaImpianti"];
break;
}
}
is_impiantiSource = OverviewAPP.listaImpianti;
},
close: function () {
OverviewAPP.app.back();
}
};
return viewModel;
};
In the setRete function, with the line "console.log(viewModel.rete);", I see this output:
d(){if(0<arguments.length)return d.Wa(c,arguments[0])&&(d.X(),c=arguments[0],d.W()),this;a.k.Ob(d);return c}
Why? How can I bind and read the selected value?
UPDATE: I've done in this way, it works:
setRete: function (e) {
OverviewAPP.IDrete = e.value;
var nRetisti = OverviewAPP.listaRetiImpianti.length;
for (i = 0; i < nRetisti; i++) {
if (OverviewAPP.listaRetiImpianti[i]["retista"]["NOME"] == e.value["NOME"])
{
OverviewAPP.listaImpianti = OverviewAPP.listaRetiImpianti[i]["listaImpianti"];
break;
}
}
//ko.applyBindings(viewModel);
},
But I don't know how update my second list, "is_impiantiSource".
Observables are functions. That's why you get a function in the console. Call the rete function to get its value:
viewmodel.rete();
Also see the Knockout: Observables help topic that describes this (under "Reading and writing observables").
This is how you can obtain a new value. Then, you need to update the dependent lookup data source. For this, make the is_impiantiSource property an observable array:
is_impiantiSource: ko.observableArray(OverviewAPP.listaImpianti),
After this, modify it in setRene like:
viewModel.is_impiantiSource(OverviewAPP.listaImpianti)
Also see Observable Arrays for how to work with arrays in Knockout
Related
I am a database developer (there's the problem) tasked with emitting JSON to be used with Knockout.js to render sets of dependent list items. I have just started working with Knockout, so this is likely something obvious that I am missing.
Here is the markup:
<select data-bind="options:data,
optionsText:'leadTime',
value:leadTimes">
</select>
<!--ko with: leadTimes -->
<select data-bind="options:colors,
optionsText:'name',
optionsValue:'key',
value:$root.colorsByLeadTime">
</select>
<!--/ko-->
Here is the test data and code:
var data = [
{
key:"1",
leadTime:"Standard",
colors:[
{ key:"11", name:"Red" },
{ key:"12", name:"Orange" },
{ key:"13", name:"Yellow" }
]
},
{
key:"2",
leadTime:"Quick",
colors:[
{ key:"21", name:"Black" },
{ key:"22", name:"White" }
]
}
]
var dataViewModel = ko.mapping.fromJS(data);
var mainViewModel = {
data:dataViewModel,
leadTimes:ko.observable(),
colorsByLeadTime:ko.observable()
}
ko.applyBindings(mainViewModel);
As this stands, it correctly populates the value attribute of the second select list. However, if I add optionsValue:'key' to the first select list then the value attribute for that is set correctly but the second select list renders as an empty list.
All I need is for the value attribute of the option tag to be set to the key value provided in the data, regardless of where the select list is in the set of dependent lists. I've looked at many articles and the docs, but this particular scenario (which I would think is very common) is eluding me.
Here is a jsfiddle with the data, JS, and markup as given above: http://jsfiddle.net/tnagle/Lyxjt11y/
To really see the issue, you can add the following code after the initialization of mainViewModel.
mainViewModel.leadTimes.subscribe(function(newValue) {
console.log(newValue);
debugger;
});
Before adding the optionsValue:'key', line above will log the following output.
Object {key: function, leadTime: function, colors: function}
But after adding optionsValue:'key', it log the following output.
"1"
or
"2"
The reason it failed was because when you assign optionsValue: 'key' to the first select list, leadTimes property of your mainViewModel which before will contain an object that has property color, now will be set to a string object. Then the select list just failed to find color property from leadTimes that has changed to a string object.
One of the way to make it work is by changing to this:
var data = [
{
key:"1",
leadTime:"Standard",
colors:[
{ key:"11", name:"Red" },
{ key:"12", name:"Orange" },
{ key:"13", name:"Yellow" }
]
},
{
key:"2",
leadTime:"Quick",
colors:[
{ key:"21", name:"Black" },
{ key:"22", name:"White" }
]
}
]
var dataViewModel = ko.mapping.fromJS(data);
var mainViewModel = new function (){
var self = this;
self.data = dataViewModel;
self.leadTimes = ko.observable();
self.selectedKey = ko.observable();
self.selectedKey.subscribe(function(selectedKey){
self.selectedData(ko.utils.arrayFirst(self.data(), function(item) {
return item.key() == selectedKey;
}));
}, self);
self.colorsByLeadTime = ko.observable();
self.selectedData = ko.observable();
}
ko.applyBindings(mainViewModel);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.3.5/knockout.mapping.js"></script>
<select data-bind="options:data,
optionsText:'leadTime',
optionsValue:'key',
value:selectedKey">
</select>
<!--ko with: selectedData -->
<select data-bind="options:colors,
optionsText:'name',
optionsValue:'key',
value:$root.colorsByLeadTime">
</select>
<!--/ko-->
Basically i have a drop down on selecting which there will be another drop down loaded. I have a computed variable depending on first drop down selected value(I know it can be subscribed,but still). But the computed is executed on page load which i dont want due to an AJAX call inside. 'What is the reason for the execution on page load and how to avoid that?
HTML:
<div>
<select id="selectmenu1" data-bind="options: departments,
optionsValue: 'id',
optionsText: 'name',
optionsCaption: 'Choose...',value: selectedDept">
</select>
<select id="selectmenu1" data-bind="options: contacts,
optionsValue: 'id',
optionsText: 'name',
optionsCaption: 'Choose...'">
</select>
</div>
And JS
// Here's my data model
var ViewModel = function(first, last) {
var self = this;
var deptArray = [];
var deptObj = {
id: "8888",
name: "Electrical"
};
deptArray[0] = deptObj;
deptObj = {
id: "9999",
name: "Admin"
};
deptArray[1] = deptObj;
self.departments = ko.observableArray(deptArray);
self.selectedDept = ko.observable();
self.contacts = ko.observableArray();
self.contactsRetrieve = ko.computed(function() {
var deptId = self.selectedDept();
console.log("entered");
$.ajax({
url: '/echo/js',
complete: function(response) {
console.log("success");
var contactArray = [];
var contactObj = {};
if (deptId == '8888') {
contactObj.id = '1234';
contactObj.name = 'Vivek';
} else if (deptId == '9999') {
contactObj.id = '5678';
contactObj.name = 'Sree';
}
contactArray[0] = contactObj;
self.contacts(contactArray);
}
});
console.log("exited");
});
};
ko.applyBindings(new ViewModel());
https://jsfiddle.net/jtjozkax/37/
if(!self.selectedDept()){return}
Using this if statement to cancel functionality within the computed if there is no selected option.
https://jsfiddle.net/jtjozkax/38/
As far as I know, it is not the page load itself that causes this behavior, but the fact that computed observables rely on other observables, in your case, self.selectedDept. Knockout discovers which other observables the computed relies on, and it is actually the change of value of selectedDept what causes the computed to be recomputed.
You cannot avoid this behavior, but you can avoid the execution of the AJAX call by adding a guarding condition (read: if statement). I assume your goal is to prevent the AJAX-call if nothing is selected in the dropdown, and, afterall, this is the most straightforward way to accomplish just that.
There is no other way around it, simply because if you think about it, the whole purpose of a computed is to reevalue itself whenever any other observable it depends on changes, without manual intervention.
I am currently having a problem:
I need to create md-checkboxes from a Database. This part works fine with ng-repeat. But I am having a problem reading those checkboxes.
Every entry in the Database has its own unique ID (I am using RethinkDB) so I thought I just can apply this as an ID.
md-card(ng-repeat="n in ideas")
md-card-content
span
md-checkbox(type="checkbox" id='n.id') {{n.idea}}
I am working with Jade / Pug as View Engine.
But how am I now able to read out all checkboxes at once?
I tried many methods like looping through all ElementsByTagName("md-checkbox") and than with a for to read the checked value but it always returns undefined.
const checkboxes = document.getElementsByTagName("md-checkbox");
console.log(checkboxes) //works fine, prints all checkboxes
for (let i = 0; i < checkboxes.length; i++) {
console.log(checkboxes[i].checked); //returns undefined
}
Do you have any Ideas how to read all Boxes at once?
Edit #1 - More Code
index.js
angular.module('votes', ['ngMaterial'])
.controller("VoteMainController", function ($scope) {
$scope.safeApply = function (fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
register = [];
//var to store database content and add it to page
$scope.ideas;
//Downloading Database stuff as JSON
$.ajax({
type: "GET",
url: "./api",
async: true,
success: function (content) {
for (let i = 0; i < content.length; i++) {
register.push({
[content[i].id]: {
checked: false
}
})
}
$scope.ideas = content;
$scope.safeApply();
},
});
function checkChecked() {
const checkboxes = document.getElementsByTagName("md-checkbox");
for (let i = 0; i < checkboxes.length; i++) {
console.log(checkboxes[i].checked);
}
}
})
index.jade
form(id="login" method="post")
md-card(ng-repeat="n in ideas")
md-card-content
span
md-checkbox(type="checkbox" id='n.id') {{n.idea}}
md-input-container
label Andere Idee?
md-icon search
input(name='idea', ng-model='idea', id='idea', type='text')
div(layout="column")
md-button(type='submit', class="md-raised md-primary unselectable")
| Senden!
Your question title asks about assigning ids but Your question, which you wrote at the very end is
"Do you have any Ideas how to read all Boxes at once?"
So if you wanna do something with multiple or all checkbox elements you should assign them some css class, say "idea-checkbox".
Then in css styles or in jQuery you can access all those checkboxes at once by using the dotted syntax of ".idea-checkbox".
css ids are used to distinctively access any one html element and classes are used to get access to all the elements at once which has that class.
You can also try to the use angular's "track by n.id" syntax if you want to track them by their id.
You should be able to do this with ng-list and ng-model:
HTML
<div ng-app="MyApp" ng-controller="MyAppController">
<ng-list>
<ng-list-item ng-repeat="n in ideas">
<p>{{n.id}}: <input type="checkbox" ng-model="n.isChecked"></p>
</ng-list-item>
</ng-list>
<p>Current state of model: {{ ideas }}</p>
</div>
<p>Current state of model: {{ ideas }}</p>
Angular
angular
.module("MyApp", ['ngMessages'])
.controller('MyAppController', AppController);
function AppController($scope) {
$scope.ideas = [{
id: 193872768,
isChecked: false
},{
id: 238923113,
isChecked: false
}];
}
Demo
This also works with the material design counterparts, md-list and md-checkbox.
I'm facing a problem in upgrading my ng-model in selection.
I have the following HTML:
<div ng-app>
<div ng-controller="Ctrl">
<select ng-model="viewmodel.inputDevice"
ng-options="i.label for i in viewmodel.inputDevices">
</select>
</div>
</div>
And the following code:
function Ctrl($scope) {
// view model
$scope.viewmodel = new function () {
var self = this;
var elem1 = {
value: '1',
label: 'input1'
};
var elem2 = {
value: '2',
label: 'input2'
}
self.inputDevices = [elem1, elem2];
self.inputDevice = {
value: '1',
label: 'input1'
};
};
}
You can use the following JSFiddle
What I want to do is put in inputDevice the same values that the first device has in the collection inputDevices.
I know that I can pass elem1 and it will work however i can't do it since i want to save the selection in Local Storage and than restore it to the ng-model object.
Any suggestion will be grateful
Thanks
You can either store the value instead of the object as Maxim has demonstrated, or you can pull the correct value from the inputDevices array with something like:
self.inputDevice = self.inputDevices.filter(function(item) {
return item.value == storedValue.value;
})[0];
as per an updated fiddle
The code in the original question works for me:
<div ng-app>
<div ng-controller="Ctrl">
<select ng-model="viewmodel.inputDevice"
ng-options="i.label for i in viewmodel.inputDevices">
</select>
<!-- displays the initial and any further selections
correctly as : {"value":"1","label":"input1"} -->
<span>{{viewmodel.inputDevice}}</span>
</div>
</div>
Your js code code works no doubt, but the viewmodel can be build a little easier:
function Ctrl($scope) {
// view model
$scope.viewmodel = {inputDevices: [
{value: '1', label: 'input1'},
{value: '2', label: 'input2'}
]};
$scope.viewmodel.inputDevice = $scope.viewmodel.inputDevices[0];
}
jsfiddle http://jsfiddle.net/8t2Ln/39/
Instead:
self.inputDevice = {
value: '1',
label: 'input1'
};
I would store index only:
self.inputDevice = 0; // or 1 - second item
and:
<select>
<option ng-repeat="i in viewmodel.inputDevices"
value="{{i.label}}"
ng-selected="viewmodel.inputDevices.indexOf(i) == viewmodel.inputDevice"
>{{i.label}}</option>
</select>
This way will work.
Fixed Demo Fiddle
Update
My original post is pretty long - here's the tl;dr version:
How do you update all properties of a knockout model after a single property has changed? The update function must reference an observableArray in the viewModel.
-- More details --
I'm using KnockoutJS. I have a Zoo and a Tapir model and three observables in the viewmodel - zoos, tapirCatalog and currentTapir. The tapirCatalog is populated from the server and the currentTapir holds the value of whichever tapir is being edited at the time.
Here's what I'm trying to accomplish: A user has added a tapir from a list of tapirs to his/her zoo. When viewing the zoo, the user can edit a tapir and replace it with another. To do this a popup window is shown with a select form populated by tapir names and a span showing the currently selected GoofinessLevel.
So, when the select element changes this changes the TapirId in currentTapir. I want that to trigger something that changes the currentTapir's Name and GoofinessLevel.
I tried subscribing to currentTapir().GoofinessLevel but cannot get it to trigger:
function Zoo(data) {
this.ZooId = ko.observable(data.ZooId);
this.Tapirs = ko.observableArray(data.Tapirs);
}
function Tapir(data) {
this.TapirId = ko.observable(data.TapirId);
this.Name = ko.observable(data.Name);
this.GoofinessLevel = ko.observable(data.Name);
}
function ViewModel() {
var self = this;
// Initializer, because I get an UncaughtType error because TapirId is undefined when attempting to subscribe to it
var tapirInitializer = { TapirId: 0, Name: "Template", GoofinessLevel: 0 }
self.zoos = ko.observableArray([]);
self.tapirCatalog = ko.observableArray([]);
self.currentTapir = ko.observable(new Tapir(tapirInitializer));
self.currentTapir().TapirId.subscribe(function (newValue) {
console.log("TapirId changed to: " + newValue);
}); // This does not show in console when select element is changed
};
Oddly enough, when I subscribe to the Goofiness level inside the Tapir model I get the trigger:
function Tapir(data) {
var self = this;
self.TapirId = ko.observable(data.TapirId);
self.Name = ko.observable(data.Name);
self.GoofinessLevel = ko.observable(data.Name);
self.TapirId.subscribe(function (newId) {
console.log("new TapirId from internal: " + newId);
}); // This shows up in the console when select element is changed
}
I suspect that this is a pretty common scenario for people using KO but I haven't be able to find anything. And I've searched for a while now (it's possible that I may not have the correct vocabulary to search with?). I did find this solution, but he references the viewmodel from the model itself -- which seems like back coding since I would think the Tapir should not have any knowledge of the Zoo: http://jsfiddle.net/rniemeyer/QREf3/
** Update **
Here's the code for my select element (the parent div has data-bind="with: currentTapir":
<select
data-bind="attr: { id: 'tapirName', name: 'TapirId' },
options: $root.tapirCatalog,
optionsText: 'Name',
optionsValue: 'TapirId',
value: TapirId">
</select>
It sounds like what you need to do is bind the select to an observable instead of the Id
<select
data-bind="attr: { id: 'tapirName', name: 'TapirId' },
options: $root.tapirCatalog,
optionsText: 'Name',
optionsValue: 'TapirId',
value: currentTapir">
</select>