I have two controllers which both load to the same outlet, so only one can be active at one time. Both observe a property on a third controller like this:
App.SearchController = Ember.ObjectController.extend({
needs: ['navigation'],
updateResults: function () {
console.log('load search data');
}.observes('controllers.navigation.search')
});
Full sample
http://jsfiddle.net/FMk7R/1/
When the property changes some data is fetched. If I click on both links so that both are loaded, then when the property changes, both controllers receive the observes event and load the data. I'd like to load the data only in the one which is visible.
How can I figure out which controller is currently active and load the data only in the active one?
Ideally your controllers should not know that they are active. One alternative is to invert the relationship, so that NavController is responsible for changing a query property of the "active" controller.
** UPDATE - Adding example based on comment **
App.SearchRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('navigation').set('active', controller);
}
});
App.ImagesRoute = Ember.Route.extend({
setupController: function(controller) {
this.controllerFor('navigation').set('active', controller);
}
});
App.SearchController = Ember.ObjectController.extend({
loadResults: function (query) {
console.log('loading web search data for: ', query);
}
});
App.ImagesController = Ember.ObjectController.extend({
loadResults: function (query) {
console.log('loading image search data for: ', query);
}
});
App.NavigationController = Ember.ObjectController.extend({
search: '',
active: null,
searchDidChange: function() {
this.get('active').loadResults(this.get('search'));
}.observes('search', 'active')
});
See http://jsfiddle.net/F3uFp/1/
Another alternative is to use computed properties instead. Ember will only refresh computed properties that are actually required to render the active view. For example:
App.SearchController = Ember.ObjectController.extend({
needs: ['navigation'],
results: function () {
console.log('loading web search data');
return("web search results");
}.property('controllers.navigation.search')
});
See updated fiddle here: http://jsfiddle.net/ZTnmp/
http://jsfiddle.net/FMk7R/1/
Related
I have a certain route that shows a list of projects, and it gets initial data from my RESTAdapter based on who the user is.
I am now implementing a search function that will issue a new API call so the user can get records besides the default ones for them, and the response should replace the model for that route. I have all that working, but I'm not sure how to do a loading or progress indicator (as the response from the database could potentially take 5-10 seconds depending on the amount of data). I know about loading substates, but in this case I'm not transitioning between routes. I just want to have at minimum a spinner so the user knows that it's working on something.
Would anyone that's done this before be willing to share how they handled a)replacing the model with new data, and b)keeping the user informed with a spinner or something?
Form action called when user clicks the Search button
searchProjects: function() {
var query = this.get('queryString');
if (query) {
var _this = this;
var projects = this.store.find('project', {q: query});
projects.then(function(){
_this.set('model', projects);
});
}
}
a) replacing the model with new data
You don't need to do anything. If you sideload records properly from the backend, Ember will automatically update them on the frontend.
b) keeping the user informed with a spinner or something
The loading substate is an eager transition. Ember also supports lazy transitions via the loading event.
You can use that event in order to display the spinner.
Here's an example from the docs:
App.ApplicationRoute = Ember.Route.extend({
actions: {
loading: function(transition, route) {
showSpinner();
this.router.one('didTransition', function() {
hideSpinner();
});
return true; // Bubble the loading event
}
}
});
UPD1
I need to do at least what I'm doing right? Setting the model to the response?
You need to reflect the search in the URL via query params. This will let the router automatically update the model for you.
what I would put in showSpinner to affect stuff on the page (like, can I use jQuery to show or hide a spinner element?), or show the actual loading substate.
I would set a property on that page's controller:
App.IndexRoute = Ember.Route.extend({
queryParams: {
search: {
refreshModel: true
}
},
model () {
return new Ember.RSVP.Promise( resolve => setTimeout(resolve, 1000));
},
actions: {
loading (transition, route) {
this.controller.set('showSpinner', true);
this.router.one('didTransition', () => {
this.controller.set('showSpinner', false);
});
return true;
}
}
});
App.IndexController = Ember.Controller.extend({
queryParams: ['search'],
search: null,
showSpinner: false,
});
Demo: http://emberjs.jsbin.com/poxika/2/edit?html,js,output
Or you could simply put the spinner into the loading template, which will hide obsolete data:
http://emberjs.jsbin.com/poxika/3/edit?html,js,output
Or you could put your spinner into the loading template:
Just in case others want to see, here's my working code based on #lolmaus's answers.
These Docs pages were helpful as well
Route's queryParams and Find method
Controller
//app/controllers/project.js
export default Ember.ArrayController.extend({
queryParams: ['q'],
q: null,
actions: {
searchProjects: function() {
var query = this.get('queryString');
if (query) {
this.set('q', query);
}
}
}
})
Route
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params) {
if (params.q) {
return this.store.find('project', params);
} else {
return this.store.findAll('project');
}
},
queryParams: {
q: {
refreshModel: true
}
},
actions: {
loading: function(/*transition, route*/) {
var _this = this;
this.controllerFor('projects').set('showSearchSpinner', true);
this.router.one('didTransition', function() {
_this.controllerFor('projects').set('showSearchSpinner', false);
});
return true; // Bubble the loading event
}
}
});
My issue now is that when I use the parameter query, it works great, but then if I clear the query (with an action, to effectively "go back") then the records fetched by the query stay in the store, so when it does a findAll() I have both sets of records, which is not at all what I want. How do I clear out the store before doing findAll again?
I read at
http://emberjs.com/guides/controllers/
the following code:
I have a search box and want to send the value of the search box to the SearchController.
App.ApplicationController = Ember.Controller.extend({ // the initial
value of the `search` property search: '',
actions: {
query: function() {
// the current value of the text field
var query = this.get('search');
this.transitionToRoute('search', { query: query });
} } });
How can i get the query parameter in the SearchController and then show it in search.hbs?
I am working with ember- cli.
The router is
import Ember from 'ember';
var Router = Ember.Router.extend({
location: NENV.locationType
});
Router.map(function() {
this.route('search');
});
export default Router;
I set up a route under routes/search.js
export default Ember.Route.extend({
model : function (params) {
console.debug("hi");
return params;
},
setupController: function(controller,model) {
var query = model.query;
console.debug("query is");
console.debug(query);
}
});
When debugging i get an error:
ember More context objects were passed than there are dynamic segments
Thanks,
David
You need to define your search route to be dynamic, so if you change your route definition to something like this
Router.map(function() {
this.resource('search', {path: '/search/:query});
})
This should work as you are expecting. Let me know if anything.
Cheers!
I am using a radialProgress as a jQuery plugins (homemade), and I need to implement it for ember but I have some issue to do that.
Quick explanation for the plugins :
var chart = $(yourElement).pieChart(options); // initialise the object to an element
chart.setCompleteProgress( complete, false ); // set how many item you have to complete the task
chart.incrementProgress(); // increment + 1 every time you call it
It's a very simple progress pie.
In my case my task are located inside my controller, but the chart as to select a dom element so I need to initialise it inside my view.
My task in the controller are called from the router from the setupController to reload the model over time.
Here is a small sample of what I would like to do :
App.ApplicationRoute = Ember.Route.extend({
setupController: function(controller) {
var promise = controller.getModel();
this._super(controller, promise);
}
})
App.ApplicationController = Ember.ArrayController.extend({
getModel: function() {
// chart.setcompleteProgress();
// A lot of code are here to get some data
// chart.incrementProgress();
return newModel;
}
})
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
var chart = $(element).pieChart(opts);
}
})
I don't know how to pass the chart object from the view to the controller to be able to have access to my plugin function.
Che chart won't be inserted into the DOM until the didInsertElement therefore you can't attempt to manipulate it in the route during setupController etc. I'd suggest creating a method in the controller setupChart and calling that on didInsertElement.
App.ApplicationView = Ember.View.extend({
prepPieChart: function() {
var chart = $(element).pieChart(opts);
this.get('controller').setupPieChart(chart);
}.on('didInsertElement')
})
App.ApplicationController = Ember.ArrayController.extend({
setupPieChart: function(chart) {
chart.setcompleteProgress();
// A lot of code are here to get some data
chart.incrementProgress();
}
})
All that being said, maybe it belongs in the view, but I'm not sure of what you're completely doing.
I'm trying to develop this single-module application with a master-detail relationship between two routes. The master is supposed to have a model which is initially loaded and subsequently updated with a single Server-Side-Events entry point. I have the following code so far:
var App = Ember.Application.create({
rootElement: '#content'
});
App.Router.map(function() {
this.resource('receptores', {path: '/'}, function() {
this.resource('receptor', {path: ':user_id'});
});
});
App.ReceptoresController = Ember.Controller.extend({
init: function() {
var sse = new EventSource('/push');
var controller = this;
sse.addEventListener('update-hhs', function(e) {
controller.set('model', JSON.parse(e.data).receptores);
});
}
});
App.ReceptorRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('receptores').findBy('id', params.user_id);
}
});
Loading the 'receptores' works and currently the data loads fine. Clicking on a link-to generated link as http://localhost:5003/#/skkkd, for example, will load the 'receptor' route and the detail data as well (the detail is just a subset of the same data loaded on the master).
If I try reloading at a detail url or entering http://localhost:5003/#/skkkd directly I'll get the following exception:
Error while loading route: TypeError: Cannot read property 'findBy' of undefined
at App.ReceptorRoute.Ember.Route.extend.model (http://localhost:5003/monitoreo/static/js/receptores.js:34:43)
at superWrapper [as model] (http://localhost:5003/static/js/ember-1.5.1.js:1292:16)
at Ember.Route.Ember.Object.extend.deserialize (http://localhost:5003/static/js/ember-1.5.1.js:36570:19)
at http://localhost:5003/static/js/ember-1.5.1.js:32972:57
at http://localhost:5003/static/js/ember-1.5.1.js:33464:19
at invokeResolver (http://localhost:5003/static/js/ember-1.5.1.js:9646:9)
at new Promise (http://localhost:5003/static/js/ember-1.5.1.js:9632:9)
at Router.async (http://localhost:5003/static/js/ember-1.5.1.js:33463:16)
at Object.HandlerInfo.runSharedModelHook (http://localhost:5003/static/js/ember-1.5.1.js:32971:16)
at Object.UnresolvedHandlerInfoByParam.getModel (http://localhost:5003/static/js/ember-1.5.1.js:33058:19)
I know the problem is the init hook isn't being called for ReceptoresController. I think what I should do is implement a model hook on either a ReceptoresRoute or the controller and load initial data through a jQuery.get() and have the server-side-events only update. But then where do I initialize the SSE?
Edit:
It turns out I misunderstood how the model hook works on child routes. In my case, I normally view the detail page through a link-to link, so Ember already provides the model for receptor. The model hook is never called in that case. It is only called when trying to access the detail page directly which is where it fails. So my problem remains.
My whole problem boils down to where I should place the SSE initialization so that both routes can have access to the same model regardless of which was loaded.
Alright, this is my solution. Pieced together from a couple questions here. Namely https://stackoverflow.com/a/21988143/410224 and https://stackoverflow.com/a/21752808/410224. Hope it helps someone out.
var App = Ember.Application.create({
rootElement: '#content'
});
App.Router.map(function() {
this.resource('receptores', {path: '/'}, function() {
this.resource('receptor', {path: ':user_id'});
});
});
App.ReceptoresRoute = Ember.Route.extend({
model: function() {
var deferredData = Ember.Deferred.create();
var data = [];
var sse = new EventSource('/push');
sse.addEventListener('update-hhs', function(e) {
var receptores = JSON.parse(e.data).receptores;
receptores.forEach(function(r) {
var item = data.findBy('id', r.id);
if (typeof(item) != 'undefined') {
data.replace(data.indexOf(item), 1, [r]);
} else {
data.pushObject(r);
data.sortBy('full_name');
}
});
deferredData.resolve(data);
});
return deferredData;
}
});
App.ReceptorRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('receptores').findBy('id', params.user_id);
}
});
Ember seems to be removing the query string from the URL.
I've stepped through the code, and I know for sure that I'm setting the flag correctly:
<script>
ENV = {FEATURES: {'query-params-new': true}};
</script>
<script src="js/libs/ember.prod-1.6.0beta+canary.js"></script>
But when my route loads, the query string is being removed, and I can't access the queryParams.
Here's my router:
App.Router.map(function () {
this.resource('simpleSearch', {path: 'simplesearch'}, function () {
this.resource('simpleSearchOption', {path: ':simpleSearchOption_id'});
this.resource('simpleSearchResults', {path: 'results'});
});
});
When I attempt the following url (which is based on the URL from the guide), the query string is stripped: [webserver]/#/simplesearch/0?simplesearch[height]=10
When the model is first initialized by the route, it builds out what the query parameters will be, and the controller's queryParams property is set by the route:
App.SimpleSearchRoute = Ember.Route.extend({
model: function () {
var optionsForSimpleSearchModel = [];
for (var i = 0; i < App.SimpleSearchOptions.length; i++) {
optionsForSimpleSearchModel[i] = App.SimpleSearchOption.create(App.SimpleSearchOptions[i]);
}
return App.SimpleSearch.create({
'simpleSearchOptions': optionsForSimpleSearchModel,
'numOfOptions': App.SimpleSearchOptions.length
});
},
setupController: function (controller, model) {
console.log(model.get('queryParams'));
controller.set('queryParams', model.get('queryParams'));
controller.set('model', model);
}
});
BUT, I've also tried explicitly setting the queryParams in the controller:
App.SimpleSearchController = Ember.ObjectController.extend({
height: null,
queryParams: ['height'],
...
I'm not sure what else I'm missing...
How does this thing really work?
It seems that I'm a silly dude.
I needed to add the params argument to the model() function:
model: function (params) {
console.log(params);
//{height: null} when queryParams['height'] is explicitly set in the controller
Is there any way that I can dynamically generate the queryParams for the controller before Ember decides there are none, if I don't set them explicitly?
Also, my URL was incorrect, (as is the one in the Ember guide). It should have been:
[webserver]/#/simplesearch/0?height=10
instead of
[webserver]/#/simplesearch/0?simplesearch[height]=10
In your model hook you need to pass in the params.
App.SimpleSearchRoute = Ember.Route.extend({
model: function (params) {
return this.store.findQuery('simpleSearch', params);
}
});
Here is another question along the same lines.
Cheers