I want to toggle a single form (see picture below) using views.
My code basically works when i add new formset it adds an empty bracket to json and then i
print the json out using eachloop
View template
{{#each view.anotherField}}
<div class="panel panel-default">
{{action 'toggleView' 'toggleViews' target='view'}}
...
</div>
{{#unless view.toggleViews}}
...content to toggle...
{{/unless}}
View controller??
actions: {
showMoreFields: function(){
this.get('anotherField').pushObject({name: ''});
,
toggleView: function(param){
this.toggleProperty(param);
}
In this given picture u can see ive toggled organization view to truth what i would like is to toggle only the clicked part not all of the forms. Is there a solution ?
Cheers,
Kristjan
If I understand correctly you need to handle events for specific parts/forms of your view. To achieve this there are at least three approaches,
1.Use the {{action}} helper passing the object you want to modify. Then in your function modify a property of that object and reflect that in your template e.g. toggle the form. Maybe in your case it could be something like ,
....
{{#each field in view.anotherField}}
<div class="panel panel-default">
{{action 'toggleView' field target='view'}}
....
2.Make a sub view/template (e.g. SubFormView) to accomodate each of your forms and handle the event of toggle within this view. Then include this via the {{view}} helper within the template of your main view.
3.Use pure js DOM handling (no {{action}} helper) and call your ember components from there.
Example of approaches 1 and 3 can be found here,
http://emberjs.jsbin.com/acUCocu/1
hbs
<script type="text/x-handlebars" data-template-name="index">
<i>using <b>{{action}}</b> helper</i>
<ul>
{{#each color in model}}
<li {{action 'test' color}}>{{color.name}}</li>
{{/each}}
</ul>
<i>using pure js DOM event handling</i>
<ul>
{{#each color in model}}
<li onclick="Ember.View.views[$(this).closest('.ember-view').attr('id')].controller.send('test2',this)">{{color.name}}</li>
{{/each}}
</ul>
</script>
js
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return colors;
},
actions:{
test:function(param){
alert('this is color:'+param.get('name')+" ("+param+")");
},
test2:function(elem){
alert('from pure js, clicked element: '+elem);
$(elem).css('color','red');
}
}
});
App.Color = Ember.Object.extend({
name:null
});
var colors=[];
colors.pushObject(App.Color.create({name:'red'}));
colors.pushObject(App.Color.create({name:'green'}));
colors.pushObject(App.Color.create({name:'blue'}));
Related
I have an ember application which works fine. But user's interaction does some DOM insertion like below...
$(.test).append(<a {{action "getData"}}>Get Data</a>);
The problem is that Ember seems do not recognize that an action "getData" has been added to the DOM. Nothing is happening when I click the element. Any thoughts on this?
Another way I am trying to do is:
//create the element
$(.test).append(<a id="idnum">Get Data</a>);
//make click listener
$('#idnum').click(function(){
console.log("get data");
}
my question is where should i place the code inside the component so the it can listen on the click event. Thanks.
You should do it in Ember way. Try handlebars {{#if}} helper to render an element dynamically.
{{#if canGetData}}
<a {{action "getData"}}>Get Data</a>
{{/if}}
Here you can set the value of the canGetData to true in the controller based on the users action.
The first example can't work because ember does not analythe the Handlebars elements in the DOM, but rather parses your Handlebars template with HTMLBars, which is a full HTML parser, and then renders it manually by inserting elements, not text into the DOM.
However the second example is the way to go if you have to rely on external code that does manual DOM manipulation. And it does work. Checkout this twiddle.
This does work:
this.$('.target').append('<a id="idnum">Get Data</a>');
this.$('#idnum').on('click', () => {
alert('clicked');
});
Just make sure that the DOM is ready. So do it in the didInsertElement hook or after the user clicked a button or so.
Like Lux suggested avoid DOM manipulation. I prefer the following approach,
if it is dynamic then you can consider wrapping DOM element as a new component and use component helper.
find sample twiddle
In application.js
export default Ember.Controller.extend({
appName: 'Ember Twiddle',
linksArray:[ Ember.Object.create({value:'Text to display',routename:'home'}),
Ember.Object.create({value:'Text to display2',routename:'home'})],
actions:{
addItem(){
this.get('linksArray').pushObject(Ember.Object.create({value:'AddedDynamically',routename:'home'}));
}
}
});
in Application.hbs
<h1>Welcome to {{appName}}</h1>
<br>
{{#each linksArray as |item|}}
{{component 'link-to' item.value item.route }}
{{/each}}
<button {{action 'addItem'}}>Add Item</button>
<br>
{{outlet}}
<br>
<br>
I'm trying to create a page that contains master/detail, which is obviously easy using a static {{ outlet }} in ember, however, I'd like the detail to slide down after the row that was selected. For example, on this page: http://jsbin.com/ijejap/1/edit, if the detail of the name was to appear after the currently selected name instead of at the bottom of the page.
The problem I'm trying to solve is that I can't have an outlet within a repeater so that when I click Row 1, I want the outlet to be positioned below Row 1, and when I click Row 2, I want the outlet after Row 2. In other words, I want to dynamically position an outlet, I think, unless there's another way to do it.
Ok, I finally made a conditionnal outlet depending on wether the user is selected or not, but I had to work with DS.Model and its FIXTURES to make this work.
To be brief, I added an action before the link to set a selected property on the user object and record which user is selected to prevent having two users at the same time.
The position of the {{outlet}} then depends on the current selected user.
Here's the template part:
{{#each model}}
<li {{action 'select' this}}>
{{#link-to "user" this}}{{first}}{{/link-to}}
</li>
{{#if selected}}
{{outlet}}
{{/if}}
{{/each}}
And the App.ApplicationController part:
App.ApplicationController = Ember.ArrayController.extend({
currentUser: null,
actions: {
select: function(newUser) {
var oldUser = this.get('currentUser');
if(newUser !== oldUser) {
if(oldUser)
oldUser.toggleProperty('selected');
this.set('currentUser', newUser);
newUser.toggleProperty('selected');
}
}
}
});
Finally, the model definition:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
App.User = DS.Model.extend({
first: DS.attr(),
last: DS.attr(),
avatar: DS.attr()
});
Here's the link to the JSBin: http://jsbin.com/cilari/3/
You can make use of components power here.
I made it on JSBin : http://jsbin.com/zovos/2/
First, you declare your user details as a new component, say user-details :
<script type="text/x-handlebars" id="components/user-details">
<li>{{user.first}}</li>
<h2>
{{user.first}} {{user.last}}
<img {{bindAttr src="user.avatar"}} class="pull-right" width=50 />
</h2>
<dl>
<dt>First</dt>
<dd>{{user.first}}</dd>
<dt>Last</dt>
<dd>{{user.last}}</dd>
</dl>
</script>
Then you call it from your list, using the current user as the user in the component :
<script type="text/x-handlebars">
<div class="container">
<div class="row-fluid">
<ul class="nav nav-list span3 well">
{{#each model}}
{{user-details user=this}}
{{/each}}
</ul>
</div>
</div>
</script>
You add the selected property to your component and an action like expandDetails :
App.UserDetailsComponent = Ember.Component.extend({
selected: false,
actions: {
expandDetails: function() {
this.toggleProperty('selected');
}
}
});
Finally, you add the action to your component template and a conditional display on the selected property :
<script type="text/x-handlebars" id="components/user-details">
<li><a {{action "expandDetails"}}>{{user.first}}</a></li>
{{#if selected}}
<h2>
{{user.first}} {{user.last}}
...
</dl>
{{/if}}
</script>
And, of course, you get rid of your user route.
Here is a link to Ember's Guide that show this on a sample post example : http://emberjs.com/guides/components/handling-user-interaction-with-actions/
Hope this helps.
Are you looking for an accordion?
http://addepar.github.io/#/ember-widgets/accordion
I have an idea but unproved yet, maybe you can try it.
For detail, you only need one template. For master, you need write several named outlet (see: http://emberjs.com/guides/routing/rendering-a-template/) and place each below the corresponding item of master view.
When each master item has been clicked, you need pass a param to the detail route in order to let it know what kind of data should be populated.
The key part is the renderTemplate hook of route (see: http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate). You can take advantage of it because it allows you to specify which named outlet should be used to render the template.
Or you may load all details' data at once and cache it, then pass an identifier when master item got clicked, in this way you can filter the model in advance and get prepared for later renderTemplate use.
This is just an idea and currently don't have spare time to try it, but as your wish, you got the dedicated route, controller and model. I think it is highly doable, let me know it you make any progress.
I try to extend a component (https://github.com/ember-addons/ember-forms) to make it possible to add extra button next to the form controls.
The idea
developer passes an extra property to the component, and a partial will be rendered next to the form control (input, select, textarea).
Problem
It works fine but if i have a partial with some action, the action wont fire.
JsBin
Here is a simplified JsBin which demonstrates the problem: http://jsbin.com/pexolude/105/edit
html
<script type="text/x-handlebars">
<h2>Component test</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{booh-whaa partial="somebutton"}}
<h3>This partial's action works</h3>
{{partial "somebutton"}}
</script>
<script type="text/x-handlebars" data-template-name='_somebutton'>
<button {{action "booh"}} >Hit me!</button>
</script>
<script type="text/x-handlebars" data-template-name='components/booh-whaa'>
<h3>This is my component</h3>
{{partial partial}}
</script>
JS.
App = Ember.Application.create();
App.IndexController = Ember.Controller.extend({
selectedCategory:null,
actions: {
booh: function() {
alert("booh!");
}
}
});
App.BoohWhaaComponent = Ember.Component.extend({
});
As Knownasilya said, actions inside a component are, by default, handled by the component. However, If you specify the target on the action helper, the action with automatically propagate and you don't have to use sendAction().
In your case, this is as simple as:
{{action 'booh' target='controller'}}
Or if the action is on the route's view:
{{action 'booh' target='parentView'}}
Another option is to use Em.TargetActionSupport to send actions with contexts and other arguments to specific targets throughout your app.
When you fire an action inside a component, that component handles the action. If you want it to continue, use this.sendAction('action') and then on your component set {{booh-whaa action='booh'}}. See the guides for more information about actions in components.
Here's a working jsbin.
Also partials no longer require an underscore.
In a toy application, I have a 'posts' template that shows all of the post titles. When you click each title, instead of heading to a 'show' view I want to expand down the rest of the contents of that post, directly inline.
I've considered having the postRoute reuse the postsRoute and set a flag that would then be checked against in the handlebars template to splat out the rest of the post content.
What would be a preferred 'Ember-ish' approach that would let a resource's singular view be rendered inline with its index view in the correct location?
I would suggest to define an itemController on PostsController which can take actions for individual post objects.
Then, in your template define the action (e.g. toggleBody) that toggles a property on the itemController. You can use this property to show or to hide the body of each post:
App.PostsController = Ember.ArrayController.extend
itemController: 'post'
App.PostController = Ember.ObjectController.extend
showBody: no
actions:
toggleBody: ->
#toggleProperty('showBody')
return false
<script type="text/x-handlebars" data-template-name="posts">
<ul>
{{#each}}
<li>{{title}} <span {{action toggleBody}} class='label'>Toggle</span>
{{#if showBody}}
<div>{{body}}</div>
{{/if}}
</li>
{{/each}}
</ul>
</script>
See also this jsFiddle for a working demo.
I am using the {{#each}} helper to iterate through the children of a parent record:
... parent template ...
{{#each foo in foos}}
{{#with foo}}
{{ partial 'foo' }}
{{/with}}
{{/each}}
In a specific case, I would like to only render records that have already been loaded. I believe you can filter such records (as per In Ember Data, how do I find only records that have been loaded?):
App.Foo.filter(function(foo) { return foo.get('isLoaded'); });
But how would one do that within the each helper? I created a loaded_foos property on the parent record, but it won't work since it is a function and not an Ember.Array.
You cannot do that within a each helper. Your approach with a property was right indeed. What did not work? It should basically look like this:
App.ParentRecord = Ember.Object.extend({
loadedFoos : function(){
this.get("foos").filter(function(foo) { return foo.get('isLoaded'); });
}.property("foos")
});
You can do this in the template with a conditional helper as well.
{{#each foos}}
{{#if isLoaded}}
{{partial "foo"}}
{{/if}}
{{/each}}
Edit: The above works if you already have a mixed collection of loaded and unloaded records in an array. The OP asked how to actually get the loaded records. I would go about it this way:
App.Foo = DS.Model.extend();
App.IndexRoute = Ember.Route.extend({
model: function() {
return App.Foo.all();
}
});
<script type="text/x-handlebars" data-template-name="index">
{{#each controller}}
{{partial "foo"}}
{{/each}}
</script>
Here's a jsfiddle example.