Here's an image that illustrates the design I was given and need to develop in ember. I'm a bit at a loss on how to handle the routing and implementation in ember.
So to explain this simplified example, say I have a search results page (results are being returned from the back end), where as you would expect the search results aren't always going to be the same. However, when I click on one of the search results, I need to be able to open the product's page as a nested route.
This raises a few problems given that someone returning at a later time will likely not get the same list of products. How would I handle the routing, something like this?
Router.map(function() {
this.resource('search', function(){
this.route('product', {route: ':productID'}, function())
});
});
I'm also not sure how to setup it up the heirarchy in terms of containerview, views, components, etc.
Help?
I think you need to create routes like that
this.resource('products', function() {
this.resource('product',{path: '/:id'});
});
After that you need to create 2 routes ProductsRoute and ProductRoute.
App.ProductsRoute = Ember.Route.extend({
model: function() {
return this.get('store').find('product');
}
});
App.ProductRoute = Ember.Route.extend({
model: function(product) {
return this.get('store').find('product', product.id);
}
});
When you will try to open page /products you will get all products. If you will use RestAdapter it sends request to your REST API /products
I think you need to create action in ProductsController to search products:
App.ProductsController = Ember.ArrayController.extend({
actions: {
searchProducts: function() {
var queryText = this.get('queryText');
if (queryText) {
return this.get('store').filter('product', {query: queryText}, function(item) {
return true;
});
}
}
}
});
This action sends request to your api with /products?query=queryText
In your products template create search form and use something like that {{action 'searchProduct'}} for Search button. Also in your template you need to show all products for that use {{#each item in model}} and to create link to each product use {{#link-to 'product' item}}. I think it will be best practice to create component for your search box http://guides.emberjs.com/v1.10.0/components/.
Related
I have an Emberjs app that has a search action that needs to be available from all routes. To accomplish this, I've defined the 'search' action in the application route like this:
App.ApplicationRoute = Ember.Route.extend({
actions: {
search: function (query) {
this.transitionTo('search', { queryParams: { q: query } });
}
}
});
The 'q' querystring parameter is defined in the SearchController:
App.SearchController = Ember.ArrayController.extend({
queryParams: ['q'],
q: ''
});
The search route calls a service that queries my database with the query parameter like this:
App.SearchRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('activity', { query: params.q }),
}
});
I know that the model hook is not called on transtionTo, but when the user is already on the search page and wants to search again with a different query, I need to reload the search route with a new model.
Is using transitionTo in the application route the wrong approach in this case?
Any ideas are greatly appreciated!
I would add a named {{outlet}} in your Application template, wherever you want the search results to appear. Then, in your Application route, inside the renderTemplate hook, I would render the search results template into the new outlet, also specifying what controller it should use.
On the controller, you can create a computed property, which would detect changes in the query string (or however you want to supply the search results). This property (or properties) would then feed the data in your search results template.
More on rendering a template inside a route:
http://emberjs.com/guides/routing/rendering-a-template/
If you decide to go with putting the renderTemplate hook in Application route, you can set the Search controller's model (or whatever you want to call it) property from any route which needs to update the model on the search controller for it to display proper results:
this.controllerFor('search').set('model', model);
You can also create a Mixin, which would contain the renderTemplate hook, which you can include in any route you want to do your searches from. In the hook, you could send your route's model into the controller:
renderTemplate: function(controller, model) {
this.render('search', {
into: 'search',
outlet: 'application',
controller: 'search',
model: model
});
}
Play around with some of these techniques. I'm sure I'm missing some details, but I think you can get them to work.
I am currently learning Ember and I am making a simple app, but I have encountered a strange problem. I have a route setup and it only pulls the data when I reload the page. Here is my code:
// Start Ember application
App = Ember.Application.create({
LOG_TRANSITIONS: true
});
// Router paths
App.Router.map(function () {
// Homepage
this.resource('index', { path: '/' });
// Book view
this.resource('book', { path: '/book/:id/:version' });
});
// Homepage route
App.IndexRoute = Ember.Route.extend({
model: function () {
// Get all the books
return Ember.$.getJSON('/books');
}
});
// Single book view
App.BookRoute = Ember.Route.extend({
model: function (params) {
// Get a single book
return Ember.$.getJSON('/book?id=' + params.id + '&version=' + params.version);
}
});
When I go to /#/book/1/1 by clicking from the homepage, the page is blank. When I just refresh the page when I'm on there it loads the data and everything works.
Why is this? What can I do to make it work when the user clicks from the homepage?
Thank you everyone for your suggestions. I figured it out with this code here:
App.BookRoute = Ember.Route.extend({
setupController: function (controller,model) {
// Get a single book
Ember.$.getJSON('/book?id=' + model.id + '&version=' + model.version,
function (data) {
controller.set('model',data);
});
}
});
I used setupController instead of model.
you should pass a model in link-to. paste your template code to check how you are creating links. check this for more info http://emberjs.com/guides/templates/links/
If you use link-to helper you must not set context model in this one... you simply need to set an id,
{{#each book in books}}
{{#link-to "book" book.id}}{{book.name}}{{/link-to}}
{{/each}}
and the model of the next route will be request.
The Ember link-to helper doesn't execute the model callback. I've dealt with a situation very much like this recently, and here's how I solved it:
// In the index template
{{#each book in model}}
{{#link-to 'book' book}}{{book.name}}{{/link-to}}
{{/each}}
When you click on the link, you'll get a blank page like you are now, because the BookRoute model callback isn't fired. However, the BookController#init function will be fired. You can use this to go and grab the rest of the details about the book there.
App.BookController = Ember.Controller.extend({
init: function() {
this._super();
// Replace doesNotHaveAllInfo with your check
if (doesNotHaveAllInfo) {
this.set('model', Ember.$.getJSON('/book?id=' + this.get('model.id') + '&version=' + this.get('model.version')));
}
},
});
This way, if the book is loaded through link-to, the new details will be fetched. Note that this will require that the books loaded on the index route contain at least id and version.
Alternatively, if you refresh the page, the model callback will occur, and your doesNotHaveAllInfo check will return false. Whether you click on a link-to or refresh the page, you should see your data.
I'd also recommend abstracting the Ember.$.getJSON code into a reusable method, perhaps on the model, like this:
App.Book.reopenClass({
find: function(id, version) {
return Ember.$.getJSON('/book?id=' + id + '&version=' + version);
}
});
You can then use App.Book.find() to return your model in both your route and your BookController.
I have a list of categories that are loaded on the application route:
ApplicationRoute = Em.Route.extend({
model: function() {
return Em.RSVP.hash({
collections: this.store.find('collection'),
categories: this.store.find('category'),
links: this.store.find('link')
});
}
});
This triggers the categories index action on the server, as expected. I am doing this because I need to load a list of categories for a menu.
I also have a category route:
Router.map(function() {
this.route('category', { path: '/categories/:category_id' });
});
CategoryRoute = Em.Route.extend({
model: function(params) {
this.store.find('category', params.category_id);
}
});
Since there's already a list of categories in the store, there's no request to the server. However, here I want to show more detailed information. I want to send a request to the server's show action.
I am probably not thinking in "the ember way", but it seems to me that I either have to, somehow, force the server request in the route, or create a separate model for the menu categories.
How should one go about this, with ember-data?
Notes:
ember.js-1.1.2 and
ember-data-1.0.0.beta.3
I ended up using a separate CategoriesList model. Still, this feels suboptimal.
my question is a little bit general. What is the best concept for route and controller with findQuery in ember.
I have api with data filtering. Data request is executed by
this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": filters})});
after that I show them in table view. The filter is updated by form views in a template.
My current solution:
Form views set controller parameters and a button call action from controller. Controller action loads parameter, executes findQuery and set('content',data).
In most cases I saw concept with a defining model: function() .. in the Route and setupController: function(controller, model) with controller.set('content',model). I like this "set" because 'content' is RecordArray (not PromiseArray) and I can easily use that for datatables and another JavaScript plugins. I think my solution isn't good.
I think your concept is correct, I have been using the following flow:
In your router:
App.Router.map(function() {
this.resource('search', { path: '/query/:filters' });
});
App.SearchRoute = Ember.Route.extend({
model: function(params) {
return this.store.findQuery('dataModel', {"q": JSON.stringify({"filters": params.filters})});
});
In your html, just bind the action which will lead to the new Search Route,
something like below :
<button {{action "doSearch"}}>Search</button>
In your controller:
App.SearchController = Ember.ArrayController.extend({
...
actions: {
doSearch: function() {
var query = buildYourQueryObject();
this.transitionToRoute("search", query);
}
}
Upon clicking on the button, the app will transition into your search route, and "query" will be serialized and sent into the Route, and the Route.model() will attempt to be populated based on the serialized parameters provided.
Note: The code has been simplified, you might need to add more stuff in order to make it work
So, I'm having some issues with Ember's new router. I'm trying to save and later return to the current path for a given dynamic segment, so my urls might look like
#/inventory/vehicle/1001
Which can then branch off into
#/inventory/vehicle/1001/details
#/inventory/vehicle/1001/photos
#/inventory/vehicle/1001/description
etc. I need a way to return to the most recent route. The Ember guides have a method for this here:
http://emberjs.com/guides/routing/redirection/
The problem with this method is that by creating the "/choose" route and assigning it to "/", this overwrites the standard "/inventory/vehicle/1001" route. For instance, if I were to try to create a linkTo a vehicle like so:
{{#linkTo "vehicle" vehicle}}
Then Ember will throw an error because the "vehicle" route no longer exists. Instead, it must be set to:
{{#linkTo "vehicle.choose" vehicle}}
Which works, activates the VehicleChooseRoute and everything. Except, since "vehicle.choose" is technically a child of "vehicle", the #linkTo ONLY has an active class applied when the current route is
#/inventory/vehicle/1001
Which instantaneously redirects to the latest filter, and so it's basically never on. So basically I'm trying to figure out a way around this. I tried changing the path of "vehicle.choose" to be the standard path (#/inventory/vehicle/1001/choose) so it doesn't overwrite the "vehicle" route, and then setting up VehicleRoute like so:
Case.Router.map(function() {
this.resource('inventory', function(){
this.route('review');
this.route('sheets');
this.resource('vehicle', { path: '/vehicle/:vehicle_id' }, function(){
this.route('choose');
this.route('details');
this.route('consignor');
this.route('additional');
this.route('price');
this.route('dmv');
this.route('expenses');
this.route('description');
this.route('tasks');
});
});
});
App.VehicleRoute = Ember.Route.extend({
model: function(params) {
return Case.Vehicle.find(params.vehicle_id);
},
setupController: function(controller, model) {
model.set('active', true);
},
redirect: function() {
this.transitionTo('vehicle.choose');
}
})
Case.VehicleChooseRoute = Ember.Route.extend({
redirect: function() {
var lastFilter = this.controllerFor('vehicle').get('lastFilter');
this.transitionTo('vehicle.' + (lastFilter || 'details'));
}
});
But the problem that arises from this (aside from feeling rather hacked together) is that redirect replaces the entire template that would normally be rendered by "vehicle" so I only get the subview. So that's not an option.