Specify a OneWay Binding in HandleBars - Ember - ember.js

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.

Related

Ember.js: sending actions to components?

I have a mixin for Ember components, named in-view, the job of which is to request that that the element be brought in view. It is provided an attribute whose value is an piece of content to be brought into view, and if that attribute matches the component's content then I call scrollIntoView or the equivalent. The code looks something like this:
// calling template
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
// mixins/in-view.js
scrollIntoView() {
if (this.get('content') === this.get('inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
// components/my-item/component.js
import InView from 'mixins/in-view';
export default Ember.Component.extend(InView,
This works fine. The question I have arises when I want to change the item in view. I can have the in-view mixin observe the inviewItem attribute:
}.on('didInsertElement').observes('inViewItem')
and this also works, but seems like a bit of a code smell.
In addition, my actual code structure is that there is a controller which knows which item is supposed to be in view, and then its template calls a my-item-list component which displays the scrollable div containing the item list, and that in turn calls the my-item component. This means I have to pass the inViewItem attribute from the controller down through two levels, as in
// resource/controller.js
inViewItem: something
// resource/template.js
{{my-item-list items=item inViewItem=inViewItem}}
// components/my-item-list/template.js
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
I could avoid this by having the my-item template hard-wired to access the inViewItem attribute on the controller:
scrollIntoView() {
if (this.get('content') === this.get('controller.inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
but that's another code smell; I don't want to build this kind of dependency on a specific controller field into the mixin. Instead I could possibly pass the component the name of the controller attribute to watch, but this seems unduly clumsy, and it's tricky to observe an attribute whose name is variable. More importantly, I don't think this will work when controllers go away in 2.0.
What I want essentially is a way to "ping" or somehow send a message to a template. I know that in principle this violates the DDAU principle, but in this particular case what I need is exactly to somehow send an "action down"--an action telling the component to adjust itself to bring itself into view.
Of course, I could give up on the entire idea of the in-view mixin and simply have the controller dig down into the generated HTML to find the item to bring into view and issue the scrollIntoView on it directly. However, this seems to violate some principle of separation of concerns; the my-item template would no longer be in complete control of itself.
What is the recommended design pattern for this kind of case?
The solution here is to go the opposite direction that you have. Your component here is a localized scope, and the pain you are feeling is that your localized scope needs to access and mutate global state (the app's scroll position).
Some people use a scroll-service for keeping track of and mutating state, I've used several variations on that myself.
It sounds though like you're dealing with a scrollable list, perhaps a div, and that what item is in view isn't merely a function of page state, but programmatically may change. For instance, a new item has been inserted and you want to scroll the new item into view.
A plugin like jquery.scrollTo or similar (collectively "scroller") would be better for that than simply jumping to the new position as it preserves the user's contextual awareness to where they are on page.
With a scrollable div or list or similar, you might choose to have your top level component control scroll state. The scroll state is still localized in this case, but instead of being localized to each item it's been localized to the scrollable region as a whole, which is where it better belongs.
There are a number of patterns for list items to register themselves with a parent list-component. In a robust scenario, I might do so, but a quick and not very dirty approach is to do something wherein on didInsertElement the new child emits an action to the parent containing it's context, which the parent then uses to check if it's the active item and if so triggers the scrollTo.

Wrapper Component for Form Controls

I'm looking for a pattern to make a wrapper component for form controls.
Its role: wrap any kind of component, (handle error states, messages)
Syntax:
{{form-control-group type="my-input" label="Some fancy label:"}}
templates/components/form-control-group.hbs
{{#if hasError }}
{{error}}
{{/if}}
{{label}}
{{component type}}
It works nice, as long as I don't need any attribute for my-input component. Usually it is not the case. Let's say I need pass the value. I could do
{{component type value=value}}
But as not that flexible as I wanted to have it. I will gonna use it with different form controls, ex.: select, checkbox etc. They all need different attributes.
I also could prepare for this different attributes:
{{component type
value=value
content=content
contentPath=contentPath
disabled=disabled
checked=checked
...
}}
In this way it can be used only with the component it is prepared for. If it is published as part of a form addon, and its consumer want to use with a datapicker component, it won't pass down the attributes.
Question:
Is there a way to stream down ALL the attributes passed to form-control-group component?
Maybe the whole concept is wrong, and there is a better pattern to handle this. Any critics and / or ideas are most welcome.
Edit
To be clear: in this question: "setProperties" does not pass "attrs" as expected I tried to solve this very problem, passing down the attrs hash, which contains all the attributes. Maybe Ember is not ready for this, but I'm sure there is a workaround.

Ember select element - unable to capture on change event

Ember question - still getting used to Ember, but making progress. Here's my issue: I have a template which references a component; the component contains a select element. The select element displays properly, and I want to update the contents of another select element based on the selection in the first element. However, I have not been able to capture the on change event of the first select element. Here is the component code containing the select:
{{view
"select"
content=types
value=selectedtType
selection=selectedtType
prompt="Select Type..."
}}
So I'm not sure how to reference the on change event in the component template, or where the function itself should go - the component's component.js file, or in the route.js file of the parent template. I've done much research on this, but haven't been able to make it work yet. Any help would be appreciated. Thanks.
Check this JSBin. In this example, I had my dependent select use a computed property as its content. This computed property's single dependent key is the value of the first select. If you are doing this with components, your component needs to take the computed property as the attribute that is the select views' content. Any time the first select changes, it will cause the computed property to recompute and thus update the content of the second select. Even in an example using components, this code would probably sit on the controller to keep your component generic enough that it simply takes a content for its select and displays it rather than controlling the display logic itself.
Other options, have a function that observes the first select value and updates a controller variable that is the second select's content. Now if you're example is more complicated in that the changing select's have different option.valuePath and option.labelPath, you can pass pass those values into your component as well.

Handlebars debug missing {{property}} of model

I would like to see in the console some kind of output if a template doesn't get all {{properties}} (for debugging)
e.g. If a template
<script type="text/x-handlebars" id="demo">
<h1>{{name}}</h1>
<p>{{desc}}</p>
</script>
is called with a model
{
name:"Max",
}
there should be an output like console.warn
Template "demo" could not find property "desc"
Is there any helper, callback, ... that is called every time a Handlebars expression like {{property}} is called?
You can do whatever you like with javascript. But do it carefully. :)
There are many way to achieve your goal depending on what you really need and how many time you can spend on hacking ember/handlebars/ember-data packages.
First and easiest variant is to override render method of SimpleHandlebarsView/_HandlebarsBoundView, which displays simple binded values (there are few other views: for #each, with, etc). This is simple sequence: override method, call an old method, check the result of getting value for rendering, console.warn if result === undefined. You can't get template name inside that method, but you can get parent view for rendered value. So, I suggest to turn on LOG_VIEW_LOOKUPS to easily match views with templates.
Example of console logs:
Rendering application with default view <(subclass of Ember.View):ember226> Object {fullName: "view:application"}
Rendering index with default view <(subclass of Ember.View):ember251> Object {fullName: "view:index"}
Rendering <(subclass of Ember.View):ember251> , property is undefined: model.undefined
JSBin with example.
Another way is to override handlebarsGet method from ember-handlebars-ext package: https://github.com/emberjs/ember.js/blob/v1.7.0/packages/ember-handlebars/lib/ext.js#L75.
Third, if you need to catch accessing to undefined properties of Ember.Objects, you can override unknownProperty method for those objects.
If you need to check a rendered data quickly, you can use {{log model}} method in Handlebar's template.
You can get deeper into patching and rewriting core of Ember, of course. But I advise not to rely much on inner structure of libraries. Good luck!
No, they use Ember.get(context, propertyName) There is no metadata returned saying the property is defined vs undefined.

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.