Knockoutjs isn't updating my UI when I change an observableArray - javascript

This is my template:
<tr>
<td>
<table>
<thead>
<th>
<span>Option name:</span>
</th>
</thead>
<tbody data-bind="template: {name: 'optionChoiceTemplate', foreach: choices, templateOptions:{choiceArray: choices} }"></tbody>
</table>
<button data-bind="click: function(){choices.push('');}">Add new</button>
</td>
</tr>
But when I click the "Add new" button, my view doesn't update to include the new option with the empty string. I've checked in the debugger that the empty string is added to the choices, and I've made sure that choices is an observableArray, what else might be going wrong?

The issue is that when using the jQuery Templates plugin with the template binding with the foreach option, empty strings are treated as null values and are not rendered.
You could work around this by using an object {text: ''} and binding against text or by pushing something other than an empty string (like a single space).
Alternatively, if you are able to move to Knockout 2.0 and use the native templates, then your empty string items will be rendered properly.

I created a fiddle that uses your HTML to display a list of items, and allows the user to add a new item in two ways. The first way is using your click function you created. The second way is using a click binding.
This should answer your question.
http://jsfiddle.net/johnpapa/4PfUr/

Related

Vue.js - Problem with dynamically created checkboxes: checking one checks all and can't pass dynamic data in value

