Open one component instance at a time in Ember Octane - ember.js

Let's say we have component answer that has isOpen property. By default isOpen is false and when the user is clicking on the answer we are changing the isOpen to true.
We are showing the list of answers. How can we close the first answer when the user clicks on second answer?
I was thinking about creating a service answersSync with globalIsOpen = false.

I would suggest keeping track of which answer is open in your parent component, the one that lists the answers. Then your parent template would look something like:
{{#each this.answers as |answer|}}
<Answer
#answer={{answer}}
isOpen={{eq this.currentlyOpenAnswer answer}}
{{on "click" (fn this.openAnswer answer)}}
/>
{{/each}}
(then have code in the parent component's js that keeps track of currentlyOpenAnswer, responding to the openAnswer action)

Related

In an ember component template how can I use 'if' to check for at least one of an array having a property equal to X?

My component is passed a notification.
A notification has an account, and an account has many contacts.
So I could easily do something like
{{#if (is-greater-than notification.account.contacts.length 0)}}
<p>stuff</p>
{{/if}}
To check for the existence of a contact, but what if I wanted to only display if at least one contact has firstName 'John', how could I do this in the component template only? Or if not possible in the template what is the next best approach?
I tried having a isHidden property in my component and then in the didInsertElement hook looping through contacts and then communications with the aim of checking myself - however I find contact.get('communications').forEach doesn't get hit because that level of data hasn't been pulled from the server at that point.
For the first question, you could write a helper to do that:
// helpers/contains-john.js
export default function(array) {
return array.any((item) => item.get('firstName') === 'John');
};
Then use it as a subexpression just like you have above:
{{#if (contains-john notification.account.contacts)}}
Stuff
{{/if}}

EmberJS calling controller function within #each

I'm trying to call a controller function inside a #each template to check if the model is selected. This needs to receive the model for each loop iteration.
jsbin is here http://emberjs.jsbin.com/bemos/4/edit
I've tried using the following template, but adding a parameter to {{controller.selected}} results in ember throwing an exception, or not doing anything (the result seems to be different in jsbin).
{{#each model}}
<li><a hres="#" {{action "clickItem" this}}>{{label}} -- selected is {{controller.selected name}} </a></li>
{{/each}}
My function in the controller looks as follows
selected: function(id) {
var list = this.get("selectedList");
console.log("Checking sel for", id);
if(list.contains(id))
return true;
return false;
}.property("selectedList")
In the jsbin, I expect to be able to click on the "Hello 1"/"Hello 2" elements, and this should change the part of the text that says "selected is false" to true. It should also update the number of items selected in the bottom. Currently, this shows 1 item is selected (since the default is selectedList:["2"]) however "Hello 2" says "selected is false" - this should say "true".
(I should add I'm pretty new to EmberJS as have been working with ExtJS for a few years!).
Thanks
Do you really have a problem in code ? I guess no, since your bin is working fine and even clickItem is working as well. In your local environment where you are facing this issue, I think you are not using same version of handlebars and ember. Please check if you are using below,
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.6.1/ember.js"></script>
Update 1 -
Here is the bin I tried to update - http://emberjs.jsbin.com/veqoqocu/1/edit
Its not fully functioning but fixes some issue.
There are multiple problems,
You should use ArrayController for lists, redesign your example otherwise selected property will never work the way you want. Here is a good example - http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/
push & pop needs to be pushObject and popObject
computer property on array works like - }.property('selectedList.[]')

View click action not returning correct object

I'm having trouble with a click handler in a view. It's not returning the expected member of the collection, but the collection as a whole.
I've created a jsfiddle to demonstrate the issue. I have an ArrayController, whose content I pre-populate. The view for this controller then uses the #each helper for the controller with another view:
{{#each controller}}
{{view App.ActivityListItemView}}
{{/each}}
This works, in that I see the name of the item on the page, and can click it.
The problem is in the click handler - if I #get('content'), the content for the parent controller is returned. How do I get the item that was clicked on? If you have a look at the console output in the jsfiddle you'll see the issue. I assume this is a context issue?
I've tried adding contentBinding="this" to the view:
{{#each controller}}
{{view App.ActivityListItemView contentBinding="this"}}
{{/each}}
but that makes no difference.
thanks,
Martin
How do I get the item that was clicked on? If you have a look at the console output in the jsfiddle you'll see the issue. I assume this is a context issue?
Exactly. You want the view's context instead of it's controller's content. So:
click: (data)->
console.log 'clicked on an activity'
selected = #get('context')
#get('controller').set('selectedActivity', selected)
console.log(#get('controller').get('selectedActivity.name'))
Why?
By default the {{#each}} helper does not create a new controller instance for items in the array. So when you#get('controller')` from the view helper it searches up the view heirarchy until a controller is found - in this case that is the array controller.
If you want to have a separate controller for each item you could provide an itemController attribute to the each helper - see http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_each
Right, I've got this working, I think the issue sprang for a lack of understanding of the contentBinding argument. Basically I've changed to using a specific name of 'activityBinding' within the #each block, and then referring explicitly to the activity in the click handler. See jsfiddle for a working demo.
{{#each controller}}
{{view App.ActivityListItemView activityBinding="this"}}
{{/each}}
and
click: ->
console.log 'clicked on an activity'
console.log #get('activity.name')
content = #get('activity')
#get('controller').set('selectedActivity', content)

binding context to action in ember textfield

I've got an ember application that needs to manage multiple chat windows. A window for each active chat is created within an {{#each}} loop. This is straightforward enough. The place that I'm having trouble is sending the chat message when the user presses enter.
The window looks like this
{{#each chats}}
... stuff to display already existing chats...
{{view Ember.TextField valueBinding="text" action="sendChat"}}
<button {{action sendChat this}}> Send </button>
{{/each}}
This works fine for the button, since I can pass this to it. By default the function defined in the textfield view action just gets the text within that textfield, which is not enough in this case. Since there can be multiple chat windows open, I need to know which window the message was typed into. Is it possible to pass this to the textfield action function? (or can you suggest a different way to solve this problem?)
Add contentBinding="this" to the definition of the view, like:
{{view Ember.TextField valueBinding="text" action=sendChat contentBinding="this"}}
EDIT
Ember master already has this change, but the official downloadable verstion still don't.. so you will need to subclass the Ember.TextField and change its insertNewline to achieve required functionality:
App.ActionTextField = Em.TextField.extend({
insertNewline: function(event) {
var controller = this.get('controller'),
action = this.get('action');
if (action) {
controller.send(action, this.get('value'), this);
if (!this.get('bubbles')) {
event.stopPropagation();
}
}
}
});
After that, the action handler will receive additional argument, the view:
{{view App.ActionTextField valueBinding="text" action=sendChat myfieldBinding="this"}}
and in controller:
sendChat: function (text, view) {
var myField = view.get('myfield');
//do stuff with my field
}
You may use ember master instead of subclassing Ember.TextField..
I hope the ember guys will release the next version soon..
I know this question has been answered but I said let me add some information that may help out someone in the situation of actions and TextField. One word "Component". TextField in Ember is a Component so if you think of TextField from that perspective it may help when it comes to sending actions and using TextField in an application.
So when you say App.SomeTextField = Ember.TexField.extend({...});App.SomeTextField is subclassing Ember.TextField (remember which is a component). You could add your logic inside and that works and you could access it from your template such as {{view App.SomeTextField}}
You may be thinking I see the word 'view' this guy sucks, TextField is a View. Well, it is sort of a View because Ember Components are a subclass of Ember.View so they have all that Views have. But there are some important things to keep in mind Components un-like Views do not absorb their surrounding context(information/data), they lock out everything and if you want to send something from the outside surrounding context you must explicitly do so.
So to pass things into App.SomeTextField in your template where you have it you would do something like {{view App.SomeTextField value=foo action="sendChat"}} where you are passing in two things value, and action in this case. You may be able to ride the fine line between View/Component for a bit but things come crashing why is your action not sending?
Now this is where things get a little trippy. Remember TextField is a Component which is subclassed from View but a View is not a Component. Since Components are their own encapsulated element when you are trying to do this.get('controller').send('someAction', someParam), "this" is referring to the Component its self, and the controller is once again the component its self in regards to this code. The action that you are hoping will go to the outside surrounding context and your application will not.
In order to fix this you have to follow the protocol for sending actions from a Component. It would be something like
App.SomeTextField = Ember.TextField.extend({
//this will fire when enter is pressed
insertNewline: function() {
//this is how you send actions from components
//we passed sendChat action in
//Your logic......then send...
this.sendAction('sendChat');
}
});
Now in the controller that is associated with where your SomeTextField component/view element is you would do
App.SomeController = Ember.Controller.extend({
//In actions hash capture action sent from SomeTextField component/view element
actions: {
sendChat: function() {
//Your logic well go here...
}
}
});
Now I said to think of TextField as a Component but I have been riding the tail of the view and declaring {{view AppSomeTextField...}}. Lets do it like a component.
So you would have in your template where you want to use it
//inside some template
`{{some-text-field}}`
Then you get a specfic template for the component with the name:
//template associated with component
<script type="text/x-handlebars" data-template-name="components/some-text-field">
Add what you want
</script>
In your JS declare your component:
//important word 'Component' must be at end
App.SomeTextFieldComponent = Ember.TextField.extend({
//same stuff as above example
});
Since we on a role you could probably get the same functionality using Ember input helpers. They are pretty powerful.
{{input action="sendChat" onEvent="enter"}}
Welp hopefully this information will help someone if they get stuck wondering why is my action not sending from this textField.
This jsBin is a sandBox for Components/Views sending actions etc....Nothing too fancy but it may help someone..
http://emberjs.jsbin.com/suwaqobo/3/
Peace, Im off this...

designing a custom component/view on emberjs

I'm having trouble doing things on Ember, and I'm sure it's because I still haven't fully grasped "the Ember way" of doing things, and I'm a trying to do a couple of things that get out of the scope of standard tutorials out there.
I'm developing some sort of textfield-with-suggestions component that will appear in every single page of my web app. I won't ask here all the details on how to do it, but just a couple specific things I am having trouble accomplishing from the start. The following are the relevant code fragments of what I have so far.
// handlebars template: searchbar.hbs
{{view App.SearchField viewName="searchField"}}
<div class="results" {{bindAttr class="searchField.hasFocus"}}>
This is where the results for whatever the user has typed are shown.
</div>
// coffeescript source of the views: searchbar.coffee
App.SearchBar: Ember.View.extend
templateName: 'searchbar'
App.SearchField: Ember.TextField.extend
placeholder: 'Search'
hasFocus: false
eventManager: Ember.Object.create
focusIn: (event, view) ->
#set('hasFocus', true)
focusOut: (event, view) ->
#set('hasFocus', false)
// somewhere in the layout of the pages in my app
<div id="header">
{{App.SearchBar}}
</div>
This will probably also need a controller, but I haven't developed it yet, because I don't know where it fits within this setup yet.
First, I want the suggestions popup panel to appear as soon as the search field obtains focus. That's the reason of my attempt above to implement a hasFocus property on the searchfield. But how do I achieve making my div.results panel react to the focus state of the input field?
In general, and this is the core of my question here, how do I connect all things to develop this component? If the answer is by attaching it to a controller, then how do I setup a controller for this component, and how do I specify that it is the controller for it, so that it acts as a context for everything?
I think you have to clearly separate concerns. Stuff related to the view (ie manipulating the DOM with jquery) should stay in the view. Stuff related to the application state should be in the controller. Though,in your case, I think you can simply bind an observer on the hasFocus property, and show the suggestions. Something like:
App.SearchField: Ember.TextField.extend
placeholder: 'Search'
hasFocus: false
eventManager: Ember.Object.create
focusIn: (event, view) ->
#set('hasFocus', true)
focusOut: (event, view) ->
#set('hasFocus', false)
focusDidChange: (->
if #hasFocus
$('.results')... // here I let you do the suggestion stuff
// based on data retrieved from the controller
else
// probably hide the results div.
).observes('hasFocus')