Ember JS Component combine three sources of CSS class names - ember.js

I want to be able to write a component which has three sources of classNames:
A set of static classes from the classNames property of the component (static JS code)
A set of user provided classes provided through the classNames property on the HB-helper
Up to here I have tested successfully -- Ember will merge the two sources which I think is nice. I cannot however find where this behaviour is documented.
Now I want to be able to add further classes to this list with a computed property.
The only way to do this seems to be with classNameBindings but this is not sufficient in my case! As the list of classes is quite dynamic and I want to calculate it explicitly.
What I have also tried is to define the classNames as property() but this seems not to be possible according to this issue.
Does anyone know a way to achieve this?

This is one case where I think you don't want to use Ember and instead use jQuery directly. Use Ember to take care of the static classes and the classes provided through the Handlebars helper, but use jQuery to add the computed property classes. For instance, let's says that you have a computed property called extraClasses. Try something like this:
extraClasses: function() {
return ['one', 'two', 'three'];
}.property(),
previousExtraClasses: [],
manageClasses: function() {
var previousExtraClasses = this.get('previousExtraClasses');
var extraClasses = this.get('extraClasses');
// Remove the classes that got removed from `extraClasses`
previousExtraClasses.forEach(function(className) {
if (extraClasses.indexOf(className) < 0) {
this.$().removeClass(className);
}
}, this);
// Add the classes that got added to `extraClasses`
extraClasses.forEach(function(className) {
if (previousExtraClasses.indexOf(className) < 0) {
this.$().addClass(className);
}
}, this);
this.set('previousExtraClasses', extraClasses);
).observesImmediately('extraClasses')
Every time you update extraClasses with a new set of classes, the manageClasses observer will take care of managing the classes on the component. As long as extraClasses returns an array of strings, it can be computed any way you like.

I'm not sure I fully understood your reasoning against classNameBindings. You can get dynamic classes list with help of that property. I've prepared an example in jsbin.
It uses rule that applies to classNameBindings: if no conditional classes have been listed (eg. classNamesBindings: ['isEnabled:enabled:disabled']), than use provided by computed property class name as is (eg. true||false would be in classes list of HTMLElement). So, you need to declare a computed property which returns a string with classes names and to list this CP in component's classNameBindings.

Related

Emberjs one way & two way bindings?

Lets say I want that my page's title will change depending on a really simple field what is the Ember way of doing it?
I didn't really understand the bindings in Ember, do I have to create an object even if all I need is just 1 field?
Does Ember support two way bindings? if it does so how can I constrain the bindings to one-way only?
I think i'm a bit confused with Ember-data & regular Ember, when I use Ember-data do I need to care about bindings at all?
Thanks in advance :)
This is a little vague (or I just don't fully understand what you're asking), so I'll shotgun approach and we can narrow down as you ask more questions.
Preface: Ember Data is a client side record management library, Ember works completely fine without it.
Title
A page's title is a little tricky since it's kind of out of the scope of the viewable dom, but the best way to handle it would be with an observer. In the example below, as the title property changes inside of my application controller I'm setting the document.title.
App.ApplicationController = Em.Controller.extend({
title:undefined,
watchTitle: function(){
document.title = this.get('title');
}.observes('title')
})
Example: http://emberjs.jsbin.com/haducafu/1
Computed Properties
Ember does support one way bindings (though rarely do you need to care about bindings). More often you want to care about dependent properties. eg if property a has changed, property b should be updated etc. In the case below, b is a computed property that depends on a, if a changed, b is dirty, and ember should re-computed it.
App.ApplicationController = Em.Controller.extend({
a:'foo',
b: function(){
return 'Hello: ' + this.get('a');
}.property('a')
})
Example: http://jsbin.com/haducafu/2/edit
Simple Binding
Additionally Ember can do just simple bindings (you can actually skip defining name, since ember would define it the first time it uses it).
App.ApplicationController = Em.Controller.extend({
name:undefined
});
<h2>Hello {{name}}</h2>
Name: {{input value=name}}
Example: http://jsbin.com/haducafu/3/edit
One Way/Read Only:
One way will take the value from its host property, unless you set it, if you set it it stops following the dependent property and becomes its own (not modifying the dependent property).
Read only will take values form the host property, and if you try and set it it will blow chunks.
App.ApplicationController = Em.Controller.extend({
name:'billy',
oneWay: Em.computed.oneWay('name'),
readOnly: Em.computed.readOnly('name')
});
Try changing name first, they will all update, then change oneWay and it will diverge and never return, then change readOnly and it will throw errors.
Example: http://jsbin.com/haducafu/4/edit

component templates defined within subfolder causing error when compiling them using grunt-ember-templates

