knockout push data from ajax to observableArray - javascript

I want to push data from ajax to knockout observableArray , but it give me an error:
The argument passed when initializing an observable array must be an
array, or null, or undefined.
efine(['uiComponent', 'ko', 'jquery'], function (Component, ko, jquery) {
return Component.extend({
initialize: function () {
this._super();
/* State and cities */
this.selectCity();
},
selectCity: function () {
var myViewModel = {};
state = ko.observableArray([]);
jquery.ajax({
url: 'http://127.0.0.1/magento/hamechio/region.php',
type: "GET",
dataType: "json",
success: function(data) {
myViewModel = data;
state.push(data);
}
});
console.log(state);
}
});
});

this line should be change as per my knowledge.
state = ko.observableArray([]);
to this
var state = ko.observableArray();

This is a ajax scope ques.
you can use 'var'.
like this:
var state = ko.observableArray([]);

Can I suggest looking through the Documentation that has lots of working examples: https://knockoutjs.com/documentation/observableArrays.html
Firstly, your View Model is the object on which all of your programme is built on. This object contains all of the data to show (as knockout observable property "methods") and commands to receive (functions). Therefore you need to define the view model to contain everything your application needs to do:
var viewModel = {
//Bindings
state = ko.observableArray();
}
Now you can write to the viewModel.state():
if data is an array and you don't want to track changes to data's items:
viewModel.state(data);
or push them one at a time:
data.foreach(function(el){ viewModel.state.push(el); });
If you want to track changes to each individual item's properties, you will need to use the second method and convert each element into an object composed of ko.observables.

Related

Knockout JS Mapping fromJS nested models

