Backbone - Save model changes to local object - javascript

I am building an app which will load a JSON file and use that to populate all models. I have to keep a list of changes and then post this back to the server after a 'publish' button is clicked.
I think a combination of using Backbone.LocalStorage and using model change events to then track which models have changes sounds right but it'd help to hear from someone who's gone down this route or solved similar!
Does this approach makes sense? Is there a better one?

If you are trying to track individual changes and not just the final state before saving, then it is probably a good idea to create an Audit model or something similar. You can hook into the change events as you suggested. Saving those Audit models to the server can be done using the standard version (or some batched modification) of Backbone.sync whenever you want. That model might look something like this:
var Audit = Backbone.Model.extend({
defaults : {
auditableType: "", auditableId: null, auditedChanges : ""
},
paramRoot : "audit"
});
var Audits = Backbone.Collection.extend({
model : Audit
});
Then create a Model prototype that all audited models can extend from:
var audits = new Audits();
var AuditedModel = Backbone.Model.extend({
initialize : function (options) {
this.listenTo(this, "change", function (model, options) {
audits.add({
auditableType : this.paramRoot,
auditableId : this.id,
auditedChanges : this.changed
});
});
}
});
Now just extend from your AuditedModel for any models you want to track changes on.
var User = AuditedModel.extend({
paramRoot : "user",
// Do whatever
});

Related

How to copy data from OData v4 to JSON with SAP UI5?

I'm using OData v4 to load data from my backend to my frontend (developed with SAP UI5) and I am using a form to display a detail page. When I click the "edit" button I'm able to edit the data. My implementation is similar to this example: https://sapui5.hana.ondemand.com/explored.html#/sample/sap.ui.layout.sample.Form354/code/Page.controller.js
When editing something, the data is directly edited at the model and, therefore, updated at the backend. However, I want to be able to choose if I want to save the changes or if I want to cancel the edit before it is updated at the backend.
I read on other questions that one can copy the ODataModel to a JSONModel and use that copy instead, by doing something like:
var oModel = this.getView().getModel();
var oModelJson = new sap.ui.model.json.JSONModel();
oModel.read("/Data", {
success: function(oData, response) {
oModelJson.setData(oData);
sap.ui.getCore().setModel(oModelJson, "oJSONModel");
alert("Success!");
},
error: function(response) {
alert("Error");
}
});
However, the read method seems not to be available for OData v4. the code of my controller where the data is loaded looks like following:
onInit: function() {
this.oModel = new ODataModel({
groupId : "$direct",
synchronizationMode : "None",
serviceUrl : '/odata/'
});
this.getView().setModel(this.oModel, 'oModel');
var oRouter = sap.ui.core.UIComponent.getRouterFor(this);
oRouter.getRoute("details").attachPatternMatched(this._onObjectMatched, this);
this._showFormFragment("display");
},
_onObjectMatched: function (oEvent) {
this.getView().bindElement({
path: "/Data(" + oEvent.getParameter("arguments").dataPath + ")",
model: "oModel"
});
//I want to copy the data from the ODataModel to the JSONModel here
},
What's the best way to accomplish this? And how to do it with OData v4?
I suppose you want to resetChanges in case user cancels the save.
For V2 ODataModel, there is deferedGroup concept which you can use to resetChanges or submitChanges.
I have not much experience with V4. Though from the documentation, it is possible.
Please try to pass a updateGroupId in the constructor. Then you can choose resetChanges or submitBatch by group Id.
mParameters.updateGroupId? The group ID that is used for update requests. If no update group ID is specified, mParameters.groupId is used. Valid update group IDs are undefined, '$auto', '$direct' or an application group ID, which is a non-empty string consisting of alphanumeric characters from the basic Latin alphabet, including the underscore.
Thank you!

Bookshelf.js set attribute not in database

