How to add/remove class to array of objects in Ember JS - ember.js

I try to learn Ember JS. And i can not find answer for my question . I have template
<script type="text/x-handlebars">
<table class="table">
{{#each App.todoListController}}
{{#view App.ViewTable todoBinding="this"}}
<td>{{title}}</td>
<td><a href="javascrpt:" {{action "deleteTodo" target="view"}}>X</a></td>
{{/view}}
{{/each}}
</table>
<button {{action "deleteTodo" target="App.todoListController"}}>Delete</button>
</div>
</script>
In app.js I have Controller and View :
/*******************
Controller
*******************/
App.todoListController = Em.ArrayController.create({
content : [],
createTodo : function(title) {
var todo = App.Todo.create({title:title})
this.pushObject(todo)
}
});
/*******************
View
*******************/
App.ViewTable = Em.View.extend({
tagName : 'tr',
classNameBindings : ['isHover:hover'],
isHover : false,
todo : null,
deleteTodo : function(){
var tr = this.get('todo');
App.todoListController.removeObject(tr);
},
click : function()
{
this.set('isHover',true);
}
})`
When i clicked to row of table , it changed class to "hover" . Now question : I can't remove class "hover" from all objects (it is necessary for only one object can be selected)
PS : Sorry for my English and sorry for the formatting.

One way to do this would be to move the "isHover" property to the "todo" item so that you can search all the "todo" items in the controller, and set/unset the "isHover" property on them :
Rename:
App.ViewTable
to
App.TableView
Ember looks for the keyword 'View' at the end of the name.
Add a name to the items in your each statement (I used "thing"):
{{#each thing in App.todoListController}}
instead of :
{{#each App.todoListController}}
that way it's easier to make references later.
Use the name you defined above (thing in this case) for your binding (and remove the quotes):
{{#view App.TableView todoBinding=thing}}
Instead of:
{{#view App.ViewTable todoBinding="this"}}
Now your tableView will have a reference to the 'todo' that it is displaying
Move "isHover" into the Todo item's object:
App.Todo = Em.Object.extend({
title:"...",
isHover: false
});
Bind "isHover" in your table view:
....
tagName : 'tr',
isHoverBinding: 'this.todo.isHover',//this should be before your classNameBindings
classNameBindings : ['isHover:hover'],
...
Now change your 'click' function to:
click : function() {
//Get a list of the other hovered items from the controller:
var otherHoveredItems = App.todoListController.get('content').filterProperty('isHover', true);
//Iterate each hovered item and set hover to false
for ( var i = 0; i < otherHoveredItems.length; i++) {
otherHoveredItems[i].set('isHover', false);
}
//set this item to hover
this.get('todo').set('isHover', true);
}
Here's a fiddle example: http://jsfiddle.net/amindunited/kY4nh/
Another method, would be to move your {{#each}} into a collectionView. The handlebars {{each}} is a collectionView, so this wouldn't be a big jump. One caveat is that the click method alone won't give you the context, BUT if you wrap the click function in an eventManager, you will get the view as the second reference...sounds messy, but it's actually tidier: http://jsfiddle.net/amindunited/5hNSZ/

Related

Ember Js Adding A Model Which is Dynamically Rendered On the UI

Hi all I have an issue i want to add another item to my model that is dynamically rendered on the UI.
However after i add it It still doest render on the UI. Do I need to call a function after adding it to the list of models?
Here is an example:
http://emberjs.jsbin.com/mugodifiki/edit?html,js,output
on clicking on an image it is suppose to add a predefined model to the array and display it on the UI
This is clearly an issue caused from the integration with owlcarousellibrary. A simple example of dynamically changing the model and rendering the ui, in plain emberjs, can be found here,
http://emberjs.jsbin.com/lodetofere/edit?html,js,output (simply click on the list).
This specific issue is caused due to the modifications of the DOM by the owlcarousel library. So to solve this, it is required to refresh the owlcarousel after changing the model and restoring the initial state of the dom.
Example,
http://emberjs.jsbin.com/qanocidoye/edit?html,js
The content part of the template is actually refreshed when the model changes by toggling a property and the carousel is reinitialized.
hbs
<script type="text/x-handlebars" data-template-name="components/test-component">
{{#if refresh}}
{{partial "owlContent"}}
{{else}}
{{partial "owlContent"}}
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="_owlContent">
<div class="owl-carousel owl-theme">
{{#each titleModels}}
<div class="item">
<img {{action "owlItemClicked" this on="click" }} {{bind-attr src=img}}>
</div>
{{/each}}
</div>
</script>
js
App.TestComponentComponent = Ember.Component.extend({
// classNames: ['owl-carousel', 'owl-theme'],
items: 8,
touchDrag: true,
mergeFit: false,
center: true,
addClassActive: true,
mouseDrag: true,
slideSpeed : 1000,
margin: 2,
refresh:false,
initCarousel:function(){
var self = this;
var options = {};
options.items = self.get('items');
options.touchDrag = self.get('touchDrag');
options.mergeFit = self.get('mergeFit');
options.center = self.get('center');
options.addClassActive = self.get('addClassActive');
options.mouseDrag = self.get('mouseDrag');
options.slideSpeed = self.get('slideSpeed');
options.margin = self.get('margin');
self._options = options;
self._owl = self.$(".owl-carousel").owlCarousel(options);
},
didInsertElement: function(){
this.initCarousel();
},
refreshCarousel:function(){
var self = this;
Em.run.next(function(){
self.initCarousel();
});
}.observes('refresh'),
actions: {
owlItemClicked: function(titleModel) {
var self = this;
var content = "<div class=\"item dodgerBlue\"><h1>test</h1></div>";
var newModel = Ember.Object.create({ index: 3, img: "http://www.cs.rice.edu/~dwallach/comp314_f99_ga/%257Eavinash/comp314/project3/pretty/pic1s.jpg"});
console.log(this.get('titleModels'));
//THIS ADDS IT TO THE MODEL BUT IS NOT RENDERED ON UI
this.get('titleModels').push(newModel);
alert('new model has been added');
console.log(this.get('titleModels'));
this.toggleProperty("refresh");
}
}
});
You may come up with a more elegant solution but this is the main idea.

How to select an element using mouse drag in ember?

I have my template like this,
<div>
{{#each model as |item|}}
{{#view "selection" model=item}}
<div class="child_div">{{item.name}}</div>
{{/view}}
{{/each}}
</div>
In my js, I am using a view for select an clicked element like,
Model:
App.IndexRoute = Ember.Route.extend({
model: function() {
return [{'is_active':false, 'name':'One'}, {'is_active':false, 'name':'Two'}, {'is_active':false, 'name':'Three'}, {'is_active':false, 'name':'Four'},{'is_active':false, 'name':'Five'}];
}
});
Selection View:
App.SelectionView = Ember.View.extend({
classNameBindings: ["isActive"],
isActive: Ember.computed.alias('model.is_active'), // No I18N
click: function (){
var self = this; self.get("controller").setEach("is_active", false); // No I18N
self.toggleProperty("isActive"); // No I18N
}
});
Here, I am selecting that div's in click event. I need to select them when I do use mouse drag.
How do I do that using mouse drag selection? Kindly help me out of that.
DEMO: JSBIN
Add the draggable attribute to your view and use the dragStart event.
attributeBindings : [ 'draggable' ],
draggable : 'true',
dragStart: function(event) {
var self = this;
self.get("controller").setEach("is_active", false); // No I18N
self.toggleProperty("isActive");
}
Here is working demo.
Here is a good article from Lauren Tan on drag n drop in Ember.

EmberJS: Property Scopes in an ArrayController?

this is probably a grossly simple question to answer, so I apologize if I am cluttering this forum in advance.
I am displaying a list of items that share the same model and controller.
I made these items editable via a <button {{ action 'edit' }}> next to each item which toggles a boolean value of a property "isEditable" in the controller.
However clicking this button causes all items in the list to become editable because they all share the controller property "isEditable". The desired effect is to make a single item editable at a time instead of all items at once.
A simplified version of my template looks like this:
{{#if isEditing}}
<p>{{input type="text" value=title}}</p>
<button {{action 'doneEditing'}}>Done</button>
{{else}}
<span class="title">{{title}}</span>
<button {{action 'edit'}}><span class="edit"</span></button>
{{/if}}
and the controller looks like this
App.ItemController = Ember.ArrayController.extend({
isEditing : false,
actions : {
edit : function(){
this.set('isEditing', true);
},
doneEditing : function(){
this.set('isEditing', false);
},
}
});
Anybody know how to accomplish this? Is it because each item shares the "isEditable" property? If so, how do I get around this? I don't want to put this into the model because it's purely a display thing, even though I know I can get it to work doing that.
Thanks :)
By default the controller lookup within an {{#each}} block will be the controller of the template where the {{#each}} was used. If each item needs to be presented by a custom controller (to hold it's own state for example) you can provide a itemController option which references a controller by lookup name. Each item in the loop will be then wrapped in an instance of this controller and the item itself will be set to the content property of that controller.
So, I assume you are displaying the list of items using the {{#each}} helper. Therefore you can specify an itemController in the {{#each}} helper to hold the isEditable state on a per item basis. This would look something like this:
{{#each item in controller itemController="item"}}
...
{{/each}}
Moreover you should define the defined itemController of type Ember.ObjectController like:
App.ItemController = Ember.ObjectController.extend({
isEditing : false,
actions : {
edit : function(){
this.set('isEditing', true);
},
doneEditing : function(){
this.set('isEditing', false);
},
}
});
And for the list you should then have an App.ItemsController of type Ember.ArrayController:
App.ItemsController = Ember.ArrayController.extend({});
See here for more info on the mentioned itemController support for the {{#each}} helper: http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_each
Hope it helps.

Ember: how to set classNameBindings on parent element of view

See http://jsfiddle.net/4ZyBM/6/
I want to use Bootstrap for my UI elements and I am now trying to convert certain elements to Ember views. I have the following problem:
I embed an input element in a DIV with a given class (control-group). If a validation error occurs on the field, then I want to add an extra class "error" to the DIV.
I can create a view based on the Ember.TextField and specify that if the error occurs the ClassNameBinding should be "error", but the problem is that class is the set to the input element and not to the DIV.
You can test this by entering a non alpha numeric character in the field. I would like to see the DIV border in red and not the input field border.
HTML:
<script type="text/x-handlebars">
<div class="control-group">
{{view App.AlphaNumField valueBinding="value" type="text" classNames="inputField"}}
</div>
</script>
JS:
App.AlphaNumField = Ember.TextField.extend({
isValid: function () {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value'),
classNameBindings: 'isValid::error'
})
Can I set the classNameBindings on the parent element or the element closest to the input ? In jQUery I would use:
$(element).closest('.control-group').addClass('error');
The thing here is that without using jQuery you cannot access easily the wrapping div around you Ember.TextField's. Also worth mentioning is that there might be also a hundred ways of doing this, but the simplest solution I can think of would be to create a simple Ember.View as a wrapper and check the underlying child views for validity.
Template
{{#view App.ControlGroupView}}
{{view App.AlphaNumField
valueBinding="value"
type="text"
classNames="inputField"
placeholder="Alpha num value"}}
{{/view}}
Javascript
App.ControlGroupView = Ember.View.extend({
classNameBindings: 'isValid:control-group:control-group-error',
isValid: function () {
var validFields = this.get('childViews').filterProperty('isValid', true);
var valid = validFields.get('length');
var total = this.get('childViews').get('length')
return (valid === total);
}.property('childViews.#each.isValid')
});
App.AlphaNumField = Ember.TextField.extend({
isValid: function () {
return /^[a-z0-9]+$/i.test(this.get('value'));
}.property('value')
});
CSS
.control-group-error {
border:1px solid red;
padding:5px;
}
.control-group {
border:1px solid green;
padding:5px;
}
Working demo.
Regarding bootstrap-ember integration and for the sake of DRY your could also checkout this ember-addon: https://github.com/emberjs-addons/ember-bootstrap
Hope it helps.
I think that this is the more flexible way to do this:
Javascript
Boostrap = Ember.Namespace.create();
To simplify the things each FormControl have the properties: label, message and an intern control. So you can extend it and specify what control you want. Like combobox, radio button etc.
Boostrap.FormControl = Ember.View.extend({
classNames: ['form-group'],
classNameBindings: ['hasError'],
template: Ember.Handlebars.compile('\
<label class="col-lg-2 control-label">{{view.label}}</label>\
<div class="col-lg-10">\
{{view view.control}}\
<span class="help-block">{{view.message}}</span>\
</div>'),
control: Ember.required()
});
The Boostrap.TextField is one of the implementations, and your component is a Ember.TextField. Because that Boostrap.TextField is an instance of Ember.View and not an Ember.TextField directly. We delegate the value using Ember.computed.alias, so you can use valueBinding in the templates.
Boostrap.TextField = Boostrap.FormControl.extend({
control: Ember.TextField.extend({
classNames: ['form-control'],
value: Ember.computed.alias('parentView.value')
})
});
Nothing special here, just create the defaults values tagName=form and classNames=form-horizontal, for not remember every time.
Boostrap.Form = Ember.View.extend({
tagName: 'form',
classNames: ['form-horizontal']
});
Create a subclass of Boostrap.Form and delegate the validation to controller, since it have to be the knowledge about validation.
App.LoginFormView = Boostrap.Form.extend({
submit: function() {
debugger;
if (this.get('controller').validate()) {
alert('ok');
}
return false;
}
});
Here is where the validation logic and handling is performed. All using bindings without the need of touch the dom.
App.IndexController = Ember.ObjectController.extend({
value: null,
message: null,
hasError: Ember.computed.bool('message'),
validate: function() {
this.set('message', '');
var valid = true;
if (!/^[a-z0-9]+$/i.test(this.get('value'))) {
this.set('message', 'Just numbers or alphabetic letters are allowed');
valid = false;
}
return valid;
}
});
Templates
<script type="text/x-handlebars" data-template-name="index">
{{#view App.LoginFormView}}
{{view Boostrap.TextField valueBinding="value"
label="Alpha numeric"
messageBinding="message"
hasErrorBinding="hasError"}}
<button type="submit" class="btn btn-default">Submit</button>
{{/view}}
</script>
Here a live demo
Update
Like #intuitivepixel have said, ember-boostrap have this implemented. So consider my sample if you don't want to have a dependency in ember-boostrap.

Ember - how to display a list of checkbox related to an array using CollectionView?

I have an application with items.
I would like to display checkboxes for a list of values in an item.
I use a Checkbox to do this:
App.DisplayedValueCheckbox = Em.Checkbox.extend({
itemValuesBinding: 'parentView.item.values',
checked: function () {
return true; //Default value is always true
}.property('content', 'itemValues.#each'),
click: function (evt) {
//update graph
}
});
And try to display it into a collection view:
App.DisplayedValuesView = Em.CollectionView.extend({
contentBinding: 'App.Item.values'
,itemBinding: 'App.Item'
, tagName: 'ul'
, itemViewClass: Em.View.extend({
itemBinding: 'parentView.item'
, templatename: 'displayed-values'
})
});
which is part of a parent view:
<script type="text/x-handlebars" id="item">
<h1>{{name}}</h1>
<hr><p>{{desc}}</p>
<div>
<div>
{{#each item in values}}
<li>{{item}}</li>
{{/each}}
</div>
<div>{{view App.DisplayedValuesView}}</div>
</div>
</script>
But the view does not display the checkboxes and there is an error:
Unable to find view at path 'App.DisplayedValuesView'
Edit: this error was corrected thank to #sly7_7's answer
Here is the jsFiddle of this problem
I know I miss something, but what ?
Something in the bindings (I am not sure how they work) ?
Should I write a specific Route (App.ItemRoute = ...) ?
Defining the checkbox view ? The template ?
The mistake is that your declare your App as a local var. Ember/Handlebars helpers needs global when you want to reference a view from the application namespace.
So just remove the 'var' before App :)