Knockoutjs very slower with 100 records - javascript

I was writing a demo when I saw some result of my knockout page and I was shocked.
This are the results:
What I do is quite simple, when someone ask to load the data I do the following:
self.items = ko.observableArray([]);
self.colors = ko.observableArray([]);
self.productModels = ko.observableArray([]);
self.loadData = function() {
var buffer;
$.getJSON('/Product/InventoryData')
.success(function(allData) {
buffer = [];
buffer = $.map(allData, function(item) { return new SDF.Data.DTO.ProductDto(item); });
self.items(buffer);
})
.error(function() {
alert("error on load data");
});
$.getJSON('/Product/GetColors')
.success(function(allData) {
buffer = [];
ko.utils.arrayForEach(allData, function (item) {
buffer.push(item);
});
self.colors(buffer);
})
.error(function () {
alert("error on load colors");
});
$.getJSON('/Product/GetProductModels')
.success(function (allData) {
buffer = [];
ko.utils.arrayForEach(allData, function (item) {
buffer.push(item.Name);
});
self.productModels(buffer);
})
.error(function () {
alert("error on load product models");
});
};
all the server method results are cached and they are very quickly.
Colors and ProductModels are observable too because I want give to the user the ability to change the "color" or the "model" of a product for each printed item.
The amount of data that I load is just 100 items.
Following my html:
<tbody data-bind="foreach: items">
<tr>
<td>
<input type="text" data-bind="value: name" />
</td>
<td>
<select data-bind="options: $root.colors, optionsCaption: 'Choose...'">
</select>
</td>
<td>
<input type="number" data-bind="value: price" />
</td>
<td>
<select data-bind="options: $root.productModels, optionsCaption: 'Choose...'">
</select>
</td>
<td>
<label data-bind="text: qty">
</label>
</td>
<td>
Sell
</td>
<td>
</td>
</tr>
</tbody>
any suggestion other than "page it" are appreciated.
UPDATE 1
I found which is the problem but now I don't know how can I resolve it.
The problem is how I write the selects foreach Item. Probably there are repaint foreach item.
How can avoid that?
UPDATE 2
The best solution that I found is use the Knockoutjs If-binding in my markup: http://knockoutjs.com/documentation/if-binding.html

Have you got this online anywhere, or could you knock together a quick jsfiddle for this, as it really shouldn't be taking anywhere near that long!
Saying that, why are you looping through the results of your AJAX calls and putting values into an array?
Have you tried only making one of your AJAX calls and seeing what effect that has on the time? Is that most of the html that is on your page, or is there a lot more? I'd be interested to see if it is the data binding that is causing the style calculations and layout time to be so large, as it really shouldn't be.

Related

passing data from clicked item to controller in AngularJS