I have a Bookshelf.js model. I want to be able to set and get attributes for this model that are not persistent in the database.
For instance lets say I have a model that looks like this:
var Domain = bookshelf.Model.extend({
tableName: 'domains',
initialize: function() {
this.on('creating', this.setDomainName);
},
setDomainName: function() {
this.set('name', getDomainFromUrl(this.url));
}
});
With a schema that looks like this:
knex.schema.createTable('domains', function (table) {
table.increments().index();
table.text('name').index();
table.timestamps();
});
I want to be able to save an attribute called url, then later parse the url into a domain before it saves.
When I try something like this:
new Domain({url: 'http://someurl.com/foo/bar'}).save()
I get the error message:
"column \"url\" of relation \"domains\" does not exist"
I've looked and looked. I cant find any way to add non-persistent attributes to a bookshelf.js model. I also couldn't find anything about adding custom getter and setter methods to a bookshelf.js model.
Any help or insight is appreciated!
On my phone, so forgive the short reply, but what you want is called 'virtual' or 'composite' fields.
https://github.com/tgriesser/bookshelf/wiki/Plugin:-Virtuals
Every database mapper has them, but when you don't know what they're called it's understandably difficult to google a solution.

Backbone/Marionette - Checking if a model exists

I have created a very basic backbone application, but I need to perform a slightly complex check which I am having trouble doing.
My code is below. What I'm doing here is creating a list of participants in a chat. In reality, I will be sending this list in through a javascript function.
Participant = Backbone.Model.extend({});
Participants = Backbone.Collection.extend({
model: Participant
});
ParticipantView = Backbone.Marionette.ItemView.extend({
template: "#participant-template",
tagName: 'div',
className: 'call-participant',
initialize: function () {
this.$el.prop("id", this.model.get("chatid") + "-" + this.model.get("participantName"));
},
});
ParticipantsView = Backbone.Marionette.CompositeView.extend({
template: "#participants-template",
tagName: 'div',
itemView: ParticipantView,
appendHtml: function(collectionView, itemView) {
collectionView.$el.append(itemView.el);
}
});
MyApp.addInitializer(function(options)) {
var participantsView = new ParticipantsView({
collection: options.participantNames
});
MyApp.participantContainer.show(participantsView);
var participantsModel = new Participants();
};
$(document).ready(function() {
MyApp.start({participantsModel: participantsModel});
})
The trouble I am having is that when people leave/join the chat, the message is being resent with a new participant list (e.g. some participantName values won't be there).
So to the question: In backbone.marionette, how and where do I tell it to compare the existing models to the new list of models (for a give chatid), and if the model is no longer in the list, remove the model, and if there's something in the list that isn't a model, add it.
You will see I am building my ID from the the chatid AND the participantName (note that the name is unique, it is actually the JID I'm sending without the #server). The reason the ID is formatted like this is that I will have around 5-10 lists on one page, so I don't want to start updating the wrong chats.
Thank you. Please ask if you require additional information.
jsFiddle: http://jsfiddle.net/966pG/175/
Kind Regards,
Gary Shergill
EDIT: I am aware of get and set, but in all honesty have no idea how to use them. I've tried reading about them "http://backbonejs.org/#Model-get".
EDIT: Live scenario below. I have a javascript function which listens for pubsub events, and when it receives the right one it creates an array of participant objects:
var participants = [];
$(iq).find('participants').each(function() {
var participantsNodes = this.childNodes;
for (var i = 0; i < participantsNodes.length; i++) {
var participantAttr = participantsNodes[i].attributes
var participant = participantAttr[0].nodeValue;
participants.push({"participantName": participant, "chatid": chatid});
}
});
var chatid = $(iq).find('chatid').text();
...
participantsModel.add(new Participants({
chatid : chatid,
participantArray : participants
}))
Based on conversation in the comments
Backbone's Collection.set function takes care of this for you. You can add a new set of participants and it will recognize which ones are new, which ones are old and will fire add and remove events appropriately.
You need to add an array of Participants though, you can't just add an array of names. You can handle that with map pretty easily though.
//array of names from sonewhere
arr = getNamesFromServer()
arrOfParticipants = arr.map(function(name) {
id = calculateChatID(name,/*whatever else you need */)
return new Participant(name:name,chatID: id);
}
participantNames.set(arrOfParticipants)
Exactly how you keep track of your participants and where you pull in updates is up to you.
Of course if you can get the information formatted into the correct format on the server, (an array of json objects matching Participant), you should just use Backbone's built in sync functions, which will handle all of this for you. Then, once you've set up a RESTful URL that matches your client side naming scheme, or overriden the url property on your model/collection, you can just call participantNames.fetch().

Right way to fetch and retrieve data in Backbone.js

I’m trying to understand how and where to use data after a fetch using Backbone.js but I’m a little confused.
I’ll explain the situation.
I have an app that, on the startup, get some data from a server. Three different kind of data.
Let’s suppose Airplanes, Bikes, Cars.
To do that, I’ve inserted inside the three collections (Airplanes, Cars, Bikes) the url where to get these data.
I’ve overwrited the parse method, so I can modify the string that I get, order it, and put it in an object and inside localstorage. I need it to be persistent because I need to use those 3 data structure.
So with a fetch i get all those data and put them inside localstorage. Is it correct doing it that way?
Now i need to make other calls to the server, like “get the nearest car”.
In the view i need to see the color, name and model of the car, all that informations are inside the object “Cars” in localstorage.
In my view “showcars.view” I just call a non-backbone js, (not a collection, model or view) where i get all the informations i need. In this js i do:
var carmodel = new Car(); //car is the backbone model of the cars
carmodel.url = '/get/nearest/car'; //that give id of the nearest car
carmodel.fetch ({
success: function () {}
//here i search the Cars object for a car with the same id
//and get name, color, model and put them in sessionstorage
})
So after that call, in the view I can get the data I need from the sessionstorage.
Is that a bad way of doing things? If so, how i should fetch and analyze those informations? I should do all the calls and operations inside the models?
Thanks
This would be the way that you might implement what you want.
var Car = Backbone.Model.extend();
var Cars = Backbone.Collection.extend({
model: Car,
url: '.../cars'
});
var NearestCar = Backbone.Model.extend({
url: '...nearest/car'
});
var cars = new Cars();
var nearestCar = new NeaerestCar();
cars.fetch({
success: function() {
nearestCar.fetch({
success: function(model) {
var oneYouWant = cars.get(model.get('id'));
// do something with your car
// e.g.:
// var carView = new CarView({model: oneYouWant});
// $('body').append(carView.render().el);
});
});
});
});
In general, Backbone keeps everything in memory (that is, the browser memory) so there is no need to save everything to local storage, as long as your Collection object is somehow reachable from the scope you are sitting in (to keep things simple let's say this is the global window scope).
So in your case I will have something like three collections:
window.Cars
window.Airplanes
window.Bikes
Now you want the nearest. Assuming you are in a Backbone View and are responding to an event, in your place I would do something like this (just shows the meaningful code):
var GeneralView = Backbone.View.extend({
events: { "click .getNearestCar": "_getNearestCar" },
_getNearestCar: function () {
$.getJson('/get/nearest/car', function (data) {
// suppose the data.id is the id of the nearest car
var nearestCar = window.Cars.get(data.id)
// do what you plase with nearestCar...
});
}
});

Saving a model in local storage

I'm using Jerome's localStorage adapter with Backbone and it works great for collections.
But, now I have a single model that I need to save. So in my model I set:
localStorage: new Store("msg")
I then do my saves and fetch. My problem is that everytime I do a refresh and initialize my app a new representation of my model is added to localStorage, see below.
What am I doing wrong?
window.localStorage.msg = {
// Created after first run
"1de5770c-1431-3b15-539b-695cedf3a415":{
"title":"First run",
"id":"1de5770c-1431-3b15-539b-695cedf3a415"
},
// Created after second run
"26c1fdb7-5803-a61f-ca12-2701dba9a09e":{
"0":{
"title":"First run",
"id":"1de5770c-1431-3b15-539b-695cedf3a415"
},
"title":"Second run",
"id":"26c1fdb7-5803-a61f-ca12-2701dba9a09e"
}
}
I ran into same issue. Maybe you have something similar to this
var Settings = Backbone.Model.extend({
localStorage: new Store("Settings"),
defaults: { a: 1 }
});
var s = new Settings;
s.fetch();
I changed to
var s = new Settings({ id: 1 });
localStorage adapter check for id like
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
so 0 or "" for id wont work and it will return all models in one
I'm new to backbone.js too, but it looks like the persistence model is analogous to database tables. That is to say, it's designed to create/delete/read records from a table. The localStorage adapter does the same, so what you are doing there is creating a Msg "table"
in localStorage, and creating a new Msg "record" each time, and the adapter gives each new Msg a unique id.
If you just have one object, it's probably easier to just use localStorage directly. The API is really straight forward:
localStorage.setItem("key","value");
Keep in mind that localStorage only deals with key/value pairs as strings, so you'd need to convert to/from string format.
Take a look a this question for more on doing that:
Storing Objects in HTML5 localStorage

Categories

Resources