Ember.js bind class change on click - ember.js

How do i change an elements class on click via ember.js, AKA:
<div class="row" {{bindAttr class="isEnabled:enabled:disabled"}}>
View:
SearchDropdown.SearchResultV = Ember.View.extend(Ember.Metamorph, {
isEnabled: false,
click: function(){
window.alert(true);
this.isEnabled = true;
}
});
The click event works as window alert happens, I just cant get the binding to.

The class is bound correctly, but the isEnabled property should be modified only with a .set call such as this.set('isEnabled', true) and accessed only with this.get('isEnabled'). This is an Ember convention in support of first-class bindings and computed properties.

In your view you will bind to a className. I have the following view in my app:
EurekaJ.TabItemView = Ember.View.extend(Ember.TargetActionSupport, {
content: null,
tagName: 'li',
classNameBindings: "isSelected",
isSelected: function() {
return this.get('controller').get('selectedTab').get('tabId') == this.get('tab').get('tabId');
}.property('controller.selectedTab'),
click: function() {
this.get('controller').set('selectedTab', this.get('tab'));
if (this.get('tab').get('tabState')) {
EurekaJ.router.transitionTo(this.get('tab').get('tabState'));
}
},
template: Ember.Handlebars.compile('<div class="featureTabTop"></div>{{tab.tabName}}')
});
Here, you have bound your className to whatever the "isSelected" property returns. This is only true if the views' controller's selected tab ID is the same as this views' tab ID.
The code will append a CSS class name of "is-selected" when the view is selected.
If you want to see the code in context, the code is on GitHub: https://github.com/joachimhs/EurekaJ/blob/netty-ember/EurekaJ.View/src/main/webapp/js/app/views.js#L100

Good answers, however I went down a different route:
SearchDropdown.SearchResultV = Ember.View.extend(Ember.Metamorph, {
classNameBindings: ['isSelected'],
click: function(){
var content = this.get('content');
SearchDropdown.SelectedSearchController.set('content', content);
var loadcontent = this.get('content');
loadcontent.set("searchRadius", $("select[name=radius]").val());
SearchDropdown.LoadMap.load(content);
},
isSelected: function () {
var selectedItem = SearchDropdown.SelectedSearchController.get('content'),
content = this.get('content');
if (content === selectedItem) {
return true;
}
}.property('SearchDropdown.SelectedSearchController.content')
});
Controller:
SearchDropdown.SelectedSearchController = Ember.Object.create({
content: null,
});
Basically stores the data of the selected view in a controller,

Related

Ember push record in model and update template