I am attempting to follow a JSFiddle, where a user can click on a <td> item, edit it, then eventually be able to save the changes.
The example uses ng-repeat and all others I have looked at do to where as I am not, I am using data passed from a resolve command in my route folder.
$stateProvider
.state('app.patents.patent', {
url: '/{patentId}',
component: 'patent',
resolve: {
patent: ['patents', '$stateParams', function(patents, $stateParams) {
return patents.find(function(patent){
return patent.id == $stateParams.patentId;
})
}]
}
})
}]);
I have attempted to use data-id (looked at How to retrieve the clicked ElementId in angularjs?), but with no success, as I assume you cannot use the same id twice and my desired functionality requires two elements that ng-show and ng-hide depending on the boolean value passed to them.
I have now got myself in a confused state, not sure which approach to take.
Question
How do I adapt my code that doesn't use ng-repeat to work with this JSFiddle? OR do you know another apporach I can take to achieve the same results?
<tr>
<th class="text-xs-right">Short Name</th>
<td>
<span data-id="123" ng-hide="$ctrl.shortTitle.editing" ng-dblclick="$ctrl.editItem(123)">{{$ctrl.patent.shortTitle}}</span>
<input type="text" data-id="123" ng-show="$ctrl.shortTitles.editing" ng-blur="$ctrl.doneEditing(123)" ng-model="$ctrl.patent.shortTitle"></input>
</td>
</tr>
angular.module('myApp').component('patent', {
templateUrl: 'p3sweb/app/components/patents/views/patent-item.htm',
controller: function() {
var vm = this;
vm.editItem = function (item) {
item.editing = true;
}
vm.doneEditing = function (item) {
item.editing = false;
};
});
As per my understanding regarding your question I have created a jsfiddle, have a look or you can create a jsfiddle with the issue you are facing for better understanding
JSFiddle
<!DOCTYPE html>
<div ng-app ng-controller="myCtrl" class="container">Double-click on the items below to edit:
<button type="button" ng-click="newItem()">Add item</button>
<table>
<tr ng-repeat="item in items">
<td>
<span ng-hide="item.editing" ng-dblclick="editItem(item)">{{item.name}}</span>
<input ng-show="item.editing" ng-model="item.name" ng-blur="doneEditing(item)" autofocus />
</td>
</tr>
</table>
</div>
You can create an array and connect each input to a specific index starting from 0 and then pass that index to your function call.
<tr>
<th class="text-xs-right">Short Name</th>
<td>
<span ng-hide="$ctrl.editing[1]" ng-dblclick="$ctrl.editItem(1)">{{$ctrl.patent.shortTitle}}</span>
<input type="text" data-id="123" ng-show="$ctrl.editing[1]" ng-blur="$ctrl.doneEditing(1)" ng-model="$ctrl.patent.shortTitle"></input>
</td>
</tr>
angular.module('myApp').component('patent', {
templateUrl: 'p3sweb/app/components/patents/views/patent-item.htm',
controller: function() {
var vm = this;
vm.editing=[];
vm.editItem = function (index) {
vm.editing[index] = true;
}
vm.doneEditing = function (index) {
vm.editing[index] = false;
};
});
Demo: http://jsfiddle.net/F7K63/381/

Jquery to compare the value of dynamically generated control

Hi I am developing a mvc4 jquery application. I have dynamically generated hidden field and I am binding some value to it as below.
#foreach (var group in Model.detailsbyclientId) {
<tr>
<td> #group.clientName </td>
<td> #group.employeeId </td>
<td> #group.employeeName </td>
<td>#group.Nationality</td>
<td> #group.documentType </td>
<td scope="col">
<input type="button" class="btn btn-primary btn-cons" value="View Document" onclick="showDocumentData('#group.upld_Id');" />
</td>
<td id="Hi">#group.currentStatus</td>
<td><input type="hidden" id="Status" value="#group.currentStatus"/></td>
<td></td>
</tr>
}
In some point of time the value of #group.currentStatus will be NotVerified. For example if I generate 5 rows of data, the value of all 5 rows will be NotVerified. In such a case I want to display some message or else display nothing. So whenever all rows of the data are holding the same value then I want to display a message. This is my jquery function and I have used below logic.
var list = new Array();
$('input[type=hidden]').each(function (i, item) {
list.push($(item).val());
if(list[i]==list[i+1]) {
fun_toastr_notify('success', 'Please verify the document');
} else {
}
});
I am not able to compare each row of data. If all the values are the same then I only want to display my toaster message once. What logic should I use here? Thank you in advance.
I tried as below now.
for(var i=0;i<list.length;i++) {
if(list[i]==list[i+1]) {
fun_toastr_notify('success', 'Please verify the documents');
}
}
Now my problem is that the toaster message will display more than one time. I want to display it only once if all elements are equal.
You may want to consider putting this logic in the code that generates the viewmodel and output a flag to display the message, rather doing it while rendering the view.
This greatly simplifies your logic. An example:
public class YourModel {
public List<ClientDetails> detailsbyclientId {get;set;}
public bool AllClientsUnverified { get { return detailsbyclientId.All(client => client.currentStatus == "Unverified"); } }
}
And then in your view (inside a client <script> block)
if (#Model.AllClientsUnverified) {
fun_toastr_notify('success', 'Please verify the documents');
}

How to populate table in Angular with JSON data from a web Service?

I have a link that displays JSON data when first name is found.
http://localhost:8080/application/names/find?firstname=f_name&LastName&Address&Status=
For Example: If I replace f_name with Tim I get this JSON response.
[{"f_name": "Tim","address": "Road","phone": "1234","status": "busy"}]
If I replace f_name with Sue I get this JSON response.
[{"f_name": "Sue","address": "Street","phone": "4321", "status": "available"}]
I want to be able to type Tim or Sue and get corresponding data. Here is what I have.
$http.get('http://localhost:8080/application/names/find?firstname=f_name&LastName&Address&Status=').
success(function(data) {
$scope.jsonData = data;
alert("Success");
}).
error(function(data) {
alert("Invalid URL");
});
$scope.results = [];
$scope.clickButton = function(enteredValue) {
$scope.items = $scope.jsonData;
angular.forEach($scope.items, function (item) {
if(item.f_name === enteredValue){
$scope.results.push({
name: enteredValue,
address: item.address,
number: item.phone,
status: item.status
});
}
})};
jsp
<table>
<tr>
<td><label>Name:</label></td>
<td>
<input id="fName" type="text" data-ng-model="enteredValue" />
<button data-ng-click='clickButton(enteredValue)'>Search</button>
</td>
</tr>
</table>
<table>
<tr data-ng-repeat="result in results" >
<td data-title="'ID'" >{{result.name}}</td>
<td data-title="'Name'">{{result.status}}</td>
<td data-title="'Status'">{{result.number}}</td>
<td data-title="'Start Date'" >{{result.date}} </td>
</tr>
</table>
I have been able to populate the dropdown successfully using then such as below.
$http.get('http://localhost:8080/application/countries').then(function(cdata)
{
$scope.countryData = cdata.data;
})
How do I initiate this search? Am I doing this the right way? Do I have to have a service for this?
looks like your server side function already been able to handle the query and return filtered result, if that's the case, what you need to do is just to cope with the request url when you send the search result. so, your clickButton function should be something like this:
$scope.clickButton = function(enteredValue){
//you may want to change logic here if you got other parameter need to be handled
var url = 'http://localhost:8080/application/names/find?firstname='+enteredValue+'&LastName&Address&Status=';
$http.get(url).success(function(data){
$scope.results = data;
});
}

Parse child nodes in JSON with knockout.js

let's say that my controller produce such json:
{
"$id": "1",
"$values": [{
"$id": "2",
"kodg": -1643387437,
"name": null,
"data": "2014-02-07T00:00:00",
"pax": 2,
"ch": 0
}, {...}]
}
I need to reach somehow values in child nodes (starting from $id: 2) to be able to bind its to UI, but I do not have any idea how to do it. Please advise.
P.S. foreach is not working here:
<script>
function BookViewModel() {
var baseUri = '/api/grafik/205693'
var self = this;
self.kodg = ko.observable("");
self.name = ko.observable("");
self.data = ko.observable("");
self.pax = ko.observable("");
self.child = ko.observable("");
var book = {
kodg: self.kodg,
name: self.name,
data: self.data,
pax: self.pax,
child: self.child
};
self.book = ko.observable();
self.books = ko.observableArray();
$.getJSON(baseUri, self.books);
}
$(document).ready(function () {
ko.applyBindings(new BookViewModel());
});
</script>
And this how I'm binding.
<table>
<thead>
<tr>
<td>kodg</td>
<td>name</td>
<td>data</td>
<td>pax</td>
<td>child</td>
</tr>
</thead>
<tbody data-bind="foreach: books">
<tr>
<td>
<input type="text" data-bind="value: $data.kodg" />
</td>
<td>
<input type="text" data-bind="value: $data.name" />
</td>
<td>
<input type="text" data-bind="value: $data.data" />
</td>
<td>
<input type="text" data-bind="value: $data.pax" />
</td>
<td>
<input type="text" data-bind="value: $data.child" />
</td>
</tr>
</tbody>
</table>
Several things.
Your usage of self is redundant. self is used to create a closure over the supposed context in a function that is passed, at a later time, as a callback of sorts - to preserve that context:
var self = this;
$('#target').on('click', function() {
self.someMethodOfTheAboveThis();
});
Additionally, getJSON is asynchronous method and it doesn't do what, it seems, you think it does.
Your usage should be:
call getJSON, while passing it a callback that will be invoked when the response is received from the server
populate your books observable array
Something along these lines:
$.getJSON(baseUri, function(data) {
self.books(data.$values);
});
The way you do it now is incorrect for 2 reasons:
you don't pass a callback into getJSON
even if did work and getJSON would, somehow, be able to just dump data onto your books - it would override the observable array without KO or bound DOM knowing about it.
EDIT: to clarify the last point - books is, conincidentally, a function, but the data that it will get populated with is not the one that should be populated onto the books.

Using knockoutJS, how to bind list items to same view?

I am new to Knockout and I am building a Simple POC for using knockout to build SPA(Single Page Application).
What I want to do is to show "Business Units" when the app loads and on selection of a business unit show all "Front End Units" under that business unit and on selection of a front end unit, show all "Sales Segments" under that front end unit.
All this will happen in a single page using the same view and the viewmodel will bind the model based on selected business unit or front end unit.
The issue I am facing is that, I have 5 business units that get bound properly first on document ready, but on selection of business unit, the front end units get repeated 5 times each. In this case, I have 2 front end units and each is shown 5 times. Same issue on selection of front end unit.
You can see this issue mimicked in the following jsFiddle sample - jsFiddle Link
Let me know if you can't access the jsfiddle link. In this sample, I have used arrays, but in actual I will be getting the data through async call to the oData service.
This is the view HTML:
<div id="divbu">
<h4 data-bind="text: Heading"></h4>
<ul data-role="listview" data-inset="true" data-bind="foreach: Collection">
<li data-role="list-divider" data-bind="text: EntityName"></li>
<li>
<a href="#" data-bind="click: $root.fnNextLevel">
<table border="0">
<tr>
<td>
<label style="font-size: 12px;">Bus. Plan: </label>
</td>
<td>
<label style="font-size: 12px;" data-bind="text: BusinessPlan"></label>
</td>
<td>
<label style="font-size: 12px;">Forecast: </label>
</td>
<td>
<label style="font-size: 12px;" data-bind="text: Forecast"></label>
</td>
</tr>
<tr>
<td>
<label style="font-size: 12px;">Gross Sales: </label>
</td>
<td colspan="3">
<label style="font-size: 12px;" data-bind="text: GrossSales"></label>
</td>
</tr>
</table>
</a>
</li>
</ul>
</div>
This is the model and view model:
function CommonModel(model, viewType) {
var self = this;
if (viewType == 'BU') {
self.EntityName = model[0];
self.BusinessUnit = model[0];
self.BusinessPlan = model[1];
self.Forecast = model[2];
self.GrossSales = model[3];
} else if (viewType == 'FEU') {
self.EntityName = model[1];
self.BusinessUnit = model[0];
self.FrontEndUnit = model[1];
self.BusinessPlan = model[2];
self.Forecast = model[3];
self.GrossSales = model[4];
} else if (viewType == 'SS') {
self.EntityName = model[2];
self.BusinessPlan = model[3];
self.Forecast = model[4];
self.GrossSales = model[5];
}
}
function ShipmentReportsViewModel(results, viewType) {
var self = this;
self.Collection = ko.observableArray([]);
for (var i = 0; i < results.length; i++) {
self.Collection.push(new CommonModel(results[i], viewType));
}
if (viewType == 'BU') {
self.Heading = "Business Units";
self.fnNextLevel = function (businessUnit) {
FetchFrontEndUnits(businessUnit);
};
self.Home = function () {
FetchBusinessUnits();
};
} else if (viewType == 'FEU') {
self.Heading = results[0][0];
self.fnNextLevel = function (frontEndUnit) {
FetchSalesSegments(frontEndUnit);
};
self.Home = function () {
FetchBusinessUnits();
};
} else if (viewType == 'SS') {
self.fnNextLevel = function () {
alert('No activity zone');
};
self.Heading = results[0][0] + ' - ' + results[0][1];
self.Home = function () {
FetchBusinessUnits();
};
}
}
You can see the complete code in the jsFiddle link.
I have also tried this with multiple views and multiple view models, where I apply bindings by giving the element ID. In this case, one flow from business unit -> sales segment is fine, but when I click on home or back button and I do binding again to that element, I face the same issue. (home and back button features are not done in jsFiddle example).
Let me know if more details are required. I did look into lot of other links in stack overflow, but nothing addressing this particular problem.
Any help is deeply appreciated. Thanks in advance.
The problem here is that you call your ko.applybindings TWICE and there is a foreach binding that iterate within 5 items, therefore the data are duplicated five times.
you should not call a ko.applybindings more than once on the same model.
Your model is always the same even if it's parametrized.
I had the same problem here: Data coming from an ObservableArray are displayed twice in my table
the fact that you have you business logic inside your viewModel is something that could be discussed, and it makes it not easy to fix this.
Make 3 classes, put them in a common model without logic inside. Then once you have applyed the ko.applyBindings once, you just have to modify the array like this:
viewModel.myArray(newValues)
Here is the fiddle with the amended code: http://jsfiddle.net/MaurizioPiccini/5B9Fd/17/
it does not do exaclty what you need but if remove the multiple bindings by moving the Collection object scope outside of your model.
As you can see the problem IS that you are calling the ko.applybindings twice on the same model.
Finally, I got this working. Thanks to #MaurizioIndenmark.
Though I have removed multiple call for ko.applybindings, I was still calling the view model multiple times. This was causing the issue.
Now, I have cleaner view model and I have different function calls for different actions and modify all the data required to be modified within these functions(events). Now, everything is working as expected.
This is how the view model looks now -
function ShipmentReportsViewModel(results) {
var self = this;
self.Heading = ko.observable();
self.BusinessUnits = ko.observableArray();
self.FrontEndUnits = ko.observableArray();
self.SalesSegments = ko.observableArray();
self.Home = function () {
var bu = FetchBusinessUnits();
self.Heading("Business Units");
self.BusinessUnits(bu);
self.FrontEndUnits(null);
self.SalesSegments(null);
};
self.fnFeu = function (businessUnit) {
var feu = FetchFrontEndUnits(businessUnit);
self.Heading(feu[0].BusinessUnit);
self.FrontEndUnits(feu);
self.BusinessUnits(null);
self.SalesSegments(null);
};
self.fnSalesSeg = function (frontEndUnit) {
var ss = FetchSalesSegments(frontEndUnit);
self.Heading(ss[0].BusinessUnit + ' - ' + ss[0].FrontEndUnit);
self.SalesSegments(ss);
self.BusinessUnits(null);
self.FrontEndUnits(null);
};
self.Home();
}
To see the entire working solution, please refer this jsFiddle
Thanks for all the valuable suggestions in getting this work.

Categories

Resources