Save JS object to Knockout Array - javascript

So I'm trying to load JSON from a server via AJAX call. I'm able to map it fine into an array that is being binded in HTML. These objects are saved into an array that is being used as the value for a select tag. If I output to the console what's in the array, all the objects show up fine. But these objects don't show as if they have been preselected in the select box.
What I'm trying to do is have the previous data that a user saved from an old session and continue where they left off without having to redo everything again. So I'm loading all old data and putting it back where it used to be by preselecting the option for them.
Here is the JS I currently have:
function BracketsViewModel() {
self.AfcTeams = ko.observableArray([]);
// Normally pulled from server via AJAX with more teams. Hardcoded for simplicity
self.AfcTeams.push(new TeamModel({
Tricode: "CIN",
DisplayName: "Bengals"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "BUF",
DisplayName: "Bills"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "DEN",
DisplayName: "Broncos"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "CLE",
DisplayName: "Browns"
}));
self.AfcTeams.push(new TeamModel({
Tricode: "SD",
DisplayName: "Chargers"
}));
// Temporary array that holds Team object
self.AfcSelectedWildCards = [];
for (var i = 0; i <= 5; i++) {
self.AfcSelectedWildCards.push(ko.observable());
}
// Holds selected teams that go to next round
self.AfcDivisionals = ko.computed(function () {
var tmp = [];
ko.utils.arrayForEach(self.AfcSelectedWildCards, function (team) {
if (team()) {
tmp.push(team());
}
});
return tmp;
});
// Other properties not shown for simplicity
// This will be loaded from server via AJAX call
var bracketsObject = {
AfcTeams: [{
Tri: "CIN",
Name: "Bengals",
Rank: "1"
}, {
Tri: "HOU",
Name: "Texans",
Rank: "2"
}, {
Tri: "NE",
Name: "Patriots",
Rank: "3"
}, {
Tri: "NYJ",
Name: "Jets",
Rank: "5"
}, {
Tri: "DEN",
Name: "Broncos",
Rank: "4"
}, {
Tri: "KC",
Name: "Chiefs",
Rank: "6"
}]
};
var afcteams = $.map(bracketsObject.AfcTeams, function (team) {
return new AltTeamModel(team);
});
// Saving objects to array that is being binded in HTML
for (var i = 0; i <= 5; i++) {
self.AfcSelectedWildCards[i] = ko.observable(afcteams[i]);
}
}
function TeamModel(data) {
if (data) {
this.Tri = data.Tricode;
this.Name = data.DisplayName;
} else {
this.Tri = "";
this.Name = "";
}
this.Rank = ko.observable(0);
}
function AltTeamModel(data) {
this.Tri = data.Tri;
this.Name = data.Name;
this.Rank = ko.observable(data.Rank);
}
ko.applyBindings(new BracketsViewModel());
Here is the Fiddle
I appreciate any help I can get.

The first issue is that you are referencing self, but never declaring it. You need to add var self = this; at the top of BracketsViewModel.
The next problem is that AfcTeams is an observable array of TeamModels, but AfcSelectedWildCards is an array of AltTeamModel. They need to be the same view model for options and value to match up.
One way around this is to set optionsValue and value both to 'Tri' as follows:
<select class="form-control"
data-bind="options: AfcTeams,
optionsText: 'Name',
optionsCaption: '-- Team --',
optionsValue: 'Tri',
value: AfcSelectedWildCards[0]().Tri"></select>
Here is a fiddle with these two fixes: http://jsfiddle.net/qpolarbear/8kkamzy7/
Only the Bengals and Broncos are selected because they are the only matching teams between AfcTeams and AfcSelectedWildCards.

