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/
Related
Hi I am trying to work with the store filterBy function but i am unable to make it work as I want.
I have four buttons(A B C D) and a list with all data related to buttons
When I click on the button I should filter accordingly
Note:- when I click A ,I should get records of A
when I click B , I should get records of B appended to A's list of records
So actually when I click on the buttons I should have a array of button ids and filter the the list using these id's.
filterList:function (list, index, target, record){
var categories=[];
categories.push(record.get('id'));
for(c=0;c<categories.length;c++)
{
Ext.getStore('mystore').filterBy(function(record){
var id = record.get('id');
if(id==categories[c])
{
return true;
}
});
}
}
Any help is appreciated.
What is wrong with your code
The filterBy return function should always return a boolean value. You are only returning true, and no false. Besides that you are filtering the store in a loop, and the parameter record exists twice (as a parameter in the function filterList and in the return function of the filterBy).
Example
I created a Fiddle to show you where I came up with. I know it is ExtJs instead of Sencha Touch, but you will get the idea. I will step through the MainController, where all the logic is.
I didn't clean the code so there is some duplication, but it's just a concept.
Setting up some logic
First I create a set of buttons on my view. See that I'm setting an action property.
...
tbar: [{
text: 'Filter a',
action: 'a'
}, {
text: 'Filter b',
action: 'b'
}],
...
Then I bind an onClick event to the button. In this event I use the action property as a searchValue, but it could be anything of course.
...
onClick: function(button, event, eOpts) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
filters = store.getFilters(),
searchValue = button.action,
regex = RegExp(searchValue, 'i'),
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
if (filters.containsKey(button.id)) {
store.removeFilter(filter);
} else {
store.addFilter(filter);
}
},
...
The magic
The magic is in the filter instance. By adding new filter instances each time you filter, you can filter with several filters. If you push button a and button b they will respect each other. In that filter I use a regex to search through all data of the current model. I config an id to recognize the filter so I can remove it afterwards.
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
If the filter doesn't exists it will be added, otherwise it will be removed.
if (filters.containsKey(button.id)) {
store.removeFilter(filter);
} else {
store.addFilter(filter);
}
Reset filters
I also created a textfield to search through all data. Instead of adding and removing filters I just call clearFilter and add a new filter with new searchvalue.
onKeyUp: function(textField, event, eOpts) {
this.filterStore(textField.getValue());
},
filterStore: function(searchValue) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
regex = RegExp(searchValue, 'i');
store.clearFilter(true);
store.filter(new Ext.util.Filter({
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
}));
}
The complete maincontroller
Ext.define('Filtering.controller.MainController', {
extend: 'Ext.app.Controller',
config: {
refs: {
mainGrid: 'maingrid'
}
},
init: function() {
var me = this;
me.listen({
global: {},
controller: {},
component: {
'segmentedbutton': {
toggle: 'onToggle'
},
'toolbar > button': {
click: 'onClick'
},
'textfield': {
keyup: 'onKeyUp'
}
},
store: {
'*': {
metachange: 'onMetaChange',
filterchange: 'onFilterChange'
}
},
direct: {}
});
},
onLaunch: function() {
var store = Ext.StoreManager.lookup('Users') || Ext.getStore('Users');
store.load();
},
onMetaChange: function(store, metaData) {
var grid = this.getMainGrid(),
model = store.getModel(),
// metadata
fields = metaData.fields,
columns = metaData.columns,
gridTitle = metaData.gridTitle;
model.fields = fields;
grid.setTitle(gridTitle);
grid.reconfigure(store, columns);
},
onFilterChange: function(store, filters, eOpts) {
var me = this,
grid = me.getMainGrid();
grid.getSelectionModel().select(0);
},
/**
* Used for the segmented buttons
*/
onToggle: function(container, button, pressed) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
//filters = store.getFilters(),
searchValue = button.action,
regex = RegExp(searchValue, 'i'),
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
if (pressed) {
store.addFilter(filter);
} else {
store.removeFilter(filter);
}
},
/**
* Used for the toolbar buttons
*/
onClick: function(button, event, eOpts) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
filters = store.getFilters(),
searchValue = button.action,
regex = RegExp(searchValue, 'i'),
filter = new Ext.util.Filter({
id: button.id,
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
});
if (filters.containsKey(button.id)) {
store.removeFilter(filter);
} else {
store.addFilter(filter);
}
},
/**
* Used for the textfield
*/
onKeyUp: function(textField, event, eOpts) {
this.filterStore(textField.getValue());
},
filterStore: function(searchValue) {
var me = this,
grid = me.getMainGrid(),
store = grid.getStore(),
regex = RegExp(searchValue, 'i');
store.clearFilter(true);
store.filter(new Ext.util.Filter({
filterFn: function(record) {
var match = false;
Ext.Object.each(record.data, function(property, value) {
match = match || regex.test(String(value));
});
return match;
}
}));
}
});
The complete view
Ext.define('Filtering.view.MainGrid', {
extend: 'Ext.grid.Panel',
xtype: 'maingrid',
tbar: [{
xtype: 'segmentedbutton',
allowMultiple: true,
items: [{
text: 'Filter a',
action: 'a'
}, {
text: 'Filter b',
action: 'b'
}]
}, {
text: 'Filter a',
action: 'a'
}, {
text: 'Filter b',
action: 'b'
}, {
xtype: 'textfield',
emptyText: 'Search',
enableKeyEvents: true
}],
//title: 'Users', // Title is set through the metadata
//store: 'Users', // we reconfigure the store in the metachange event of the store
columns: [] // columns are set through the metadata of the store (but we must set an empty array to avoid problems)
});
Assuming you have two toggle buttons and you want to filter store on toggling of those buttons,
Controller
Ext.define('namespace',{
extend:'controller...',
config:{
refs:{
btnA:'#btnA',
btnB:'#btnB',
},
controls:{
btnA:{
change:'btnChange'
},
btnB:{
change:'btnChange'
}
}
},
btnChange:function(ths,val){
var btnA = this.getBtnA().getValue(),
btnB = this.getBtnB().getValue();
Ext.getStore('mystore').filterBy(function(r){
if(btnA && btnB){
return r.get('id') === btnA.getValue() || r.get('id') === btnB.getValue();
}else if(btnA){
return r.get('id') === btnA.getValue();
}else if(btnB){
return r.get('id') === btnB.getValue();
}
return false;
});
}
});
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
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);
I am trying to build datagrid with sorting, searching and paging enabled. Therefore, I am using fuelux-datagrid.
MY backbone view looks like this:
var app = app || {};
$(function ($) {
'use strict';
// The Players view
// ---------------
app.PlayersView = Backbone.View.extend({
template: _.template( $("#player-template").html() ),
initialize: function () {
if(this.collection){
this.collection.fetch();
}
this.listenTo(this.collection, 'all', this.render);
},
render: function () {
this.$el.html( this.template );
var dataSource = new StaticDataSource({
columns: [
{
property: 'playername',
label: 'Name',
sortable: true
},
{
property: 'age',
label: 'A',
sortable: true
}
],
data: this.collection.toJSON(),
delay: 250
});
$('#MyGrid').datagrid({
dataSource: dataSource,
stretchHeight: true
});
}
});
});
The player template just contain the template as given in fuelux datagrid . My routing code somewhere instantiate app.playerview with collection as
new app.PlayersView({
collection : new app.PlayersCollection
}));
My players collection contains list of player model as below
[{
"id":1,
"playername":"rahu",
"age":13
},
{
"id":2,
"playername":"sahul",
"age":18
},
{
"id":3,
"playername":"ahul",
"age":19
}]
My datasource class/function to construct datasoruce with columns and data method is as given in datasource constructor
However, I get the error the " datasource in not defined ". Can anybody help me?
I just wanted to hack the code so that instead of datasource constructed from local data.js in given example, I want to construct the datasource so that it takes data from playercollection.
Also, how to add the one extra column so that we can put edit tag insdie and its should be able to edit the particular row model on clicking that edit.
I have been stucking around these a lot. It would be great help to figure out the answer.
I was stucking around datasource.
I modified the datasource as follows and then it worked.
var StaticDataSource = function (options) {
this._formatter = options.formatter;
this._columns = options.columns;
this._delay = options.delay || 0;
this._data = options.data;
};
StaticDataSource.prototype = {
columns: function () {
return this._columns;
},
data: function (options, callback) {
var self = this;
setTimeout(function () {
var data = $.extend(true, [], self._data);
// SEARCHING
if (options.search) {
data = _.filter(data, function (item) {
var match = false;
_.each(item, function (prop) {
if (_.isString(prop) || _.isFinite(prop)) {
if (prop.toString().toLowerCase().indexOf(options.search.toLowerCase()) !== -1) match = true;
}
});
return match;
});
}
// FILTERING
if (options.filter) {
data = _.filter(data, function (item) {
switch(options.filter.value) {
case 'lt5m':
if(item.population < 5000000) return true;
break;
case 'gte5m':
if(item.population >= 5000000) return true;
break;
default:
return true;
break;
}
});
}
var count = data.length;
// SORTING
if (options.sortProperty) {
data = _.sortBy(data, options.sortProperty);
if (options.sortDirection === 'desc') data.reverse();
}
// PAGING
var startIndex = options.pageIndex * options.pageSize;
var endIndex = startIndex + options.pageSize;
var end = (endIndex > count) ? count : endIndex;
var pages = Math.ceil(count / options.pageSize);
var page = options.pageIndex + 1;
var start = startIndex + 1;
data = data.slice(startIndex, endIndex);
if (self._formatter) self._formatter(data);
callback({ data: data, start: start, end: end, count: count, pages: pages, page: page });
}, this._delay)
}
};
Infact, I just removed following code and its associated braces.
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['underscore'], factory);
} else {
root.StaticDataSource = factory();
}
}(this, function () {
I dont know what exactly the above code is doing an what dependdencies they have over.
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("");
};