Ember - Rolling back model after edit cancel - ember.js

I've been having some trouble with the following situation.
I have a resource, let's call it 'user', which has two nested sibling resources, 'actions' and 'dates', thereby having the two routes:
/user/actions
/user/dates
The user template is something along the lines of:
{{user.name}}
{{outlet}}
with the target of displaying the 'actions' and 'dates' lists associated with the user in the outlet.
This works fine, but I would also like to be able to click on the username and change it, namely edit it so the template would look something along the lines of:
{{#unless inedit}}
<span {{action='triggerUsernameEdit'}}>{{user.name}}</span>
{{else}}
{{input value=user.name}}
<button {{action='saveChanges'}}>Save</button>
<button {{action='cancelChanges'}}>Cancel</button>
{{/unless}}
{{outlet}}
I am NOT using Ember Data, and this works well up until I try to revert to the original model. I am looking into cloning the model in the UserRoute and then setting back the original 'username' field, but don't know if this would be the correct way to go.
I've seen other examples where the edit action would be handled on a different sub-route, let's say:
/user/usernameEdit
but I think that would make the 'actions' or 'dates' lists to disappear, which I don't want.
Any help would be greatly appreciated.

If you're attempting to do a rollback, create a copy.
Large (multiple fields) or small (single field) scale, it's a copy. Bind to the copied items and allow them to edit those, on commit, you merge the copy into the original. On cancel, destroy the copy.
Switching routes will still present the same problem, you will still either have to bind to the original model, or a copy.

Related

Concurrent states with Ember.router

I'd like to implement this statechart in Ember.js v1.5.x using Ember.Router, but I have problems with the concurrency and history mechanism.
Basically when I activate the Summary route/state I'd like to transition to No changes made and Transient state in the same time. How can I achieve this?
P.S. I know that e.g. stativus have these capabilities but don't know how to use it with Ember.js routing. An example would bee good.
(image source: Ian Horrocks: Constructing the User Interface With Statecharts p.153).
:)
Yeah statecharts are lovely and all, but Ember actually affords sub-states through computed properties.
I'm not overly familiar with state charts, and I'd really need to consume the resources (horrocks) you mentioned here (https://github.com/emberjs/ember.js/issues/4767#issuecomment-41458710) before I'd be fully conversant in the nomenclature of that particular example (which I can do if you'd like).
To that end, and having said that, please take my answer with a grain of salt, because I may not fully understand the context. I just hope to help.
So in Ember you have routes. Those routes explain the interface of your application. These will effectively be your states. Routes are not your actions, or events. They provide a URL for your app to present itself to the world.
So, state A seems to be presenting the Students. You have two sub-states in there... 0 students and >0 students. You would handle these with the same Route (call it StudentsRoute), because they're both about the same set of data, just different substates of it. The route would have a path called /students probably. At that point, you'd have a controller gets fed a model by the router (the list of students), so to that end, this controller would be an extension of Em.ArrayController.
This array controller (auto-named StudentsController, extends Em.ArrayController) automatically has a 'model', and that model, once resolved, is the students "array".
In StudentsController, you could easily have a computed property called zeroCount which represents the state of zero or not about the model. Computed properties automatically stay up to date. That'd be defined like this:
App.StudentsController = Em.ArrayController.extend({
zeroCount: function() {
// zeroCount is true if zero, otherwise false
return this.get('model.length') == 0;
}.property('model.length');
});
In your students template, you could conditionally render one of two sub-templates depending on this zeroCount state... you'd do that like this:
{{#if zeroCount}}
{{partial "noStudents"}}
{{else}}
{{partial "someStudents"}}
{{/if}}
Mind you, for this example, that'd be somewhat overkill... you probably don't need to render other templates (partials) like that.. there's an easier simpler way to do it because this is a common pattern in ember (rendering a list, and optionally rendering something else if there are no items in it, without needing the zeroCount property).
{{#each model}}
<p>This renders against each student... <br>
so if your students have a property called name, <br>
then you could just write {{name}} and it'd render the
students name</p>
{{else}}
<p>This renders when there are no students</p>
{{/each}}
You'd put a delete link on each of those items... and the live bound properties handle all the states for you... (thus, when model has zero items in it, the template goes into the else block of the each... otherwise it goes into the main block).
The delete action, handled by something like Delete inside your #each model template (handlebars) directive goes to the controller and looks for an action inside of it called, unsurprisingly, delete... and that'd look like this:
App.StudentsController = Em.ArrayController.extend({
actions: {
delete: function(itemToDelete) {
itemToDelete.delete();
// assuming the model "class" understands delete
}
}
});
The edit state would have its own route... possibly a nested route on the students, called edit, possibly not depending on if you wanted the list to appear on the screen while the edit page appears...
The "changes made" state is effectively handled not on the route, but on the model... as it should be... the model is responsible for persisting the object graph, or telling the view and controller whether or not the model has changed (Ember Data, for example, afford isDirty as a state on each model instance that can tell you whether it has changed or not)...
Hopefully this whets your appetite. I recommend going through some of the examples on the Ember site... they really do help, and following the Ember TODOMVC app if you haven't checked that out...
Ember thrives on these kind of flow-based state driven UIs... check out Tom and Yehuda's keynote at confreaks if you haven't already... they talk about flows in exactly the same way you're talking about these states and sub-states.
Hope that helps.

Advice on WYSIWYG architecture for an Ember app

I'm having a hard time coming up with a solution for this problem.
I'm building a WYSIWYG designer, for micro sites. The templates for these microsites will be supplied by an intermediate user, and the end-user will manipulate these templates. So, there are really two groups of users of the app: template-buiders, and end-users. Think MailChimp.
This means my Ember app will start off with a template from a template-builder, say
<h1>An awesome product</h1>
<h2 contenteditable="true">Subtitle away</h2>
<p>{{#if optionA}} One thing {{else}} Another thing {{/if}}<p>
and the end user, having chosen this template, will then be able to customize it. There are a few requirements:
There will be static, uneditable portions of the page (h1 above)
There will be static, editable portions of the page (h2 above)
There will be options that affect the layout, style, etc. (p above)
So far, my attempts have lead me to build a handlebars helper that takes a string and a context, and returns a rendered template. This is because the above template will actually be coming over from a database, as a string - remember, it's user-generated.
That means, in my application's template, it would look like
<div class="preview">
{{preview-userTemplate template context}}}
</div>
where
template: '<h1>An awesome product</h1><h2 contenteditable="true">Subtitle away</h2><p>{{#if optionA}} One thing {{else}} Another thing {{/if}}<p>',
context: {optionA: true}
Now, this actually works, in the sense that it will update if context is updated, so I can add controls along the sides for the end-user to manipulate those options. So I have those (more of less) under control.
It's the WYSIWYG content editing that is giving me trouble. In the above template (from my template-builder users), I want them to be able to add contenteditable="true" to places in their templates where the end-users can change the content.
Now, these changes need to be saved. Ideally, I would be able to retrieve the instance view's template back from the DOM, after the inline edits have been made. Is this possible? The original {{data}} placeholder would need to be preserved.
Alternatively, I could make a view/component, so the template-builder would do something like this:
<h1>An awesome product</h1>
{{#editable}}
<h2>Subtitle away</h2>
{{/editable}}
<p>{{#if optionA}} One thing {{else}} Another thing {{/if}}<p>
but this seems like it would require a good deal of finagling to get the edits to stick - I'm not even sure right now how I'd go about it.
What do you guys think? Is there an easier solution that I'm overlooking? Thanks for any pointers!

Ember.js: Passing a template's current context object to an action

I'm working on an Ember.js app similar to this:
http://jsfiddle.net/rzb2m/
Now this does pretty much what I want except for one crucial problem. The setFavorite action at the bottom has a hard-coded 0. This means that if you change Carol's favorite activity, it actually changes Alice's favorite activity. I haven't been able to figure out how to structure the application so that it has the correct behavior. How do I pass the correct object to be changed (this.content.people[i] in this case) to the setFavorite function?
Note: My real application is actually a bit more complicated than this because I have two separate lists of people on the page. So the solution shouldn't just involve passing an index into setFavorite because that won't scale to the more complex situation with multiple lists. This is also why the person template looks unused. In the real app, there is complexity that justifies it.
You are relying on implicit binding resolution from controllers to views. But this breaks down because when you separate things out into partials you have no means to lookup the parent object in the #each iteration.
While #each controller works, you are better off with #each person in controller. Then you can access the properties from the view in the partial view, and pass it forward as needed.
Change the main #each in index template to,
{{#each person in people}}
<li>{{view App.PersonView contentBinding=person}}</li>
{{/each}}
Then in person template,
{{view App.PersonDetailsView contentBinding=view.content}}
And in personDetails template.
{{#each fave in view.content.getActivities}}
All this gives you the ability to pass the person to the setFavorite method,
<a {{action setFavorite view.content fave}} href="#">
So the setFavorite switches to a simple assignment on that person.
setFavorite: function(person, fave) {
person.set('favoriteActivity', fave.name);
},
Here's the updated jsfiddle.

Value binding to existing HTML elements

As Emberjs suggests in order to bind elements values/attributes etc.. to controllers I have to create that element using Emberjs. For example:
App.SearchTextField = Em.TextField.extend({ });
and the view:
{{view App.SearchTextField placeholder="Twitter username"
valueBinding="App.tweetsController.username"}}
This means that pretty much the entire content of the page should be under Emberjs control. This isn't very convenient.
Is is possible to do any type of value or event binding to an existing element?
In order for bindings to work, Ember needs to know when a bound value changes so that it syncs it. For Ember to know that a value has changed, you need to use Ember.set. Example:
object.set('value', 'I changed');
// This says: Ember, my value has changed, please sync!
So, when a certain property changes without using Ember.set, Ember doesn't know that it should sync it. As a result, we need to listen to this event change, and tell Ember that it changed using Ember.set.
That's what happens on input values. When you type, the value changes, but Ember.set was not called. So what Ember.TextField does, is listen to keyup and change and other events and calls Ember.set when one of these events is fired.
Of course, you don't have to use them, but then you will have to manually listen to these changes and tell Ember that the value changed: this.set('value', this.$().val()))
I don't see why you have a problem with using Ember.TextField as it can do almost anything, and all it does it save you the trouble of listening to the events yourself.
One thing that might help you is that you can directly use it in your template, instead of creating a view to extend it:
{{view Em.TextField placeholder="Twitter username" valueBinding="username" type="search"}}
which is almost the same amount of typing as:
<input type="search" placeholder="Twitter username" value="some value" />
Note: From the example you provided above, it looks like you are following an old tutorial, be careful, lots have changed since then.

Instantiate a controller class using the {{view}} helper?

While connectOutlet("basename") automatically creates an instance of BasenameController, I was wondering if there's a way to do the same using the {{view}}-helper.
I have tried several things I've seen in examples, but non of them seem to work:
{{view controllerBinding=App.BasenameController}}
{{view controllerBinding=App.basenameController}}
{{view controllerBinding="App.BasenameController"}}
{{view controllerBinding="App.basenameController"}}
I have also tried to do the same using controller instead of controllerBinding, unfortunately without success, and I was also unable to find out where exactly the difference is between the two of them.
Does anybody know how to achieve my goal?
You probably want to use an outlet. The connectOutlet/outlet functions are meant for rending other controller/view pairs.
Lets say we have a person view, but inside that view we want to have another controller/view pair. For this, we need to use a named outlet, our template would look like this.
Person View!
{{name}} = the person's name!
{{controller}} = PersonController!
{{outlet other}} = our outlet
Then inside the router when you want to attach another controller/view to that outlet you can simple use connectOutlet on the personController.
router.get('personController').connectOutlet('other', 'other');
That will wire OtherController and OtherView together and display them in your template. The first param is the outlet name, the 2nd is the controller/view.
This allows you to easily swap different controllers and views onto that outlet. For example, using a different connectOutlet api, we could
router.get('personController').connectOutlet({
outletName: 'other',
controller: router.get('carsController'),
viewClass: App.CarsView
});
...
Btw, to answer you original question. You can get access to other controllers from your view by doing this: {{view controllerBinding="controller.target.otherController"}}. Every controller will have a target property that points back to the router. However, I do not recommend using this code. It's brittle, hard to test, hard to debug, and will come back and bite you in the future.