knockout push item to non-existing observableArray - arrays

From within a viewmodel I'm trying to dynamically push items to an observableArray. The ajax returns the data correctly.
html :
<li class="liTagulTagsChild" data-bind="click:$root.GetEmissions">/li>
javascript:
var TagDetail = function (di_titre, di_diffusion) {
this.di_titre = ko.observable(di_titre);
this.di_diffusion = ko.observable(di_diffusion);
}
var testA = [];
this.test = ko.observableArray(testA);
this.GetEmissions = function (c, event) {
var element = event.target;
var tag_id = element.getAttribute('tag_id');
$.AjaxAntiforgery({
url: 'Emission/GetDetailsByTagID/',
data: {
tag_id: tag_id
},
success: function (result) {
for (var i = 0; i < result.length; i++) {
var tD = new TagDetail(result[i].DI_TITRE, result[i].DI_DIFFUSION);
this.test.push(tD);
}
}
});
}
Problem: From within the GetEmissions function I cannot push items to this.test because this.test is null (not defined).
In other words,
this.test.push(tD);
fails. (null reference or not defined)
A solution anyone ?

In Javascript "this" doesn't mean what you think it means. Check out How to access the correct `this` context inside a callback?

Related

AngularJS - create object array where label is $translated

I have the following array:
vm.roles = ['ROLE1', 'ROLE2', 'ROLE3', 'ROLE4'];
and I need this form of array:
vm.translatedRoles = [{id:0, label:'Role1'}, {id:1, label:'Role2'}, ...]
Therefore I wrote this function to transfer from vm.roles to vm.translatedRoles.
My Problem now is that translatedRoles stays empty, so there are no objects in it. Does anyone know why?
function translateRoles() {
var translatedRoles = [];
for(var i = 0; i < vm.roles.length; i++) {
$translate(vm.roles[i]).then(function(text) {
var role = {};
role.id = i;
role.label = text;
translatedRoles.push(role);
});
}
return translatedRoles;
}
That can't work. $translate() returns a promise. The function passed to $translate is executed later, asynchronously, when the translations are available. So, the return statement happens before translatedRoles is populated by the function.
You need to return a promise of array, or hope that the translations are already available and use $translate.instant():
function translateRoles() {
var translatedRoles = [];
for (var i = 0; i < vm.roles.length; i++) {
translatedRoles.push({
id: i,
label: $translate.instant(vm.roles[i]);
});
}
return translatedRoles;
}

ng-if only works when referencing var direct from service, instead of var in controller scope

I am trying to understand why my ng-if statement doesn't work when I reference a local variable in my controller that is assigned to a value from a service, but it works properly if assigned directly to the value from that service.
For example, this works:
<div class="map" ng-if="interactiveMap.mapService.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
with the following controller:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.mapService = map;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
But if I were to assign the "esriLoaded" var to a local var in the scope, like this:
<div class="map" ng-if="interactiveMap.esriLoaded">
<esri-map id="map1"
map-options="interactiveMap.mapOptions"
load="interactiveMap.load"
register-as="interactiveMap">
</esri-map>
</div>
Controller here:
angular.module('tamcApp')
.controller('InteractivemapCtrl', function (map, config) {
var self = this;
self.map = {};
self.esriLoaded = map.esriLoaded;
self.mapOptions = {
basemap: 'mcgiStreet',
extent: config.globals.initialExtent,
sliderStyle: 'small'
};
self.load = function(){
map.getMap('interactiveMap').then(function(thisMap) {
console.log(thisMap);
self.map = thisMap;
});
};
});
Then it doesn't work. The value for "esriLoaded" is always false (which is the default value for esriLoaded). It's like it isn't updating the value of self.ersiLoaded when the value gets updated in the "map" service. Here is the code for the "map" service, just in case folks need it to answer this question.
angular.module('tamcApp')
.service('map', function (config, esriLoader, esriRegistry, esriMapUtils) {
// AngularJS will instantiate a singleton by calling "new" on this function
var self = this;
self.esriLoaded = false;
self.lazyload = function() {
// Make a call to load Esri JSAPI resources.
// A promise is provided for when the resources have finished loading.
esriLoader.bootstrap({
url: config.globals.esriJS
}).then(function() {
// Set Loaded to be true
self.esriLoaded = true;
// DEFINE CUSTOM BASEMAP USED BY ALL MAPS
esriMapUtils.addCustomBasemap('mcgiStreet', {
urls: ['http://myhost.com/arcgis/rest/services/BaseMap/StreetMap/MapServer'],
title: 'MCGI Street Map',
thumbnailurl: ''
});
});
};
if (!self.esriLoaded) {
self.lazyload();
}
self.getMap = function(id){
return esriRegistry.get(id);
};
});
That is actually not because of angular, but because of JavaScript. map.esriLoaded is a boolean value, a primitive and thus not an object, which leads to your local self.esriLoaded not becoming a reference (as only objects can be referenced), but just a plain copy of the boolean value contained in map.esriLoaded.
A short example to make it more clear:
//Primitive
var a = 5; //primitive
var b = a; //b just copies the value of a
a = 6; //This will change a, but not b
conosle.log(b); //will print 5
//Object
var a = { someValue: 5 }; //a is now a reference to that object
var b = a; //b also becomes a reference to the object above
a.someValue = 1337; //will change the object a is referencing, thus also
//changing the object b is referencing, as its the same object
console.log(b.someValue); //will print 1337

