Backbone.sync clarification - javascript

After reading the docs, this is my understanding of sync.
I instantiate some Backbone.Model and call Collection.create(). create() eventually calls sync() and the Model is POSTed to the server. Then there is a sync in the opposite direction such that the Model on the client is given an id.
Does this update then trigger componentDidUpdate()?
Note: componentDidUpdate is a ReactJS thing, so if that doesn't make sense, the question reduces to "Is the client-side model updated and the view re-rendered?"
Since inside of my componentDidUpdate() I am making a call to save() to keep everything up to date, this subsequently makes a call to sync() which then fires a PUT request (because the Model already has an id).
I'm asking, because in my current application, creating a TodoItem seems to result in a POST and then a PUT which I find redundant. Perhaps it is for an unrelated reason.
It actually fires two POSTS and then two PUTS when adding one item, but that is another question.

The first time you save a model (one which doesn't have an id) it will make a POST, thereafter it will make a PUT (update). I think you are confusing when to use create/add/save:
Use save at any point to save the current client collection/model state to the server
Use add to add Model(s) to a collection (a single Model, an array of Models, or an array of objects which contain attributes and let the collection create them)
Use create as a shorthand for creating a model, adding it to the collection, and syncing the collection to the server.
My guess is that you are calling create and save in one operation - you should be using add and save instead, or just create.
The view will not automatically update for you, you will need to listen to changes or events on the collection/model and update the view yourself - there is no equivalent of componentDidUpdate. For example:
initialize: function() {
this.listenTo(this.collection, 'sync', this.onCollectionSync);
},
onCollectionSync: function() {
this.render();
}

Related

How to trigger a Client JS method when a subscribed data is changed

I want my app to fire a method (client side) when a particular subscribed data is changed? for example, the client side has this following subscription
Meteor.subscribe('thePlayers');
thePlayers subscription returns a collection of data which is being displayed in the html through the template.
so whenever the collection get changed, Meteor automatically change the data in the HTML also. Besides this feature, I want my app to fire a method say fire() to be executed as soon as data get changed. What should i do to achieve this?
As David Weldon correctly, cursor.observerChanges is the way to go. Here's how you can use it for your example (assuming your collection is called thePlayers):
client-side
methodCaller = function (methodName) {
return function (/* arguments */) {
Meteor.apply(methodName, arguments)
}
}
var fireCaller = methodCaller('fire')
thePlayers.find().observeChanges({
added: fireCaller,
changed: fireCaller,
removed: fireCaller
})
In case you need this fire() to be run on server, you don't need a method, you can just rely on the observeChanges feature or just observe in your publication. See this question to get an example of how you can achieve that.
In case you need this fire() to be run on client, keep in mind that every helper in your template is reactive, that means it will re-run each time your collection is changed. I assume that it requires that you use the subscription inside it, but that needs to be confirmed.

Backbone fetch and create with different callback method

We use Backbone's fetch to get some record from server and add to the page, use create to create record and add to the page, the add to the page part is done by add method cause create and fetch will trigger it, now I want they have diff way to add to the page, created record I want to insert(prepend) to the page and fetched record append to the page, is there good way to do this?
I'm making the assumption that you're using the add -event to listen for when to add the record to the page.
Both the create and fetch methods take an options -object as a parameter. This obejct gets passed around quite a bit and it will end up as the 3rd parameter in your add -event triggers as well.
Now what you can do to differentiate between the add -events from create and fetch, add some variable to your options -object that you can easily recognize. For example
var onAdd = function(model, collection, options) {
if(options.isFetch) console.log('this model was fetched!');
if(options.isCreate) console.log('this model was created!');
}
model.on('add', onAdd);
model.fetch({isFetch: true});
collection.create({foo: "bar"}, {isCreate: true});
There might be some more eloquent way of achieving this by examining the options Backbone internally sets in create and fetch for differences, but this way you'll know for sure.

Destroying records does not actually remove them

I've run into a problem where calling destroy() on a model object triggers the "destroy" event but doesn't actually discard the model object.
I am using relations, but seem to be coming across the problem with or without relationships.
var stagelet = stage.stagelets().findByAttribute("reference", id);
stagelet.destroy();
Triggers the destroy event, but then calling:
stage.stagelets().all();
will still return the recently destroyed object. Are there situations in which these destroyed objects remain in the store or should I look elsewhere for my issue?
That looks like a bug, objects that are destroyed should not be returned by .all()
If you can submit a test case via a pull request on github we can look into it further.

rivets.js: prepopulate model with data from view on init

Perhaps this seems a bit backwards, but I have a view bound with Rivets.js for which I'd like the view to populate the model on initialization.
The usecase is that I'm using server-side rendering to return a snippet (the view) including rivets' data-attributes. So NO JSON is returned from server to client.
Now, by pressing 'edit' a user may put the content in 'edit'-mode, and start editing at will. (Using contenteditable, but this is out of scope here I guess).
So how to make sure the model is populated with values from the view on init?
I know that this question is a little outdated but I recentry tried rivets and I came across the same problem.
The solution:
// In your rivets configuration you disable preload:
rivets.configure({
templateDelimiters: ['[[', ']]'],
preloadData: false
});
// you bind your data
var binding = rivets.bind($('#auction'), {auction: auction});
// you manually publish it once to populate your model with form's data
binding.publish();
And that's it. I still don't know how to disable prelaod per bind
From the example on Rivets website (assign to 'rivetBinding')
var view = rivets.bind($('#auction'), {auction: auction});
doing rivetBinding.publish(); will bootstrap the model with values from the view for all bindings that have 'publishes = true'.
This question is old but it still has no accepted answer, so here goes:
You need to disable the preload configuration so rivets doesn't override whatever is in the input with what you have in your model at the time you do the binding. This can be done via the preloadData=false configuration, either globally (rivets.configure(...)) or view-scoped (third param to rivets.bind(...)).
After the binding, you need to publish the view (pull the values to your model). You also need to set up the observers via sync() call, otherwise your binded methods won't be triggered.
Using the same example as the previous answers:
var view = rivets.bind($('#auction'), { auction: auction }, {
preloadData: false
});
view.publish();
view.sync();

backbone.js and states that are not in models

In the data-driven paradigm of Backbone, the backbone views/routers should subscribe to model changes and act based on the model change events. Following this principle, the application's views/routers can be isolated from each other, which is great.
However, there are a lot of changes in the states of the applications that are not persisted in the models. For example, in a to-do app, there could be buttons that lets you look at tasks that are "completed", "not completed", or "all". This is an application state not persisted in the model. Note that the completion state of any task is persisted, but the current filter in the view is a transient state.
What is a good way to deal with such application state? Using a plain, non-backboned state means that the views/routers cannot listen to the changes in this state, and hence become difficult to code in the data-driven paradigm.
Your buttons filter example can be properly solved using Model events.
I suppose your buttons handlers have access to the tasks Collection. Then filter the collection and trigger events over the selected Models like:
model.trigger( "filter:selected" )
or
model.trigger( "filter:un-selected" )
The ModelView can be listening to these events on its Model and acts accordingly.
This is following your requirements of respecting the not use or "attributes that are not persistent" like selected but I don't have any trauma to use special attributes even if they shouldn't be persistent. So I also suggest to modify the selected attribute of your Models to represent volatile states.
So in your buttons handlers filter the collection and modify the selected attribute in your Models is my preferred solution:
model.set( "selected", true )
You can always override Model.toJSON() to clean up before sync or just leave this special attributes to travel to your server and being ignored there.
Comment got long so I'll produce a second answer to compare.
First, I feel like "completed", "not completed" are totally item model attributes that would be persisted. Or maybe if your items are owned by many users, each with their own "completed" "not completed" states, then the item would have a completedState submodel or something. Point being, while #fguillen produced two possible solutions for you, I also prefer to do it his second way, having models contain the attributes and the button / view doing most of the work.
To me it doesn't make sense for the model to have its own custom event for this. Sounds to me like a filter button would only have to deal with providing the appropriate views. (Which items to show) Thus, I would just make the button element call a function that runs a filter on the collection more or less directly.
event: {
'click filterBtnCompleted':'showCompleted'
},
showCompleted: function(event) {
var completedAry = this.itemCollection.filter(function(item) {
return item.get('completed');
});
// Code empties your current item views and re-renders them with just filtered models
}
I tend to tuck away these kind of convenience filter functions within the collection themselves so I can just call:
this.ItemCollection.getCompleted(); // etc.
Leaving these attributes in your model and ignoring them on your server is fine. Although again, it does sound to me like they would be attributes you want to persist.
One more thing, you said that using plain non-backboned states sacrifices events. (Grin :-) Not so! You can easily extend any object to have Backbone.Event capabilities.
var flamingo = {};
_.extend(flamingo, Backbone.Events);
Now you can have flamingo trigger and listen for events like anything else!
EDIT: to address Router > View data dealings -------------------//
What I do with my router might not be what you do, but I pass my appView into the router as an options. I have a function in appView called showView() that creates subviews. Thus my router has access to the views I'm dealing with pretty much directly.
// Router
initialize: function(options) {
this.appView = options.appView;
}
In our case, it may be the itemsView that will need to be filtered to present the completed items. Note: I also have a showView() function that manages subviews. You might just be working directly with appView in your scenario.
So when a route like /items/#completed is called, I might do something like this.
routes: {
'completed':'completed'
},
completed: {
var itemsView = ItemCollectionView.create({
'collection': // Your collection however you do it
});
this.appView.showView(itemsView);
itemsView.showCompleted(); // Calls the showCompleted() from View example way above
}
Does this help?

Categories

Resources