I am having trouble understanding how to work with Knockout JS Mapping Plugin. I have some nested models (as seen below) and what I am currently doing is just using the ko.mapping.fromJS() in the parent model. What I am noticing is that computed values are not being ...computed.
I tried to understand the "create":
var mapping = {
'children': {
create: function(options) {
return new myChildModel(options.data);
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
But right now in my current scenario I am not sure how to implemented.
Current structure:
var ProductModel = function($name, $price, $quantity) {
var self = this;
self.id = ko.observable();
self.name = ko.observable($name);
self.quantity = ko.observable($quantity);
self.price = ko.observable($price);
self.price.total = ko.computed(function() {
return self.price() * self.quantity();
});
};
var CartModel = function (){
var self = this;
// Model Properties
self.id = ko.observable();
self.products = ko.observableArray();
self.fetch = function() {
$.ajax({
url: "route to get the specific cart",
type: "GET",
success: function(data) {
ko.mapping.fromJS(data, {}, self);
}
});
};
// Convert to mapping format
ko.mapping.fromJS(ko.mapping.toJS(self));
};
var ViewModel = ko.validatedObservable(new CartModel());
ko.applyBindings(new ViewModel());
I am not sure how to get the ProductModel to trigger the computed inside the ProductModel, I am not sure if I have to call the ko.mapping.fromJS inside every single Model (I have many more models, I stripped them out to make it simpler).
After the fetch function I alerted the products().length and it actually contains the quantity that was previously saved. but the computed is not showing. How do I implement the create method of knockout mapping plugin for nested observable(and observablearrays) that are models with computed inside of them.
P.S: The reason I have it self.price.total is in order to avoid the toJS send the total key as well. I can't ignore the properties of nested models using ignore:
See this thread for more: Knockout JS Mapping fromJS nested models
How does your ajax response looks like? Does it returns the whole cart, or just the products?
Anyway, if you want to handle your product list as an observableArray of viewmodels you could do something like this:
ko.utils.arrayForEach(products, function(item, index) {
self.products.push(new ProductModel(item.name, item.price, item.qty);
});
If you want to use the mapping plugin, I haven't made a custom implementation of the "create" method, but I would do something like:
var ProductModel = function(product) {
var self = this;
ko.mapping.fromJS(product, {}, self);
self.total = ko.computed(function() {
return self.price() * self.quantity();
});
};
Edit
I have re-read your question, and I think this answer may be what you're looking for, the only downside of that approach, well for me at least, is that you would need to define a mapping config object for each viewmodel with nested viewmodels in your code

ember js, how can i use the array in the controllers from the routes

Hi I am new to ember and I want to use deal with some data in a array fetched from my server side. But I do not know how to do that. The below code is in routes.
searchList: null,
model: function() {
// This is so that we don't refresh it all the time
// arguably, this is a hack.
var searchList = this.get('searchList');
if(searchList !== null) {
return searchList;
}
var self = this;
return Ember.$.ajax({
url: 'http://coen268.peterbergstrom.com/timezones.php',
dataType: 'jsonp'
}).then(function(response) {
var cities = []; <-----------------------this is an array of names from server side
if (response && response.length) {
for(var i=0; i<response.length; i++) {
cities.push(Ember.Object.create(response[i]));
}
}
/*
cities.sort(function(a, b) {
return a.id - b.id
})
*/
self.set('searchList', cities);
return cities;
I just want to use the cities var in my controller so that I can do something like sort, add and reorganize the output to final view html.
Many thanks!
If your route is called, say App.CitiesRoute, then you need to declare your CitiesController like this:
App.CitiesController = Ember.ArrayController.extend({});
You can then use sorting with simple Controller properties, or filtering when calling filter() on this (which refers to your ArrayController), as shown on Ember's Guides,
You also have access to all functions and properties of ArrayController, as shown in the Ember API documentation.
You can even specify a specific Controller for a every item of your array by specifying the itemControllerproperty in your ArrayController, like:
App.CitiesController = Ember.ArrayController.extend({
itemController: 'city'
});
// Here you can specify an ObjectController linked to all your items in the cities array
App.CityController = Ember.ObjectController.extend({
cityNameLowercase: function() {
return this.get('cityName').toLowerCase();
}.property('cityName')
});
which will be bound to each city of your array.

Backbone console.log attributes and pass them to the View

I am not sure if I am using Models and Collections correctly. If I'm not I would really appreciate any guidance or advice into what I am doing wrong.
I have set up a Model and a Collection. The Collection has a url which is executed using the .fetch() method. I pass the Collection to the View where I log the results to the console. When I console.log(this.model) in the View I see the attributes nested a few levels deep. I would like to see the attributes in the console.log. The .toJSON() method doe not seem to work.
Here's a Fiddle to my current code: http://jsfiddle.net/Gacgc/
Here is the JS:
(function () {
var DimensionsModel = Backbone.Model.extend();
var setHeader = function (xhr) {
xhr.setRequestHeader('JsonStub-User-Key', '0bb5822a-58f7-41cc-b8a7-17b4a30cd9d7');
xhr.setRequestHeader('JsonStub-Project-Key', '9e508c89-b7ac-400d-b414-b7d0dd35a42a');
};
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata'
});
var dimensionsCollection = new DimensionsCollection();
var DimensionsView = Backbone.View.extend({
el: '.js-container',
initialize: function (options) {
this.model.fetch({beforeSend: setHeader});
_.bindAll(this, 'render');
this.model.bind('reset', this.render());
return this;
},
template: _.template( $('#dimensions-template').html() ),
render: function () {
console.log( this.model.toJSON() ); //Why does this return an empty array???
return this;
}
});
var myView = new DimensionsView({model: dimensionsCollection});
}());
Is this what you're looking for?
If you're passing a collection to the view you should assign it to the collection property:
// It's a collection. Backbone views have a collection
// property. We should totally use that!
var myView = new DimensionsView({collection: dimensionsCollection});
When you attempt to bind the reset event to your view's render function, you're actually invoking the function immediately (by including the braces):
// Omit the braces to assign the function definition rather than invoke
// it directly (and immediately)
this.model.bind('reset', this.render);
But that's beside the point, because backbone's collection doesn't trigger a reset event (see documentation). One approach would be to assign the view's render function to the success parameter of the options object you pass to your collection:
var self = this;
this.collection.fetch({
beforeSend: setHeader,
success: function() {
self.render();
}
});
Finally, you need a parse function in your collection to pull the dimensions array out of the JSON you're loading:
var DimensionsCollection = Backbone.Collection.extend({
model: DimensionsModel,
url: 'http://jsonstub.com/calltestdata',
parse: function(response) {
return response.dimensions;
}
});

Update KnockoutJS viewModel using mapping and localStorage to maintain persistence

I need the data saved to localStorage, "savedData", to update the currently empty view model when the page is revisted/refreshed, possibly using the KO mapping plug-in in the line:
ko.mapping.fromJS(this, retrievedData);
But of course it's not working!
function viewModel() {
var self = this;
self.employees = ko.observableArray([]);
self.removeEmployee = function (employee) {
self.employees.remove(employee);
};
self.addEmployee = function () {
self.employees.push(new Employee());
};
self.save = function () {
var savedData = ko.toJSON(this);
localStorage.setItem('savedData', savedData);
//console.log('savedData', JSON.parse(savedData))
}
if (localStorage && localStorage.getItem('savedData')) {
var retrievedData = JSON.parse(localStorage.getItem('savedData'));
ko.mapping.fromJS(this, retrievedData);
}
}
var vm = new viewModel();
ko.applyBindings(vm);
Problem is in fromJS parameters. Second parameter is mapping options but you are trying to pass data instead. Proper usage should be following
var viewModel = ko.mapping.fromJS(retrievedData, mappingOptions);
If you don't want map retreivedData to viewModel using mappingOptions
ko.mapping.fromJS(retrievedData, {}, viewModel);
This will convert your data to viewModel without any specific mapping options.
NOTE: If you call mapping.fromJS with two arguments and second argument IS mapping object (__ko_mapping__ property defined) it will treat second argument as a viewModel and you can use function in a different manner:
ko.mapping.fromJS(retrievedData, viewModel);
Basically this scenario is valid when you are not creating viewModel, but instead updating previously created using mapping viewModel with some data.
ANSWER: You should update your code co map properly by passing empty options parameter. An of course data should go first:
if (localStorage && localStorage.getItem('savedData')) {
var retrievedData = JSON.parse(localStorage.getItem('savedData'));
ko.mapping.fromJS(retrievedData, {}, self);
}

Using jQuery dynatree with Knockout and Breeze

OK, following the suggestion from PW Kad I'm splitting this part of the question off from where it started on question ID 17973991.
I have a viewmodel that utilises a datacontext built around breeze and it fetches the data I want and populates observable arrays. I have a requirement to use data already retrieved by Breeze to populate another (observable) array to use in a treeview.
As the existing data does not have the correct fieldnames, I need to be able to create a new array with correct fieldnames that the dynatree/fancytree plugin can use.
My first attempt: (subsequently shown to not work so don't do this!)
So in my viewmodel I added the following at the top of the .js file:
var treeMaterials = ko.observableArray();
var treeMaterial = function (data) {
var self = this;
self.name = ko.observable(data.name);
self.id = ko.observable(data.id);
self.children = ko.observableArray();
$.each(data.children, function (index, item) {
self.children.push(new Person(item));
});
};
I then added an "asTreeMaterials" method to my module:
var asTreeMaterials = function (treeMatsObservable, matsObservable) {
treeMatsObservable([]); //clear out array as we're rebuilding it in here
var tmpArray = treeMatsObservable(); //create local temp array to avoid ko notifications on each push
$.each(matsObservable, function (index, mat) {
tmpArray.push(new treeMaterial({
id: mat.id,
name: mat.materialName,
children: []
}));
});
treeMatsObservable(tmpArray);
};
(borrowing heavily from John Papa's coding there, thanks John!)
Note: there will be more code going into the "children" bit once I have the basics working
And finally changing the "activate" method to use the new method:
var activate = function () {
// go get local data, if we have it
return datacontext.getMaterialPartials(materials),
asTreeMaterials(treeMaterials, materials);
};
....
and then returning the new array from the module:
var vm = {
activate: activate,
materials: materials,
treeMaterials: treeMaterials,
title: 'My test app page 1',
refresh: refresh
};
means that I don't hit the server again for the treeview version of the data.
Edit 2.
Following the guidance from PW Kad on the other question (will be added to this question shortly) I have modified the "asTreeMaterials" method as follows:
var asTreeMaterials = function () {
treeMaterials([]); //clear out array as we're rebuilding it in here
var matArray = materials().slice();
var tmpArray = [];
$.each(matArray, function (index, mat) {
tmpArray.push(new treeMaterial({
id: mat.id,
name: mat.materialName,
children: []
}));
});
treeMaterials(tmpArray);
};
The reason (I think) I have to create a separate new array is that the existing "materials" observable that I slice does not contain the correct properties. Dynatree/fancytree requires (among other things) an "ID" and a "name". I have the ID, but I have "materialName" in the materials observable hence the "$.each" on the array created by the slicing of the materials observable to push the "materialname" property into the "name" property in my new array (tmpArray). I'm new to all this, I may be miles off the mark here!
Do I actually need an observable array...? I don't think I do if I understand what observable arrays are for... my materials are pretty much set in stone and will change very, very rarely. I presume I can simply leave "treeMaterials" as a standard javascribt object array and return that in the viewmodel instead of making it an observableArray?
Either way, currently the values for materialname and ID are not passed into the relevant properties in the tmpArray I'm making. Instead I'm getting the functions from the materials observable so I think I need to approach this with an "unwrap" of some sort to get at the actual values?
You are not populating the treeMaterials because you don't have any data in materials when you are sending it to asTreeMaterials. I am making some assumptions here but basically it looks like this is what you are trying to do -
At the top of your view model, I assume you have two observableArrays
var treeMaterials = ko.observableArray();
var materials = ko.observableArray();
For your activate method, you need to go get some data, and then when your datacontext returns a promise, go make a tree out of it of some object type -
var activate = function () {
return datacontext.getMaterialPartials(materials).then(
makeMyTree);
};
You don't need to pass treeMaterials or materials because they are within the scope of the view model already, and you are just trying to make a tree of objects out of your materials.
var makeMyTree = function () {
treeMaterials([]);
ko.utils.arrayForEach(materials(), function (mat) {
treeMaterials.push(new treeMaterial(mat));
});
};
This is going to make an observableArray of objects with observable properties, meaning if you are passing them or trying to get their value you would need to use something like treeMaterials()[0].name().
In case your dynatree doesn't take observables, or isn't playing well with them
I am not sure how your dynatree or w/e works with observables, so here is a standard array of non-observable objects instead of an observable array -
var treeMaterials = [];
var makeMyTree = function () {
treeMaterials[];
ko.utils.arrayForEach(materials(), function (mat) {
treeMaterials.push(new treeMaterial(mat));
});
};
var treeMaterial = function (data) {
var self = this;
self.name = data.name;
self.id = data.id;
self.children = [];
$.each(data.children, function (index, item) {
self.children.push(new Person(item));
});
};

Categories