getting a key out of a javascript hash - javascript

I'm working with the latest draft of the twitter annotations api. An example bit of data looks like
status {
annotations : [
{myAnnotationType:{myKey:myValue}},
{someoneElsesAnnotationType:{theirKey:theirValue}},
]
}
now i want to check a status to see if it has an annotation with myAnnotationType in it. If annotations was a hash instead of an array I could just write var ann = status.annotations.myAnnotationType. But its not so I wrote this instead:
function getKeys(obj){
var keys = [];
for (key in obj) {
if (obj.hasOwnProperty(key)) { keys[keys.length] = key; }
}
return keys;
}
function getAnnotation(status, type){
for(var i=0;i<status.annotations.length;i++){
var keys = getKeys(status.annotations[i]);
for(var j=0;j<keys.length;j++){
if(keys[j] == type){
return status.annotations[i];
}
}
}
}
var ann = getAnnotation(status, "myAnnotationType");
There must be a better way! Is there?
PS I can't use jquery or anything as this is js to be used in a caja widget and the container doesn't support external libs

If I understand the purpose of this function correctly, you are trying to return the first object in an array (status.annotations) that contains a key (type)
function getAnnotation(status, type){
for (var i=0; i<status.annotations.length ; i++) {
var obj = status.annotations[i];
if (obj.hasOwnProperty(type)) return obj;
}
}

Ah man, jQuery would make it easy if you could be sure they'd never collide:
var annotations = $.extend.apply($, status['annotations'] || []);
var annotation = annotations['myAnnotationType'];
I guess you could write your own budget extend:
function collapse(annotations) {
var result = {};
for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i];
for (var key in annotation) {
if (annotation.hasOwnProperty(key)) {
result[key] = annotation[key]; // Using a deep cloning function possible here
}
}
}
return result;
}
Or you could have a hack at adapting jQuery's extend.

Related

Mixing Index Array with "Associative Array"

Since I need to access my items sometime by index and sometime by code. Is it a good idea to mix integer index with string index?
Note that the code, index, amount of items never changes after the data is loaded.
I'm thinking of doing something like this, where the same object is pushed and set as a hashtable.
function DataInformation(code, dataValue) {
this.code = code;
this.dataValue = dataValue;
}
var dataList = [];
function fillDataList() {
addNewData(new DataInformation("C1", 111));
addNewData(new DataInformation("C2", 222));
addNewData(new DataInformation("C3", 333));
}
function addNewData(newData) {
dataList.push(newData);
dataList[newData.code] = newData;
}
Then I would be able to access the object with either:
dataList[0].dataValue
dataList["C1"].dataValue
Before I used to loop to find the item.
function findItemByCode(code) {
for (var i = 0; i < dataList.length; i++) {
if (dataList[i].code == code) {
return dataList[i];
}
}
return null;
}
findItemByCode("C1").dataValue
Do you ever need to iterate dataList in strict order? Or is it just a bag of items for which you want random access by a certain key?
If ordered iteration is not a concern, use an object instead of an array. Watch out for key clashes, though.
var dataList = {};
function addNewData(newData) {
dataList[newData.code] = newData;
dataList[newData.dataValue] = newData;
}
// that's it, no other changes necessary
If key clashes can occur - or ordered iteration is necessary, or if you just want to make it particularly clean, use an array and an accompanying index object.
var dataList = [];
var dataIndex = {
byCode: {},
byValue: {}
};
function addNewData(newData) {
dataList.push(newData);
dataIndex.byCode[newData.code] = newData;
dataIndex.byValue[newData.dataValue] = newData;
}
Here is my try using Proxies
// Code goes here
function DataInformation(code, dataValue) {
this.code = code;
this.dataValue = dataValue;
}
var _dataList = [];
var dataList = new Proxy(_dataList, {
get: function(target, name) {
if (target && target.myMap && target.myMap[name]) return target[target.myMap[name]];
return target[name];
},
set: function(obj, prop, value) {
// The default behavior to store the value
obj.myMap = obj.myMap || {};
obj.myMap[value.code] = prop;
obj[prop] = value;
return true;
}
});
function fillDataList() {
addNewData(new DataInformation("C1", 111));
addNewData(new DataInformation("C2", 222));
addNewData(new DataInformation("C3", 333));
}
function addNewData(newData) {
dataList.push(newData);
}
fillDataList();
console.log(dataList[0].dataValue);
console.log(dataList["C1"].dataValue);

