Value binding to existing HTML elements - ember.js

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.

Related

Route that observes property on component that links to it

I have a list if items in an items route that uses a component event-item to display each of them. This component has two computed's on it that are setting some classes right now to show the user some info about each item...
classNameBindings: ['winning','closed'],
item: null,
winning: Ember.computed('item.item_high_bid_user_id','userService.user_id',function(){
return this.get('item.item_high_bid_user_id') == this.get('userService.user_id');
}),
closed: Ember.computed('item.item_status',function(){
return this.get('item.item_status') === 2;
})
In the component template each item in the list is wrapped in a link-to that links to the item route, which displays a single item.
In the item template and even route I would like to observe the winning and closed computed's that are on the corresponding component to show or hide some things in the item template (IE. hid the bidding section if an item is closed, etc.)
What would be the proper way to do this?
BTW I'm on Ember 2.2.0 Ember Data 2.2.0 and Ember-cli 1.13.13
If your event-item component is linking to an item route, I assume you're passing the item model into the link-to helper, which means all the attributes needed to compute these properties are still going to be available in the item controller.
// templates/whichever-template-holds-items.hbs
{{#each items as |item|}}
{{event-item model=item}}
{{/each}}
// templates/components/event-item.hbs
<div>
{{link-to 'item' model}} // pass model to item route
</div>
// controllers/item.js
import Ember from 'ember';
export default Ember.Controller.extend({
// include userService
winning: Ember.computed.equal('model.item_high_bid_user_id','userService.user_id'),
closed: Ember.computed.equal('model.item_status', 2)
});
// templates/item.hbs
{{#if winning}}
// show winning stuff
{{/if}}
{{#if closed}}
// show closed stuff
{{/if}}
Also, I noticed you had a mix of both == and === for your conditionals in the code you posted. Most of the time you will want to use ===, see this post.
Almost forgot - Ember.computed.equal
UPDATE (in response to your comment below)
There are a couple ways to alert a controller that a value in a component has changed, but neither are really conducive in your current situation.
The first way (which is ok to do) would be to follow DDAU (data down, actions up) and send an action from your component up to your controller, but this only works if the component is inside the controller's view, which is not the case for what you're doing.
The second way (which is not really ideal IMO) would be to use a service in sort of a pub/sub fashion which would allow distant component/controllers to talk to each other (you can read more about this method here). You'll probably get mixed responses as far as doing things this way since it can be kind of disruptive to the data flow of your app. But sometimes you're choices are limited.
With all this said, I would probably stick with re-computing in the controller rather than trying to send data across your app from one controller to another. In the end it will still be less code and less work for the framework. Hope this was helpful.

Ember - Rolling back model after edit cancel

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.

How can I save something in one page and link to a new one and display data?

Not using a data store... besides FixtureAdapter.
On one page I have an input:
{{input type="text" id="form-name" value=personName placeholder="What's your name?"}}
I then go to the next page by clicking this:
{{#link-to 'lets-design' class='btn btn-primary'}}Let's design!{{/link-to}}
How would I save that value of personName so on the next page I can display it?
What matters in Ember is that there is only one copy of personName. The one displayed on the first is the same as the second. You must think in terms of your Route, which is the connection to what data is available on a given page. Read this for how best to use routes to control access to data.
App= Ember.Application.create();
App.set('name', 'Steve');
then in any page:
{{App.name}}
In your case, your binding to personName could be to App.personName, but that is not a very good general-purpose way of doing it. Use this sparingly for special stuff that doesn't get stored on the server. Most data is bound to your templates via routes.

Specify a OneWay Binding in HandleBars - Ember

Is there a way to Specify a OneWay Binding in HandleBars? bind-attr always calls Ember.bind, which always create a two way binding. This seems to be the case for elements that don't even change:
<img {{bind-attr class=":class-name-to-always-apply"}}>
But even in cases where the element could change, we might have reasons to update it manually (e.g. performance or we don't want to change it on textChanged, but do it manually)
There is the {{unbound}} helper that does not put metamorph scripts in the DOM and does not update when the underlying value changes.
http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_unbound
You could also have your template bind to a property in your controller that looks like:
something: Ember.computed.oneWay('somethingElse')
using the template
{{input value=something}}
something will carry the same value as somethingElse until your change the value in the text box. At that point, something will carry the same value as what is in the text box and the value of somethingElse is forgotten.

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.