Ember: Return randomized property value on each reload - ember.js

I have a loading template in which I want to show a random "please wait" message each time the loading route is used, but while the randomness works the first time, the property is then set and doesn't refresh. How do I return a random value each time the route is loaded?
I tried creating a timestamp property and adding that to the watched fields, but again, that property is only set once and then doesn't refresh. I also tried just adding "Date()" to the watch list, and it really didn't like that (understandable since it isn't a field).
Here's my loading controller code:
import Ember from 'ember';
//app/controllers/loading.js
export default Ember.ObjectController.extend({
loadingText: [
'Your wish is my command.',
'Loading, please hang on.',
'I\'ll be back in two shakes of a lamb\'s tail.',
'Your request is very important to us...',
'Hmmmm hmm hmmm...',
'It shall be as you say...',
'Let me get that for you.'
],
randLoadingText: Ember.computed('loadingText', function() {
var loadingText = this.loadingText;
return (loadingText[Math.floor(Math.random()*loadingText.length)]);
})
});

Did you try to use volatile option on property?
//app/controllers/loading.js
export default Ember.ObjectController.extend({
loadingText: [
'Your wish is my command.',
'Loading, please hang on.',
'I\'ll be back in two shakes of a lamb\'s tail.',
'Your request is very important to us...',
'Hmmmm hmm hmmm...',
'It shall be as you say...',
'Let me get that for you.'
],
randLoadingText: function() {
var loadingText = this.loadingText;
return (loadingText[Math.floor(Math.random()*loadingText.length)]);
}.property().volatile()
});

Related

Apollo client useMutation in expo renders twice for every call

I have a basic expo app with React Navigation.
In the top function Navigation I am initiating a useMutation call to an Apollo server like so:
import { callToServer, useMutation } from '../graphQL';
function Navigation() {
console.log("RENDERED");
const [call] = useMutation(callToServer);
call({ variables: { uid: 'xyz', phoneNumber: '123' } });
...
And my GraphQL settings is as follows:
import {
ApolloClient,
createHttpLink,
InMemoryCache,
useMutation,
} from '#apollo/client';
import { onError } from '#apollo/client/link/error';
import { callToServer } from './authAPI';
const cache = new InMemoryCache();
const httpLink = createHttpLink({
uri: `XXXXXXX/my-app/us-central1/graphql`,
});
const errorLink = onError(({ graphQLErrors, networkError }) => {
...
});
const client = new ApolloClient({
cache,
link: errorLink.concat(httpLink),
});
export {
useMutation,
callToServer,
};
export default client;
I want to clarify that I removed the httpLink from the client setting and I still get the two renders per call. I can see in the console that console.log("RENDERED") prints three times. Once when the app loads (normal) and twice after the useMutation call (not normal?)
What's going on here? Why is react re-renders twice per useMutation call? How do I avoid it?
UPDATE
I did further digging and it seems that useMutation does indeed cause the App to render twice - once when the request is sent, and once when it receives a response. I'm not sure I'm loving this default behavior which seems to have no way to disable. Why not let us decide if we want to re-render the App?
If someone has more insight to offer, Id love to hear about it.
Probably it's too late and maybe you've already found the solution, but still...
As I see you do not need data returned from mutation in the code above. In this case you can use useMutation option "ignoreResults" and set it to "true". So mutation will not update "data" property and will not cause any render.

ember: waiting until instance-initializer completes before doing anything else

I have an instance-initializer (below) for a shopping cart. The cart is an ember-data model because I am persisting to the back end. I am wanting all instances of my ember app to have a cart ID assigned before doing anything else.
I know the ideal is async, but item prices on the site are dependent on the cart. I am finding it would be easier to handle both the front end and the back end if I can depend on the cart always existing.
How can I tell ember to not do anything else until the cart instance-initializer completes and the ember-data cart model resolves?
My instance initializer:
export function initialize(appInstance) {
let CartService = appInstance.factoryFor('service:cart');
let cart = CartService.create();
// check if there's a cart id present in localStorage
if (window.localStorage) {
if (window.localStorage.getItem('cart')) {
//previous cart id found
cart.loadCart(window.localStorage.getItem('cart'))
} else {
// no LS cart found, create a new one
cart.createCart();
}
}
appInstance.register('cart:main', cart, { instantiate: false });
appInstance.inject('route', 'cart', 'cart:main');
appInstance.inject('controller', 'cart', 'cart:main');
appInstance.inject('component', 'cart', 'cart:main');
}
export default {
name: 'cart',
initialize
};
It handles two scenarios: a cart exists in localStorage, or no cart exists, create a new one. Those two methods are:
// cart service methods
loadCart(jsonCart) {
// called from instance-initializer when a previous cart is found in localStorage
let incomingCart = JSON.parse(jsonCart); // the raw cart in LS
this.get('store').findRecord('cart', incomingCart.cartid).then(cart => {
cart.setProperties(incomingCart);
cart.save();
set(this, 'cartObj', cart);
});
},
createCart() {
// called from instance-initializer when no prev cart is found
let cart = this.get('store').createRecord('cart');
cart.save().then(response => {
window.localStorage.setItem('cart', JSON.stringify(response));
});
set(this, 'cartObj', cart);
}
I've the same issue at these days. I've found these alternatives:
Using application initializers (not application instance initializer): Application initializers have deferreadiness method
to defer the readiness of the application. I think this is not applicable for my situation, because my services are not initialized yet.
Using sync remote call: Browsers warn about this. It is bad for user experience.
Waiting async operation in application route: The application route's model hook will return a promise that will be satisfied when all async opearions are finished. Shown in this sample twiddle.
I think I'll prefer the third one. But I'm open for new ideas.

Reload model/update template on createRecord save

I see this question is being ask all over again still don't find solution that works for such a trivial task.
This url displays a list of navigations tabs for workspaces.
http://localhost:4200/users/1/workspaces
Each of tab resolves to
http://localhost:4200/users/1/workspaces/:wid
Also on the I have a button that suppose to create a new workspace as well as new tab.
Here how controller for looks:
export default Ember.Controller.extend({
actions: {
newWorkspace: function () {
this.get('currentModel').reload();
var self = this;
var onFail = function() {
// deal with the failure here
};
var onSuccess = function(workspace) {
self.transitionToRoute('dashboard.workspaces.workspace', workspace.id);
};
this.store.createRecord('workspace', {
title: 'Rails is Omakase'
}).save().then(onSuccess, onFail);
}
}
});
When I click on button I see in ember inspector new record indeed created as well as url redirected to id that represents newly created workspace.
My question is how to force model/template to reload. I have already killed 5h trying model.reload() etc. Everything seem not supported no longer. Please please help.
UPDATE
When adding onSuccess
model.pushObject(post);
throws Uncaught TypeError: internalModel.getRecord is not a function
I believe you should call this.store.find('workspace', workspace.id) for Ember Data 1.12.x or earlier. For 1.13 and 2.0 there are more complicated hooks that determine whether or not the browser should query the server again or use a cached value; in that case, call this.store.findRecord('workspace', workspace.id, { reload: true }).
I do not know if this help. I had a similar problem. My action was performed in the route. Refresh function took care of everything.

How should errors be handled when using the Ember.js Data RESTAdapter?

ember-data.js: https://github.com/emberjs/data/tree/0396411e39df96c8506de3182c81414c1d0eb981
In short, when there is an error, I want to display error messages in the view, and then the user can 1) cancel, which will rollback the transaction 2) correct the input errors and successfully commit the transaction, passing the validations on the server.
Below is a code snippet from the source. It doesn't include an error callback.
updateRecord: function(store, type, record) {
var id = get(record, 'id');
var root = this.rootForType(type);
var data = {};
data[root] = this.toJSON(record);
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
this.didUpdateRecord(store, type, record, json);
}
});
},
Overall, what is the flow of receiving an error from the server and updating the view? It seems that an error callback should put the model in an isError state, and then the view can display the appropriate messages. Also, the transaction should stay dirty. That way, the transaction can use rollback.
It seems that using store.recordWasInvalid is going in the right direction, though.
This weekend I was trying to figure the same thing out. Going off what Luke said, I took a closer look at the ember-data source for the latest commit (Dec 11).
TLDR; to handle ember-data update/create errors, simply define becameError() and becameInvalid(errors) on your DS.Model instance. The cascade triggered by the RESTadapter's AJAX error callback will eventually call these functions you define.
Example:
App.Post = DS.Model.extend
title: DS.attr "string"
body: DS.attr "string"
becameError: ->
# handle error case here
alert 'there was an error!'
becameInvalid: (errors) ->
# record was invalid
alert "Record was invalid because: #{errors}"
Here's the full walk through the source:
In the REST adapter, the AJAX callback error function is given here:
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
Ember.run(this, function(){
this.didUpdateRecord(store, type, record, json);
});
},
error: function(xhr) {
this.didError(store, type, record, xhr);
}
});
didError is defined here and it in turn calls the store's recordWasInvalid or recordWasError depending on the response:
didError: function(store, type, record, xhr) {
if (xhr.status === 422) {
var data = JSON.parse(xhr.responseText);
store.recordWasInvalid(record, data['errors']);
} else {
store.recordWasError(record);
}
},
In turn, store.recordWasInvalid and store.recordWasError (defined here) call the record (a DS.Model)'s handlers. In the invalid case, it passes along error messages from the adapter as an argument.
recordWasInvalid: function(record, errors) {
record.adapterDidInvalidate(errors);
},
recordWasError: function(record) {
record.adapterDidError();
},
DS.Model.adapterDidInvalidate and adapterDidError (defined here) simply send('becameInvalid', errors) or send('becameError') which finally leads us to the handlers here:
didLoad: Ember.K,
didUpdate: Ember.K,
didCreate: Ember.K,
didDelete: Ember.K,
becameInvalid: Ember.K,
becameError: Ember.K,
(Ember.K is just a dummy function for returning this. See here)
So, the conclusion is, you simply need to define functions for becameInvalid and becameError on your model to handle these cases.
Hope this helps someone else; the docs certainly don't reflect this right now.
DS.RESTAdapter just got a bit more error handling in this commit but we are still not yet at a point where we have a great recommendation for error handling.
If you are ambitious/crazy enough to put apps in production today with ember-data (as I have been!), it is best to make sure that the likelihood of failures in your API is extremely low. i.e. validate your data client-side.
Hopefully, we can update this question with a much better answer in the coming months.
I just ran into such a situation, not sure if this is already explained anywhere.
I am using:
Em.VERSION : 1.0.0
DS.VERSION : "1.0.0-beta.6"
Ember Validations (dockyard) : Version: 1.0.0.beta.1
Ember I18n
The model was initially mixedin with Validation mixin.
App.Order = DS.Model.extend(Ember.Validations.Mixin, {
.....
someAttribute : DS.attr('string'),
/* Client side input validation with ember-validations */
validations : {
someAttribute : {
presence : {
message : Ember.I18n.t('translations.someAttributeInputError')
}
}
}
});
In the template, corresponding handlebars is added. (note that ember validations will automatically add errors to model.errors.<attribute> in case of input validations, I will be using same trade-off in server validations as well)
<p>{{t 'translations.myString'}}<br>
{{view Ember.TextField valueBinding="attributeName"}}
{{#if model.errors.attributeName.length}}<small class="error">{{model.errors.attributeName}}</small>{{/if}}
</p
Now, we will be saving the Order
App.get('order').save().then(function () {
//move to next state?
}, function(xhr){
var errors = xhr.responseJSON.errors;
for(var error in errors){ //this loop is for I18n
errors[error] = Ember.I18n.t(errors[error]);
}
controller.get('model').set('errors', errors); //this will overwrite current errors if any
});
Now if there is some validation error thrown from server, the returned packet being used is
{"errors":{"attributeName1":"translations.attributeNameEror",
"another":"translations.anotherError"}}
status : 422
It is important to use status 422
So this way, your attribute(s) can be validated client side and again on server side.
Disclaimer : I am not sure if this is the best way!
Since there's currently no good solution in stock Ember-Data, I made my own solution by adding an apiErrors property to DS.Model and then in my RestAdapter subclass (I already needed my own) I added error callbacks to the Ajax calls for createRecord and updateRecord that save the errors and put the model in the "invalid" state, which is supposed to mean client-side or server-side validations failed.
Here's the code snippets:
This can go in application.js or some other top-level file:
DS.Model.reopen({
// Added for better error handling on create/update
apiErrors: null
});
This goes in the error callbacks for createRecord and updateRecord in a RestAdapter subclass:
error: function(xhr, textStatus, err) {
console.log(xhr.responseText);
errors = null;
try {
errors = JSON.parse(xhr.responseText).errors;
} catch(e){} //ignore parse error
if(errors) {
record.set('apiErrors',errors);
}
record.send('becameInvalid');
}

How do I observe *all* property changes on a model object?

I have a model built from a JSON object.
// extend the json model to get all props
App.Model = Ember.Object.extend(window.jsonModel);
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
EDIT: // adding the solution I currently go
For now I do:
// XXX Can't be right
for (var prop in window.jsonModel) {
if (window.jsonModel.hasOwnProperty(prop)) {
App.model.addObserver(prop, scheduleSave);
}
}
This is a large form, which means I'm adding tons of observers – it seems so inefficient.
A firebug breakpoint at Ember.sendEvent() reveals that there are events called App.model.lastName:change being sent. I could hack in an intercept there, but was hoping for an official way.
You can bind to isDirty property of subclass of DS.Model. The isDirty changes from false to true when one of model properties changes. It will not serve well for all cases because it changes only once until reset or committed, but for your case -
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
it may work fine.
From the article:
autosave: function(){
this.save();
}.observes('attributes'),
save: function(){
var self = this,
url = this.get('isNew') ? '/todos.json' : '/todos/'+this.get('id')+'.json',
method = this.get('isNew') ? 'POST' : 'PUT';
$.ajax(url, {
type: 'POST',
// _method is used by Rails to spoof HTTP methods not supported by all browsers
data: { todo: this.get('attributes'), _method: method },
// Sometimes Rails returns an empty string that blows up as JSON
dataType: 'text',
success: function(data, response) {
data = $.trim(data);
if (data) { data = JSON.parse(data); }
if (self.get('isNew')) { self.set('id', data['todo']['id']); }
}
});
},
isNew: function(){
return !this.get('id');
}.property('id').cacheable(),
I had the same requirement, and not finding a suitable answer, I implemented one.
Try this: https://gist.github.com/4279559
Essentially, the object you want to observe all the properties of MUST be a mixed of Ember.Stalkable. You can observe the properties of that object as 'item.#properties' (or, if you bake observers directly on the Stalkable, '#properties' alone works. "#ownProperties", "#initProperties" and "#prototypeProperties" also work, and refer to (properties that are unique to an instance and not defined on any prototype), (properties that are defined as part of the create() invocation), and (properties that are defined as part of the class definition).
In your observers, if you want to know what properties changed and invoked the handler, the property "modifiedProperties", an array, will be available with the names of the changed properties.
I created a virtual property _anyProperty that can be used as a dependent key:
import Ember from 'ember';
Ember.Object.reopen({
// Virtual property for dependencies on any property changing
_anyPropertyName: '_anyProperty',
_anyProperty: null,
propertyWillChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
},
propertyDidChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
}
});