Changing nested attribute with model.set in Backbone - javascript

I'm trying to do what I hope is a simple thing -- doing a model.set only on a sub attribute of the object.
Right now, I have a model that looks like this:
{
"attr1" : true,
"attr2" : this.model.get("username"),
"attr3" : $('#tenant_select').val(),
"attr_array": [
{
"id": "sub_1",
"state": "active"
},
{
"id": "sub_22",
"state": "not_active"
}
]
}
I want to be able to grab reach into myMode.attr_array.state and change the value. However, using .set I've only been able to change attributes on the first level, i.e. attr_array.
Is there an way to do this using model.set?

You can do it (I'm wondering why you didn't manage to do it). But you have to be careful:
var array = this.get('attr_array');
array[1].state = 'active';
this.set('attr_array', array);
What's the problem here? Your model holds a reference of the object. Therefore the last line is useless, it won't change anything at all. It's simply equivalent to:
this.get('attr_array')[1].state = 'active';
And you lose any internal stuff Backbone does when you use set.
So what to do? Clone your object:
var array = _.clone(this.get('attr_array'));
array[1].state = 'active';
this.set('attr_array', array); // will trigger 'change' and 'change:attr_array' events

Related

Turn Observable Array into nested JSON

I'm having a problem getting an array of information stored properly as JSON.
I made a fiddle to illustrate the problem. Enter a set of tags and take a look at the console to see the output.
More explanation:
So I have an input that takes in a comma-separated list of tags, which I then format.
function createTagArray() {
// given an input value of 'tag1, tag2, tag3'
// returns array = ['tag1', 'tag2', 'tag3']
}
I thought what I needed to do next was the following:
loop over the array and create a 'tag' object for each item which also includes an id for the tag and the id of the contact the tag is associated with.
Each object is pushed to tags, an observable array.
function single_tag(id, contactId, tagLabel) {
var self = this;
self.id = id;
self.contactId = contactId;
self.tagLabel = tagLabel;
}
function createTags() {
var array = createTagArray();
for (var i = 0; i < array.length; i++) {
self.tags().push(new single_tag(uuid.generate(), self.contactId, array[i]));
}
}
Then, I converted it into JSON
self.contactInformation = function() {
return ko.toJS({
"id": self.contactId,
"firstname": self.firstname(),
"lastname": self.lastname(),
... other fields ...
"tags": self.tags(),
})
}
But, when I inspect the console output of calling this function, tags is a collection of arrays, not a nice json object.
How do I get it formatted correctly?
I tried this suggestion, and the tag json is structured correctly, but it is stored with escaped quotes, so that seems wrong.
Thanks for all the help!
I would recommend you knockout.mapping plugin for KO, it allow map complicated JSON structure to view model, even without declarations.
From the documentation
Let’s say you have a JavaScript object that looks like this:
var data = {
name: 'Scot',
children: [
{ id : 1, name : 'Alicw' }
]
}
You can map this to a view model without any problems:
var viewModel = ko.mapping.fromJS(data);
Now, let’s say the data is updated to be without any typos:
var data = {
name: 'Scott',
children: [
{ id : 1, name : 'Alice' }
]
}
Two things have happened here: name was changed from Scot to Scott and children[0].name was changed from Alicw to the typo-free Alice. You can update viewModel based on this new data:
ko.mapping.fromJS(data, viewModel);
And name would have changed as expected. However, in the children array, the child (Alicw) would have been completely removed and a new one (Alice) added. This is not completely what you would have expected. Instead, you would have expected that only the name property of the child was updated from Alicw to Alice, not that the entire child was replaced!
...
To solve this, you can specify which key the mapping plugin should use to determine if an object is new or old. You would set it up like this:
var mapping = {
'children': {
key: function(data) {
return ko.utils.unwrapObservable(data.id);
}
}
}
var viewModel = ko.mapping.fromJS(data, mapping);
In the jsfiddle you were using Knockout 3.0 which doesn't have support for textInput. This was added in 3.2. To use version 3.2 you need to use a cdn such as this: http://cdnjs.com/libraries/knockout
There was typeo in your binding. sumbit should be submit.
There was a problem with your constructor for single_tag. id was not used so I removed it:
function single_tag(contactId, tagLabel) {
var self = this;
self.contactId = contactId;
self.tagLabel = tagLabel;
}
Currently also contactId is not set because the observable has not been set to a value.
To convert to JSON you need to use ko.toJSON instead of ko.toJS:
self.contactInformation = function() {
return ko.toJSON({
"firstname": self.firstname(),
"tags": self.tags(),
})
}
Now when the console writes out an array appears:
{
"firstname":"test",
"tags":[
{"tagLabel":"test1"},
{"tagLabel":"test2"},
{"tagLabel":"test3"}
]
}
JsFiddle
So my problem was more basic than I was realizing. I'm using JSON Server to serve up my data, and I was pulling information from two parts of the database (contacts & tags).
When I tried to update my tags, I was trying to apply them to a property that didn't exist on the contact JSON in my database. Posting the tags separately worked though.

