I have a notification box in my Ember.js app that will change the text, styling, and buttons based on the value of a Status model. How do I switch just the view (or template) for the notification box? I don't want to transitionTo because the rest of the page shouldn't update, just that notification box.
I have a JSFiddle with a complete example. But here's the relevant parts to glance at:
The main template will render the notification bar ("statusState") and the regular view data.
<script type="text/x-handlebars" data-template-name="status">
{{render "statusState" content}}
<p>
Other information not related to the status state, but still related to status goes here.
</p>
{{outlet}}
</script>
There's a separate template for each Status state ("pending", "ready", "finished"):
<script type="text/x-handlebars" data-template-name="status/pending">
<div style="background-color: grey">
The status is pending.
</div>
</script>
<script type="text/x-handlebars" data-template-name="status/ready">
<div style="background-color: blue">
The status is ready.
</div>
</script>
<script type="text/x-handlebars" data-template-name="status/finished">
<div style="background-color: green">
The status is finished.
</div>
</script>
The Status object is nothing special, and belongs to the StatusController:
App.StatusRoute = Ember.Route.extend({
model: function() {
return App.Status.create();
}
});
App.Status = Ember.Object.extend({
value: 0
});
My first attempt was to change the templateName of the view whenever the Status' value changes. But this feels hacky and doesn't even seem to respond to Status value changes:
App.StatusStateView = Ember.View.extend({
templateName: function() {
var templateName = 'status/pending';
var value = this.get('controller.model.value');
if (value === 1) {
templateName = 'status/ready';
}
else if (value === 2) {
templateName = 'status/finished';
}
return templateName;
}.property('controller.model.value')
});
In short, how would I change the view or template for just part of the page based on a model value that has more than 2 choices?
Here's how I would approach this. Add boolean computed properties to the model or controller and use a single template with the {{#if }} helper. e.g.
App.Status = Ember.Object.extend({
value: 0,
ready: function(){
return this.get('value') === 1;
}.property('value'),
finished: function(){
return this.get('value') === 2;
}.property('value'),
pending: function(){
var value = this.get('value');
return value !== 1 && value !== 2;
}.property('value')
});
And in a single template:
{{#if pending}}
I'm pending
{{/if}}
{{#if ready}}
I'm ready!
{{/if}}
{{#if finished}}
I'm finished.
{{/if}}
Related
I am having problem while trying to update the model values, when PendingActionController.updateStage method is called I need it to update the related model & reflect the updated values. If I create another method in PendingController like ShowMessage it displays the alert.
Please explain What approach should I use?
For example, following is the code:
<script type="text/x-handlebars" id="pending/_actions">
<div class="content-actions">
<h2>Pending Actions</h2>
<ul>
{{#each pendingstages}}
<li>
{{#unless refreshingStage}}
{{render 'pendingAction' this}}
{{/unless}}
</li>
{{/each}}
</ul>
</div>
</script>
<script type="text/x-handlebars" id="pendingAction">
<div class="actionsBox">
<div class="actionsBar">
<div {{bindAttr class=":actionStatus completed:blue:green"}} {{action updateStage this}}> </div>
</div>
<div class="clear-both"></div>
</div>
</script>
PendingController:
App.PendingController = App.BaseObjectController.extend(App.ActionsControllerMixin, {
needs: ['application'],
postRender: function () {
//Some code here....
},
pendingstages: function(){
return App.PendingStage.find({Id: this.get('model.id')});
}.property('model.id', 'model.#stages.completed', 'refreshStage'),
ShowMessage: function(){
alert('Inside Sohw message.');
},
});
PendingActionController
App.PendingActionMixin = {
isEditing: false,
canDelete: true,
canEdit: true,
toggleIsEditing: function(){
this.toggleProperty('isEditing');
}
};
App.PendingActionController = App.BaseObjectController.extend(App.PendingActionMixin, {
needs: 'pending',
postRender: function(){
//some code here...
},
updateStage: function(stage){
var self = this;
this.get('controllers.pending').send('pendingstages');
},
});
EDIT (1):
Followignt are the versions of Ember & ember-data:
ember-1.0.0-master.js
ember-data-master.js: CURRENT_API_REVISION: 12
Problem can be solved by using store.fetch instead of store.find.
store.fetch always calls the API, whether that particular data exists in local ember-data store or not. Use it like this..
pendingstages: function(){
return App.PendingStage.fetch({Id: this.get('model.id')});
}.property('model.id', 'model.#stages.completed', 'refreshStage'),
See ember-data/store.js code. It is deprecated now. But you'll find new methods instead of this.
Hello I've been stuck for days how to display a post's delete button only for the post's author in Ember.js (I'm using ember-cli to build this). I don't know where to put the logic of "When hovering a post (list), if the post's author is equal to currently logged in user, then display the delete button" I am lost. Please help me.
in template app/templates/posts.hbs
{{#each}}
<div class="eachPost">
{{#view 'posts'}}
<div class="postProfilePhoto">
{{#link-to 'users' }}
<img src="" alt="Profile Photo">
{{/link-to}}
</div>
<div class="eachPostContent">
<p class="postAuthor"><strong>{{user.id}}</strong></p>
<p class="postContent">{{body}}</p>
<span class="timePosted"><em>somtimes ago</em></span>
{{#if view.entered}}{{#if isAuthor}}
<a class="deletePost" {{action "removePost" this}}>Delete</a>
{{/if}}{{/if}}
</div>
{{/view}}
</div>
{{/each}}
in views app/views/posts.js
var Posts = Ember.View.extend({
classNames: ['eachPostContent'],
mouseEnter: function(event){
this.set('entered', true);
this.get('controller').send('isAuthor', this.get('post').user);
},
mouseLeave: function(){
this.set('entered', false);
}
});
export default Posts;
in controller app/controllers/posts.js
var PostsController = Ember.ArrayController.extend({
...
isAuthor: function(user){
if(this.get('session').user !== null && user === this.get('session').user){
return true;
} else {
return false;
console.log('You are not author');
}
}
}
});
export default PostsController;
SOLVED
in app/templates/posts.hbs
{{#each itemController="post"}}
<div class="eachPost">
created app/controllers/post.js
var PostController = Ember.ObjectController.extend({
isAuthor: function(){
return this.get('user') === this.get('session').user;
}.property('user')
});
export default PostController;
delete following code from app/views/posts.js
this.get('controller').send('isAuthor', this.get('post').user);
and deleted isAuthor function from app/controllers/posts.js
As mentioned above, you'll want to use an itemController
var PostsController = Ember.ArrayController.extend({
itemController:'post',
...
});
And then in the itemController you will create a computed property that checks the user id against the author id of the post
var PostController = Ember.ObjectController.extend({
isAuthor: function(){
//assuming the user property exists on an individual post model
return this.get('user') === this.get('session').user;
}.property('user')
})
In my Ember template, I want to be able to loop over each item coming from the model (an array) and if the value is 'blue', display some text next to the value.
My template looks like this:
<script type="text/x-handlebars" data-template-name="index">
<h2>Loop over colors</h2>
<ul>
{{#each color in model}}
<li>{{color}} {{#if isBlue}} - Its Blue!{{/if}} </li>
{{/each}}
</ul>
</script>
And my app.js file looks like this:
App = Ember.Application.create({});
App.Router.map( function() {
this.resource( 'about');
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
App.IndexController = Ember.ArrayController.extend({
isBlue: function() {
return this.get('content') == 'blue';
}.property()
});
I'm using this.get('content') because I thought that was supposed to be a reference to the actual model data.
I've tried numerous variations of the code but I'm now blocked. Hope someone can help.
You are defining the isBlue property on the IndexController, which is an ArrayController, and not on each item in the content. You can instruct the {{each}} helper to use an itemController for each item in the loop. By doing that you are able to define additional computed properties, that are not present in the original objects, and make them available within the each loop:
<script type="text/x-handlebars" data-template-name="index">
<h2>Loop over colors</h2>
<ul>
{{#each color in model itemController="color"}}
<li>{{color}} {{#if isBlue}} - Its Blue!{{/if}}</li>
{{/each}}
</ul>
</script>
App.ColorController = Ember.ObjectController.extend({
isBlue: function() {
return this.get('content') === 'blue';
}.property('content')
});
You can also check out JSBIN.
ArrayController means that the content property is an array, not just an object. Also, you don't want to access content directly. Controllers proxy their models, so use the controller as if it was an array. So your isBlue function is wrong in a few ways. It's probably possible to do what you want using the isBlue property, but I would use something like this:
colorItems: Em.computed.map('#this', function(color) {
return {
color: color,
isBlue: color === 'blue'
};
})
Then, in your template:
{{#each colorItems}}
<li>
{{color}}
{{#if isBlue}}
- It's Blue!
{{/if}}
</li>
{{/each}}
I have an index.html file which has a button with an action handler.
<button {{action 'modules'}} > press </button>
But nothing happens when I press the button.
please look at the code at jsbin:
http://jsbin.com/OREMORe/1/edit
Feel free to correct any other inconsistencies.
Appreciate any help. Thx.
You'd do it like this. Please note that there were a few things wrong with your code:
1) You handlebars code with your button in it was not wrapped in a handlebars script tag
2) You're 'modules' template was in the application template
3) You did not include valid links to the necessary libraries
4) There was not rootElement specified for the Application
5) You tried to access ModulesController in the ApplicationRoute. I inserted a redirect to 'modules'
6) You tried to access module in your {{#each}} loop, but that does not exists in your model. What you want is content.
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="modules">
<ul>
{{#each content}}
<li>{{this}}</li>
{{/each}}
<button {{action 'modules'}} > press </button>
</ul>
</script>
And:
var Test1 = Ember.Application.create({
rootElement: 'body',
ready: function() {
console.log("App ready1");
}
});
Test1.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('modules');
}
});
Test1.Router.map( function() {
this.route('modules');
});
Test1.ModulesController = Ember.Controller.extend({
actions: {
modules: function() {
console.log('clicked the button');
/* this.transitionToRoute('modules');*/
}
}
});
Test1.ModulesRoute = Ember.Route.extend({
model: function() {
return ['item1', 'item2'];
}
});
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.