I'm writing a user settings page in Angular that will be used to update the users profile settings. This is how I've done it (pardon my use of jquery please)
$scope.userObj = {};
var userObjTemp = {};
$http.get('/api/to/get/user').success(function(data) {
if (data.success != true) {
$state.go('index');
} else {
$scope.userObj.user = data.response; //scope variable to show in html form
userObjTemp.user = data.response; //temp data in case someone cancels editing
var tempDob = $scope.userObj.user.dob;
$scope.userObj.user.dob = tempDob.split('-')[2] + '/' + tempDob.split('-')[1] + '/' + tempDob.split('-')[0];
console.log({
userObjData: $scope.userObj
});
console.log({
tempData: userObjTemp
});
}
});
$scope.showSetting = function(target) {
$('.setting-edit-row').hide();
$('.jr-input-setting').show();
$('#' + target + '-input').hide();
$('#' + target).show();
}
$scope.saveSetting = function(key) {
var postDict = {};
postDict[key] = $scope.userObj.user[key];
$http.put('/api/user', postDict).success(function(data) {
$scope.userObj.user = data.response;
$('.setting-edit-row').hide();
$('.jr-input-setting').show();
})
}
$scope.shutSetting = function(target) {
$scope.userObj.user = {};
$scope.userObj.user = userObjTemp.user;
$('#' + target).hide();
$('#' + target + '-input').show();
}
My HTML is as follows:
<div class="row setting-fixed-row">
<div class="col-lg-2">
<div class="setting-label">
Name
</div>
</div>
<div class="col-lg-8">
<input class="jr-input-setting" id="setting-name-input" disabled="true" ng-model="userObj.user.display_name" type="text" placeholder="Display Name">
</div>
<div class="col-lg-2">
<div class="edit-btn" ng-click="showSetting('setting-name')">
Edit
</div>
</div>
<div class="col-lg-12 setting-edit-row" id="setting-name">
<div class="row">
<div class="col-lg-12">
<span class="glyphicon glyphicon-remove shut-det" style="margin-bottom: 5px;" ng-click="shutSetting('setting-name')"></span>
</div>
<div class="col-lg-offset-2 col-lg-8">
<input class="jr-input-edit" ng-model="userObj.user.display_name" placeholder="Display Name" id="display_name" ng-change="showVal()">
</div>
<div class="col-lg-2">
<div class="save-settings" ng-click="saveSetting('display_name')">
Save
</div>
</div>
</div>
</div>
</div>
The idea behind shutSetting() is to shut the editing panel setting-edit-row and restore the original data that I got from the api. However, when I do this, it shows me the temp variable being the same as the $scope.userObj variable. I added a $scope.showVal function to show the variables on change of the input form:
$scope.showVal = function(){
console.log({userObj: $scope.userObj});
console.log({temp: userObjTemp});
}
For some reason, both variables are getting updated. How do I fix this as I've never faced something similar before.
The problem is that you are referencing objects instead of copying them. Thus
$scope.userObj.user = data.response;
userObjTemp.user = data.response;
points all to the same object. Then, when you update one of the two also the other gets updated.
userObjTemp.user = angular.copy(data.response)
this makes a copy.
Just in case: https://jsfiddle.net/qzj0w2Lb/
The problem is, that both of your variables are referencing the same object data.response. Depending on the object, you could use $.extend(true, {}, data.response); to get a clone of the object.
Be aware though, that this is not a true "deep copy" when custom objects are involved!
Related
It is not like it is slow on rendering many entries. The problem is that whenever the $scope.data got updated, it adds the new item first at the end of the element, then reduce it as it match the new $scope.data.
For example:
<div class="list" ng-repeat="entry in data">
<h3>{{entry.title}}</h3>
</div>
This script is updating the $scope.data:
$scope.load = function() {
$scope.data = getDataFromDB();
}
Lets say I have 5 entries inside $scope.data. The entries are:
[
{
id: 1,
title: 1
},
{
id: 2,
title: 2
},
......
]
When the $scope.data already has those entries then got reloaded ($scope.data = getDataFromDB(); being called), the DOM element for about 0.1s - 0.2s has 10 elements (duplicate elements), then after 0.1s - 0.2s it is reduced to 5.
So the problem is that there is delay about 0.1s - 0.2s when updating the ng-repeat DOM. This looks really bad when I implement live search. Whenever it updates from the database, the ng-repeat DOM element got added up every time for a brief millisecond.
How can I make the rendering instant?
EDITED
I will paste all my code here:
The controller:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = [];
$scope.loadViewModels($scope.orderBy, table);
}
$scope.loadViewModels = function (orderBy, table, cb) {
if (!$scope.endOfPage) {
let searchKey = $scope.page.searchString;
let skip = ($scope.currentPage - 1) * $scope.itemsPerPage;
let searchClause = '';
if (searchKey && searchKey.length > 0) {
let searchArr = [];
$($scope.vmKeys).each((i, key) => {
searchArr.push(key + ` LIKE '%` + searchKey + `%'`);
});
searchClause = `WHERE ` + searchArr.join(' OR ');
}
let sc = `SELECT * FROM ` + table + ` ` + searchClause + ` ` + orderBy +
` LIMIT ` + skip + `, ` + $scope.itemsPerPage;
sqlite.query(sc, rows => {
$scope.$apply(function () {
var data = [];
let loadedCount = 0;
if (rows != null) {
$scope.currentPage += 1;
loadedCount = rows.length;
if (rows.length < $scope.itemsPerPage)
$scope.endOfPage = true
for (var i = 0; i < rows.length; i++) {
let item = rows.item(i);
let returnObject = {};
$($scope.vmKeys).each((i, key) => {
returnObject[key] = item[key];
});
data.push(returnObject);
}
$scope.viewModels = $scope.viewModels.concat(data);
}
else
$scope.endOfPage = true;
if (cb)
cb(loadedCount);
})
});
}
}
The view:
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
<div class="row note-list" ng-if="showList">
<h3>Notes</h3>
<input ng-model="page.searchString" id="search"
ng-keyup="search('notes')" type="text" class="form-control"
placeholder="Search Notes" style="margin-bottom:10px">
<div class="col-12 note-list-item"
ng-repeat="data in viewModels track by data.id"
ng-click="edit(data.id)"
ontouchstart="touchStart()" ontouchend="touchEnd()"
ontouchmove="touchMove()">
<p ng-class="deleteMode ? 'note-list-title w-80' : 'note-list-title'"
ng-bind-html="data.title"></p>
<p ng-class="deleteMode ? 'note-list-date w-80' : 'note-list-date'">{{data.dateCreated | displayDate}}</p>
<div ng-if="deleteMode" class="note-list-delete ease-in" ng-click="delete($event, data.id)">
<span class="btn fa fa-trash"></span>
</div>
</div>
<div ng-if="!deleteMode" ng-click="new()" class="add-btn btn btn-primary ease-in">
<span class="fa fa-plus"></span>
</div>
</div>
<div ng-if="!showList" class="ease-in">
<div>
<div ng-click="back()" class="btn btn-primary"><span class="fa fa-arrow-left"></span></div>
<div ng-disabled="!isDataChanged" ng-click="save()" class="btn btn-primary" style="float:right">
<span class="fa fa-check"></span>
</div>
</div>
<div contenteditable="true" class="note-title"
ng-bind-html="selected.title" id="title">
</div>
<div contenteditable="true" class="note-container" ng-bind-html="selected.note" id="note"></div>
</div>
</div>
<script src="../js/pages/note.js"></script>
Calling it from:
$scope.loadViewModels($scope.orderBy, 'notes');
The sqlite query:
query: function (query, cb) {
db.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
return cb(res.rows, null);
});
}, function (error) {
return cb(null, error.message);
}, function () {
//console.log('query ok');
});
},
It is apache cordova framework, so it uses webview in Android emulator.
My Code Structure
<html ng-app="app" ng-controller="pageController">
<head>....</head>
<body>
....
<div id="pageContent" class="root-page" ng-controller="noteController" ng-cloak>
....
</div>
</body>
</html>
So there is controller inside controller. The parent is pageController and the child is noteController. Is a structure like this slowing the ng-repeat directives?
Btw using track by is not helping. There is still delay when rendering it. Also I can modify the entries as well, so when an entry was updated, it should be updated in the list as well.
NOTE
After thorough investigation there is something weird. Usually ng-repeat item has hash key in it. In my case ng-repeat items do not have it. Is it the cause of the problem?
One approach to improve performance is to use the track by clause in the ng-repeat expression:
<div class="list" ng-repeat="entry in data track by entry.id">
<h3>{{entry.title}}</h3>
</div>
From the Docs:
Best Practice: If you are working with objects that have a unique identifier property, you should track by this identifier instead of the object instance, e.g. item in items track by item.id. Should you reload your data later, ngRepeat will not have to rebuild the DOM elements for items it has already rendered, even if the JavaScript objects in the collection have been substituted for new ones. For large collections, this significantly improves rendering performance.
For more information, see
AngularJS ngRepeat API Reference -- Tracking and Duplicates
In your html, try this:
<div class="list" ng-repeat="entry in data">
<h3 ng-bind="entry.title"></h3>
</div>
After thorough research, I found my problem. Every time I reset / reload my $scope.viewModels I always assign it to null / empty array first. This what causes the render delay.
Example:
$scope.search = function (table) {
$scope.currentPage = 1;
$scope.endOfPage = false;
$scope.viewModels = []; <------ THIS
$scope.loadViewModels($scope.orderBy, table);
}
So instead of assigning it to null / empty array, I just replace it with the new loaded data, and the flickering is gone.
I have an array of objects. Each object contain few parameters. What I am trying to do is to run over the array and get from each cell the data and present it on a tabel/grid.
Example code
function addButton(){
var phoneNumber = document.getElementById("phoneNumber").value;
var email = document.getElementById("email").value;
var typeNotification = document.getElementById("typeNotification").value;
var valueNumber = document.getElementById("valueNumber").value;
var delayLimit = document.getElementById("delayLimit").value;
var timeStart = document.getElementById("timeStart").value;
var timeEnd = document.getElementById("timeEnd").value;
var notify = {
typeNotification : typeNotification,
email : email,
delayLimit : delayLimit,
timeStart : timeStart,
timeEnd : timeEnd,
valueNumber : valueNumber,
phoneNumber : phoneNumber
};
var notificationList = [];
notificationList.push(notify);
console.log(notificationList);
var notificationListLength = notificationList.length;
for(var i = 0;i < notificationListLength;i++){
<div class="container">
<div class="row">
<div class="col">
1 of 2
</div>
<div class="col">
2 of 2
</div>
</div>
<div class="row">
<div class="col">
1 of 3
</div>
<div class="col">
2 of 3
</div>
<div class="col">
3 of 3
</div>
</div>
</div>
}
}
Inside the loop it gives me error with the Tags.
As you can see, when the user press the AddButton it gets all the data and restore it in object, then I add the object into Array, and then I run on the array and get from each cell of the array the data and I want to show it in Grid.
Like I said it gives me error inside the loop with the tags.
Any idea how can I solve it?
You need to build up the output of html/tags you want, then add that to a targeted element on the page, like so:
<div id="target"></div>
<button onclick="addButton()">Add</button>
<script>
const
target = document.querySelector('#target'),
notificationList = [{
typeNotification: 'typeNotification',
email: 'email',
delayLimit: 'delayLimit',
timeStart: 'timeStart',
timeEnd: 'timeEnd',
valueNumber: 'valueNumber',
phoneNumber: 'phoneNumber'
}];
let output = '';
function addButton() {
notificationList.forEach(item => {
output = `
<div class="container">
<div class="row">
<div class="col">
${item.email} - 1 of 2
</div>
<div class="col">
${item.typeNotification} - 2 of 2
</div>
</div>
<div class="row">
<div class="col">
${item.timeStart} - 1 of 3
</div>
<div class="col">
${item.timeEnd} - 2 of 3
</div>
<div class="col">
${item.delayLimit} - 3 of 3
</div>
</div>
</div>
`;
target.innerHTML += output
});
}
</script>
As you can see, you can adjust the output before you add it to the target.
I have made all the properties in notificationList strings to save time, but in your example, they would be made up of other elements on the page.
Here is a working example: https://jsfiddle.net/14o8n30u/6/
--EDIT--
Here is the same thing, but this time a table gets updated, using javascript builtin table functions:
<button onclick="addButton()">Add</button>
<table></table>
<script>
const
targetTable = document.querySelector('table'),
headerRow = targetTable.createTHead().insertRow(),
tableBody = targetTable.createTBody(),
notificationList = [];
headerRow.insertCell().textContent = 'Notification Type';
headerRow.insertCell().textContent = 'Email';
headerRow.insertCell().textContent = 'Delay Limit';
function addButton() {
while (tableBody.firstChild) {
tableBody.removeChild(tableBody.firstChild);
}
notificationList.push({
typeNotification: `Type${Math.floor(Math.random() * Math.floor(10))}`,
email: `Name${Math.floor(Math.random() * Math.floor(1000))}#test.com`,
delayLimit: `${Math.floor(Math.random() * Math.floor(100))} seconds`,
});
notificationList.forEach(item => {
const newRow = tableBody.insertRow(0);
newRow.insertCell().textContent = item.typeNotification;
newRow.insertCell().textContent = item.email;
newRow.insertCell().textContent = item.delayLimit;
});
}
</script>
On mine, I used random data for illustration purposes, but in yours the data will be built from elements on the page.
Note that you have to keep the notificationList outside the addButton, or it gets reset to an empty array each time the button is clicked.
When the page loads, the table head and body are populated.
Then when the addButton is clicked, the table body is emptied, then a new notification is added to the notificationList.
Then, inside the forEach, which loops over the notifications, all the notifications get their own row added at the top of the table (i.e. the entire table body is repopulated each time addButton is clicked).
You may not need to populate your own table header, but if you do, you can see how in this example.
Here it is working (I couldn't get it to work on jsfiddle for some reason): https://codepen.io/leonsegal/pen/oyKBQb
Hope that helps
var notificationListLength = notificationList.length;
var html_data = '';
for(var i = 0;i < notificationListLength;i++)
{
html_data += '<div class="container">
<div class="row">
<div class="col">
1 of 2
</div>
<div class="col">
2 of 2
</div>
</div>
<div class="row">
<div class="col">
1 of 3
</div>
<div class="col">
2 of 3
</div>
<div class="col">
3 of 3
</div>
</div>
</div>';
}
/* Make a div(id="html_div") when you need display data */
document.getElementById("html_div").innerHTML = html_data ;
I am creating a dynamic, single-paged forum site using AngularJS as the front-end and Firebase as the back-end. The page consists of a list of threads on the left-hand side and the thread content on the right-hand side. The thread content displayed is based on the thread selected from the list.
I can successfully select a thread from the list and display its contents. However, when a thread is selected from the list, all of the other threads in the list become replicas of the selected thread. By this, I mean that the attribute values for the title, comments and votes of the selected thread are assigned to the same attributes in all of the other threads simultaneously, making them all identical. The ID of each thread does not change.
Can anybody give me some insight as to what is causing this issue? I can't identify anything in my code that would cause the attribute values of each Firebase object to be reassigned.
Here is the main.html page that contains the list and thread content sections
<div ng-controller="mainPageController">
<div>
<h3>
Welcome {{user.name}}! <button class="btn-danger img-rounded" ng-click="logout()" id="LogoutBtn">Logout</button>
</h3>
</div>
<div class="col-md-6">
<h2>All Threads</h2>
<div id="searchThreads" class="input-group col-md-5 img-rounded">
<input type="text" class="col-xs-5 form-control" ng-model="searchThread" placeholder="Search threads...">
</div>
<div id="addThread" class="input-group">
<input type="text" class="col-xs-5 form-control" ng-model="newThreadTitle" placeholder="New thread title..."/>
<button ng-click="addThread()">Add thread</button>
</div>
<!-- Thread List -->
<div>
<div ng-repeat="thread in threads | filter:searchThread | orderObjectBy:'votes'">
<button class="glyphicon glyphicon-chevron-up" ng-click="upvote(thread.$id, thread.votes)"></button> |
<button class="glyphicon glyphicon-chevron-down" ng-click="downvote(thread.$id, thread.votes)"></button>
<a href ng-click="showThread(thread)">{{thread.votes}}<span style="margin-left:1em"> {{thread.title}} by {{thread.username}}</span></a>
</div>
</div>
</div>
</div>
<!-- Thread content viiew -->
<div class="col-md-6">
<div ng-controller="threadPageController">
<h1>{{currentThread.title}} by {{currentThread.username}}</h1>
<div>
<input type="text" ng-model="newComment" placeholder="Write a comment..."/>
<button ng-click="addComment()">Add Comment</button>
</div>
<div>
<div ng-repeat="comment in currentThread.comments">{{comment.username}}: {{comment.text}}
</div>
<div ng-if="!currentThread.comments.length">There are no comments on this thread</div>
</div>
</div>
</div>
The mainPageController
angular.module('richWebApp')
.controller('mainPageController', function($scope, $location, userService, threadService, fb, $firebaseAuth, $filter){
$scope.user = userService.getLoggedInUser();
$scope.newThreadTitle = '';
$scope.currentThreadId = '';
$scope.threads = threadService.getAllThreads();
$scope.threads.$loaded().then(function(){
console.log($scope.threads)
});
$scope.users = userService.getLoggedInUsers();
$scope.addThread = function(){
if(!$scope.newThreadTitle){
return false; //Don't do anything if the text box is empty
}
var newThread = {
title: $scope.newThreadTitle,
username: $scope.user.name,
comments: [],
votes: 0
};
$scope.threads.$add(newThread);
$scope.newThread = '';
$scope.newThreadTitle = ''; //Clear the text in the input box
}
$scope.showThread = function(thread) {
$scope.$emit('handleEmit', {id: thread.$id});
};
$scope.upvote = function(threadId, threadVotes) {
var newVotes = threadVotes + 1;
var ref = new Firebase(fb.url);
var threadRef = ref.child("threads");
threadRef.child(threadId).update({
votes: newVotes
});
}
$scope.downvote = function(threadId, threadVotes) {
var newVotes = threadVotes - 1;
var ref = new Firebase(fb.url);
var threadRef = ref.child("threads");
threadRef.child(threadId).update({
votes: newVotes
});
}
$scope.logout = function(){
userService.logout();
}
});
The threadPageController
angular.module('richWebApp')
.controller('threadPageController', function($scope, $location, $routeParams, threadService, fb, userService){
$scope.$on('handleBroadcast', function (event, args) {
var threadId = args.id;
var currentThread = threadService.getThread(threadId);
currentThread.$bindTo($scope, 'currentThread') //creates $scope.thread with 3 way binding
});
$scope.newComment = '';
$scope.addComment= function(){
if(!$scope.newComment){
return false; //Don't do anything if the text box is empty
}
var currentUser = userService.getLoggedInUser();
var newComment = {
text: $scope.newComment,
username: currentUser.name
};
$scope.currentThread.comments = $scope.currentThread.comments || [];
$scope.currentThread.comments.push(newComment);
$scope.newComment = ''; //Clear the input box
}
});
threadService
angular.module("richWebApp").service("threadService", function($firebaseArray, $firebaseObject, fb){
this.getAllThreads = function(){
var ref = new Firebase(fb.url + '/threads');
return $firebaseArray(ref);
};
this.getThread = function(threadId){
var ref = new Firebase(fb.url + '/threads/' + threadId);
return $firebaseObject(ref);
};
});
I've pasted the relevant snippet of my code below.
I know the base angularJS is working because the HTML document doesn't display the {{}} variables, but instead, I get a blank.
I believe it's because it's getting a null value for the variables. When I do a few console.log expression WITHIN the controller, I can see that the value is updated accordingly, but it is different outside the controller.
Code:
<div ng-controller="rangeSort">
<h3>Range Selection</h3>
<div class="row">
<div class="col-md-4">
<h5><b>Observer</b></h5>
<select class="form-control" ng-model="obSelection">
<option value="" >All</option>
<option ng-repeat="observer in observersS" value="{{observer}}">{{observer}}</option>
</select>
</div>
<div class="col-md-4">
<h5> <b>Host</b></h5>
<select class="form-control" ng-model="hostSelection">
<option value="" >All</option>
<option ng-repeat="host in hostsS" value="{{host}}">{{host}}</option>
</select>
</div>
<div class="col-md-4">
<h5> <b>Bug</b></h5>
<select id="chooseBug" class="form-control" ng-model="bugSelection">
<option value="" >All</option>
<option ng-repeat="bug in bugsS" value="{{bug}}" >{{bug}}</option>
</select>
</div>
</div>
<!--div ng-repeat="bugItem in bugsJSONList | filter: obSelection | filter: hostSelection | filter: bugSelection" > {{bugItem.bug}} on {{bugItem.host}} , observer {{bugItem.observer}} </div-->
<div class="row">
<div style="text-align: center; margin-bottom: 15px" ng-repeat="observer in observerKeys | filter: obSelection" >
<h5><b> {{observer}}</b></h5>
<div class="row">
<div class="col-md-6" ng-repeat="host in hostKeys | filter: hostSelection" >
<br> <span style="text-decoration:underline"> Host {{host}} </span>
<div style="text-align: left" ng-repeat="bug in bugsS | filter: bugSelection" >
<div ng-repeat="item in JSONbugsList[observer][host][bug]">{{bug}} from {{item.start}} to {{item.end}}</div>
</div>
</div>
</div>
</div>
</div>
<script>
var summaryApp = angular.module('summaryApp', []);
summaryApp.controller("rangeSort", function($scope) {
hosts =[], observers =[], bugs =[], JSONbugsList =[];
hostKeys = [], observerKeys = [], bugKeys = [];
bugTracker = {};
$.getJSON('../java_output/bug_summary.json', function (data) {
JSONbugsList = data;
var numObservers = data.numObservers;
var bugTracker = {};
for (var observer = 1; observer <= numObservers; observer++) {
observers.push(observer);
observerKeys = Object.keys(data);
observerKeys.splice(observerKeys.indexOf('numObservers'));
for (var host in data["observer" + observer]) {
if (hosts.indexOf(host) == -1) {
hosts.push(host);
}
hostKeys = Object.keys(data["observer" + observer]);
for (var bug in data["observer" + observer][host]) {
if (bugs.indexOf(bug) == -1) {
bugs.push(bug);
}
for (var i in data["observer" + observer][host][bug]) {
bugTracker[bug] = true;
var dateVar = data["observer" + observer][host][bug][i];
var intoList = {"observer":observer, "host":host, "bug":bug, "start":(new Date(1000*dateVar.start)), "end":(dateVar.end==null?' the end.':(new Date(1000*dateVar.end)))}
}
}
}
}
console.log ("host keys " + hostKeys);
var overviewVars = ['Congestion','Highlatency','LownumOIO'];
var overviewSpaceVars = ['Congestion','High latency','Low numOIO'];
for (var i in overviewVars) {
console.log (overviewVars[i]);
$('#' + overviewVars[i] + 'Content' ).append ('<p>There are' + ((bugTracker[overviewSpaceVars[i]])?' errors in ':' no errors in ') + overviewVars[i] + '.</p>');
$('#' + overviewVars[i] + 'Content' ).append ('<button type=\"button\" class=\"btn\" onclick=\"displayRanges('+overviewSpaceVars[i]+')\">View Bug Ranges</button>');
}
function displayRanges (bug) {
$('#chooseBug').val(bug);
}
//console.log(bugsCounter);
for (var i = 1; i <= numObservers; i++) {
$('#links').append('Observer ' + i + ' ');
if (i != numObservers) {
$('#links').append(' - ');
}
}
$scope.hostsS = hosts;
$scope.bugsS = bugs;
$scope.observersS = observers;
$scope.JSONbugsList = JSONbugsList;
$scope.hostKeys = hostKeys;
$scope.observerKeys = observerKeys;
$scope.start = 'start';
$scope.end = 'end';
});
});
</script>
<hr>
</div>
getJSON is not an Angular call, therefore triggers no digest cycle, therefore never updating your view. It's not a good idea to mix Angular and jQuery because of this. Angular comes with jqLite - a smaller subset of jQuery functions.
Your data call should be using $http to keep within the digest cycle - a quick workaround would be to call $scope.$apply() at the end of your code to trigger a digest cycle, however refactoring to use Angular dependencies would be the best choice of action.
I am trying to create a computed observable and display it on the page, and I have done it this way before but I am starting to wonder if knockout has changed - Everything works except the binding on totalAmount - for some reason it never changes...Any ideas?
My model is as follows:
var cartItem = function(item){
this.itemName = item.title;
this.itemPrice = item.price;
this.itemID = item.id;
this.count=0;
}
var cartModel = {
self:this,
footers:ko.observableArray([{title:'item1',text:'this is item1 text',image:'images/products/items/item1.png',price:15.99,id:1},
{title:'item2',text:'this is item2 text',image:'images/products/items/item2.png',price:25.99,id:2},
{title:'item3',text:'this is item3 text',image:'images/products/items/item3.png',price:55.99,id:3},
{title:'item4',text:'this is item4 text',image:'images/products/items/item4.png',price:5.99,id:4},
]),
cart:ko.observableArray([]),
addToCart:function(){
if(cartModel.cart().length>0){
for(var i=0;i<cartModel.cart().length;i++){
if(this.id==cartModel.cart()[i].itemID){
cartModel.cart()[i].count += 1;
}
else{
cartModel.cart().push(new cartItem(this));
}
}
}else{
cartModel.cart().push(new cartItem(this));
}
console.log(cartModel.cart().length);
}
}
this.cartModel.totalAmount=ko.computed(function(){
return this.cart().length;
},this.cartModel);
ko.applyBindings(cartModel);
And here is the associated HTML:
<div data-bind="template:{name:'footerTemplate',foreach:cartModel.footers}">
<script type="text/html" id="footerTemplate">
<div class="row">
<span class="span2"><h3 data-bind="text: title"></h3></span>
<span class="span2"><img data-bind="attr:{src: image}"/></span>
<span class="span5" data-bind="text:text"></span>
<span class="span1" data-bind="text:price"></span>
<spand class="span2"><button data-bind="click:$parent.addToCart">add</button></span>
</div>
</script>
</div>
<div class="row">
<span class="span2" data-bind="text:totalAmount"></span>
</div>
You are calling the push method on the internal array, not on the observableArray wrapper, thus the changes are never notified.
i.e. instead of:
cartModel.cart().push(new cartItem(this));
use simply:
cartModel.cart.push(new cartItem(this));
For more info take a look at the official documentation for observableArray, and in particular at the Reading information from an observableArray and Manipulating an observableArray sections.