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.
Related
EmberJS version 1.11.3
I have a button that when pressed, sends an Ajax query to my server and returns some data. I would like to refresh the view under this button with the new data each time.
Here's the simplified handlebar template :
<script type="text/x-handlebars" data-template-name='query'>
<form class="form-vertical" {{action "sendQuery" model}}>
<div class="control-group">
{{input value=this.totoQuery type="text" placeholder="Enter an SQL query...."}}
</div>
<button type="submit" class="btn">Send ! </button>
</form>
</script>
Here's the controller for that template:
App.QueryController = Ember.Controller.extend({
totoQuery: '',
actions: {
sendQuery: function(model, route) {
var promise = asyncAjax(queryservice + '?query=' + this.totoQuery);
promise.success(function(data) {
model=data.entries;
})
}
}
})
The Ajax call works fine and the model defined in the route too ( i tested with a dummy model ). However when i try to update the model with :
model=data.entries; //The template does not update with it!!! The template still displays the data from the dummy model.
I can't modify it with this.set('model',data) because "Undefined function!! this.set"..
My dummy model :
App.QueryRoute = Ember.Route.extend({
model: function() {
return [{
'title': 'A book title like Harry Potter of Fifty Sha... nvm.. '
}];
}
});
Any advice is greatly appreciated!!!
PS: I've been spending way too much time on this :(
You can get the model inside controller action in the following way:
sendQuery: function() {
var self = this,
totoQuery = this.get('totoQuery');
var promise = asyncAjax(queryservice + '?query=' + totoQuery);
promise.done(function(data) {
$.each(data.entries, function (index, entry){
self.get('model').pushObject(entry);
});
})
}
And you do not need to pass model as parameter in action. You can do something like this:
<form class="form-vertical" {{action "sendQuery"}}>
The controller automatically inherit the model property. And one thing is, getting model property using this.totoQuery will be deprecated in Ember. Instead use model.totoQuery.
If you are struggling too much, you can also render something from your controller instead of the model directly from the route, that way you can update the variable from controller and would change easily.
App.QueryController = Ember.Controller.extend({
queryResponse: [],
totoQuery: '',
actions: {
sendQuery: function(model, route) {
var self = this;
var promise = asyncAjax(queryservice + '?query=' + this.totoQuery);
promise.success(function(data) {
//model=data.entries;
self.set('queryResponse', data.entries);
})
}
})
And in your template you can render it directly
<ul>
{{#each item in controller.queryResponse}}
<li>item.name</li>
{{/each}}
</ul>
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.
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}}
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/
Say I have a model App.Page, an ArrayController of App.Page, and an App.PageView to render each App.Page.
I'm trying to figure out how to best implement App.MyPagesView so it works like so:
if App.showAllPages is true: I want MyPagesView to contain an App.PageView(s) for displaying each of the App.Page in App.pages
Else: I want MyPagesView only show one App.PageView, bound to App.pages.currentPage.
The most straightforward implementation that occurs to me is using a template like so:
// MyPagesViewApproach1
{{#unless App.showAllPages}}
{{view App.PageView pageBinding="pages.currentPage"}}
{{else}}
{{#each pages}}
{{view App.PageView pageBinding="this"}}
{{/each}}
{{/unless}}
But won't this create new views for the existing models every time the user toggles showAllPages on and off? Also, I get emberJS warnings about performance issues when I try to use this template.
The PageView(s) could be quite complex and expensive to render. I'd really like to create a PageView once for each Page, and just remove/hide the irrelevant PageViews from the DOM when they're not in use.
App = Ember.Application.create({
showAllPages: false,
pages: Ember.ArrayController.create({
content: []
currentPage: null
}),
ready: function () {
this.pages.pushObject(App.Page.create({title: 'Page One'});
this.pages.pushObject(App.Page.create({title: 'Some Other Page'});
this.pages.pushObject(App.Page.create({title: 'Grrreatest Page Evar'});
this.pagesController.set('currentPage',
this.pagesController.get('firstObject'));
}
});
App.Page = Ember.Object.extend({
title: null
// etc, etc...
});
App.PageView = Ember.View.extend({
templateName: 'page',
page: null // should be bound to an App.Page
});
App.MyPagesView_Approach1 = Ember.View.extend({
pagesBinding: 'Elicitation.pages'
// ???
});
App.MyPagesView_Approach2 = Ember.ContainerView.extend({
// ???
});
And my HTML:
<script type="text/x-handlebars" data-template-name="page">
The title of this page is {{ page.title }}
</script>
{{view App.MyPagesView }}
To recap, what's the proper EmberJS-y way to implement MyPagesView so it responds to App.showAllPages without re-creating all the views each time its toggled?
Should it be some sort of ContainerView? Or should I use the unless/else template shown at the top of the question? Or something entirely different? I feel like a really simple solution exists in EmberJS, but its elluding me.
Here's the best I've come up with, encapsulated as a re-usable View class called "CurrentCollectionView". I'm using CollectionView, and using view.set('isVisible') to hide/show appropriate child views. Basically use it like a CollectionView, but you can set currentContent to hide all but one element of content, or use showAllContent to override currentContent.
App.CurrentCollectionView = Ember.CollectionView.extend({
showAllContent: false,
currentContent: null,
currentContentChanged: function () {
console.log("Elicitation.PagesView.currentContentChanged()");
var showAllContent = this.get('showAllContent');
if (Ember.none(showAllContent) || !showAllContent) {
var contents = this.get('content');
var currentContent = this.get('currentContent');
this.get('childViews').forEach(function (view, i) {
var isVisible = contents.objectAt(i) == currentContent;
view.set('isVisible', isVisible);
});
} else {
this.get('childViews').forEach(function (view) {
view.set('isVisible', true);
});
}
}.observes('currentContent', 'showAllContent', 'childViews')
});
An example of using CurrentCollectionView to implement MyPagesView:
App.MyPagesView = App.CurrentCollectionView.extend({
itemViewClass: App.PageView,
contentBinding: 'App.pages',
currentContentBinding: 'App.pages.currentPage',
showAllContentBinding: 'App.showAllPages',
});
or as using it inline as a template:
{{view App.CurrentCollectionView itemViewClass="App.PageView" contentBinding="App.pages" currentContentBinding="App.pages.currentPage" showAllContentBinding="App.showAllPages"}}
Hope somebody else finds this useful and/or can improve on it (please!)