We are relying heavily on ember components. Initially we had all our hbs templates associated with components written within "templates/components" folder, but since the number of components are growing we wanted to give more structure and define the templates within subfolder within components folder. When doing so, ember complains
You are looking for a [XYZ] component in the [SUBFOLDER] namespace, but the namespace could not be found
My compiled template looks like:
Ember.TEMPLATES["components/answers/checkbox-answer"] = ...
But things work if I were to make following change :
Ember.TEMPLATES["components/checkbox-answer"] = ...
The work around I have now is to modify gruntfile like this
emberTemplates: {
options: {
templateName: function(sourceFile) {
var fileName = sourceFile.replace(/.*\/templates\//, '');
if((/^components\//).test(fileName)){
/*if component hbs files are defined within sub-folder, remove the subfolder part*/
return "components"+fileName.substr(fileName.lastIndexOf('/'));
}
return sourceFile.replace(/.*\/templates\//, '');
}
}
I am not sure if this is an ember thing or something else. But putting the question out there hoping for some better solution.
UPDATE
It seems like the problem is caused because of the way we were loading components. We are basically using helper function that loads the component based on parameter we pass to it like :
Ember.Handlebars.helpers[componentToLoad].call(this, options);
I checked the Ember.Handlebars.helpers and it doesn't seem to have components that were defined within any folder within component.

Ember Computed Property using JS apply()

I would like to make the following code work if at all possible. The goal is to have a computed property that depends on a list of strings by using Javascripts "apply" method.
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
App.View = Em.View.extend
myProps: ['content.prop1', 'content.prop2']
myComputed : (->
return "super valuable things"
).property.apply(#get("myCompted"), #get("myProps"))
So far...no luck. It just seems to reject my most earnest desire to make this work.
Any help would be supremely appreciated!
Steve
You don't need to use the apply function and I'm not sure why you think you need to do that. The property function only wants an argument of the property it depends upon. In this case it is 'myProps'. You do not need to get that property either, Ember will work it all our for you.
Take a look at this jsfiddle and you'll see what I mean. You can do all your manipulation of the array inside the myComputed property and the array will have been made available for you already.
App.ApplicationView = Em.View.extend({
myProps: ['123', '456'],
myComputed : function () {
return this.get('myProps');
}.property('myProps')
});

ember js compare values in DOM if statement or at least in a View with value from DOM

I been trying to compare some values in handlebars if statement {{#if value == 'otherValue'}}, obviously unsuccessfully because handlebars do not like this and expecting a string, boolean, or function name. Well that would be ok, but then I tried to pass parameter in the function like you can do with {{action}} helper, and well that didn't workout either, got this in console
Error: assertion failed: You must pass exactly one argument to the if helper
So then I decided to do this in a View, even so ember js guides points that accessing template values in-scope is unusual and they provide only poor paragraph with no examples.
http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_accessing-template-variables-from-views
So when I tried to do this, I got a problem of accessing those variables, I tried this way this.get('controller.templateVariables') and via full path to View, but value was either undefined or .get() wasn't exists as a method.
So at this moment I decided to save variable in the DOM data property, but turns out this {{#view App.TabsView data-title="{{tab}}"}} is going to literately give me a string {{tab}} when I try to access it from View with this.get('data-title').
The only way left to me was to insert additional element inside view and store variable there, and afterwards access it with jQuery class selector. but element is not yet exist in the DOM at the time of isVisible function gets executed, so I have no access to values at that time. That explains why this.get('element') was returning null.
Similar examples on ember js mostly ends up with something like if (someLogic) {}, but how I can do any logic when there is no variables available to me.
Question
To simplify my question - is there a way how I can do such a thing in ember js? Simple as
// have objects stored in controller
var obj = [{title:'Tab1'}, {title:'Tab2'}, {title:'Tab3'}];
// loop via them in the DOM
obj.forEach(function(tab) {
// do this kind of comparison
if( tab.title == currentTab() ) {
// do something here
}
});
If that is not possible, then what would be the other way to achieve similar functionality?
You can write a handlerbar helper to do this
{{activeTab tab}}
Handlebars.registerHelper('activeTab', function(tab) {
})
See a question about the same issue
Active Tab
Or look at existing helpers to write your own
Bind Helper
Template Helper
I think the best way for me to demonstrate this is with a heavily commented JSFiddle that I've put together for you: http://jsfiddle.net/PbLnm/
Please ask any questions below if you're not sure about anything.
The main part which determines when to add the active class is in the computed property:
// Determine if the object we have for this view is the same as the activeTab's object. If it is the same, then this view is the current active tab.
active: function() {
return Boolean(this.get('parentView.activeTab') == this.get('tab'));
}.property('parentView.activeTab')

How to extend Ember's Object?

I'm looking for a way to extend Ember's Object to include some additional methods, so that they become available every object (View, ArrayController, etc) in my app.
Specifically, I want to define some methods that introduces some naming conventions of the controllers, models, views, templates, helpers, etc.
For example:
If the class name of the View is ArticlesListView then its associated model is Article, the controller action is named list within ArticlesController, the template is in app/articles named list.js.hjs...
The end result should be, for example, App.ArticlesListView.model() would return App.Article.
So how do I extend the core Ember Object?
Ember.Object.extend({ // <--- ???
model: function(context, params){
}
});
The answer to the general question of enhancing an existing object is to use reopen:
Ember.Object.reopen({
foo: function(){
return 'bar';
}
});
As to your more specific question, that is more challenging. An object doesn't typically know about the name of the property it is assigned to. You might be able to achieve your goal by traversing the properties of your namespaces (including App) and find the one that matches the current class. You could cache that property name for future performance.
Another approach would be to define a helper method for defining new models, controllers, etc. which you pass the name into. The method could handle creating the subclass, assigning it to a property of App, and setting an instance variable with the name.
Basically like Luke wrote, with one substantial difference. If you do:
Ember.Object.reopen({
foo: function(){
return 'bar';
}
});
Above replace (not extend - if foo method exist already) Object's property foo.
If you want to extend Ember Object's property foo you need to call _super() in order to include original implementation of the foo method.
Ember.Object.reopen({
foo: function(){
this._super(); // include original `foo` method stuff here
return 'bar'; // then add to `foo` method whatever you want
}
});
BTW, you can extend particular instance as well. var someObject = Ember.Object.extend({}); someObject.reopen({});