Create an AngularJS factory / object to hold an array

How can I update this code to hold an array of values? I want to hold FIELDNAME and the VALUE.
I want to set / add to the list by doing the following - add a value to the array/list.
userFilters.setData(' lastname', 'smith');
userFilters.setData(' firstname', 'bob');
userFilters.setData(' Mi', 'D');
And have the object hold an Array of
'lastname','smith'
'firstname','bob'
'mi','D'
App.factory('userFilters', [function () {
var data = {};
var getData = function (field) {
return data[field];
};
var setData = function (field, value) {
data[field] = value;
};
return {
getData: getData,
setData: setData
}
}]);
You could recreate this fairly simply without any need for the methods you are creating.
Javascript Objects are designed to do exactly what you are looking for here.
App.factory('userFilters', function() {
return {};
});
Rather than using a getter and setter, you could instead get and set values with square bracket accessors.
// setting properties
userFilters['lastname'] = 'smith';
userFilters['firstname'] = 'bob';
userFilters['Mi'] = 'D';
// getting properties
userFilters['lastname']; // 'smith'
userFilters['firstname']; // 'smith'
If you want to be able to have full control of what happens at get/set time, you could look at intercepting these calls with internal getters and setters, providing you know the property names before hand.
Finally, you could also wrap your own get and set functions around the object in order to hide it. However, this would make more sense as a Service.
App.service('userFilters', function() {
var store = {};
this.get = function(key) {
return store[key];
};
this.set = function(key, value) {
store[key] = value;
};
});
If it's important that your factory/service exposes an array then I would recommend sticking to using an object to store keys and values, but adding an array export method.
App.service('userFilters', function() {
var store = {};
this.toArray = function() {
var records = [];
return Object.keys(store).map(function(key) {
records.push([key, store[key]]);
});
};
this.get = function(key) {
return store[key];
};
this.set = function(key, value) {
store[key] = value;
};
});
If you want to loop through the properties, you can use a for-in loop.
for(var key in userFilters) {
var value = userFilters[key];
console.log(key, value);
}
You can also check whether there any keys at all, using the Object.keys method.
Object.keys(userFilters); // ['lastname', 'firstname', 'Mi']
This will return an array of all the keys in the object. If it has length 0, then you know it's empty.
In order to use an array for storage you will need to update both getData and setData methods and define data as an array []:
App.factory('userFilters', [function () {
var data = [];
var getData = function (field) {
for(var i=0; i<data.length; i+=2) {
if(data[i] == field) {
return data[i+1];
}
}
return null;
};
var setData = function (field, value) {
data = data.concat([field, value]);
};
return {
getData: getData,
setData: setData
}
}]);

passing data to a collection in backbone

So I am trying storing product types from a json file before trying to add them to a collection but am getting some strange results (as in I dont fully understand)
on my router page i setup a variable for cached products as well as product types
cachedProductTypes: null,
productType : {},
products : {},
getProductTypes:
function(callback)
{
if (this.cachedProductTypes !== null) {
return callback(cachedProductTypes);
}
var self = this;
$.getJSON('data/product.json',
function(data)
{
self.cachedProductTypes = data;
callback(data);
}
);
},
parseResponse : function(data) {
result = { prodTypes: [], products: [] };
var type;
var types = data.data.productTypeList;
var product;
var i = types.length;
while (type = types[--i]) {
result.prodTypes.push({
id: type.id,
name: type.name,
longName: type.longName
// etc.
});
while (product = type.productList.pop()) {
product.productTypeId = type.id,
result.products.push(product);
}
};
this.productType = result.prodTypes;
console.log( "dan");
this.products = result.products;
},
showProductTypes:function(){
var self = this;
this.getProductTypes(
function(data)
{
self.parseResponse(data);
var productTypesArray = self.productType;
var productList=new ProductsType(productTypesArray);
var productListView=new ProductListView({collection:productList});
productListView.bind('renderCompleted:ProductsType',self.changePage,self);
productListView.update();
}
);
}
when a user goes to the show product types page it runs the showProductsType function
So I am passing the products type array to my collection
on the collection page
var ProductsType=Backbone.Collection.extend({
model:ProductType,
fetch:function(){
var self=this;
var tmpItem;
//fetch the data using ajax
$.each(this.productTypesArray, function(i,prodType){
tmpItem=new ProductType({id:prodType.id, name:prodType.name, longName:prodType.longName});
console.log(prodType.name);
self.add(tmpItem);
});
self.trigger("fetchCompleted:ProductsType");
}
});
return ProductsType;
now this doesnt work as it this.productTypesArray is undefined if i console.log it.
(how am I supposed to get this?)
I would have thought I need to go through and add each new ProductType.
the strange bit - if I just have the code
var ProductsType=Backbone.Collection.extend({
model:ProductType,
fetch:function(){
var self=this;
var tmpItem;
//fetch the data using ajax
self.trigger("fetchCompleted:ProductsType");
}
});
return ProductsType;
it actually adds the products to the collection? I guess this means I can just pass an array to the collection and do not have to add each productType?
I guess this means I can just pass an array to the collection and do not have to add each productType?
Yes, you can pass an array to the collection's constructor, and it will create the models for you.
As far as your caching code, it looks like the problem is here:
if (this.cachedProductTypes !== null) {
return callback(cachedProductTypes);
}
The callback statement's argument is missing this - should be return callback(this.cachedProductTypes).