I have this route in my Ember app:
model: function(params, transition) {
var self = this;
return Ember.RSVP.hash({
photo: self.store.find('photo', params.id),
comments: self.store.query('comment', {id: params.id})
});
},
actions: {
newComment: function(comment) {
var record = this.store.createRecord('comment', comment);
}
}
The template:
{{#each model.comments as |comment|}}
<div class="card">
<div data-userId="{{comment.userId}}">
<b>{{comment.username}}</b>
</div>
<div>
{{comment.content}}
</div>
<span class="hide-on-small-only">{{i18n 'createdAt_lbl'}}: </span>{{format comment.createdAt type='date'}}
</div>
{{/each}}
{{post-comment newComment='newComment' comments=model.comments}}
and the comment model:
export default DS.Model.extend({
commentHash: DS.attr('string'),
content: DS.attr('string'),
createdAt: DS.attr('date'),
username: DS.attr('string'),
userHash: DS.attr('string'),
userId: DS.attr('number'),
});
The post-comment component is the one responsible to call the newComment action:
// post-comment component
var self = this;
// get the new comment content from textarea
var $contentArea = this.$('#postCommentContent');
var content = $contentArea.val();
var newComment = {
userId: localStorage.getItem('userId'),
content: content,
createdAt: moment().format('MMMM Do YYYY, h:mm:ss a')
};
self.sendAction('newComment', newComment);
What I need is to be able to add a new local comment (without persisting it on the server) dinamically and make the template update to show the newly added record without a complete page refresh
Make a modifiable copy of the list of comments and keep it on the controller:
setupController(controller, model) {
this._super(...arguments);
controller.set('comments', model.comments.toArray());
}
The reason you need to make a copy is that the return value from store.query is, for various reasons, unwritable. There may be other ways to make a copy but toArray seems to work well.
Add the new comment to this list after creating it:
actions: {
newComment: function(comment) {
var record = this.store.createRecord('comment', comment);
this.get('comments').pushObject(record);
}
}
In your template, loop over the controller property:
{#each comments as |comment|}}
It should simply work like this:
newComment: function(comment) {
var record = this.store.createRecord('comment', comment);
var commentsModel = this.get('model.comments'); // or this.get('comments'), depending where you action resides
commentsModel.pushObject(record);
this.set('model.comments', commentsModel); // or this.set('comments'), depending where you action resides
}
This only works if you actually have comments. If not, you first need to initialize your comments as an empty array. Otherwise:
newComment: function(comment) {
var record = this.store.createRecord('comment', comment);
var commentsModel = this.get('model.comments'); // or this.get('comments'), depending where you action resides
if(!commentsModel){
commentsModel = Ember.A();
}
commentsModel.pushObject(record);
this.set('model.comments', commentsModel); // or this.set('comments'), depending where you action resides
}
}

Ember editable recursive nested components

I'm currently trying to build a component that will accept a model like this
"values": {
"value1": 234,
"valueOptions": {
"subOption1": 123,
"subOption2": 133,
"subOption3": 7432,
"valueOptions2": {
"subSubOption4": 821
}
}
}
with each object recursively creating a new component. So far I've created this branch and node components and its fine at receiving the data and displaying it but the problem I'm having is how I can edit and save the data. Each component has a different data set as it is passed down its own child object.
Js twiddle here : https://ember-twiddle.com/b7f8fa6b4c4336d40982
tree-branch component template:
{{#each children as |child|}}
{{child.name}}
{{tree-node node=child.value}}
{{/each}}
{{#each items as |item|}}
<li>{{input value=item.key}} : {{input value=item.value}} <button {{action 'save' item}}>Save</button></li>
{{/each}}
tree-branch component controller:
export default Ember.Component.extend({
tagName: 'li',
classNames: ['branch'],
items: function() {
var node = this.get('node')
var keys = Object.keys(node);
return keys.filter(function(key) {
return node[key].constructor !== Object
}).map(function(key){
return { key: key, value: node[key]};
})
}.property('node'),
children : function() {
var node = this.get('node');
var children = [];
var keys = Object.keys(node);
var branchObjectKeys = keys.filter(function(key) {
return node[key].constructor === Object
})
branchObjectKeys.forEach(function(keys) {
children.push(keys)
})
children = children.map(function(key) {
return {name:key, value: node[key]}
})
return children
}.property('node'),
actions: {
save: function(item) {
console.log(item.key, item.value);
}
}
});
tree-node component:
{{tree-branch node=node}}
Anyone who has any ideas of how I can get this working would be a major help, thanks!
Use:
save(item) {
let node = this.get('node');
if (!node || !node.hasOwnProperty(item.key)) {
return;
}
Ember.set(node, item.key, item.value);
}
See working demo.
I think this would be the perfect place to use the action helper:
In your controller define the action:
//controller
actions: {
save: function() {
this.get('tree').save();
}
}
and then pass it into your component:
{{tree-branch node=tree save=(action 'save')}}
You then pass this same action down into {{tree-branch}} and {{tree-node}} and trigger it like this:
this.attrs.save();
You can read more about actions in 2.0 here and here.

Delete item from ember-tables

I'm trying add a delete button with an ember action from a controller. For some reason Ember.Handlebars.compile('<button {{action "deletePerson"}}>Delete</button> returns a function and not the compiled string.
Here's a jsbin
Here's the relevant portion of code:
App.ApplicationController = Ember.Controller.extend({
columns: function() {
...
buttonColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 100,
headerCellName: 'Action',
getCellContent: function(row) {
var button = Ember.Handlebars.compile('<button {{action "deletePerson" this}}>Delete</button>');
return button; // returns 'function (context, options) { ...'
}
});
...
}.property()
...
After looking through the link from #fanta (http://addepar.github.io/#/ember-table/editable) and a lot of trial and error, I got it working.
Here's the working jsbin.
Here are some key points:
Instead of using getCellContent or contentPath in the ColumnDefinition, you need to use tableCellViewClass and to create a view that will handle your cell
Pass in this to the action on your button — and modify content off that. One gotcha is to edit content, you need to copy it using Ember.copy
Here's the relevant code:
App.ApplicationController = Ember.Controller.extend({
columns: function() {
...
buttonColumn = Ember.Table.ColumnDefinition.create({
columnWidth: 100,
headerCellName: 'Action',
tableCellViewClass: 'App.PersonActionCell'
});
...
}.property(),
onContentDidChange: function(){
alert('content changed!');
}.observes('content.#each'),
...
});
App.PersonActionCell = Ember.Table.TableCell.extend({
template: Ember.Handlebars.compile('<button {{action "deletePerson" this target="view"}}>Delete</button>'),
actions: {
deletePerson: function(controller){
// Will NOT work without Ember.copy
var people = Ember.copy(controller.get('content'));
var row = this.get('row');
// For some reason people.indexOf(row) always returned -1
var idx = row.get('target').indexOf(row);
people.splice(idx, 1);
controller.set('content', people);
}
}
});

Updating a property is not visible in the DOM

I have the following drop-downs:
{{view SettingsApp.Select2SelectView
id="country-id"
contentBinding=currentCountries
optionValuePath="content.code"
optionLabelPath="content.withFlag"
selectionBinding=selectedCountry
prompt="Select a country ..."}}
...
{{view SettingsApp.Select2SelectView
id="city-id"
contentBinding=currentCities
selectionBinding=selectedCity
prompt="Select a city ..."}}
The bound properties are defined in a controller:
SettingsApp.ServicesEditController = SettingsApp.BaseEditController.extend(SettingsApp.ServicesMixin, {
needs : ['servicesIndex'],
selectedCountry : null,
selectedCity : null,
currentCountries : null,
currentCities : null,
init : function () {
this._super();
},
setupController : function (entry) {
this._super(entry);
var locator = SettingsApp.locators.getLocator(this.get('content.properties.locator'));
var countryCode = locator.get('country'), city = locator.get('city');
this.set('currentCountries', SettingsApp.countries.getCountries());
this.set('currentCities', SettingsApp.locators.getCities(countryCode));
this.set('selectedCountry', SettingsApp.countries.getCountry(countryCode));
this.set('selectedCity', city);
// Add observers now, once everything is setup
this.addObserver('selectedCountry', this.selectedCountryChanged);
},
selectedCountryChanged: function () {
var countryCode = this.get('selectedCountry.code');
var currentCities = SettingsApp.locators.getCities(countryCode);
var selectedCity = currentCities[0];
this.set('currentCities', currentCities);
this.set('selectedCity', selectedCity);
},
...
});
Initial setup is working fine, but changing the country selection does not update the city selection in the drop-down, even though the observer (selectedCountryChanged) is called and the this.set('selectedCity', selectedCity); is working as expected (as seen in console logging). The currentCities are properly set after the observer runs, but the active (selected) value is not correct (remains unchanged).
Are there any known issues with the programmatic update of bound properties, in this case selectionBinding?
My Select2SelectView is:
SettingsApp.Select2SelectView = Ember.Select.extend({
prompt: 'Please select...',
classNames: ['input-xlarge'],
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
},
processChildElements: function() {
this.$().select2({
// do here any configuration of the
// select2 component
escapeMarkup: function (m) { return m; } // we do not want to escape markup since we are displaying html in results
});
},
willDestroyElement: function () {
this.$().select2('destroy');
}
});
Check whether selected city is getting displayed by removing select2(replacing with normal select). If that's the case, selectionbinding has to be propagated to select2.

EmberJS: How to create modal forms

Whats a good way or a pattern to creating modal forms in EmberJS. Something like this.
I will describe the way i manage modal views with a CSS approach:
Add CSS class names:
.modal {
-webkit-transition: -webkit-transform #modal-duration #ease-animation-style;
-webkit-transform: translate3d(0,0,0);
-webkit-backface-visibility: hidden;
}
.modal.from-left.is-hidden {
-webkit-transform: translate3d(-320px,0,0);
}
.modal.from-right.is-hidden {
-webkit-transform: translate3d(320px,0,0);
}
.modal.from-up.is-hidden {
-webkit-transform: translate3d(0,-1*#app-height,0);
}
.modal.from-down.is-hidden {
-webkit-transform: translate3d(0,#app-height,0);
}
Add custom events to your Application Namespace to receive the transitionEnd event in your view:
Yn.Application = Em.Application.extend( Em.Evented, {
customEvents: {
webkitTransitionEnd: 'transitionEnd',
......
}
});
Now create a mixin to be used in your view as:
Yn.Modal = Em.Mixin.create({
isHidden: true,
classNameBindings: ['isHidden'],
classNames: ['modal'],
transitionEnd: function(event) {
// transitionEnd triggers multiple times
// http://stackoverflow.com/questions/4062105/webkit-transitionend-event-grouping
if ( event.originalEvent.propertyName === '-webkit-transform' &&
$(event.target)[0] === this.$()[0] ) {
var eventName = this.get('isHidden') ? 'transitionHide' : 'transitionShow' ;
this.trigger(eventName);
}
}
});
You can now insert the view in the DOM via appendTo or any handlebars view template or whatever method you use, and manages your view with its isHidden property which can be bound for example to a controller UI property, you could also interact with the view with the view lifecycle hooks as didInsertElement or the new defined as transitionHide, transitionShow hooks.
You can use my modified Bootstrap.ModalPane
ex:
App.ContactView = Bootstrap.ModalPane.extend({
closeOnEscape: false,
showBackdrop: true,
showCloseButton: false,
heading: 'Contact',
primary: "Save changes",
secondary: "Cancel",
classNames: ["large-modal"],
bodyViewClass: Ember.View.extend({
templateName: 'contact-body'
}),
callback: function (opts, event) {
var controller = this.get('controller');
if (opts.primary) {
controller.save();
} else {
controller.cancel();
}
}
});
In you controller you can then do something like this:
editContact: function (contact) {
var contactController = this.controllerFor('contact');
contactController.set('content', contact);
var contactView = App.ContactView.create({
controller: contactController
});
contactView.append();
},
You can also define your own modalPaneTemplate with customizations. It's the principle of how the Boostrap.ModelPane works that matters, default it only supports 2 buttons at the bottom. If you want 5 buttons, or buttons in the header, start coding yourself and create a custom modalPaneTemplate.