BackboneJS - get specific value from collection using _.max method

I want to get a specific value from a model inside a Collection. The Collection is sorted by ID (contest_id) which is served by the database and gets delivered as JSON. So, the JSON looks like:
data : [{
"contest_id" : "3",
"artist" : {
"artist_name": "some name",
"artist_cover" : "some image.jpg"
}
},
"contest_id" : "1",
....
}]
Now, I have glued something together:
var contestImage = _.max(this.collection.toJSON(), function(cnt){
return cnt.contest_id;
});
I get the highest contest_id, which is what I want but how do I proceed when I want to grab the image? and even display it?
Thanks in advance...
The max method should actually be available directly from your Backbone collection (although it's an Underscore method, it's mixed into Backbone's Collections), which means you can simplify your code a bit.
Something like this should do the trick:
var model = this.collection.max(function (cnt) {
return cnt.contest_id;
});
var contest_id = model.get('contest_id');
The first section returns the model you want, and the second gets the contest ID attribute.
To get the artist cover attribute, you can then convert it to JSON:
var artist_cover = model.toJSON().artist.artist_cover;
Or get the artist attribute from the model:
var artist_cover = model.get('artist').artist_cover;
This actually made it work:
var contestImage = _.max(this.collection.toJSON(), function(cnt){
return cnt.contest_id;
});
var latestImage = contestImage.artist.artist_cover;
Thanks for the answers though :-)

Changing an array of objects structure in Javascript for Sencha Touch Ext.ux.TouchCalendar

I'm building a Sencha Touch app that utilizes the awesome calendar plugin https://github.com/SwarmOnline/Ext.ux.TouchCalendar , however, some custom implementation is needed in order to utilize the events functionality.
I've already tied the events template to a store, which fetches data from the server. It works as planned, but the problem is the plugin looks for ALL records in the store and counts each one as an event (because in the event model, it looks for "date" as the start and end point). So every day looks like has an event, even though those without "items" are blank, see: http://cl.ly/image/3j461O2L2Y1k. I only want to display events with "items"
My data from the server that comes back in the following format (many days do not have "items"):
[
{
"day":28,
"iscurrentmonth":false,
"issunday":false,
"date":"2013-05-28",
"items":[
{
"id":134513,
"title":"Subject",
"typeid":3,
"typename":"Essay",
"author":"Bryan Fisher",
"classname":"English 9A",
"classid":344499,
"courseid":60555
},
{
"id":134485,
"title":"Subject",
"typeid":3,
"typename":"Essay",
"author":"Bryan Fisher",
"classname":"English 10",
"classid":344500,
"courseid":60555
}
]
}
]
So, I have to change the data array's structure into the following format:
[
{
"date":"2013-05-28",
"id":134513,
"title":"Subject",
"typeid":3,
"typename":"Essay",
"author":"Bryan Fisher",
"classname":"English 9A",
"classid":344499
},
{
"date":"2013-05-28",
"id":134485,
"title":"Subject",
"typeid":3,
"typename":"Essay",
"author":"Bryan Fisher",
"classname":"English 10",
"classid":344500
}
]
How can I change the original object to match the new format? (take the "date" and insert it into the "items" node) ?
I am completely open to something like underscore.js
Thanks in advance
Maybe I over thought this whole thing...
A bit of a hack...
in TouchCalendarEvents.js I added the following method to check for an empty event div
hideOthers: function(){
var bar = $('.event-bar');
for (var i = 0; i < bar.length; i++){
var allBars = bar[i];
if (allBars.innerHTML == ''){
console.log('number ' + i + 'is Empty!' );
allBars.remove();
}
}
},
and call it in refreshEvents
refreshEvents: function(){
// scroll the parent calendar to the top so we're calculating positions from the base line.
if(this.calendar.getScrollable()){
this.calendar.getScrollable().getScroller().scrollTo(0,0);
}
this.removeEvents();
this.getViewModeProcessor().generateEventBars(); // in turn calls this.renderEventBars(this.eventBarStore);
this.createEventWrapper();
this.hideOthers();
if (this.getAllowEventDragAndDrop()) {
this.createDroppableRegion();
}
},
Works well enough for now!

How can i navigate through the json?

