-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UX - Are you sure you want to discard your drafts? #64
Comments
The way I have thought about doing something similar was to set your 'state bucket' to use buffered-proxies. This gives you the isolation properties of state-services with the staging and rollback properties of buffered-proxy. From a highlevel it would look like this: // app/states/comment-draft
import BufferedProxy from 'ember-buffered-proxy/proxy';
export default BufferedProxy.extend(); // app/controllers/foo
export default Ember.Controller.extend({
commentDraft: stateFor('comment-draft', 'post'),
// ...
}); // app/routes/foo
export default Ember.Route.extend({
actions: {
willTransition(transition) {
// hasChanges comes from bufferedProxy
if (this.controller.get('commentDraft.hasChanges') {
alert('Oops! You still have changes');
transition.abort();
}
}
}
}); You mention const commentDraft = this.controller.get('commentDraft');
commentDraft.hasChanged('body'); => true/false As for the open questions:
I'm not sure if this needs to be in the addon. I think its probably safe for end users to just roll this themselves or we can create a separate addon that overrides the willTransition with its own logic.
I will defer to @stefanpenner as I try to not deal with ember-data as much as possible :-) |
@thoov I like the pattern you've used here, and I think that this solves another (important) use case involving transitioning away from the route and resetting state back to "clean". What I'm referring to here here is the ability to warn users that they may have something "dirty" or otherwise in "draft" form at the time they essentially try to leave the app via doing something like closing the tab. The key difference being that the user may be far away from the controller/route that deals with the state in question, so it seems like it may be an application-level issue? |
This is the super hacky "don't try this at home" thing I'm using for a demo. Works for any state objects, but ultimately uses Because no reference to state objects is created that might interfere w/ the "weakness" of the weakmap (the function for the Time complexity is
I would expect (a) (c) and (d) to be very small for most use cases app/routes/application.jsimport Ember from 'ember';
const { Route, inject } = Ember;
export default Route.extend({
'draft-watcher': inject.service(),
// Modern browsers won't display this for security/scamming reasons,
// but IE8-10 will show it
confirmMessage: `You have unsaved drafts which will be lost if you leave.
Are you sure?`,
activate() {
this._super(...arguments);
window.onbeforeunload = this._onBeforeUnload.bind(this);
},
_onBeforeUnload() {
if (this.get('draft-watcher.hasImportantDrafts')) {
// Seems that the important thing to do here is to return
// a value only if we wish to pop up the "Are you really sure?"
return this.get('confirmMessage');
}
}
}); app/services/draft-watcher.jsimport Ember from 'ember';
import stateFor from 'ember-state-services/state-for';
const {
Service,
inject,
computed
} = Ember;
export default Service.extend({
store: inject.service(),
// ember-data record types to check
recordTypesToCheck: {
// for each record type to check, state types to check
post: {
// for each state type, properties to check (for emptiness)
'post-info': ['body']
}
},
/**
* Check to see if a given state's property is to be considered "dirty"
* For contextual usefulness, also give access to the name of the record
* type, the state name, and the property key on the state object
*
* @private
*/
_isStatePropertyDirty(recordType, stateType, propKey, propValue) {
return !!propValue;
},
/**
* Hacky way of using public APIs only to get access to the WeakMap
* for a given state name. Can't create CPs on Ember.Object.create anymore
*
* @private
*/
_weakMapForState(stateName) {
let cachedSource = this.get(`_weakMapsForStates.${stateName}`);
if (cachedSource) {
return cachedSource;
} else {
// jscs:disable disallowDirectPropertyAccess
let newSource = Ember.Object.extend({
// jscs:enable disallowDirectPropertyAccess
recordStateMap: stateFor(stateName)
}).create().get('recordStateMap');
this.set(`_weakMapsForStates.${stateName}`, newSource);
return newSource;
}
},
_weakMapsForStates: {},
_statesForRecords(weakmap, records) {
return records.map((r) => weakmap.get(r));
},
/**
* A boolean (volatile) CP that tells us if any of our "important"
* properties on certain states, for certain ember-data records are
* non-empty
*
* @public
*/
hasImportantDrafts: computed(function() {
// Get all of the record types to check ( ['post'] )
let recordTypesToCheck = this.get('recordTypesToCheck');
// Iterate over them
for (let typ in recordTypesToCheck) { // typ = 'post'
// Get all of the loaded records of this type
let records = this.get('store').peekAll(typ);
// Get names of all of the states we're interested in checking
for (let stateName in recordTypesToCheck[typ]) {
// Get the weakmap for this state
let recordStateMap = this._weakMapForState(stateName);
// Transform the array of records to the corresponding array
// of states.
let stateObjects = this._statesForRecords(recordStateMap, records);
// Iterate over all state objects for this record
for (let i = 0; i < stateObjects.length; i++) {
let stateObj = stateObjects[i];
// Iterate over all property keys we consider "important" on this state
let statePropNames = recordTypesToCheck[typ][stateName];
for (let j = 0; j < statePropNames.length; j++) {
let statePropKey = statePropNames[j];
// Finally, get the value of this state's property
let statePropValue = stateObj.get(statePropKey);
// Check to see if it's "dirty" or not
if (this._isStatePropertyDirty(typ, stateName, statePropKey, statePropValue)) {
// ...and early terminate this check as soon as we find anything
return true;
}
}
}
}
}
}).volatile()
}); |
small nitpick:
|
Yes, just a function. In my demo app there was usefulness in binding to the
|
A self-nitpick here - Just realized that @thoov confirm? |
This is a nice UX pattern that goes along with many of this addon's use cases.
The info we need here in order to provide a nice API surface for the simplest use case would be:
A practical example
post
is an ember-data model in our appcomment-draft
is the name of our statebody
is the "important" property key on thecomment-draft
stateLet's just say "important" means something we need to ensure ensure is not thrown away (via reload/navigation/closing browser tab) without user consent.
In the component for our post, we'll of course have some UI element bound to
commentDraft.body
and wire up the state usingstateFor
as instructed.What I as a developer would want to express via some API
Open questions
post
to evaluate when making this check), while still providing the flexibility to make this useful broadly for any valid "key" object?Not sure whether this would be best implemented as a new addon or as part of this addon, but this seems like the best place for the discussion
The text was updated successfully, but these errors were encountered: