The problem
I have a view which displays a list of workers. This view has a button which calls a controller method named refresh. Originally I was setting the content to this list, but noticed that the UI would flicker, which would be especially annoying once this is run on a timer.
I managed to make everything work without flickering using the following:
App.WorkersIndexController = Ember.ArrayController.extend
refresh: ->
console.log 'refreshing workers'
# set a property to a new collection
#set 'refreshing', App.Worker.find()
# observer that watches the new collection load
update: (->
refreshing = #get 'refreshing'
return unless refreshing.get('isLoaded')
# what this question is about...
Ember.run =>
#set 'content', refreshing
).observes('refreshing.isLoaded')
As you can see I'm setting the refreshed content to a temporary variable, and observing the loading of the content and then updating the content. The content flickered less until I added a call to Ember.run which removed the flicker entirely.
I understand why the flicker is occurring, and I understand why Ember.run makes it go away but my question is what is the best way to solve this without resorting to Ember.run? I feel like calling Ember.run is kind of a last resort... Any help would be greatly appreciated!
Edit: Also I'm not using ember data. The find method looks like this:
App.Worker.reopenClass
find: ->
console.log 'loading workers'
workers = Em.A []
request = $.getJSON '/api/admin/workers', (data) =>
console.log ' -> loaded workers'
data.forEach (d) =>
worker = App.Worker.create(d)
worker.set 'isLoaded', true
workers.pushObject(worker)
request.error =>
console.log ' -> failed to load workers'
workers.set 'isFailed', true
request.complete =>
workers.set 'isLoaded', true
workers
I have avoided flicker in similar situations by having a fixed list who's objects I just change.
In the simplest case, lets' say you were displaying names, and knew that you knew you'd never have more than 3 elements you could make
App.names = [
Ember.Object.create({name:"", active:false}),
Ember.Object.create({name:"", active:false}),
Ember.Object.create({name:"", active:false})
]
and have your template be
{{#each App.names}}
{{#if active}}
<li> {{name}} </li>
{{/if}}
{{/each}}
you could then add names by running
App.names[0].set("name", "Frank");
App.names[0].set("active", true);
and update the list without any flicker. Only a single element in the list is changed at a time, so you don't have the flicker from the whole list being redrawn.
In practical terms you might need App.names to be an ArrayController so that you can handle expanding the list gracefully, but the gist of that will work.
Related
I'm trying to call a controller function inside a #each template to check if the model is selected. This needs to receive the model for each loop iteration.
jsbin is here http://emberjs.jsbin.com/bemos/4/edit
I've tried using the following template, but adding a parameter to {{controller.selected}} results in ember throwing an exception, or not doing anything (the result seems to be different in jsbin).
{{#each model}}
<li><a hres="#" {{action "clickItem" this}}>{{label}} -- selected is {{controller.selected name}} </a></li>
{{/each}}
My function in the controller looks as follows
selected: function(id) {
var list = this.get("selectedList");
console.log("Checking sel for", id);
if(list.contains(id))
return true;
return false;
}.property("selectedList")
In the jsbin, I expect to be able to click on the "Hello 1"/"Hello 2" elements, and this should change the part of the text that says "selected is false" to true. It should also update the number of items selected in the bottom. Currently, this shows 1 item is selected (since the default is selectedList:["2"]) however "Hello 2" says "selected is false" - this should say "true".
(I should add I'm pretty new to EmberJS as have been working with ExtJS for a few years!).
Thanks
Do you really have a problem in code ? I guess no, since your bin is working fine and even clickItem is working as well. In your local environment where you are facing this issue, I think you are not using same version of handlebars and ember. Please check if you are using below,
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.6.1/ember.js"></script>
Update 1 -
Here is the bin I tried to update - http://emberjs.jsbin.com/veqoqocu/1/edit
Its not fully functioning but fixes some issue.
There are multiple problems,
You should use ArrayController for lists, redesign your example otherwise selected property will never work the way you want. Here is a good example - http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/
push & pop needs to be pushObject and popObject
computer property on array works like - }.property('selectedList.[]')
http://jsbin.com/qoyudape/4/edit
Every time you click update, item gets inserted. I would like to blink item red for one second after insertion, like on stackoverflow when linking to answer, it blinks orange for a second https://stackoverflow.com/a/22645880/1175593
How do I do that ?
Only CSS doesn't work, because I want the effect only on newly added items, and not on initial load
I don't know CSS, but this, Using CSS for fade-in effect on page load, seems to be just fine...
http://jsbin.com/qoyudape/6/edit
note, the render stuff I did isn't necessary, you could have just as easily done it without it...
http://jsbin.com/qoyudape/8/edit
or when the item is inserted into the page, you can do time sensitive animation
App.BlinkingFooView = Em.View.extend({
didInsertElement: function(){
if(this.get('controller.b') % 2 === 0){
this.$().addClass('mod')
}
}
});
http://jsbin.com/qoyudape/11/edit
We have an example ember app where we CRUD recipes. For the route where we create a new Recipe, if we pass attributes into createRecord like so:
Cookbook.RecipesNewRoute = Ember.Route.extend
model: ->
Cookbook.Recipe.createRecord(title: "blank")
the recipe appears immediately in the list on the left side of the screen. Here's the jsbin to show what I mean
However, if I create the recipe without args a la
Cookbook.Recipe.createRecord()
I don't see the recipe appear in the list until I edit one of the attributes. This happens even though I've specified default values, as proved by this jsbin.
My question is: Why does this happen and how can I create a record with no params specified and still have it show up immediately?
Looking at your jsbin's the only thing you are missing is #get('store').commit() after you create the empty record.
Here an example:
Cookbook.RecipesNewRoute = Ember.Route.extend
model: ->
Cookbook.Recipe.createRecord()
#get('store').commit()
and here your working jsbin. I've added id's in parenthesis to the template so you can see the record is created correctly.
EDIT After your comment I've made some changes to the jsbin, basically what I've changed is this:
Cookbook.RecipesNewRoute = Ember.Route.extend
setupController: (controller) ->
controller.startEditing()
Cookbook.RecipesNewController = Ember.ObjectController.extend
startEditing: ->
#controllerFor('recipes').pushObject(Cookbook.Recipe.createRecord())
I've added the method startEditing for convenience (you can call it what ever you want).
In the startEditing method as you can see we get the recipesController and add a new record to the content array, this causes the new recipe to show up even if not commited to the store. Give it a try.
Hope it helps
So it seems pretty hacky, but here's how I got this working the way I wanted it to:
Cookbook.RecipesNewRoute = Ember.Route.extend
model: ->
recipe = Cookbook.Recipe.createRecord()
#controllerFor('recipes').get("content").addReference(recipe.get("_reference"))
recipe
I don't know if this is kosher, at all, but it seems to work. Feedback and improvements are welcome. I won't mark this as answered for a few days to give anyone else a chance to come up with something better.
look into the HTML and you will see that the <li> element has been created, but the title property of this model is empty, so the new recipe doesn't show up in the list.
<li>
<a id="ember413" class="ember-view" href="#/recipes/ember369">
<script id="metamorph-12-start" type="text/x-placeholder"></script>
<script id="metamorph-12-end" type="text/x-placeholder"></script>
</a>
</li>
I've got an ember application that needs to manage multiple chat windows. A window for each active chat is created within an {{#each}} loop. This is straightforward enough. The place that I'm having trouble is sending the chat message when the user presses enter.
The window looks like this
{{#each chats}}
... stuff to display already existing chats...
{{view Ember.TextField valueBinding="text" action="sendChat"}}
<button {{action sendChat this}}> Send </button>
{{/each}}
This works fine for the button, since I can pass this to it. By default the function defined in the textfield view action just gets the text within that textfield, which is not enough in this case. Since there can be multiple chat windows open, I need to know which window the message was typed into. Is it possible to pass this to the textfield action function? (or can you suggest a different way to solve this problem?)
Add contentBinding="this" to the definition of the view, like:
{{view Ember.TextField valueBinding="text" action=sendChat contentBinding="this"}}
EDIT
Ember master already has this change, but the official downloadable verstion still don't.. so you will need to subclass the Ember.TextField and change its insertNewline to achieve required functionality:
App.ActionTextField = Em.TextField.extend({
insertNewline: function(event) {
var controller = this.get('controller'),
action = this.get('action');
if (action) {
controller.send(action, this.get('value'), this);
if (!this.get('bubbles')) {
event.stopPropagation();
}
}
}
});
After that, the action handler will receive additional argument, the view:
{{view App.ActionTextField valueBinding="text" action=sendChat myfieldBinding="this"}}
and in controller:
sendChat: function (text, view) {
var myField = view.get('myfield');
//do stuff with my field
}
You may use ember master instead of subclassing Ember.TextField..
I hope the ember guys will release the next version soon..
I know this question has been answered but I said let me add some information that may help out someone in the situation of actions and TextField. One word "Component". TextField in Ember is a Component so if you think of TextField from that perspective it may help when it comes to sending actions and using TextField in an application.
So when you say App.SomeTextField = Ember.TexField.extend({...});App.SomeTextField is subclassing Ember.TextField (remember which is a component). You could add your logic inside and that works and you could access it from your template such as {{view App.SomeTextField}}
You may be thinking I see the word 'view' this guy sucks, TextField is a View. Well, it is sort of a View because Ember Components are a subclass of Ember.View so they have all that Views have. But there are some important things to keep in mind Components un-like Views do not absorb their surrounding context(information/data), they lock out everything and if you want to send something from the outside surrounding context you must explicitly do so.
So to pass things into App.SomeTextField in your template where you have it you would do something like {{view App.SomeTextField value=foo action="sendChat"}} where you are passing in two things value, and action in this case. You may be able to ride the fine line between View/Component for a bit but things come crashing why is your action not sending?
Now this is where things get a little trippy. Remember TextField is a Component which is subclassed from View but a View is not a Component. Since Components are their own encapsulated element when you are trying to do this.get('controller').send('someAction', someParam), "this" is referring to the Component its self, and the controller is once again the component its self in regards to this code. The action that you are hoping will go to the outside surrounding context and your application will not.
In order to fix this you have to follow the protocol for sending actions from a Component. It would be something like
App.SomeTextField = Ember.TextField.extend({
//this will fire when enter is pressed
insertNewline: function() {
//this is how you send actions from components
//we passed sendChat action in
//Your logic......then send...
this.sendAction('sendChat');
}
});
Now in the controller that is associated with where your SomeTextField component/view element is you would do
App.SomeController = Ember.Controller.extend({
//In actions hash capture action sent from SomeTextField component/view element
actions: {
sendChat: function() {
//Your logic well go here...
}
}
});
Now I said to think of TextField as a Component but I have been riding the tail of the view and declaring {{view AppSomeTextField...}}. Lets do it like a component.
So you would have in your template where you want to use it
//inside some template
`{{some-text-field}}`
Then you get a specfic template for the component with the name:
//template associated with component
<script type="text/x-handlebars" data-template-name="components/some-text-field">
Add what you want
</script>
In your JS declare your component:
//important word 'Component' must be at end
App.SomeTextFieldComponent = Ember.TextField.extend({
//same stuff as above example
});
Since we on a role you could probably get the same functionality using Ember input helpers. They are pretty powerful.
{{input action="sendChat" onEvent="enter"}}
Welp hopefully this information will help someone if they get stuck wondering why is my action not sending from this textField.
This jsBin is a sandBox for Components/Views sending actions etc....Nothing too fancy but it may help someone..
http://emberjs.jsbin.com/suwaqobo/3/
Peace, Im off this...
I'm having trouble doing things on Ember, and I'm sure it's because I still haven't fully grasped "the Ember way" of doing things, and I'm a trying to do a couple of things that get out of the scope of standard tutorials out there.
I'm developing some sort of textfield-with-suggestions component that will appear in every single page of my web app. I won't ask here all the details on how to do it, but just a couple specific things I am having trouble accomplishing from the start. The following are the relevant code fragments of what I have so far.
// handlebars template: searchbar.hbs
{{view App.SearchField viewName="searchField"}}
<div class="results" {{bindAttr class="searchField.hasFocus"}}>
This is where the results for whatever the user has typed are shown.
</div>
// coffeescript source of the views: searchbar.coffee
App.SearchBar: Ember.View.extend
templateName: 'searchbar'
App.SearchField: Ember.TextField.extend
placeholder: 'Search'
hasFocus: false
eventManager: Ember.Object.create
focusIn: (event, view) ->
#set('hasFocus', true)
focusOut: (event, view) ->
#set('hasFocus', false)
// somewhere in the layout of the pages in my app
<div id="header">
{{App.SearchBar}}
</div>
This will probably also need a controller, but I haven't developed it yet, because I don't know where it fits within this setup yet.
First, I want the suggestions popup panel to appear as soon as the search field obtains focus. That's the reason of my attempt above to implement a hasFocus property on the searchfield. But how do I achieve making my div.results panel react to the focus state of the input field?
In general, and this is the core of my question here, how do I connect all things to develop this component? If the answer is by attaching it to a controller, then how do I setup a controller for this component, and how do I specify that it is the controller for it, so that it acts as a context for everything?
I think you have to clearly separate concerns. Stuff related to the view (ie manipulating the DOM with jquery) should stay in the view. Stuff related to the application state should be in the controller. Though,in your case, I think you can simply bind an observer on the hasFocus property, and show the suggestions. Something like:
App.SearchField: Ember.TextField.extend
placeholder: 'Search'
hasFocus: false
eventManager: Ember.Object.create
focusIn: (event, view) ->
#set('hasFocus', true)
focusOut: (event, view) ->
#set('hasFocus', false)
focusDidChange: (->
if #hasFocus
$('.results')... // here I let you do the suggestion stuff
// based on data retrieved from the controller
else
// probably hide the results div.
).observes('hasFocus')