Unable to access knockout observable array item - javascript

I fetch data from web api and push the data into observable array. I would like to make the observable array item to be observable. However, it seems that i could not access the object if i make it observable.
function KnockoutViewModel() {
var self = this;
self.ProfileList = ko.observableArray([]);
self.GetProfile = function() {
$.ajax({
type: 'GET',
success: function() {
$.each(data.ProfileList, function (index, value) {
self.ProfileList.push(value);
alert(self.ProfileList()[index].Name) // success
}
}
});
}
self.GetProfile();
}
function KnockoutViewModel() {
var self = this;
self.ProfileList = ko.observableArray([]);
self.GetProfile = function() {
$.ajax({
type: 'GET',
success: function() {
$.each(data.ProfileList, function (index, value) {
self.ProfileList.push(ko.observable(value));
alert(self.ProfileList()[index].Name) // fail. Object does not support property or method 'Name'
}
}
});
}
self.GetProfile();
}

you are directly pushing object (by making it observable) into observableArray does it sound right ? Nah (you may want to make Name as observable i believe) . Tough you can get the output by doing something like this self.ProfileList()[index]().Name check here
Preferred way :
viewModel:
function convert(data) {
this.Name = ko.observable(data.Name)
this.place = ko.observable(data.place)
this.age = ko.observable(data.age)
}
function KnockoutViewModel() {
var self = this;
self.ProfileList = ko.observableArray([]);
self.GetProfile = function () {
var data = [{
'Name': 'Super',
'place': 'Ind',
'age': 25
}, {
'Name': 'Cool',
'place': 'Aus',
'age': 15
}]
//Manual way with function defined
//self.ProfileList(ko.utils.arrayMap(data, function (value) {
// return new convert(value)
//}))
//Using Mapping Plugin
ko.mapping.fromJS(data,{},self.ProfileList)
}
self.GetProfile();
}
ko.applyBindings(new KnockoutViewModel());
working sample here

Try to use the mapping module:
self.ProfileList.push(ko.mapping.fromJS(value));
This will automatically wrap value's properties in knockout observables.

Related

Get data from php file to javascript

The javascript below is supposed to be some sort of autocomplete. I am using bootstrap typeahead.
When I type items in my input field, I am able to see suggestions, the problem is I am not able to select them and populate the input field.
Any idea what may be wrong with it?
<script type="text/javascript">
$('#typeahead').typeahead({
source: function (query, process) {
objects = [];
map = {};
return $.get('live_search.php?filter=relation', { query: query }, function (data) {
console.log(data);
var data = $.parseJSON(data);
return process(data);
});
$.each(data, function(i, object) {
map[object.name] = object;
objects.push(object.name);
});
process(objects);
},
updater: function(item) {
$('#getSelection').val(map[item].name);
$('#getValue').val(map[item].name);
return item;
}
});
</script>
It looks like your source method returns immediately from the $.get call and never enters the $.each iteration. Move the iteration code into the get request callback block.
Also you're references to objects and map are in the scope of the source method, but you reference them in the updater method
var typeahead = {
objects: [],
map: {},
};
$("#typeahead").typeahead({
source: function(query, process) {
return $.get("live_search.php?filter=relation", { query: query }, function(
data
) {
var data = $.parseJSON(data);
$.each(data, function(i, object) {
typeahead.map[object.name] = object;
typeahead.objects.push(object.name);
});
return process(data);
});
},
updater: function(item) {
$("#getSelection").val(typeahead.map[item].name);
$("#getValue").val(typeahead.map[item].name);
return item;
},
});

How to pass a parameter to Collection parse? backbone.js

How could one pass a parameter through the parse/fetch function?
I want to pass the variable VARIABLE_PARAMETER in the lower Initialize-part.
Otherwise I have to write three mostly identical Collections.
Thank you for you help.
app.js
//--------------
// Collections
//--------------
DiagnoseApp.Collections.Param1_itemS = Backbone.Collection.extend({
model: DiagnoseApp.Models.Param1_item,
url: 'TestInterface.xml',
parse: function (data) {
var parsed = [];
$(data).find(/*VARIABLE_PARAMETER*/).find('PARAMETER').each(function (index) {
var v_number = $(this).attr('Number');
var v_Desc_D = $(this).attr('Desc_D');
parsed.push({ data_type: v_data_type, number: v_number, Desc_D: v_Desc_D});
});
return parsed;
},
fetch: function (options) {
options = options || {};
options.dataType = "xml";
return Backbone.Collection.prototype.fetch.call(this, options);
}
});
This is the way I initialize the app:
//--------------
// Initialize
//--------------
var VARIABLE_PARAMETER = "OFFLINE";
var offline_Collection = new DiagnoseApp.Collections.Param1_itemS();
var offline_Collection_View = new DiagnoseApp.Views.Param1_itemS({collection: offline_Collection});
//VARIABLE_PARAMETER has to be passed here in fetch I guess ??
offline_Collection.fetch({
success: function() {
console.log("JSON file load was successful", offline_Collection);
offline_Collection_View.render();
},
error: function(){
console.log('There was some error in loading and processing the JSON file');
}
});
The fetch method accepts an option argument : http://backbonejs.org/#Collection-fetch
The parse method also accepts an option argument: http://backbonejs.org/#Collection-parse
These objects are actually the same. So you may write:
parse: function (data, options) {
var parsed = [];
$(data).find(options.variableParameter).find('PARAMETER').each(function (index) {
var v_number = $(this).attr('Number');
var v_Desc_D = $(this).attr('Desc_D');
parsed.push({ data_type: v_data_type, number: v_number, Desc_D: v_Desc_D});
});
return parsed;
},
Not sure I understand your question, but if you want to "pass a parameter" from fetch to parse, and if that parameter value doesn't change for a given collection, you could just store it in the collection. You could pass the parameter to fetch as an additional property in options:
fetch: function (options) {
options = options || {};
options.dataType = "xml";
this.variableParameter = options.variableParameter;
return Backbone.Collection.prototype.fetch.call(this, options);
},
And then simply retrieve it
parse: function (data) {
// do something useful with this.variableParameter
// ...
}

How to add elements from response after Backbone fetch into another fetch

var ChannelStatsView = Backbone.View.extend({
el: "#id-channel-stats",
initialize: function() {
var _this = this;
this.modelChannelList = new channelListModel();
this.modelChannelStats = new channelStatsModel();
this.channelstatsCollection = new channelStatsCollection();
this.channelNames = [];
this.listOfObjects = [];
this.modelChannelList.fetch({
success: function(model, response, options) {
model.set();
_this.formatChannelIds();
},
error: function(model, xhr, options) {
}
});
},
formatChannelIds: function() {
_this = this;
_.filter(_this.modelChannelList.toJSON(), function(channelObj) {
if (channelObj['isactive'] == true) {
_this.updateStats(channelObj['id'], channelObj['name']);
}
});
},
updateStats: function(id, name) {
var _this = this;
_this.modelChannelStats.fetch({
data: {
channel: id
},
processData: true,
success: function(model, response, options) {
_this.response = response;
_this.listOfObjects.push(_this.response.records[0]);
_this.channelNames.push(name);
}
}).done(function(model, response, options) {
_this.render();
});
},
render: function() {
var _this = this;
if (_this.listOfObjects.length == 0) {
} else {
_this.template = channelTemplate;
_this.$el.html(_this.template({
orderData: _this.listOfObjects,
channelNames: _this.channelNames
}));
}
}
});
In my code i am taking the response from one model.fetch query i.e this.modelChannelList and getting all the active id's then supplying it to another fetch to get the response i know this solution is really bad can someone help me how to make it faster and effective.
I am considering using Promises
The main issue you need to deal with here is the number of fetch requests that you are making. Promises are cool so I've included that too. Here's what I recommend you do:
1) Update your model class to assign the fetch function as a deferred
var channelListModel = Backbone.Model.extend({
initialize: function() {
// Assign the Deferred issued by fetch() as a property
this.deferred = this.fetch();
}
});
2) Modify your updateStats/formatChannels logic to create an array of ids and pass those through your fetch to get a complete data set. This will save tons of time by reducing the number of calls you have to make
initialize: function() {
// other stuff here...
this.modelChannelList.deferred.done(function(model) {
model.set();
view.formatChannelIds();
});
// other stuff here...
}
formatChannelIds: function() {
var _this = this,
ids = [];
_.filter(_this.modelChannelList.toJSON(), function(channelObj) {
if (channelObj['isactive'] == true) {
ids.push(channelObj['id']);
}
_this.updateStats(ids);
});
}
You will have to change up your data service a bit, but this is a change that is ultimately necessary anyways.