So after taking a break from this project, I finally figured out how to bind the objects loaded via AJAX. The problem was that Knockout was binding the document before the AJAX calls were finished so, for whatever reason, the binding wasn't reflecting these changes. What I decided to do was time out the document from applying the binding and load everything from the server first. I then passed in all the objects into the View Model and apply the binding on a half second delay. Everything works great now. Here is the code:
function TeamModel(data, isPreData) {
if (isPreData) {
this.Tri = data.Tri;
this.Name = data.Name;
this.Rank = ko.observable(data.Rank);
} else {
if (data) {
this.Tri = data.Tricode;
this.Name = data.DisplayName;
} else {
this.Tri = "";
this.Name = "";
}
this.Rank = ko.observable(0);
}
}
var afcteams;
$.getJSON('/Brackets/GetBrackets', { id: someId}, function (bracketsObject) {
if (bracketsObject) {
afcteams = $.map(bracketsObject.AfcTeams, function (team) {
return new TeamModel(team, true);
});
}
}).fail(function () {
alert("There was an error getting data from the server.");
});
var teams;
$.getJSON('/Brackets/GetAFCTeams', function (data) {
teams = $.map(data, function (team) {
return new TeamModel(team, false);
});
});
function SetBindings(afcteams, teams) {
ko.applyBindings(new BracketsViewModel(afcteams, teams));
}
setTimeout(function() {
SetBindings(afcteams, teams);
}, 500);

Related

How do I deal with an object sent into AngularJS controller?

In my controller I have a function that recieves an object from Java controller. My AngularJS variable is simple:
var self = this;
self.item = {};
And my function where I get the object:
function getItem() {
MyService.getItem(REST_SERVICE_URI)
.then(
function (d) {
self.item = d;
},
function (errResponse) {
console.error('Error while getting item');
}
);
}
Object that's received has rather complicated structure. It has id, name and list of child objects, who have also id and name fields. How do I get into this object's fields and list in the AngularJS controller? I tried loop though list using fucntion below to even count duplicate values but it didn't work. I tried even to include one more loop into it with outputing result in console, no effect. It only returns zeros.
var i = "SOME TEST NAME VALUE TO CHECK";
function getCount(i) {
var iCount = iCount || 0;
for (var el in self.item) {
console.log("let me see what are you: " + el);
if (el == i) {
iCount++;
}
}
return iCount;
}
The object I recieve is ok, I can see it content in Chrome using F12 - Network - Response or Preview.
added later:
On my page I test it like this
<tr class="my_item" ng-repeat="p in ctrl.item.children">
<span>getCount {{p.name}}: {{ctrl.getCount(p.name)}}</span>
</tr>
It displays p.name in the span btw. Java object structure is
public class Item {
private int id;
private String name;
List<Child> children = new ArrayList<>();
}
Child class is simple
public class Child {
private int id;
private String name;
}
As per your question, the content is complex and has recursive properties inside child content.
So you need to iterate on content recursively, inside one forEach loop.
See this example working Demo:
var myApp = angular.module('myApp', []);
myApp.controller('ExampleController', function() {
var vm = this;
vm.count = 0;
vm.searchTxt = "";
vm.getCount = function() {
vm.count = 0; //clear count before search
recursive(vm.content);
}
function recursive(dataArray) { //recursive function
dataArray.forEach(function(data) {
if (vm.searchTxt == data.name) { //match name property
vm.count = vm.count + 1;
}
if (data.child.length > 0) {
recursive(data.child); // call recursive function
}
});
}
vm.content = [{ //example content
id: 1,
name: 'one',
child: [{
id: 1.1,
name: 'new one',
child: [{
id: 1,
name: 'one',
child: []
}]
}]
}, {
id: 2,
name: 'two',
child: [{
id: 1.1,
name: 'new two',
child: []
}]
}]
});
<script src="https://code.angularjs.org/1.5.2/angular.js"></script>
<div ng-app="myApp" ng-controller="ExampleController as vm">
<input ng-model="vm.searchTxt" placeholder="ender search.." />
<br>
<button ng-click="vm.getCount()">Search</button>
<br>
<span>Match 'Name' count : {{vm.count}}</span>
</div>

