I have an "index" view and an accompanying "pagination" view. On initialization, the index view fetches the relevant collection. The initially fetched collection is limited to 100 models and contains the count of all values in the collection. The count is passed to the pagination view and it accordingly produces page numbers. After the 10th page (10 records/page) The next 100 models are fetched, and so on the pattern continues.
Keeping the aforementioned in mind, when I add one or more models to the collection the model count will need to be re-fetched from the server (so the pages can be re-calculated), even though I do:
#collection.add [new_model]
However, in the event of changing a value in a model, I simply want the collection to be re-rendered.
With the following initialization code, I'm able to have the collection re-rendered after a change. But in the event of a "add" nothing happens. How can I construct the view to re-fetch from the server the new collection and count?
Note: I'm using fetch(add: true)
initialize: ->
#collection = new MyApp.MyCollection()
#collection.on('add', #render, #)
#collection.on('change', #render, #)
#collection.fetch(add: true)
To clarify the events logic:
reset: will be triggered after a fetch from the server. A fetch drops all content of the collection and just inserts everything in the collection that comes from the server
add: will be triggered after a model has been added to the collection. ATTENTION: add will not be triggered by a fetch, unless you use the add:True option.
change: will be triggered after the content/field of a model has changed. ATTENTION: change will not be triggered by fetch nor by adding a model.
So the events do hardly overlap but all serve a specific use case. You have that in mind when binding the events to methods, especially the render method.
Related
I am using NetChart from ZoomCharts, It provides an option to filter nodes by providing a function in nodeFilter argument which is called by chart.updateFilters(). I want to know if there exists an event that is fired after updateFilters is completed and new chart with filtered nodes is rendered, so that I can get list of nodes that are currently rendered.
I found an event onChartUpdate but it doesn't work everytime chart.updateFilters() is called.
When you are calling chart.updateFilters() this triggers data update, where the nodes are validated against the filters in the next immediate rendering cycle. So, for now you can simply use setTimeout() and use api call nodes() to get the visible nodes. Alternatively, you can hook into nodeFilter() method to sync the data model state externally to keep the state always updated.
You can also request a new feature development if you need this feature in your project (write to support#zoomcharts.com)
So there's a piece of functionality with which I've been struggling for a while now. I'm using .where() method in order to retrieve an array of objects from the Collection and then I reset this Collection with this array.
# Fetch the collection
collection = App.request(collection:entites)
console.log collection
>collection {length: 25, models: Array[25] ... }
When the event fires it passes options for .where() method and starts reset process:
# Get new models
new_models = collection.where(options)
# Reset collection with the new models
collection.on 'reset', (model, options) ->
console.log options.previousModels
return
collection.reset(new_models)
console.log collection
>collection {length: 5, models: Array[5] ... }
In the View responsible of rendering this collection I listen to the 'reset' event and render the View accordingly.
initialize: ->
#listenTo(#collection, 'reset', #render)
It works just as expected: event fires, collections undergoes reset and the View re-renders reseted collection. But when the event fires second time the collection doesn't sync with the server and new_models = collection.where(options) receives a collection that was already reseted in a previous event run and returns an empty array.
What are my options here? Each event run I need an initial collection of all models to work with. Should I just request new instance of the collection on the each run or can I make it in a more cleaner manner, i.e. save original state somewhere and pass it for the event run instead of fetching new collection from the server? Please advise.
Yes. Another way to achieve it is, when you filter the collection, using .where(), you can trigger Backbone.Events custom event which you view can listen to. This way, the original collection does not reset, there only is a change in array which will trigger the custom event.
A sample code for using custom events in backbone is:
var object = {};
_.extend(object, Backbone.Events);
object.on("collectionFiltered", function(arrayOfFilteredModels) {
// do stuff to render your view with the new set of array.
// You can use underscore templating to traverse the array for rendering view.
});
object.trigger("collectionFiltered", collection.where(options);
What i'm trying to achieve
I want to render posts into a timeline. Timeline consists of two columns that i append posts to. I want to be able to append a post to the shorter column. New posts need to be prepended to the first column.
I actually have a working code that solves this problem, but i feel that it's not as efficient as it could be. Marionette by default attaches newly created elements to a documentFragment buffer, which is supposed to speed up rendering (less layout changes and such). Also i need to fetch posts after i render the timeline composite view (as both columns need to be attached to DOM to have height).
What i tried
This is controller responsible for showing view in a certain region in app:
class APP.Controllers.Companies extends Marionette.Controller
initialize: ->
#vent = _.extend {}, Backbone.Events
show: ->
posts = APP.request "posts:collection"
directoriesPromise = APP.request "directories:all"
postsView = new APP.Views.Posts.Layout
vent: #vent
collection: posts
postsView.on "render", -> posts.fetch()
#_wait [directoriesPromise], (directories) =>
APP.execute "show:main", postsView
Please note that i'm fetching posts after i have rendered the composite view, to make sure that collection is initially empty and columns are attached to DOM. Otherwise there are situations where posts collection is cached and is fetched before the view is rendered, which breaks getShortestColumn (see below) function.
_wait is a wrapper for jQuery.wait function.
This is part of Timeline class, that extends Marionette.CompositeView:
appendHtml: (compositeView, itemView, index) ->
#getShortestColumn().append itemView.el
getShortestColumn: ->
minHeight = Number.MAX_VALUE
shortest = null
#ui.columns.each ->
if $(this).outerHeight() < minHeight
shortest = $(this)
minHeight = $(this).outerHeight()
shortest
The goal
I want to basically keep current functionality but leverage buffering available in marionette. I also want to be able to fetch posts before i show view (as i do with directories), to prevent timeouts or slow connection from hindering user experience.
It would seem i was not clear on what i'm asking about, so here's the question: how do i achieve that?
Any suggestions or solutions are welcome.
Thanksalot.
edit: i forgot to mention that i use Marionette 1.8.6 and unfortunately i can't update.
Instead of doing fetch in the render event (which is called more frequently than you might expect anyhow), you can override the show method, do the fetch there, and then call the base show method after the fetch is done/failed.
Or better yet, you have a "controller" which is responsible for fetching data and then handing it to the view rather than the view fetching anything itself.
I'm building a Backbone app which displays a list of books, but when I add a new book, through the Edit view, he goes to the bottom of the list instead to the top. So basically I want to reverse the order of my collection with a Comparator but what I tried it's not working:
comparator: function (model) {
return -model.get('id');
}
Here is a JSFiddle with all the code: http://jsfiddle.net/swayziak/9R9zU/4/
I don't know if the problem is only related with the comparator or if I need to change something more in other part of the code.
Why not a simple prepend instead of append
this.$el.prepend(
Your comparator is looking for the model IDs:
comparator: function (model) {
return -model.get('id');
}
but none of your models have id attributes. Usually the id would come from the server so the server would supply the initial id values when bootstrapping the collection and then new ids would be assigned (by the server) when you save the model.
If you add ids to your data then things will start to make more sense.
You'll also need to adjust your fiddle to:
this.listenTo(this.collection, 'add', this.render);
instead of:
this.listenTo(this.collection, 'add', this.renderBook);
since your editing panel will kill off all the HTML and you'll need to re-render the whole collection.
Once you get past that, you'll find that your Edit link no longer works. That's because you're trying to re-use views while messing around with the content of their el's. Instead, you should:
Stop trying to re-use views, it is almost never worth the hassle.
Give each view its own unique el.
Call view.remove() to get rid of a view before putting a new one in the common container.
Then create the new view, render it, and put its el in the container.
You'll find that since all your views share a common container, you'll no longer need to bind your collection view to the collection's 'add' event, you'll be tossing and rebuilding the whole view instead.
I want to remove a model from a collection:
var item = new Backbone.Model({
id: "01",
someValue: "blabla",
someOtherValue: "boa"
});
var list = new Backbone.Collection([item]);
list.get("01").destroy();
Result:
item is still in the backbone array ....
I have reservations about the accepted answer. When a model is destroyed, the "destroy" event bubbles through any collection the model is in. Thus, when you destroy a model you should not have to also remove the model from the list.
model.destroy();
Should be enough.
Looking at your code it looks correct: (If the intent is to destroy + remove, not just remove)
list.get('01').destroy();
Are you sure that your resource is getting properly destroyed? Have you tried placing a success and error callback in your destroy() call to ensure the destroy was executed? For example, if your model URL is incorrect and it can't access the resource, the destroy call would return an error and your collection will still have the model. This would exhibit the symptoms you outline in your question.
By placing the remove() after the destroy call, your collection will definitely remove the model. But that model will still be floating around (still persisted). This may be what you want, but since you're calling destroy() I'm assuming you want to obliterate it completely. If this is the case, while remove seems to work, what it's really doing is masking that your model has been destroyed when in fact it may not.
Thus, this is what I have a feeling is actually happening. Something is preventing the model from being destroyed. That's why when you call destroy(), then check your collection - the model is still there.
I could be wrong though. Could you check this and update your findings?
You should also remove the model from the collection.
var model = list.get('01');
model.destroy();
list.remove(model);