Binding view with javascript knockout

Here i have my get method that gets the data that i want to return in order to bind it with the view page. I am having trouble wrapping my head to how i could bind this information to the view.
Get Method:
var getRoster = function () {
Ajax.Get({
Url: ....,
DataToSubmit: {id: properties.Id },
DataType: "json",
OnSuccess: function (roleData, status, jqXHR) {
console.log("roles:", roleData.length);
Ajax.Get({
Url: ...,
DataToSubmit: { pageNumber: 1, id: properties.Id },
DataType: "json",
OnSuccess: function (userData, status, jqXHR) {
for (var x in roleData)
{
var role = roleData[x];
console.log(role);
for (var y in userData)
{
var user = userData[y];
if (user.ContentRole == role.ContentRole)
{
rosterViewModel.PushUser(new userViewModel(user));
console.log(user);
}
}
roleTypesViewModel.PushRole(new roleViewModel(role));
}
}
});
}
});
rosterViewModel.PushUser = function (user) {
viewModel.RosterUsers.push(new userViewModel(user));
};
roleTypesViewModel.PushRole = function (role) {
viewModel.RosterRoleTypes.push(new roleViewModel(role));
}
var userViewModel = function (data) {
var _self = this;
_self.ID = ko.observable(data.ID);
_self.Name = ko.observable(data.Name);
_self.Email = ko.observable(data.Email);
_self.ContentRole = ko.observable(data.ContentRole);
};
var roleViewModel = function (data) {
var _self = this;
_self.ContentRole = ko.observable(data.ContentRole);
_self.RoleName = ko.observable(data.RoleName);
_self.RoleRank = ko.observable(data.RoleRank);
_self.UserCount = ko.observable(data.UserCount);
};
var viewModel = {
RosterRoleTypes: ko.observableArray([]),
RosterUsers: ko.observableArray([])
};
View:
<div id="gridView" data-bind="foreach: RosterRoleTypes">
<h3 class="roleHeader"><span data-bind="text:RoleName"></span>
<span class="userCount">(<span data-bind="text:UserCount"></span>)</span>
</h3>
<div data-bind="template: { name: 'grid', foreach: RosterUsers}">
</div>
</div>
How can i bind my data to display in my view?
If you are trying to bind multiple areas of your page to different view models, that is possible by passing in an additional parameter to your ko.applyBindings() method that you call. Your problem is that you are mixing models and view models and using them improperly. If you want to have one view model adjust your code to include all of the functions of your view model and set your models as models instead of viewmodels -
function rosterViewModel() {
var self = this;
self.RosterRoleTypes = ko.observableArray([]),
self.RosterUsers = ko.observableArray([])
self.PushUser = function (user) {
viewModel.RosterUsers.push(new userModel(user));
};
self.PushRole = function (role) {
viewModel.RosterRoleTypes.push(new roleModel(role));
};
self.getRoster = function () {
Ajax.Get({
Url: ....,
DataToSubmit: {id: properties.Id },
DataType: "json",
OnSuccess: function (roleData, status, jqXHR) {
Ajax.Get({
Url: ...,
DataToSubmit: { pageNumber: 1, id: properties.Id },
DataType: "json",
OnSuccess: function (userData, status, jqXHR) {
for (var x in roleData)
{
var role = roleData[x];
for (var y in userData)
{
var user = userData[y];
if (user.ContentRole == role.ContentRole)
{
self.PushUser(new userModel(user));
}
}
self.PushRole(new roleModel(role));
}
}
});
}
});
}
var userModel = function (data) {
var _self = this;
_self.ID = ko.observable(data.ID);
_self.Name = ko.observable(data.Name);
_self.Email = ko.observable(data.Email);
_self.ContentRole = ko.observable(data.ContentRole);
};
var roleModel = function (data) {
var _self = this;
_self.ContentRole = ko.observable(data.ContentRole);
_self.RoleName = ko.observable(data.RoleName);
_self.RoleRank = ko.observable(data.RoleRank);
_self.UserCount = ko.observable(data.UserCount);
};
ko.applyBindings(new rosterViewModel());
This assumes you want to use a single view model for your view. If you are combining multiple content areas that should be bound separately you can create two view models and merge them as shown in this question - KnockOutJS - Multiple ViewModels in a single View - or you could also bind them separately by passing in an additional parameter to the ko.applyBindings() method as showm here - Example of knockoutjs pattern for multi-view applications
All of the data that you want to bind to UI will be properties of your viewmodel as KO observable or observable arrays. Once the view model is created and its members are assigned with data(callbacks in your case), you need to apply bindings using ko.applyBindinds so that the data is bound to UI. In your case the last AJAX success callback seems to be the appropriate place.
Also your HTML makes using of template bindings however apparently there is no template defined with name 'grid'. Check on this.
Knockout tutorial link http://learn.knockoutjs.com/#/?tutorial=intro
Add
ko.applyBindings(viewModel);
somewhere in your application.

Set a knockout.js observable without subscriptions firing

How can I set an observable property without any subscriptions firing for it?
I have a scenario were the page loads, an ajax call is made to get some data, the data is looped over and the currently selected item is then set to an observable. I want to be able to set this observable without any subscriptions for it firing because the first time this observable is set is considered its initial sate and the subscriptions should not execute on initial state.
function PlanViewModel() {
var self = this;
self.plans = ko.observableArray();
self.selectedItem = ko.observable();
self.getAllPlans = function () {
$.ajax({
url: "/Backoffice/Home/GetAllPlans",
type: "POST",
data: {},
context: this,
success: function (result) {
var planList = this.plans;
// clear the plan list
planList.removeAll();
$.each(result.plans, function () {
var planDetail = new PlanDetail(this, self);
if (this.IsSelected) {
self.selectedItem(planDetail); // how do I set this without the subscriptions firing?
}
planList.push(planDetail);
});
},
error: function (result) {
alert("An error occured getting plans.");
}
});
}
self.selectedItem.subscribe(function (newItem) {
newItem.repositoryUpdateSelectedPlan();
} .bind(self));
}
You could restructure your code like this:
function PlanViewModel() {
var self = this;
self.plans = ko.observableArray();
self.getAllPlans = function () {
$.ajax({
// …
success: function (result) {
// …
$.each(result.plans, function () {
var planDetail = new PlanDetail(this, self);
if (this.IsSelected) {
self.selectedItem = ko.observable(planDetail);
}
planList.push(planDetail);
});
if (self.selectedItem === undefined) {
self.selectedItem = ko.observable();
}
self.selectedItem.subscribe(function (newItem) {
newItem.repositoryUpdateSelectedPlan();
}.bind(self));
},
// …
});
}
}
That is, only start Knockout after your desired initial state is achieved.
Thanks, I went down that route and its working with some modifications. The selectedItem observable must be defined on the model from the get go because its used in bindings all over the place but I did move the subscription portion like you've suggested and that's working out great.
function PlanViewModel() {
var self = this;
var selectedItemSubscription = null;
self.plans = ko.observableArray();
self.selectedItem = ko.observable();
self.getAllPlans = function () {
$.ajax({
url: "/Backoffice/Home/GetAllPlans",
type: "POST",
data: {},
context: this,
success: function (result) {
var planList = this.plans;
// clear the plan list
planList.removeAll();
$.each(result.plans, function () {
var planDetail = new PlanDetail(this, self);
if (this.IsSelected) {
if (selectedItemSubscription != null)
selectedItemSubscription.dispose();
self.selectedItem(planDetail);
}
planList.push(planDetail);
});
selectedItemSubscription = self.selectedItem.subscribe(function (newItem) {
newItem.repositoryUpdateSelectedPlan();
}.bind(self));
},
error: function (result) {
alert("An error occured getting plans.");
}
});
}
}

Categories