At what point do you choose the Backbone model that the presentation depends on?

I seem to often find myself in a situation where I am viewing a view, but the model on which this view depends is not yet loaded. Most often, I only have a model identifier taken from a URL, for example. for a hypothetical market application, the user lands on the application with this URL:

http://example.org/#/products/product0

In my ProductView I create a ProductModel and set its id, product0 , and then I fetch() . I render once with placeholders, and when the selection completes, I reprocess. But I hope that there will be a better way.

Waiting for the model to load before something feels refractory. Re-rendering causes flickering and adding “loading ... please wait” or spinners everywhere makes presentation templates very complex (especially if the model fails because the model does not exist or the user does not have the right to view the page).

So, what is the right way to render a view when you don't have a model yet?

Do I need to leave hashtag-views and use pushState ? Can the server give me a push? I'm all ears.

Download from an already loaded page:

I feel that you can do more when the page is already loaded, and not directly on the Product page.

If an application displays a link to a product page, say, by rendering the ProductOrder collection, is there anything else that can be done?

 <ul id="product-order-list"> <li>Ordered 5 days ago. Product 0 <a href="#/products/product0">(see details)</a></li> <li>Ordered 1 month ago. Product 1 <a href="#/products/product1">(see details)</a></li> </ul> 

My natural way to handle this link-to-detail-page template is to define a route that does something on these lines:

 routes: { 'products/:productid': 'showProduct' ... } showProduct: function (productid) { var model = new Product({_id: productid}); var view = new ProductView({model: model}); //just jam it in there -- for brevity $("#main").html(view.render().el); } 

I usually call fetch() inside the initialize view function and call this.render() from the this.listenTo('change', ...) event this.listenTo('change', ...) . This leads to complex cases of render() , and objects appear and disappear from the view. For example, my view template for Product may reserve some screen properties for user comments, but only if comments are present / included in the product, and this is usually unknown before the model is completely retrieved from the server.

Now, when / when is it best to sample?

  • If I load the model before the page transitions, this leads to a simple presentation of the code, but introduces delays noticeable to the user. The user will click on an item in the list and will have to wait (without changing the page) for the returned model. Response points are important, and I have not studied this study, but I think that users are used to seeing how pages change immediately as soon as they click the link.

  • If I load a model inside ProductView initialize using this.model.fetch() and listen to model events, I have to display twice, once before with empty placeholders (because otherwise you have to look at the white page) and one times after. If an error occurs during loading, then I should erase the view (which flicker / buggy appears) and show some error.

Is there another option that I don’t see, perhaps with a download transitional page that can be reused between views? Or is it good practice to always make the first call to render() display some turn / load indicators?

Edit: Download via collection.fetch()

It can be assumed that since the items are already included in the collection list (the collection used to display the list of links), they can be retrieved until the link is clicked using collection.fetch() . If the collection was indeed a Product suite, then it would be easy to display a representation of the product.

Collection used to create the list may not be a ProductCollection . It could be a ProductOrderCollection or something else that just has a link to the product identifier (or some sufficient information about the product to link to it).

Getting all Product using collection.fetch() can also be prohibitive if the Product model is large, especially. to avoid clicking one of the product links.

A chicken or an egg? The collection.fetch() approach also does not solve the problem for users who go directly to the product page ... in this case, we still need to display the ProductView page for which we need to select the Product model identifier (or any other URL of the product page) .

+5
source share
2 answers

Good, therefore, in my opinion, there are many ways you can fix this. I have listed everything that I thought about, and I hope you will work with you, or at least it will inspire you to find the best solution.

I am not completely opposed to the answer of T J. If you just go in and do collection.fetch () on all products when the website loads (usually users expect that there will be some loading time), then you have all your data , and you can just pass this data round as he mentioned. The only difference between what he offers and what I usually do is that I usually have a link to the application in all my views. So, for example, in my initialize function in app.js, I will do something like this.

 initialize: function() { var options = {app: this} var views = { productsView: new ProductsView(options) }; this.collections = { products: new Products() } // This session model is just a sandbox object that I use // to store information about the user session. I would // typically store things like currentlySelectedProductId // or lastViewedProduct or things like that. Then, I just // hang it off the app for ease of access. this.models = { session: new Session() } } 

Then, in my productsView.js initialize function, I would do the following:

 initialize: function(options) { this.app = options.app; this.views = { headerView: new HeaderView(options), productsListView: new ProductsListView(options), footerView: new FooterView(options) }; } 

The mobilities that I create in the initialization in productsView.js are arbitrary. I basically just tried to demonstrate that I continue to pass this parameter object also in view representations.

What this does allows each view, whether it is a top-level view or a deeply nested preview, each view knows about all other views, and each view has a link to the application data.

These two code examples also introduce the concept of defining your functionality as accurately as possible. Do not try to have an idea that does everything. Transfer functionality to other views so that each view has one specific goal. This will facilitate the reuse of views. Especially complex modalities.

Now back to the current topic. If you were going to go ahead and load all the products in front, where should they be taken? Because, as you said, you do not want the blank page to just sit there in front of your user. So my advice is to trick them. Download as much of your page as possible and block only the part that requires data loading. In this way, the page looks like it is loading while you are really doing work behind the scenes. If you can fool a user into thinking that the page is steadily loading, they are much less likely to look impatient when loading the page.

So, referring to the initialization from productsView.js, you can continue and enable the rendering of headerView and footerView. Then you can make your selection in the rendering of productsListView.

Now I know what you are thinking. I lost my mind. If you make a selection in the rendering function, then there is no way to return the call before we hit the line that actually displays the productsViewList template. Well, fortunately, there are several ways. One way is to use Promises . However, as I usually do, just use the render function as my own callback. Let me show you.

 render: function(everythingLoaded) { var _this = this; if(!!everythingLoaded) { this.$el.html(_.template(this.template(this))); } else { // load a spinner template here if you want a spinner this.app.collection.products.fetch() .done(function(data) { _this.render(true); }) .fail(function(data) { console.warn('Error: ' + data.status); }); } return this; } 

Now, by structuring our rendering in this way, the actual template will not load until the data is fully loaded.

As long as we have a rendering function, I want to introduce another concept that I use everywhere. I call it postRender. This is a function in which I execute any code that depends on the DOM elements in place after the template has finished loading. If you just encoded a simple .html page, then this is the code that is traditionally included in $ (document) .ready (function () {}); . It may be worth noting that I do not use .html files for my templates. I am using javascript embedded files (.ejs) . Continuing to work, the postRender function is a function that I basically added to my boiler room code. Therefore, whenever I call the rendering for presentation in the code base, I immediately click on it postRender. I also use postRender as a challenge for myself, as I did with the render. So essentially the previous code example would look something like this in my code base.

 render: function(everythingLoaded) { var _this = this; if(!!everythingLoaded) { this.$el.html(_.template(this.template(this))); } else { // load a spinner template here if you want a spinner this.app.collection.products.fetch() .done(function(data) { _this.render(true).postRender(true); }) .fail(function(data) { console.warn('Error: ' + data.status); }); } return this; }, postRender: function(everythingLoaded) { if(!!everythingLoaded) { // do any DOM manipulation to the products list after // it loaded and rendered } else { // put code to start spinner } return this; } 

By linking these functions in this way, we guarantee that they will be executed sequentially.

==================================================== =========================

So, this is one way to solve the problem. However, you mentioned that you do not want all products to be loaded in advance, fearing that the request may take too long.


Side Note: You should really consider getting some kind of information related to the product call, which can cause the call to take a considerable amount of time, and make most of the information a separate request. I get the feeling that users will be more asking for the data to take some time to load, if you can get their basic information very quickly, and if the thumbnails associated with each product take a little longer to load, it should not be then Peace. This is just my opinion.


Another way to solve this problem is that you just want to go to a specific product page, and then just implement the render / postRender template , which I described above on a separate ProductView. However, note that your productView.js will probably look something like this:

 initialize: function(options) { this.app = options.app; this.productId = options.productId; this.views = { headerView: new HeaderView(options), productsListView: new ProductsListView(options), footerView: new FooterView(options) }; } render: function(everythingLoaded) { var _this = this; if(!!everythingLoaded) { this.$el.html(_.template(this.template(this))); } else { // load a spinner template here if you want a spinner this.app.collection.products.get(this.productId).fetch() .done(function(data) { _this.render(true).postRender(true); }) .fail(function(data) { console.warn('Error: ' + data.status); }); } return this; }, postRender: function(everythingLoaded) { if(!!everythingLoaded) { // do any DOM manipulation to the product after it // loaded and rendered } else { // put code to start spinner } return this; } 

The only difference here is that productId was passed in the options object for initialization, and then fetched and used in .fetch in the rendering function.

==================================================== ======================== In conclusion, I hope this helps. I'm not sure that I answered all your questions, but I think I made a pretty good pass. For the sake of this, I’m going to stay here too long and let you digest it and ask any questions you have. I assume that I will probably have to do at least 1 update to this post to clear it even further.

+3
source

You started saying:

I have a list of items in one collection view

So what does the collection do? Models ..!

When you execute collection.fetch() , you retrieve all the models.

When the user selects an element, just pass the appropriate model to the element view, for example:

 this.currentView = new ItemView({ model: this.collection.find(id); // where this points to collection view // and id is the id of clicked model }); 

Thus, there will be no delay / incorrect rendering.

What if the endpoint of your collection returns a huge amount of data.?

Then implement the usual methods like pagination, lazy loading, etc.


I will build a product model with this ID

It sounds wrong to me. If you have a collection of products, you should not create such models manually.

Ask the collection to select your models before displaying the list. This way you can avoid the whole problem that you talked about.

+2
source

Source: https://habr.com/ru/post/1236106/


All Articles