Mithril - how to populate drop down list of view from API

I'm trying to populate a drop down box rendered by Mithril's view from methods being called outside of its module (not sure if this terminology is correct, but outside of the property which contains the view, model and controller).
This Chrome extension adds a new field to an existing page and depending on what the user select, the drop down box should refresh to items pertaining to the selected item. I can get up to the stage of getting the new list of items, but i cannot get the drop down list to redraw with the new objects.
The following shows the module which gets inserted inside an existing page:
var ItemsList = {
model: function () {
this.list = function (id) {
var d = m.deferred()
// Calls Chrome extension bg page for retrieval of items.
chromeExt.getItems(pId, function (items) {
// Set default values initially when the controller is called.
if (items.length === 0) {
items = [
{name: 'None', value: 'none'}
]
}
d.resolve(items || [])
})
return d.promise
}
},
controller: function () {
this.model = new ItemsList.model()
this.index = m.prop(0)
this.onchange = function (e) {
console.info('ctrl:onchange', e.target)
}
// Initialise the drop down list array list.
this.dropDownItemsList = m.prop([]);
// This sets the default value of the drop down list to nothing by calling the function in the model,
// until the user selects an item which should populate the drop down list with some values.
this.getItems = function(pId) {
this.model.list(pId).then(function (data) {
this.dropDownItemsList(data)
m.redraw()
}.bind(this))
}
this.getItems(0);
},
view: function (ctrl) {
var SELECT_ID = 'record_select'
return vm.Type() ? m('div', [
m('.form__item', [
m('.label', [
m('label', {
htmlFor: SELECT_ID
}, 'ID')
]),
m('.field', [
m('select#' + SELECT_ID, {
onchange: ctrl.onchange.bind(ctrl)
},
ctrl.dropDownItemsList().map(function (it, i) {
return m('option', {
value: it.value,
checked: ctrl.model.index === i
}, it.name)
})
),
])
]),
]) : null
}
}
And it is mounted using
m.mount("element name here", ItemsList);
The code which checks to see if the item has changed is using a mutation observer, and whenever it detects changes to a certain field, it will call a method to get the new values. I can see that the return value has my new items.
I have tried various different methods on trying to update the drop down list, first by trying to set the "this.list" with the new items list i've got, or trying to create a returnable method on the controller which i can call when the mutation observer fires.
After getting the new items, how can i make the drop down list show the new items which has been retrieved?
I have read guides which shows functions in the controller or model being run - but only if they've been defined to use them already in the view (i.e. have an onclick method on the view which calls the method) but so far i cannot figure out how to update or call methods from outside of the module.
Is there a way to achieve the above or a different method i should approach this?
After some more research into how Mithril works, seems like that it's not possible to call any functions defined within the component.
Due to this, i have moved the model outside of the component (so now it only has the controller and the view defined) and bound the view to use the model outside of the component.
Now calling a function which updates the model (which is now accessible from elsewhere in the code) and redrawing shows the correct values that i need.
If I understand correctly, you need to have two variables to store your lists, one to store the old list and one to store the updated list so you can always map the updated one and go to your old one if you need.
Here is a simple implementation of a drop down list with some methods to update and search. You can update the list on the fly using the methods.
mithDropDown
jsFiddle
var MythDropDown = function(list) {
if (Array.isArray(list))
this.list = list;
else
list = [];
if (!(this instanceof MythDropDown))
return new MythDropDown(list);
var self = this;
this.selected = {
name: list[0],
index: 0
};
this.list = list;
};
MythDropDown.prototype.view = function(ctrl) {
var self = this;
return m('select', {
config: function(selectElement, isinit) {
if (isinit)
return;
self.selectElement = selectElement;
self.update(self.list);
},
onchange: function(e) {
self.selected.name = e.target.value;
self.selected.index = e.target.selectedIndex;
}
},
this.list.map(function(name, i) {
return m('option', name);
}));
};
MythDropDown.prototype.getSelected = function() {
return (this.selected);
};
MythDropDown.prototype.update = function(newList) {
this.list = newList;
this.selectElement.selectedIndex = 0;
this.selected.name = newList[0];
this.selected.index = 0;
};
MythDropDown.prototype.sort = function() {
this.list.sort();
this.update(this.list);
};
MythDropDown.prototype.delete = function() {
this.list.splice(this.selected.index, 1);
this.update(this.list);
};
var list = ['test option 1', 'test option 2'];
var myList = new MythDropDown(list);
var main = {
view: function() {
return m('.content',
m('button', {
onclick: function() {
var L1 = ['Banana', 'Apple', 'Orange', 'Kiwi'];
myList.update(L1);
}
},
'Fruits'),
m('button', {
onclick: function() {
var L1 = ['Yellow', 'Black', 'Orange', 'Brown', 'Red'];
myList.update(L1);
}
},
'Colors'),
m('button', {
onclick: function() {
myList.sort();
}
},
'Sort'),
m('button', {
onclick: function() {
myList.delete();
}
},
'Remove Selected'),
m('', m.component(myList),
m('', 'Selected Item: ' + myList.selected.name, 'Selected Index: ' + myList.selected.index)
)
);
}
};
m.mount(document.body, main);
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.2.3/mithril.min.js"></script>

