I need some suggestions around what should be the best-practice for the below scenario :
1. *Who (Model or View ) should hold the promise when a "FETCH" is called upon the Model ?*
For example :
Model FOO --> initialize: { .....this.promise = this.fetch(); }
View view --> someWhere: { var foo=new Foo(); foo.promise.done(..do something here) }
OR
Model FOO --> initialize: { ... }
View view --> someWhere: { var foo=new Foo();
var myPromise=foo.fetch();myPromise.done(..do something..) }
....
2. Second Question is around whether Initializing a Model
should automatically call the fetch ? ( As noted in the above Example )
There is no harm in doing either ways but I am not able to figure pros-cons of over each either style. Your opinion is appreciated
All anwers are in the official docs.
1) You don't need to hold that promise unless you going to use it later in other methods (which looks strange).
fetch method returns a jqXHR which is exactly a promise (Deferred). You can use it immediately in the place where you call fetch without holding a promise instance.
E.g.:
model.fetch().done(..do something here..);
2) Quote from the docs:
When your app first loads, it's common to have a set of initial models
that you know you're going to need, in order to render the page.
Instead of firing an extra AJAX request to fetch them, a nicer pattern
is to have their data already bootstrapped into the page. You can then
use reset to populate your collections with the initial data.
Related
Ember 2.17
I am calling an helper from my template :
{{#each invoice.invoiceLines as |line| }}
{{pricings/full-pricing line.pricing}}
{{/each}}
invoice,invoiceLine, as well as pricing are ember models.
Here is how invoice is created in model () :
model(params) {
let invoice= this.store.findRecord('invoice',params.invoice_id)
return Ember.RSVP.hash({
invoice: invoice,
allShares: invoice.then((i)=>{return i.allShares()}),
detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
The goal of the helper is to take pricing, extract numbers (everything is in the model, no more relations) and return a string formatting the initial price and the subscription price.
The helper is as following :
import { helper } from '#ember/component/helper';
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
export default helper(pricingsFullPricing);
When I run the page, debugger is called twice (the template loop run once).
First time p is null, the second time it is a pricing.
Isn't then supposed to prevent that? Why does it behave like that?
Your route is wrong, routes are promise aware (that's what hash is for), it should be:
model(params) {
return Ember.RSVP.hash({
invoice: this.store.findRecord('invoice',params.invoice_id)
//allShares: invoice.then((i)=>{return i.allShares()}),
//detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
Then your handlebars is just:
{{#each model.invoice.invoiceLines as |line| }}
{{line}}
{{/each}}
You also shouldn't call methods like you are on a model. It's not really clear what allShares(), etc does but these should (probably) be computed in the controller. Something along the lines of:
import { computed } from '#ember/object';
export default Controller.extend({
allShares:computed('model.invoice', function(){
return this.get('model.invoice').allShares();
});
});
Though this doesn't seem ideal. Like I said, it's hard to be explicit as it's not clear what your trying to do here. It'd probably make more sense if your extracted these methods into a service.
You then don't need the helper at all. This appears to be just trying to work around promises.
It makes life a lot easier if you try and load all server side data in the route before load.
First rule of helpers
Each time the input to a helper changes, the compute function will be called again.
Second, there's nothing about helpers that will make this block subsequent calls because you are returning a promise.
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
You've created a simple helper here that will use the promise itself as the value. Look at ember-promise-helpers/await to see how a class based helper is used to manually set the value that's displayed in the template.
Now, if you're wondering why the recomputation is happening, I'm going to have to speculate based off the knowledge I have of Ember data just from being part of the Ember community (I've never actually used Ember Data). You know line.pricing is a promise? I can then assume your using some sort of relationship, which will most likely have to be loaded via an ajax call (hence the promise). But these relationships in Ember data, iirc, use this PromiseProxyMixin that allow them to behave simultaneously like a promise or like an object (depending on whether the data is in the store already or not). This is what allows you to reference the promise in your template without then
See this article for a better understanding of what I mean
I want to store a private variable on each DS.Model. Its purpose is to store a pending callback (in case I want to cancel it).
I have tried this (and it works):
DS.Model.reopen({
init() {
let _pending; // my private var
this._getPending = () => _pending; // get private var
this._setPending = callback => _pending = callback; // set private var
this._super(...arguments);
}
});
I have placed this in an initializer, and it works as I expect it to.
My questions are: Is this a good practise? is it likely to mess anything up? ...and, is there a better way?
Personally, I'm happy with the way it works.. but I'm not sure if its the "Ember" way. This is going to go into an Ember-cli addon, so I would like it to be the most "best practise" as possible. (the _getPending/_setPending method are only to be used internally within the addon.)
Here are my 2 cents on this. I would say no it is not a good practice, but it should be okay since they are just Ember Objects. The question here is what is Ember data model used for? From doc it says:
"Models are objects that represent the underlying data that your application presents to the user."
By definition this is not what they are designed for, so just because you are able to it does not mean that you should use them like this.
Pending callback so it can be canceled? Ember model API has defined state objects that can be used for this purpose. http://emberjs.com/api/data/classes/DS.Model.html Flags like isDeleted, isValid, isNew...gives all possible state.
I would place them in router actions where they are easy tested with integration tests.
You can check this screencast that explains them:
https://www.emberscreencasts.com/posts/102-ember-data-20-model-states-and-flags
Hope it helps.
I seem to often end up in a situation where I am rendering a view, but the Model on which that view depends is not yet loaded. Most often, I have just the model's ID taken from the URL, e.g. for a hypothetical market application, a user lands on the app with that 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 fetch completes, I re-render. But I'm hoping there's a better way.
Waiting for the model to load before rendering anything feels unresponsive. Re-rendering causes flickering, and adding "loading... please wait" or spinners everywhere makes the view templates very complicated (esp. if the model fetch fails because the model doesn't exist, or the user isn't authorized to view the page).
So, what is the proper way to render a view when you don't yet have the model?
Do I need to step away
from hashtag-views and use pushState? Can the server give me a push? I'm all ears.
Loading from an already-loaded page:
I feel there's more you can do when there's already a page loaded as opposed to landing straight on the Product page.
If the app renders a link to a Product page, say by rendering a ProductOrder collection, is there something more that can be done?
<ul id="product-order-list">
<li>Ordered 5 days ago. Product 0 (see details)</li>
<li>Ordered 1 month ago. Product 1 (see details)</li>
</ul>
My natural way to handle this link-to-details-page pattern is to define a route which does something along 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 tend to then call fetch() inside the view's initialize function, and call this.render() from an this.listenTo('change', ...) event listener. This leads to complicated render() cases, and objects appearing and disappearing from view. For instance, my view template for a Product might reserve some screen real-estate for user comments, but if and only if comments are present/enabled on the product -- and that is generally not known before the model is completely fetched from the server.
Now, where/when is it best to do the fetch?
If I load the model before the page transition, it leads to straightforward view code, but introduces delays perceptible to the user. The user would click on an item in the list, and would have to wait (without the page changing) for the model to be returned. Response times are important, and I haven't done a usability study on this, but I think users are used to see pages change immediately as soon as they click a link.
If I load the model inside the ProductView's initialize, with this.model.fetch() and listen for model events, I am forced to render twice, -- once before with empty placeholders (because otherwise you have to stare at a white page), and once after. If an error occurs during loading, then I have to wipe the view (which appears flickery/glitchy) and show some error.
Is there another option I am not seeing, perhaps involving a transitional loading page that can be reused between views? Or is good practice to always make the first call to render() display some spinners/loading indicators?
Edit: Loading via collection.fetch()
One may suggest that because the items are already part of the collection listed (the collection used to render the list of links), they could be fetched before the link is clicked, with collection.fetch(). If the collection was indeed a collection of Product, then it would be easy to render the product view.
The Collection used to generate the list may not be a ProductCollection however. It may be a ProductOrderCollection or something else that simply has a reference to a product id (or some sufficient amount of product information to render a link to it).
Fetching all Product via a collection.fetch() may also be prohibitive if the Product model is big, esp. in the off-chance that one of the product links gets clicked.
The chicken or the egg? The collection.fetch() approach also doesn't really solve the problem for users that navigate directly to a product page... in this case we still need to render a ProductView page that requires a Product model to be fetched from just an id (or whatever's in the product page URL).
Alright, so in my opinion there's a lot of ways that you can fix this. I'll list all that I've thought of and hopefully one will work with you or at the very minimum it will inspire you to find your optimal solution.
I'm not entirely opposed to T J's answer. If you just go ahead and do a collection.fetch() on all the products when the website is loading (users generally expect there to be some load time involved) then you have all of your data and you can just pass that data round like he mentioned. The only difference between what he's suggesting and what I normally do is that I usually have a reference to app in all my views. So, for example in my initialize function in app.js I'll 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's 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 this:
initialize: function(options) {
this.app = options.app;
this.views = {
headerView: new HeaderView(options),
productsListView: new ProductsListView(options),
footerView: new FooterView(options)
};
}
The subviews that I create in the initialize in productsView.js are arbitrary. I was mostly just trying to demonstrate that I continue to pass that options object to subviews of views as well.
What this does is allows every view, whether it be a top level view or deeply nested subview, every view knows about every other view, and every single view has reference to the application data.
These two code samples also introduce the concept of scoping your functionality as precise as you possibly can. Don't try to have a view that does everything. Pass functionality off to other views so that each view has one specific purpose. This will promote reuse of views as well. Especially complex modals.
Now to get back to the actual topic at hand. If you were going to go ahead and load all of the products up front where should you fetch them? Because like you said you don't want a blank page just sitting there in front of your user. So, my advice would be to trick them. Load as much of your page as you possibly can and only block the part that needs the data from loading. That way to the user the page looks like it's loading while you're actually doing work behind the scenes. If you can trick the user into thinking the page is steadily loading then they are much less likely to get impatient with the page load.
So, referencing the initialize from productsView.js, you could go ahead and let the headerView and footerView render. Then, you could do your fetch in the render of the productsListView.
Now, I know what you're thinking. Have I lost my mind. If you do a fetch in the render function then there's no way that the call will have time to return before we hit the line that actually renders the productsViewList template. Well, luckily there's a couple of ways around that. One way would be to use Promises. However, the way I typically do it is to just use the render function as its 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 render this way the actual template won't load until the data has fully loaded.
While we have a render function here I want to introduce another concept that I use every where. I call it postRender. This is a function where I execute any code that depends on DOM elements being in place once the template has finished loading. If you were just coding a plain .html page then this is code that traditionally goes in the $(document).ready(function() {});. It may be worth noting that I don't use .html files for my templates. I use embedded javascript files (.ejs). Continuing on, the postRender function is a function that I have basically added to my boiler plate code. So, any time I call render for a view in the code base, I immediately chain postRender onto it. I also use postRender as a call back for itself like 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's loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
By chaining these functions like this we guarantee that they'll run sequentially.
=========================================================================
So, that's one way to tackle the problem. However, you mentioned that you don't want to necessarily load all of the products up front for fear that the request could take too long.
Side Note: You should really consider taking out any information related to the products call that could cause the call to take a considerable amount of time, and make the larger pieces of information a separate request. I have a feeling that users will be more forgiving about data taking a while to load if you can get them the core information really fast and if the thumbnails related to each product takes a little longer to load it shouldn't be then end of the world. That's just my opinion.
The other way to solve this problem is if you just want to go to a specific product page then just implement the render/postRender pattern that I outlined above on the individual productView. However note that your productView.js will probably have to 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's
// loaded and rendered
}
else {
// put code to start spinner
}
return this;
}
The only difference here is that the productId was passed along in the options object to the initialize and then that's pulled out and used in the .fetch in the render function.
=========================================================================
In conclusion, I hope this helps. I'm not sure I've answered all of your questions, but I think I made a pretty good pass at them. For the sake of this getting too long I'm going to stop here for now and let you digest this and ask any questions that you have. I imagine I'll probably have to do at least 1 update to this post to further flush it out.
You started saying:
I have a listing of items in one Collection view
So what does a collection have..? Models..!
When you do collection.fetch() you retrieve all the models.
When the user selects an item, just pass the corresponding model to the item view, something like:
this.currentView = new ItemView({
model: this.collection.find(id); // where this points to collection view
// and id is the id of clicked model
});
This way, there there won't be any delay/ improper rendering.
What if your collections end point returns huge volume of data..?
Then implement common practices like pagination, lazy loading etc.
I construct a Product model with the given ID
To me that sounds wrong. If you have a collection of products, you shouldn't be constructing such models manually.
Have the collection fetch your models before rendering the list view. This way all the problem you mentioned can be avoided.
For most JS frameworks and libraries, the value they bring are often in the form of a new structure as to how to build an application (Backbone, React), or new ideas that effectively power-up the language (Angular), or simply the methods they offer are well tested, fast, and really convenient (jQuery).
Usually the ideas and methods they provide are pretty straightforward usage of JavaScript, but with a very clever team behind it that find interesting ways to do things which you can think through and get a solid guess as to how the guts work.
However, I've been unable to think through the ability to two-way bind JS models to view components. What is the secret sauce at the heart of this feature that makes this work? Changing an internal variable from a user input is simple, but what about the reverse? How would you be able to "know" when a JS variable has changed in order to update the display instantly? Surely it can't be polling, so what then?
Whenever a block of your JS runs that angular triggered it will run a digest cycle when the block finishes executing. This basically checks all the values that might of changed and would require updates to the view.
If angular didn't trigger the code then it won't know that something might of changed so your bindings can get out of sync. For example if you run something like this
setTimeout(function() {$scope.myValue = '123'});
Angular won't know that myValue changed and it actually won't update the view. That's why Angular has it's own services for doing everything. e.g. $timeout or $http.
If you have some callback function that Angular doesn't know about then you can manually tell it to check for changes by calling $scope.$apply()
there are several ways to do it. Object.observe is great, but lacks good support. You can poll for values as well, keeping a 2nd copy of the object around to compare. You can also write your own explicit set/get methods to update the model like backbone does.
One neat method i use a lot is using getters/setters to keep the model synced to the dom:
//a demo "model" of data:
model = {
name: "Fred"
};
function change(k,v){alert([k,v]);} // a stand-in change monitor for demo
// iterate model and replace values with getter/setter combos:
Object.keys(model).forEach(function(key) {
var val = model[key];
delete model[key];
Object.defineProperty(model, key, {
get: function() {
return val;
},
set: function(v) {
val = v;
change(key, val);
} //call change upon setting
});
change(key, val); //update view "onload"
}); // alerts "Fred";
//update model (fires change() with "name" and "sally" arguments:
model.name="sally"; // alerts "sally";
the change function is quite simple and for your case should just find elements bound to keys.
the advantage here is that you don't need special custom CRUD methods, you can just modify the object properties via assignment like it's 1999. It also doesn't poll, and works correctly all the way back to IE9 and any other ES5 environments. It's the simplest way to bind JS>DOM (afaik) without custom methods.
It does have some limits: nested objects are tricky to get/set upon, you can't do the whole object at once, you can only "watch" primitives. Arrays are a problem too: you can't really replace expando properties with getters/setters without side-effects. But, upon a relatively flat collection of JSON-safe data, get/set works a charm and needs no complex libs to get operational.
checkout a complete example using this method: http://pagedemos.com/xg3szbguqnwu/4
I can speak to how it's done in Backbone, which has a relatively low-level perspective on data-binding.
It's a combination of 1. the library having control over attribute setter methods 2. invoking callback functions when attributes change (e.g. by dispatching events) in order to update the UI.
The essential pseudocode is this:
class Model:
method set(name, value):
if value != this.attributes[name]
this.triggerEvent('change', name, value)
this.attributes[name] = value
m = new Model()
someInputWidget.onEvent('userChangedInput', function(value) {
m.set(someInputWidget.name, value)
})
m.onEvent('change', function(name, value) {
getInputWidgetByName(name).setValue(value)
})
Backbone does not do any data binding to the UI, but you can refer to Backbone's annotated source for the actual event-dispatching implementation.
I want to override backbone.sync i have already asked this but the problem is i don't quite get it. I need to know where to put the codes if i were to override the sync function.
If i put it on the model like this
model = Backbone.Model.extend({ sync:"" });
Then how should i call it? if i were to use the save method. Also i need to change the methodMap of create from POST to PUT. temporarily i resorted to this 'create': 'PUT', actually editing the backbone.js file ( iknow its not good ). Before i forgot i also need to add this
sendAuthentication = function (xhr) {
xhr.setRequestHeader('Authorization', auth)
};
As a beforeSend parameter since my server has authentication. Again where should i do it? Where should i go and put the codes? in my model? in my collection? or in my views? Any help? THank you.
update
Also can i override the sync on my collection? i mean can i have something like this?
collection = Backbone.Collection.extend({ sync:""});
The strategy behind Backbone framework is to make it simple for editing and flexible for every need. So if you look up the source code you'll find out that every method, which calls Backbone.sync in fact calls first "this.sync".
From the Backbone manual you can read :
The sync function may be overriden globally as Backbone.sync, or at a
finer-grained level, by adding a sync function to a Backbone
collection or to an individual model.
So you have two options
Option One - Replacing global Backbone.sync function
If you override the global Backbone.sync you should place your code in your global application file ( actually anywhere you want, but it must be evaluated ( executed ) at your initial javascript loading, to work as expected
// Anywhere you want
Backbone.sync = function(method, collection, options) {
console.log(method, collection options)
}
This will override Backbone.sync and actually will display on your console what is called every time you call collection.fetch, save, delete, etc.
Here you have no default Methodmap, infact you have nothing else except the arguments :
method - which is a string - 'read', 'create', 'delete', 'update'
collection - which is your collection instance which calls the method
options - which has some success, error functions, which you may or may not preserve.
Debug this in your browser, while reading the Backbone source code, it's very easy to understand.
Option Two - Adding to your model/collection sync method
This is used if you wish to use the default Backbone.sync method for every other model/collection, except the one you specifically define :
mySocketModel = Backbone.Model.extend({
sync : function(method, collection, options) {
console.log('socket collection '+this.name+' sync called');
}
});
Partners = new mySocketModel({ name : 'partners' });
Users = new mySocketModel({ name : 'users' });
Log = new Backbone.Collection;
So if you call Partners.fetch() or Users.fetch(), they won't call Backbone.sync anymore, but yor Log.fetch() method will.