Search if property is match does not work

I want to return true if my property is a match in my search input. I managed to do that with my other properties from my JSON data, but in my nested data I cannot. Tried it with for loop and also just with if but without success.
this is how i get my json from DB:
db.query("One/docs").then(function(dataone){
var oness = [];
for(var i=0; i < dataone.rows.length; ++i) {
var x = new one();
x.fromJS(dataone.rows[i].value);
oness.push(x);
}
self.OneInfos(oness);
})
Maybe I'm forgetting something, I don't really know :P.
My Json data:
code 1 :
function one(){
var self = this;
self.AssemblyName = ko.observable();
self.Description = ko.observable();
self.Name = ko.observable();
self.Obsolete = ko.observable();
self.TypeName = ko.observable();
self.Properties = ko.observable();
self.Implements = ko.observable();
self.Implements.Interfaces = ko.observable();
self.IsInSearch = ko.pureComputed(function(){
var searchRegEx= App.instance.SearchRegEx();
if(!searchRegEx)
return true;
if(searchRegEx.test(self.TypeName())) {
return true;
}
if(searchRegEx.test(self.Name())) {
return true;
}
if(searchRegEx.test(self.Description())) {
return true;
}
//todo - look if there is a filter for interfaces
if (searchRegEx.test(self.Implements.Interfaces.TypeName())){
return true;
}
return false;
});
}
And here is my second solution with for loop (it's in the same line under "todo - comment"):
for(var i=0; i < Implements.length;++i) {
var Implement = Implements[i];
if (searchRegEx.test(self.Interfaces.TypeName)){
return true;
}
}
Notice: My implements are displayed with a select input
you don't need the if (self.Implements() && self.Implements().Interfaces).
var interfaces = self.Implements();
for(var i=0; i < interfaces.length;++i) {
var currentInterface = interfaces[i];
if (searchRegEx.test(currentInterface.TypeName)){
return true;
}
}
Modified the code from your answer above.
I think this works as it searches through your Implements and property TypeName.
Hope this helps
I'm not sure why your data mapping works, since fromJS doesn't seem to be in your one viewmodel. It is in ko.mapping however, so I'll just assume you're somehow accessing it correctly.
var x = new one();
x.fromJS(dataone.rows[i].value);
According to the knockout documentation, ko.mapping.fromJS works like so:
All properties of an object are converted into an observable. If an update would change the value, it will update the observable.
This means Implements is an observable. Implements.Interfaces would then be undefined, but Implements().Interfaces would return your array.
To search the TypeName values of this array, you'll have to use:
if (self.Implements() && self.Implements().Interfaces) {
var interfaces = self.Implements().Interfaces;
for(var i=0; i < interfaces.length;++i) {
var currentInterface = interfaces[i];
if (searchRegEx.test(currentInterface.TypeName)){
return true;
}
}
}
EDITS: Note that I've made some edits after our conversation in the comments. Seems that this question wasn't just about accessing data inside an observable, but about checking for undefined as well...

Can get some values in JSON by key, others I cannot get

I have some JSON:
[{
"cityid":101,
"city":"Alta"
},
{
"cityid":102,
"city":"Bluffdale"
},
{
"cityid":105,
"city":"Draper"
},
{
"cityid":107,
"city":"Holladay"
}]
I can successfully search this array, and get the "city" value, with this function:
function getLocality(cid){
var storedlist = localStorage.getItem("citylist");
var clist = JSON.parse(storedlist);
for(var i = 0; i < clist.length; i++)
{
if(clist[i].cityid == cid) {
return clist[i].city;
} else {
}
}
}
My Issue:
When I try to use another function to get the city, instead of getting the cityid, it does not work.
The function I am using to try and get the city id, is as follows:
function getCityid(cid){
var storedlist = localStorage.getItem("citylist");
var clist = JSON.parse(storedlist);
for(var i = 0; i < clist.length; i++)
{
if(clist[i].city == cid) {
return clist[i].cityid;
} else {
}
}
}
I am calling the function as so:
getCityid('Draper');
I just opened up the console and tried both of your functions and they both seem to work. Since you didn't provide any sample input/output of your issue, I'll recommend the following:
Verify that you're passing in correct parameters into each function. The getLocality() should take in a cityid and getCityid() should take in a city.
Refrain from using == in javascript as this operator performs type coercion wherein things which are disparate types are "forced" to be the same type in order to perform comparison. You should instead use the === operator which will not perform type coercion. If the two things being compared are different types, it will simply evaluate to false.
Try this.
function getCityid(city) {
var storedlist = localStorage.getItem("citylist");
var clist = JSON.parse(storedlist);
for(var i = 0; i < clist.length; i++){
if(clist[i].city === city) {
return clist[i].cityid;
}
}
}

Array that can store only one type of object?

Is it possible to create an array that will only allow objects of a certain to be stored in it? Is there a method that adds an element to the array I can override?
Yes you can, just override the push array of the array (let's say all you want to store are numbers than do the following:
var myArr = [];
myArr.push = function(){
for(var arg of arguments) {
if(arg.constructor == Number) Array.prototype.push.call(this, arg);
}
}
Simply change Number to whatever constructor you want to match. Also I would probably add and else statement or something, to throw an error if that's what you want.
UPDATE:
Using Object.observe (currently only available in chrome):
var myArr = [];
Array.observe(myArr, function(changes) {
for(var change of changes) {
if(change.type == "update") {
if(myArr[change.name].constructor !== Number) myArr.splice(change.name, 1);
} else if(change.type == 'splice') {
if(change.addedCount > 0) {
if(myArr[change.index].constructor !== Number) myArr.splice(change.index, 1);
}
}
}
});
Now in ES6 there are proxies which you should be able to do the following:
var myArr = new Proxy([], {
set(obj, prop, value) {
if(value.constructor !== Number) {
obj.splice(prop, 1);
}
//I belive thats it, there's probably more to it, yet because I don't use firefox or IE Technical preview I can't really tell you.
}
});
Not directly. But you can hide the array in a closure and only provide your custom API to access it:
var myArray = (function() {
var array = [];
return {
set: function(index, value) {
/* Check if value is allowed */
array[index] = value;
},
get: function(index) {
return array[index];
}
};
})();
Use it like
myArray.set(123, 'abc');
myArray.get(123); // 'abc' (assuming it was allowed)

With KnockoutJS, how to bind to a child property of an array item

I have a question similar to Bind text to property of child object and I am having difficulty properly creating the KO observable for a child object.
For example, I do an HTTP Get to return a JSON array of People, and the People array is inside a property called "payload". I can get the basic binding to work, and do a foreach on the payload property, displaying properties of each Person; however, what I need to do is add a "status" property to each Person, which is received from a different JSON, example
/api/people (firstname, lastname, DOB, etc.)
/api/people/1/status (bp, weight, height, etc)
I have tried binding to status.bp, and status().bp, but no luck.
The js example:
var TestModel = function (data) {
var len = data.payload.length;
for (var i = 0; i < len; i++) {
var elem = data.payload[i];
var statusdata = $.getJSON("http://localhost:1234/api/people/" + elem.id + "/status.json", function (statusdata) {
elem.status = statusdata;
data.payload[i] = elem;
});
}
ko.mapping.fromJS(data, {}, this);
};
var people;
var data = $.getJSON("http://localhost:1234/api/people.json", function (data) {
people= new TestModel(data);
ko.applyBindings(people);
});
2 important things I will need:
1) properly notify KO that "payload" is an array to key on ID property
2) make "status" an observable
Help!
[UPDATE] EDIT with working fix based on Dan's answer:
var TestModel = function(data) {
...
this.refresh = function () {
$.getJSON("http://localhost:1234/api/people", function (data) {
self.payload = ko.observableArray(); // this was the trick that did it.
var len = data.payload.length;
for (var i = 0; i < len; i++) {
var elem = data.payload[i];
$.getJSON("http://localhost:1234/api/people/" + elem.id + "/status", function (statusdata) {
// statusdata is a complex object
elem.status = ko.mapping.fromJS(statusdata);
self.payload.push(elem);
});
}
// apply the binding only once, because Refresh will be called with SetInterval
if (applyBinding) {
applyBinding = false;
ko.applyBindings(self);
}
}
I am still new to Knockout and improvements to the refresh function are most welcome. The mapping is still being reapplied each time.
You need to define an observable array and then push your data into it.
elem.status = ko.observableArray();
for (var i = 0; i < statusdata.length; i++) {
elem.status.push(statusdata[i]);
}
I can't tell what the full structure of the data is by the example. But if status is a complex object, you may what to give it its own model.
for (var i = 0; i < statusdata.length; i++) {
elem.status.push(new statusModel(statusdata[i]));
}

Categories