I'm having a very tricky problem with dynamic checkboxes in Vue. This is what my code looks like at the moment.
<table class="table table-striped">
<thead>
<tr>
<th>Student</th>
<th>Status</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
<tr v-for="(s,index) in c.data['Students']" :key='index'>
<td> {{s['First Name']}} {{s['Last Name']}} </td>
<td>
<input type="checkbox"
:id="{{s['First Name'] s['Last Name']}}"
value="{{student:s['First Name'], status: absent}}" // need to pass object here
v-model="attendance">
<label for="absentCheckbox">A</label>
</td>
</tr>
</tbody>
</table>
The problem is twofold. First, when one check box is selected it selects all of the others as well. Second, I can't figure out how to pass an object dynamically in the value attribute of each checkbox. I know the value="{ student:s['First Name'], status: absent}" line is completely wrong but I am not sure how to do it or if it's even possible. My attendance array needs to have objects containing both student name and the status values.
The attendance array is on the data function as required like this:
data() {
return {
attendance: [],
}....
Maybe it is not possible to do what I am trying to do so I'd appreciate any tips or advice on how to achieve this. Thanks in advance for any help!
If
value="{{student:s['First Name'], status: absent}}"
Is exactly what you hard coded on your iteration, the note that's the reason why all your checkbox are checked on a click.
The values of the checkbox should be unique.
To pass dynamic values to ur input, you should bind them.
E.g
:value="object"
// Where object is a valid know JavaScript object
Binding let's you pass dynamic value to your template.

Increasing Angular Performance in Custom Angular Dropdown with Filtering Functionality with One Way Binding

I have a custom angular component that works as a dropdown select element with filtering functionality. The input is a basic input element with several angular directives:
<input type="text" class="form-control" ng-change="ctrl.filterTextChangeLocal($event)" ng-model="ctrl.ngModelValue" ng-click="ctrl.openDropdown($event)" />
The above input is responsible for opening the dropdown and allowing the user to type, as the user types the data in the dropdown element is filtered accordingly. The filtering is done in the controller with the basic angular filter:
ctrl.filteredItems = ctrl.$filter("filter")(ctrl.items, ctrl.ngModelValue);
The dropdown element looks like this:
<table class="table">
<thead>
<tr>
<th ng-repeat="heading in ctrl.gridColumnHeaders" class="text-center">{{heading}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in ctrl.filteredItems" ng-mousedown="ctrl.onSelectedLocal(row, $event)">
<td ng-repeat="value in ctrl.sort(row) track by $index" class="text-center">
{{value}}
</td>
</tr>
</tbody>
</table>
All works fine but the as the number of items passed into the component increases (100+ items) and the dropdown increases accordingly, the filtering is slow. Meaning as I type a character the filtering of the dropdown lags behind.
I understand Angular filters aren't the best performing but I believe the majority of the lag is coming from ng-repeat and the two-way binding. I can't create a one-way binding inside the ng-repeat itself as so:
ng-repeat="value in ::ctrl.filteredItems"
because I lose the binding for the filtering. Will just setting a one-way binding on the value increase performance:
{{::value}}
Or what's the best way to increase the performance here? Really need to try and stay with in the Angular framework so looking for an Angular solution.
Thanks
Have you tried to simply apply:
one-time-binding="true"
directly on the ng-repeat?
Usually a two-way-binding on 100+ elements should not hurt performance -
but if you have a nested loop inside your loop, then..

Angular: search filter on ng-repeat containing external data

Buildings have codes. Example: Main Hall's code is "MNH"
Rooms have numbers that are derived from the building's code. Example: "MNH-101"
In the database, the code is attached to the building, not the room. The room just has the building's ID as a foreign key.
So, how can I create a table that contains the rooms written as [Building Code]-[Room Number] while still being able to search on the room's
"full" number: "MNH-101"?
So far, I have this:
<form>
<input type="text" ng-model="searchRoom">
</form>
<table class="table table-striped table-bordered">
<tr ng-repeat="room in view.rooms|filter:searchRoom">
<td>
<a ng-href="room/{{room.id}}">
{{ view.building.code }}-{{ room.number }}
</a>
</td>
</tr>
</table>
The combined room numbers display okay and I can search on the room number, that is "100", but I can't search on "MNH".
I'm pretty new to angular, so my problem is mostly that I don't know what to google for on this one. Ideally, I want to keep the single search box and I also want to keep the building code and the number in the same column.
A custom filter needed?
Any help?
EDIT:
So, in the controller, I just iterated over the rooms and shoved in the building code.
for (var i = rooms.length; i--;) {
rooms[i].number = vm.building.code + "-" + rooms[i].number;
}
Not elegant? Is there a better way?
Generally, it is better to avoid filters where you can, since they can be run on every digest, even if the list or filter criteria haven't changed. Instead, whenever the filter criteria changes, call a controller function to manually create a new filtered list for the view to bind to. This allows you to use any logic you like.
The basic structure then becomes:
<form>
<input type="text" ng-model="searchRoom" ng-change="view.roomFilterChanged(searchRoom)">
</form>
<table class="table table-striped table-bordered">
<tr ng-repeat="room in view.filteredRooms track by room.id">
<td>
<a ng-href="room/{{room.id}}">
{{ view.building.code }}-{{ room.number }}
</a>
</td>
</tr>
</table>
And in your controller:
function RoomViewController() {
// (set this.rooms here)
this.filteredRooms = this.rooms;
}
RoomViewController.prototype.roomFilterChanged = function (filterText) {
this.filteredRooms = this.rooms.filter(function (room) {
// (filter criteria here)
});
};
The exact filter criteria has been left as an exercise for you.
Note that I used track by in the ng-repeat expression. This is a good practice to get into, since it improves performance of ng-repeat whenever the collection changes by allowing it to reuse an existing DOM node for a given item in the collection if it is still in the collection after the change. In this case, the .id is a good choice for a uniquely identifying property on each item.

Adding dynamic row to ngTable not displaying

I have created an application with ngTable using grouping functionality, The application is working fine but the problem is that when I add dynamic data (rows) to the table, its not reflecting dynamically, unless or otherwise when we click the table title for sorting or when we click the pagination
I have recreated the problem within a plunker, there you can find a button, when clicked one Dynamic row is added but not reflecting within the table
PLUNKER
<body ng-app="main" ng-controller="DemoCtrl">
<button ng-click="addDynamicDatas()">Add Datas</button>
<table ng-table="tableParamsOne" class="table">
<tbody ng-repeat="group in $groups">
<tr class="ng-table-group" ng-hide="group.data[0].role==='None'">
<td>
<strong>{{ group.value }}</strong>
</td>
</tr>
<tr ng-repeat="user in group.data">
<td sortable="name" data-title="'Name'">
<span ng-class="{'bold-text': user.role=='None'}" ng-show="user.role==='None'"> {{user.name}}</span>
</td>
<td sortable="age" data-title="'Age'">
{{user.age}}
</td>
</tr>
</tbody>
</table>
</body>
add function
$scope.addDynamicDatas = function()
{
$scope.myDataOne.push({name: "Abcd", age: 10, role: 'Administrator'});
}
Can anyone please tell me some solution for this?
This is probably not an ideal solution but is the only one that I could find.
You can add $scope.tableParamsOne.reload(); after you update your array.
Also currently when your grid is updating when you click a header it is not updating the amount of pages in the pagination. To solve this you can add $scope.tableParamsOne.total($scope.myDataOne.length);
I am also using ng-table(#4.0.0) to make local/client-side insertion/deletion after a successful server submit and got the similar problem of update ng-table. #Bradley's answer above does not work directly. So I chased the source code and found that the reload() called the getData(). After insert a row into the target rowset (ctrl.tbContents.rows in my case), I have to add bellow line in mytbParams definition to make ctrl.tbParams.reload() work:
getData: function() {
return ngTableDefaultGetData(ctrl.tbContents.rows, ctrl.tbParams);
}
Note to inject the ngTableDefaultGetData.

How to map to an Array coming from server object using Knockout Mapping plugin in templates?

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

Categories

Resources