I am using a Kendo template with an array to dynamically add rows to a form, however, when I push an item onto the array, it adds two rows, both bound to the same data object in the MVVM (so with two objects, there are four rows). I've run the page with a debugger; line in the template, and as suspected, it hits twice before it finishes.
And what's weirder is that it renders the rows in order, then in reverse order, so if I make a change on the first row, it then makes the same change to the last row (since they are bound to the same object in the array), etc.
HTML
Here is the HTML where the form resides (the observable object classInfo is already bound to the <form> tag, hence the reason it's missing from the data-bind):
<fieldset id="classWhen">
<p>
<!-- other form stuff -->
</p>
<div id="classDates" data-bind="source: classInfo.ClassDates" data-template="classDateTemplate"></div>
</fieldset>
Kendo Template
Here is the template which is a row that contains a dropdown list and two date pickers:
<script id='classDateTemplate' type='text/kendo-ui-template'>
<p>
<select class="classDateTypeDropdown">
<option><!-- TYPE 1 --></option>
<option><!-- TYPE 2 --></option>
<option><!-- TYPE 3 --></option>
<option><!-- TYPE 4 --></option>
</select>
<input class="classDatePicker" data-bind="value: DateStart" style="width: 125px;" /> to
<input class="classDatePicker" data-bind="value: DateStop" style="width: 125px;" />
</p>
</script>
Kendo Observable Object
Here is the Kendo Observable which has an array, which is formatted as such:
var classModel = new kendo.observable({
classInfo: {
//.....
ClassDates: [],
//.....
}
});
Javascript push Function
And an addDate() function which pushes a new item to the array:
function addDate() {
classModel.get("classInfo.ClassDates").push({
"ClassType": "Type 1",
"DateStart": null,
"DateStop": null
});
//change inputs to DatePickers
//change select to DropDownList
}
I have tried running it without creating the DropDownList and DatePickers, using the basic HTML elements, but with the same result. Any help would be greatly appreciated.
So, I'm not sure why this was happening (some research will need to be involved), but the cause of the problem was with my binding. Apparently the Kendo template does not like binding to object arrays that belong to other objects, as I have with classInfo.ClassDates.
I changed the bindings from:
kendo.bind($('#addClassWindow'), classModel);
<div data-bind="source:classInfo.ClassDates"data-template="classDateTemplate"></div>
to:
kendo.bind($('#addClassWindow'), classModel.classInfo);
<div data-bind="source:ClassDates"data-template="classDateTemplate"></div>
and now it works fine. For whatever reason.
Related
I'm using this angular treeview project:
https://github.com/nickperkinslondon/angular-bootstrap-nav-tree
I think that this treeview haven't got functions to do searches over treeview, so I implemented mine using a form to write the label to find.
<form name="searchForm" novalidate style="margin-bottom: 50px">
<div> <input ng-model="search" type="text" class="form-control" placeholder="Buscar..." name="searchTerm" required />
</div>
</form>
This form has a .watch to detect when the user writes some text:
$scope.$watch('search', function(newTerm, oldTerm) {
filteredResponse = !newTerm ? response : updateFilteredResponse(normalize(newTerm));
updateTree();
}, true);
The function 'updateFilteredResponse' filter the nodes with label containing newTerm over original data set (read from json) and returns an array with the items to show in treeview.
The 'updateTree' function use this array and transform my custom items in the array to treeview items to add to the treeview. This items are added to
$scope.tree_data = [];
And this array is the one that uses abn-tree directive:
<abn-tree tree-data="tree_data" tree-control="my_tree" ng-if="loaded" expand-level = "2"></abn-tree>
This part is working fine. My problem comes when the result treeview is shown at screen, the treeview always appears completely collapsed.
If I put a button similar to the library example code like this:
<div style="vertical-align:top">
<button ng-click="my_tree.expand_all()" class="btn btn-default btn-sm">Expand All</button>
</div>
And declaring this in the controller as the example:
var tree;
$scope.my_tree = tree = {};
When the users click the button to expand all over the search results, it works fine. By I need to auto-expand the treeview after a search, and remove the expand-all-button.
For that, I'm trying to call my_tree.expand_all() in my controller. I tried different calls:
$scope.my_tree.expand_all();
tree.expand_all();
In different parts of my controller and my html (using ngIf and onload directives). Even I tried to do a 'watch' over $scope.my_tree for try to use the expand_all() function when var is prepared but I always have the same error:
$scope.my_tree.expand_all is not a function
Can anyone help me with that please?
You can put expand_all() function into setTimeout function as below.
setTimeout(function() {
$scope.my_tree.expand_all();
$scope.$digest();
}, 0);
It have to do because it take some delay time while binding data to treeview.
Working on an update form which I would like to generate and capture inputs for a variable sized array
The current unhappy version only supports the first three statically defined elements in the constituency array. So the inputs look like this...
<input #newConstituency1 class="form-control" value={{legislatorToDisplay?.constituency[0]}}>
<input #newConstituency2 class="form-control" value={{legislatorToDisplay?.constituency[1]}}>
<input #newConstituency3 class="form-control" value={{legislatorToDisplay?.constituency[2]}}>
and the function to update pulls the values of the form using the static octothorpe tags.
updateLegislator(newConstituency1.value, newConstituency2.value, newConstituency3.value)
But this doesn't allow for a variable sized Constituency array.
I am able to use *ngFor directive to dynamically create input fields for a theoretically infinitely sized constituency array:
<div *ngfor constit of legislatorToDisplay?.constituency>
<input value={{constit}}>
</div>
but have not successfully been able to capture that information thereafter. Any kind assistance would be greatly appreciated.
You just have to have a form object in your component that matches the HTML input components that were created.
Template
<div *ngfor constit of legislatorToDisplay?.constituency>
<input value={{constit}} formControlName="{{constit}}">
</div>
Component
/* create an empty form then loop through values and add control
fb is a FormBuilder object. */
let form = this.fb.group({});
for(let const of legislatorToDisplay.constituency) {
form.addControl(new FormControl(const))
}
Use two-way data binding:
<div *ngFor="constit of legislatorToDisplay?.constituency; let i = index">
<input [(ngModel)]="legislatorToDisplay?.constituency[i]">
</div>
I'm new to AngularJS and JavaScript. I've come up with a way to build a multi-select list, which is working. The ng-model associated with the dropdown is part of a "user" DTO object, specifically a member which contains an array of "groups" the user can belong to. The "master list" of possible groups are read from a database table using a webservice, put in an array and this is what's used to build the dropdown when the page is displayed.
Those elements in the list that are included in the "groups" field of the user object are displayed below the dropdown in a "preview" field. That's all working - if a user has two selected groups, they come up in that pre-field when the page is populated... but I don't understand why these groups are not highlighted in the dropdown. Sometimes they are...like sometimes when I refresh the page, but most of the time when the page is displayed and populated from the user information, these groups contained in the user are not highlighted in the dropdown.
Here's how the code is set up (again, I'm new to AngularJS/JavaScript and webservices so bear with me).
The HTML code is like this:
<div class="form-group">
<label for="Groups" class="col-sm-2 control-label">Group Memberships: </label>
<div class="col-sm-10">
<select name="userGroups" id="userGroups" ng-model="user.userGroups" multiple style="width: 300px;">
<option ng-repeat="group in approverGroups" value="{{group.name}}" selected >{{group.name}}</option>
</select>
<pre>{{user.userGroups.toString()}}</pre>
</div>
</div>
The JavaScript side looks like this. The "get" is used to read all possible groups from a table, and that populates the dropdown:
$http({
method : 'GET',
url : '/pkgAART/rs/ApproverGroupService/approvergroups',
data : {
applicationId : 3
}
}).success(function(result) {
// Create an array from the groups for use
// in the multi-select UI component for groups
var arr = [];
for (var i = 0; i < result.approvergroup.length; i++) {
var id = result.approvergroup[i].approverGroupId;
var value = result.approvergroup[i].name;
var pair = {id : id, name : value };
arr.push(pair);
}
$scope.approverGroups = arr;
});
Here's a screenshot of the page. This is how it looks:
So again, it works, and "SOMETIMES" when I pull up the page, the items listed in the lower <pre> box are actually highlighted in the dropdown, but not often. I don't understand how to ensure they come up highlighted. In the picture I manually clicked these elements. But if I refresh the page, sometimes they are and sometimes they are not.
I think I figured it out. Per Peter's suggestion I changed to ng-options for the dropdown, but modified the array that I use as my options to just use the name string. Here's the HTML
<div class="form-group">
<label for="Groups" class="col-sm-2 control-label">Group Memberships: </label>
<div class="col-sm-10">
<select name="userGroups"
id="userGroups"
ng-model="user.userGroups"
multiple="true"
style="width: 300px;"
ng-options="group for group in approverGroups">
</select>
<pre>{{user.userGroups.toString()}}</pre>
</div>
</div>
and the js file that builds up the array of strings looks like this:
$http({
method : 'GET',
url : '/pkgAART/rs/ApproverGroupService/approvergroups',
data : {
applicationId : 3
}
}).success(function(result) {
// create an array from the groups for use
// in the multi-select UI component for groups
var arr = [];
for (var i = 0; i < result.approvergroup.length; i++) {
var value = result.approvergroup[i].name;
arr.push(value);
}
$scope.approverGroups = arr;
});
It's now showing the multi-select list's items as selected if they are contained in "user.userGroups"
Mark
I think I figured it out.
First off, you should use ng-options in the select instead of ng-repeat on <option>. This makes it so the options are bound to the model of the select.
Check out this fiddle: http://jsfiddle.net/mcxqjngm/3/
Here is the relevant select html:
<select
name="userGroups"
id="userGroups"
ng-model="user.userGroups"
multiple="true"
style="width: 300px;"
ng-options="group.name for group in approverGroups track by group.id">
</select>
The button mimics an ajax call. I gotta run, but I can answer questions in a bit.
I have lots of models and show them in tables. When user needs to do something with several models, we need to give him ability to choose rows.
How can I implement it with checkboxes? Of course I don't want to create special field on my models for every table.
This is simple example.
https://ember-twiddle.com/0b8f429f6ad3e0572438
My tries were:
{{input type='checkbox' checked=model.someNotExistedField}}
But in this case input just doesnt work.
And:
<input type="checkbox" {{action marked model}} checked={{in-arr record selectedItems}} />
In second example I've tried to keep selected ids in an array on context. But id doesnt work too.
There are a few steps to solving this problem, which you have not shown in your code examples.
you dont need to worry about binding a checked value on the checkbox.. it can manage its own internal state, and you can take advantage of it when selecting an item... a native <input> should be fine
<input type="checkbox">
You will need an action (preferably a closure action) that handles what to do when a record is selected
onchange={{action (action "valueHasChanged" item) value="target.checked"}}
You will need an array to store the "selected items"
this.selectedItems = [];
I put together a twiddle as one example of how these pieces fit together.
(This answer should be valid with ember version 1.13.0 and up)
I'am guessing your model is an array of rows.
So try adding a chked (boolean) property to the model structure so you now have one for each row and bind it to the respective checkbox.
I finished with such technic:
components/action-based-checkbox.hbs
{{#if checked}}
<input type="checkbox" {{action changed}} checked="checked" />
{{else}}
<input type="checkbox" {{action changed}} />
{{/if}}
In context we have array with selected items, for instance "selectedItems"
In context we have an action, that manages array
//2 and 3 steps example
actions:{
marked(id){
this.selectedItems.push(id);
var uniq = this.selectedItems.uniq();
this.set('selectedItems', uniq);
},
selectedItems:[],
4.next I put the component to my cell
{{inputs/action-based-checkbox changed=(action marked record.id) checked=(in-arr record.id selectedItems)}}
in-arr my "in_array?" helper, ember 2.3
I'm having some trouble understanding how ko.mapping.fromJS and ko.mapping.toJS work.
Here the explanation of my problem simplified:
I have a Risk Array object coming from the Server, that Risk array has a Places array.
For some strange reason, after calling the ko.mapping.FromJS my child Places array gets cleared or hidden, so my template can't access its contents... I found that by using ko.mapping.ToJS I can get access to Places contents but by doing this It doesn't seem to refresh my template after adding an Item!
I'm trying to build a very simple grid where I can add places to the first Risk in the array for simplification purposes:
var FromServer = {"Risk":[{"SourceKey":0,"Places":{"Place":[{"SourceKey":1}]}}]}
var viewModel =
{
places : ko.mapping.fromJS(FromServer.Risk[0].Places.Place),
addPlace : function()
{
alert('Entering add place, places count:' + this.places.length);
this.places.push({SourceKey:"New SK"});
}
}
//If I leave this line it will update list but not refresh template
//If I comment it out it will show my Places array as empty!!!
viewModel = ko.mapping.toJS(viewModel)
ko.applyBindings(viewModel);
Here my sample HTML code for my grid:
<p>You have asked for <span data-bind="text: places.length"> </span> place(s)</p>
<table data-bind="visible: places.length > 0">
<thead>
<tr>
<th>SourceKey</th>
</tr>
</thead>
<tbody data-bind='template: { name: "placeRowTemplate", foreach: places}'></tbody>
</table>
<button data-bind="click: addPlace">Add Place</button>
<script type="text/html" id="placeRowTemplate">
<tr>
<td><input class="required" data-bind="value: $data.SourceKey, uniqueName: true"/></td>
</tr>
</script>
Here is my jsFiddle: jsFiddle Sample
My question is: why do I have to unwrap my viewModel with ko.mapping.ToJS so I can manipulate my child array?, and how can I have my template refreshing in this scenario?
Please help!
You had a few things wrong with your code. New JSFiddle here:
http://jsfiddle.net/ueGAA/4/
You needed to create an observable array for the places array, otherwise knockout will not know when it has been updated. The method call is
ko.observableArray(arrayVar)
You do no want to call toJS on your view model. That unwraps all of the observables and makes Knockout not capable of updating your bindings
When referencing an observable array, you need to use parens: ie. viewModel.places().length.
In your FromServer object your Place object contained an array with the object {"SourceKey": 1} inside of it. I assumed you intended for the place object to just have a simple property called SourceKey