Datatable data binding using knockoutjs

I want to render data into table using datatable knockoutjs binding.
I am using the below link and code for rendering data into table. http://datatables.net/dev/knockout/
The only change I did in above example is while rendering age data I have added input box in age col for ever record and Updatebutton at the bottom of table, so that user can change his age and click of update button data should be updated automatically and in next page it should reflect in table.
The issue am facing is that i am unable to update local js "people" model and hence unable to bind updated data using knockoutjs.
ko.observableArray.fn.subscribeArrayChanged = function(addCallback, deleteCallback) {
var previousValue = undefined;
this.subscribe(function(_previousValue) {
previousValue = _previousValue.slice(0);
}, undefined, 'beforeChange');
this.subscribe(function(latestValue) {
var editScript = ko.utils.compareArrays(previousValue, latestValue);
for (var i = 0, j = editScript.length; i < j; i++) {
switch (editScript[i].status) {
case "retained":
break;
case "deleted":
if (deleteCallback)
deleteCallback(editScript[i].value);
break;
case "added":
if (addCallback)
addCallback(editScript[i].value);
break;
}
}
previousValue = undefined;
});
};`
`var data = [
{ id: 0, first: "Allan", last: "Jardine", age: 86 },
{ id: 1, first: "Bob", last: "Smith", age: 54 },
{ id: 2, first: "Jimmy", last: "Jones", age: 32 }
]; `
`var Person = function(data, dt) {
this.id = data.id;
this.first = ko.observable(data.first);
this.last = ko.observable(data.last);
this.age = ko.observable(data.age);
// Subscribe a listener to the observable properties for the table
// and invalidate the DataTables row when they change so it will redraw
var that = this;
$.each( [ 'first', 'last', 'age' ], function (i, prop) {
that[ prop ].subscribe( function (val) {
// Find the row in the DataTable and invalidate it, which will
// cause DataTables to re-read the data
var rowIdx = dt.column( 0 ).data().indexOf( that.id );
dt.row( rowIdx ).invalidate();
} );
} );
};
$(document).ready(function() {
var people = ko.mapping.fromJS( [] );
//loadData();
var dt = $('#example').DataTable( {
"bPaginate": false,
"bInfo" : false,
"bAutoWidth" : false,
"sDom" : 't',
"columns": [
{ "data": 'id' },
{ "data": 'first' },
{ "data": 'age',
"mRender": function (data, type, row ) {
var html = '<div style="display:inline-flex">' +
'<input type="text" class="headerStyle h5Style" id="ageId" value="'+data()+'"/>' +
'</div>';
return html;
}
}
]
} );
// Update the table when the `people` array has items added or removed
people.subscribeArrayChanged(
function ( addedItem ) {
dt.row.add( addedItem ).draw();
},
function ( deletedItem ) {
var rowIdx = dt.column( 0 ).data().indexOf( deletedItem.id );
dt.row( rowIdx ).remove().draw();
}
);
// Convert the data set into observable objects, and will also add the
// initial data to the table
ko.mapping.fromJS(
data,
{
key: function(data) {
var d = data;
return ko.utils.unwrapObservable(d.id);
},
create: function(options) {
return new Person(options.data, dt);
}
},
people
);
} );
This is the way to do it... I have made a jsfiddle showing this:
Edit: Recently worked out a way to get this binding using vanilla knockout. I've tested this out on the latest version of knockout (3.4) Just use this binding and knockout datatables works!
ko.bindingHandlers.dataTablesForEach = {
page: 0,
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
valueAccessor().data.subscribe(function (changes) {
var table = $(element).closest('table').DataTable();
ko.bindingHandlers.dataTablesForEach.page = table.page();
table.destroy();
}, null, 'arrayChange');
var nodes = Array.prototype.slice.call(element.childNodes, 0);
ko.utils.arrayForEach(nodes, function (node) {
if (node && node.nodeType !== 1) {
node.parentNode.removeChild(node);
}
});
return ko.bindingHandlers.foreach.init(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var options = ko.unwrap(valueAccessor()),
key = 'DataTablesForEach_Initialized';
ko.unwrap(options.data); // !!!!! Need to set dependency
ko.bindingHandlers.foreach.update(element, valueAccessor, allBindings, viewModel, bindingContext);
(function() {
console.log(options);
var table = $(element).closest('table').DataTable(options.dataTableOptions);
if (options.dataTableOptions.paging) {
if (table.page.info().pages - ko.bindingHandlers.dataTablesForEach.page == 0)
table.page(--ko.bindingHandlers.dataTablesForEach.page).draw(false);
else
table.page(ko.bindingHandlers.dataTablesForEach.page).draw(false);
}
})();
if (!ko.utils.domData.get(element, key) && (options.data || options.length))
ko.utils.domData.set(element, key, true);
return { controlsDescendantBindings: true };
}
};
JSFiddle
I made a fiddle with a solution
http://jsfiddle.net/Jarga/hg45z9rL/
Clicking "Update" will display the current knockout model as text below the button.
What was missing was the linking the change of the textbox to the observable by adding a listener in the render function. Also each row's textbox was being given the same id, which is not a good idea either. (Note: the event aliases are just to prevent collision with other handlers)
Changing the render function to build useful ids and adding the following should work:
$('#' + id).off('change.grid')
$('#' + id).on('change.grid', function() {
row.age($(this).val());
});
Ideally Knockout would handle this for you but since you are not calling applyBindings nor creating the data-bind attributes for the html elements all that knockout really gives you here is the observable pattern.
Edit: Additional Solution
Looking into it a little bit more you can let Knockout handle the rendering by adding the data-bindattribute into the template and binding your knockout model to the table element.
var html = '<div style="display:inline-flex">' +
'<input type="text" class="headerStyle h5Style" id="' + id + '" data-bind="value: $data[' + cell.row + '].age"/>'
And
ko.applyBindings(people, document.getElementById("example"));
This removes the whole custom subscription call when constructing the Personobject as well.
Here is another fiddle with the 2nd solution:
http://jsfiddle.net/Jarga/a1gedjaa/
I feel like this simplifies the solution. However, i do not know how efficient it performs nor have i tested it with paging so additional work may need to be done. With this method the mRender function is never re-executed and the DOM manipulation for the input is done entirely with knockout.
Here is a simple workaround that re-binds the data in knockout and then destroys/recreates the datatable:
// Here's my data model
var ViewModel = function() {
this.rows = ko.observable(null);
this.datatableinstance = null;
this.initArray = function() {
var rowsource1 = [
{ "firstName" : "John",
"lastName" : "Doe",
"age" : 23 },
{ "firstName" : "Mary",
"lastName" : "Smith",
"age" : 32 }
];
this.redraw(rowsource1);
}
this.swapArray = function() {
var rowsource2 = [
{ "firstName" : "James",
"lastName" : "Doe",
"age" : 23 },
{ "firstName" : "Alice",
"lastName" : "Smith",
"age" : 32 },
{ "firstName" : "Doug",
"lastName" : "Murphy",
"age" : 40 }
];
this.redraw(rowsource2);
}
this.redraw = function(rowsource) {
this.rows(rowsource);
var options = { paging: false, "order": [[0, "desc"]], "searching":true };
var datatablescontainer = $('#datatablescontainer');
var html = $('#datatableshidden').html();
//Destroy datatable
if (this.datatableinstance) {
this.datatableinstance.destroy();
datatablescontainer.empty();
}
//Recreate datatable
datatablescontainer.html(html);
this.datatableinstance = datatablescontainer.find('table.datatable').DataTable(options);
}
};
ko.applyBindings(new ViewModel("Planet", "Earth")); // This makes Knockout get to work
https://jsfiddle.net/benjblack/xty5y9ng/