Using javascript prototype function to initialise variable in 'this' context

I'm finding it difficult to explain in words, so here's a snippet of code I'm trying out but Firefox/firebug goes into tailspin!
I'm trying to follow this and this as a guide. What I'm trying to do here is
new MyObject.Method('string',optionsArray);
optionsArray items are iterated and saved using the prototype function Set()
if(typeof(MyObj) == 'undefined') MyObj= {};
MyObj.Method = function initialise(id,options)
{
this.id = id;
this.options = options;
this.properties ={};
for (var i = 0; i < this.options.length; i++) // =>options.length=2 (correct)
{
var obj = this.options[i];
//get the keynames, pass with values to Set() to update properties
for (var keys in obj)
{
console.log(keys); //=> correctly prints 'property1' and 'currentValue'
this.Set(keys,obj); //=> this is i guess where it enters a loop?
}
}
}
//sets properties
MyObj.Method.prototype.Set = function (name, value)
{
this.properties[name.toLowerCase()] = value;
}
and in my html page script block, i have
window.onload = function () {
var options = [
{ property1: {
show: true,
min: 0,
max: 100
}
},
{
currentValue: {
show: true,
colour: 'black'
}
}
];
var myObj = new MyObj.Method('someDivId',options);
}
please advise if I'm over complicating the code. I think checking for hasOwnProperty would help.
This should be a cleaner way of achieving what you want:
function MyObj(id, options) { // a function that will get used as the constructor
this.id = id;
this.options = options;
this.properties = {};
this.set(options); // call the set method from the prototype
}
MyObj.prototype.set = function(options) { // set the options here
for(var i = 0, l = options.length; i < l; i++) {
var obj = this.options[i];
for(var key in obj) {
if (obj.hasOwnProperty(key)) { // this will exclude stuff that's on the prototype chain!
this.properties[key] = obj[key];
}
}
}
return this; // return the object for chaining purposes
// so one can do FooObj.set([...]).set([...]);
};
var test = new MyObj('simeDivId', [...]); // create a new instance of MyObj
test.set('bla', [...]); // set some additional options
Note: For what hasOwnProperty is about please see here.
I made a declaration for MyObj and removed the function name initialise since you're obviously declaring this function to be a property of MyObj. Your final code will then be like below, and that runs for me just fine. Please note that you cannot actually call the function until after you declare the prototype function because else the object will have no notion of the Set function.
var MyObj = {};
MyObj.Method = function (id,options)
{
this.id = id;
this.properties ={};
for (var i = 0; i < options.length; i++) // =>options.length=2 (correct)
{
var obj = options[i];
//get the keynames, pass with values to Set() to update properties
for (var keys in obj)
{
console.log(keys); //=> correctly prints 'property1' and 'currentValue'
this.Set(keys,obj); //=> this is i guess where it enters a loop?
}
}
}
MyObj.Method.prototype.Set = function (name, value)
{
this.properties[name.toLowerCase()] = value;
}
var options = [
{ property1: {
show: true,
min: 0,
max: 100
}
},
{
currentValue: {
show: true,
colour: 'black'
}
}
];
var myObj = new MyObj.Method('someDivId',options);
var MyObj = {};
MyObj.Method = function initialise(id,options) {
this.id = id;
this.options = options;
this.properties = {};
for (var i = 0; i < this.options.length; i++)
{
var obj = this.options[i];
for (var keys in obj) {
this.Set(keys,obj[keys]);
//*fix obj => obj[keys]
// (and it should be singular key rather then keys
}
}
console.log(this.properties) // will output what you want
}
//sets properties
MyObj.Method.prototype.Set = function (name, value) {
this.properties[name.toLowerCase()] = value;
}
var options = [{
property1: {
show: true,
min: 0,
max: 100
}
},{
currentValue: {
show: true,
colour: 'black'
}
}];
var myObj = new MyObj.Method('someDivId',options);
this should work problem is you had your myObj = new MyObj... outside your onload event and options was out of its scope as it was declared as private variable to the anonymous function bound to the onload event.
I've fixed also the way you was copying the values to the properties as it doubled the names of the property and made it a bit messy.

Resources