I have some JSON which I have in a object but I can seem to return the values a sample of the json is as follows.
{
"rootLayout":"main",
"layoutDescriptions":[
{
"id":"main",
"container" : {
"type":"Tabs",
"content":[
{
"type":"Panel",
"label":"Simple Address",
"layout":"SimpleForm",
"comment":"This form is simple name value pairs",
"content":[
{ "type":"label", "constraint":"newline", "text":"Org Name" },
{ "type":"text", "property":"propOne" },
{ "type":"label", "constraint":"newline", "text":"Address" },
{ "type":"text", "property":"addrLine1" },
{ "type":"text", "property":"addrLine2" },
{ "type":"text", "property":"addrLine3" },
{ "type":"label", "constraint":"newline", "text":"Postcode" },
{ "type":"text", "property":"postcode" }
]
},
I am trying to return the rootLayout using
obj[0].rootLayout.id
This doesn't work also I am wondering how to access the content elements.
I am new to json and I have been thrown in the deep end I think. I cannot find any good reading on the internet can anyone recommend some.
Thanks.
Some explanation because you don't seem to understand JSON
It's not as complicated as one may think. It actually represents javascript objects as if they'd be written by code.
So if you have JSON written as:
{
id : 100,
name: "Yeah baby"
}
This means that your object has two properties: id and name. The first one is numeric and the second one is string.
In your example you can see that your object has two properties: rootLayout and layoutDescriptions. The first one jsonObj.rootLayout is string and will return "main" and the second one is an array:
layoutDescriptions: [ {...}, {...},... ]
Apparently an array of objects because array elements are enclosed in curly braces. This particular array element object that you provided in your example has its own properties just like I've explained for the top level object: id (string), container (another object because it's again enclosed in curlies) etc...
I hope you understand JSON notation a bit more.
So let's go to your question then
You can get to id by accessing it via:
jsonObj.layoutDescriptions[0].id
and further getting to your content objects:
var contentObjects = jsonObj.layoutDescriptions[0].container.content[0].content;
for(var i = 0; i < contentObjects.length, i++)
{
// assign this inner object to a variable for simpler property access
var contObj = contentObjects[i];
// do with this object whatever you need to and access properties as
// contObj.type
// contObj.property
// contObj.text
// contObj.constraint
}
Mind that this will only enumerate first content object's content objects... If this makes sense... Well look at your JSON object and you'll see that you have nested content array of objects.
The object is an object, not an array, and it doesn't have a property called 0.
To get rootLayout:
obj.rootLayout
However, rootLayout is a string, not an object. It doesn't have an id. The first item in the layoutDescriptions array does.
obj.layoutDescriptions[0].id
Are you trying to get one of layoutDescriptions with id equals to obj.rootLayout?
var targetLayout = {};
for(var i = 0; i < obj.layoutDescriptions.length; i++) {
if(obj.layoutDescriptions[i].id == obj.rootLayout) {
targetLayout = obj.layoutDescriptions[i]; break;
}
}
console.log(targetLayout);

Read text file into variables with javascript

I have a text file located on the server that contains a list of events with their target time. It looks something like the following:
2012/02/11-10:00:00 EventStart Red
2012/02/11-10:10:00 EventStop Green
...
What I need to do is somehow read the text file and based on the current time select the next upcoming event and assign each element for that event to a variable. So for example if the time is currently 2012.02.11-10:08:00, javascript variables time = '2012/02/11-10:10:00'; title = 'EventStop'; color = 'Green'; would be created.
I'm able to read the text file with:
jQuery.get('schedule.txt',function(data){
alert(data);
});
Just don't know where to go from there, or if that's even the best way to start. I do have control over the formatting of the text file, so modifying that is an option.
You say you can modify the file's contents, so I suggest converting it to JSON (and separating the date/time).
[{"date": "2012/02/11", "time": "10:00:00", "title": "EventStart", "color": "Red"}, {"date": "2012/02/11", "time": "10:10:00", "title": "EventStop", "color": "Green"}]
Then you can use getJSON to get/parse it.
jQuery.getJSON('schedule.txt',function(data){
// data is an array of objects
$.each(data, function(){
console.log(this.title); // log each title
});
});
From here, you can read the times and figure out which one is the latest. Something like this should work:
if(Date.now() <= Date.parse(this.date+' '+this.time))
So, putting it all together:
jQuery.getJSON('schedule.txt',function(data){
var matchedSchedule = {};
// data is an array of objects
$.each(data, function(){
if(Date.now() <= Date.parse(this.date+' '+this.time)){
matchedSchedule = this; // If time matches, set a variable to this object
return false; // break the loop
}
});
console.log(matchedSchedule.title);
// to set a "global" variable, add it to `window`
window.eventTitle = matchedSchedule.title;
});
Without changing your text file
$.get('sometext.txt',function(data){
var perLine=data.split('\n');
var myVars=[];
for(i=0;i<perLine.length;i++)
{
var line=perLine[i].split(' ');
myVars[i]={
'time':line[0],
'event':line[1],
'color':line[2]
}
}
console.log(myVars);
console.log(myVars[0].time);
console.log(myVars[0].event);
console.log(myVars[0].color);
});
Sounds like you've already done the hard part. Now just parse the data that is returned.
jQuery.get('schedule.txt',function(data){
yourParseFunction(data);
});

Categories

Resources