Update Value In textbox using Knockout + Typeahead Custom Binding

I am making an application using knockout + typeahead that will show suggestion list of entered character matches the list.
All thing working fine.
Only problem is when I select the item from list it stores the whole object.
I want to store only name is first textbox and as per it store related value in second textbox.
I am not getting how to do that ?
can do using subscribe ?
put subscribe on first textbox, it will get whole object then it will process the object then store values in related textbox.
HTML :
<input type="text" class="form-control" data-bind="typeahead: data, dataSource: categories"/> *
<input type="text" class="form-control" data-bind="value: item"/>
Java Script :
var ViewModel = function () {
var self = this;
self.categories = [
{ name: 'Fruit', items: 'abc' },
{ name: 'Vegetables', items: 'xyz' }
];
self.data = ko.observable();
self.item = ko.observable();
};
var viewModel = new ViewModel();
ko.bindingHandlers.typeahead = {
init: function (element, valueAccessor, allBindingsAccessor) {
var $e = $(element);
var accessor = valueAccessor();
var source = allBindingsAccessor().dataSource || [];
var names = new Bloodhound({
datumTokenizer: function (d) {
return Bloodhound.tokenizers.whitespace(d.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
local: source
});
names.initialize();
var substringMatcher = function() {
return function findMatches(q, cb) {
var matches, substrRegex;
substrRegex = new RegExp(q, 'i');
$.each(source, function(i, p) {
if (substrRegex.test(p.name)) {
matches.push({ value: p });
}
});
console.dir(matches);
cb(matches);
};
};
$e.typeahead({
hint: true,
highlight: true,
minLength: 1
},
{
name: 'name',
displayKey: 'name',
source: names.ttAdapter()
}).on('typeahead:selected', function (el, datum) {
console.dir(datum);
accessor(datum);
}).on('typeahead:autocompleted', function (el, datum) {
console.dir(datum);
console.log(accessor);
});
},
update: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
console.log(value);
$(element).val(ko.utils.unwrapObservable(valueAccessor()));
}
};
ko.applyBindings(viewModel);
Here is jsfiddle of it.
I have asked question in morning also but i don't get answer so I code myself by studying the tutorials, but now I am stuck Here.
Any Suggestion on this situation ??
There are certain limitations with the temporary workout like knockout extension for Bootstrap Typeahead. I used it before in my project but it lead to a lot of limitations like custom control over templating, custom control over selected object property and selected object value. So I moved on to plugin jqAuto which is based on JQuery-UI autocomplete.
This plugin has enormous range of customizations and is faster than typeahead. I replicated your code in the jqAuto code and it is faster and cleaner than bootstrap typeahead.
JavaScript
var ViewModel = function() {
self.categories = [
{ name: 'Fruit', items: 'abc' },
{ name: 'Vegetables', items: 'xyz' }
];
self.data = ko.observable("");
self.item = ko.observable();
};
ko.applyBindings(new ViewModel());
HTML
<input data-bind="jqAuto: { source: categories, value: data, dataValue : data, inputProp: 'name', valueProp: 'name', labelProp: 'name' }" />
You can control the label propery, input property as well as value property in granular level.
Fiddle demo : http://jsfiddle.net/rahulrulez/uGGb8/4/
Detailed documentation of jqAuto plugin is given here : https://github.com/rniemeyer/knockout-jqAutocomplete

KnockOutJS trigger parent function on child subscribe

I am currently trying to learn KnockOutJS. I thought it would be a great idea to create a simple task-list application.
I do not want to write a long text here, let's dive into my problem. I appreciate all kind of help - I am new to KnockOutJS tho!
The tasks are declared as followed:
var Task = function (data) {
var self = this;
self.name = ko.observable(data.name);
self.status = ko.observable(data.status);
self.priority = ko.observable(data.priority);
}
And the view model looks like this
var TaskListViewModel = function() {
var self = this;
self.currentTask = ko.observable();
self.currentTask(new Task({ name: "", status: false, priority: new Priority({ name: "", value: 0 }) }));
self.tasksArr = ko.observableArray();
self.tasks = ko.computed(function () {
return self.tasksArr.slice().sort(self.sortTasks);
}, self);
self.sortTasks = function (l, r) {
if (l.status() != r.status()) {
if (l.status()) return 1;
else return -1;
}
return (l.priority().value > r.priority().value) ? 1 : -1;
};
self.priorities = [
new Priority({ name: "Low", value: 3 }),
new Priority({ name: "Medium", value: 2 }),
new Priority({ name: "High", value: 1 })
];
// Adds a task to the list
// also saves updated task list to localstorage
self.addTask = function () {
self.tasksArr.push(new Task({ name: self.currentTask().name(), status: false, priority: self.currentTask().priority() }));
self.localStorageSave();
self.currentTask().name("");
};
// Removes a task to a list
// also saves updated task list to localstorage
self.removeTask = function (task) {
self.tasksArr.remove(task);
self.localStorageSave();
};
// Simple test function to check if event is fired.
self.testFunction = function (task) {
console.log("Test function called");
};
// Saves all tasks to localStorage
self.localStorageSave = function () {
localStorage.setItem("romaTasks", ko.toJSON(self.tasksArr));
};
// loads saved data from localstorage and parses them correctly.
self.localStorageLoad = function () {
var parsed = JSON.parse(localStorage.getItem("romaTasks"));
if (parsed != null) {
var tTask = null;
for (var i = 0; i < parsed.length; i++) {
tTask = new Task({
name: parsed[i].name,
status: parsed[i].status,
priority: new Priority({
name: parsed[i].priority.name,
value: parsed[i].priority.value
})
});
self.tasksArr.push(tTask);
}
}
};
self.localStorageLoad();
}
What I want to do in my html is pretty simple.
All tasks I have added are saved to localStorage. The save function is, as you can see, called each time an element has been added & removed. But I also want to save as soon as the status of each task has been changed, but it is not possible to use subscribe here, such as
self.status.subscribe(function() {});
because I cannot access self.tasksArr from the Task class.
Any idea? Is it possible to make the self.tasksArr public somehow?
Thanks in advance!
Try this:
self.addTask = function () {
var myTask = new Task({ name: self.currentTask().name(), status: false, priority: self.currentTask().priority() })
myTask.status.subscribe(function (newValue) {
self.localStorageSave();
});
self.tasksArr.push(myTask);
self.localStorageSave();
self.currentTask().name("");
};

Categories

Resources