I have default route like this:
Route::set('default', '(<controller>(/<action>(/<id>(/<id2>))))')
->defaults(array(
'controller' => 'index',
'action' => 'index',
'directory' => 'frontend'
));
I also have articles controller with action add and action show.
What i need is hide show (action name) in url but keep add (action name) in url.
I have tried to add another route:
Route::set('article_show', 'article(/<id>)')
->defaults(array(
'directory' => 'frontend',
'controller' => 'article',
'action' => 'show',
));
But then only show action is working for all urls.
I need article/id url working and article/add working but article/show/id should not be working.
I think this should work:
Route::set('article_show', 'article((/<action>)/<id>)', array('action' => 'add', 'id' => '\d+'))
->defaults(array(
'directory' => 'frontend',
'controller' => 'article',
'action' => 'show',
));
I also advice you to replace the default route (only meant as an example, not to be used) with as many routes as you need.
Related
I am writing a spec for an Angular component that displays a button that will navigate to another page. The component makes use of Router::navigate() but does not itself have a router outlet. A parent component has the outlet. In my spec, the test should confirm that clicking on the button routes to the correct path.
My current (broken) spec tries to use RouterTestingModule to provide a route to a DummyComponent. When the button is clicked in the spec I get the following error:
'Unhandled Promise rejection:', 'Cannot find primary outlet to load 'DummyComponent'', '; Zone:', 'angular', '; Task:', 'Promise.then', '; Value:', Error{__zone_symbol__error: Error{originalStack: 'Error: Cannot find primary outlet to load 'DummyComponent'
Obviously I am approaching this problem in the wrong manner. What is the correct way to test router navigation when the component does not have a router outlet?
The component (pseudo-code):
#Component({
template: `
Go to the <button (click)="nextPage">next page</button>
`
})
export class ExampleComponent {
public myId = 5;
constructor(private _router: Router);
public nextPage(): void {
this._router.navigate(['/example', this.myId]);
}
}
The spec. This does not work:
const FAKE_ID = 999;
describe('ExampleComponent Test', () => {
let exampleComponent: ExampleComponent;
let fixture: ComponentFixture<ExampleComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ DummyComponent ],
imports: [
RouterTestingModule.withRoutes([
{ path: 'example/:id', component: DummyComponent }
]);
]
});
fixture = TestBed.createComponent(exampleComponent);
exampleComponent = fixture.componentInstance;
});
it('should route to example/:id', inject([Router, Location], (router: Router, location: Location) => {
fixture.detectChanges();
exampleComponent.myId = FAKE_ID;
const LINK_BUTTON = fixture.debugElement.query(By.css('button'));
LINK_BUTTON.nativeElement.dispatchEvent(new Event('click'));
expect(location.path()).toEqual('/example/' + FAKE_ID);
});
});
There needs to be an outlet (<router-outlet>) for the DummyComponent. If the DummyComponent is a route being navigated to from the ExampleComponent, then the ExampleComponent should have the outlet. You also also need to add the ExampleComponent to the declarations`
#Component({
tempalte: `
<router-outlet></router-outlet>
<button (click)="nextPage">next page</button>
`
})
class ExampleComponent{}
declarations: [ ExampleComponent, DummyComponent ]
If you want to avoid having to set up this infrastructure just to test the route being navigated to, the better option might be to just mock the Router, and just check that the navigate method is called with the correct path.
beforeEach(()=>{
TestBed.configureTestingModule({
providers: [
{
provide: Router,
useValue: { navigate: jasmine.createSpy('navigate') }
}
]
})
})
With this, you don't need to configure an routing at all, as you're using a fake Router. Then in your test
it('should route to example/:id', inject([Router], (router: Router) => {
expect(router.navigate).toHaveBeenCalledWith(['/example', FAKE_ID]);
});
I'm trying to bind a custom controller for a route by using:
# app/routes/products/category.coffee
`import Ember from 'ember'`
route = Ember.Route.extend
controllerName: 'categories/selector'
`export default route`
This works, but when I try to bubble events from that controller to the route it gets in a recursive loop.
I found that this is because the custom controller can't get to the route, because 'target' is the controller:
# app/controllers/categories/selector.coffee
`import Ember from 'ember'`
controller = Ember.ArrayController.extend
actions:
back: ->
console.log "controller back action handle"
console.log (# == #get 'target') # => true
return false # Returning true creates recursive loop
`export default controller`
The template:
# app/templates/products/category.hbs
{{render 'categories/selector'}}
Is this normal behavior? How would I get to the route from this custom controller?
The problem was in the template, you should be using:
# app/templates/products/category.hbs
{{partial 'categories/selector'}}
render creates its own context/controller, as stated by #givanse.
I'm trying to make use of _super in the handler of a Promise inside of a Controller action, but it doesn't work because it seems to lose the correct chain of functions.
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#_super()
I want to revert to the Mixin's default behavior on the else but I need to resolve user asynchronously before I can do a conditional statement. I tried:
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
_super = #_super
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
_super()
and
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#_super.bind(#)()
Neither works.
This answer claimed this should work as of 1.5.0, but I'm using 1.7.0-beta.5 and it's no go. Is there a way to get this to work, even in terms of approaching this differently?
Ember currently doesn't support calling _super asynchronously. In that example I'm not actually calling _super asynchronously, it's synchronous still.
http://emberjs.com/blog/2014/03/30/ember-1-5-0-and-ember-1-6-beta-released.html#toc_ever-present-_super-breaking-bugfix
In order to continue the bubbling you need to call this.target.send() with the name of the action.
see: How can I bubble up an Ember action inside a callback function?
Something like this should work:
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#target.send('sessionAuthenticationSucceeded')
Let's say I have a Photo model and a Post model, and I want both of those to have Comment's. In Rails, my routes would look like this:
Rails.application.routes.draw do
resources :posts, only: [ :show ] do
resources :comments, only: [ :index ]
end
resources :photos, only: [ :show ] do
resources :comments, only: [ :index ]
end
end
This generates the following routes:
GET /posts/:post_id/comments(.:format)
GET /posts/:id(.:format)
GET /photos/:photo_id/comments(.:format)
GET /photos/:id(.:format)
Okay, makes sense. If I want to get the path to the Comment's for the Photo with an ID of 9, I'd use photo_comments(9).
If I wanted to create the same routes in Ember, I'd do:
App.Router.map () ->
#resource 'posts', ->
#resource 'post', { path: '/:post_id' }, ->
#resource 'comments'
#resource 'photos', ->
#resource 'photo', { path: '/:photo_id' }, ->
#resource 'comments'
In Ember, this generates the following URLs:
#/loading
#/posts/loading
#/posts/:post_id/loading
#/posts/:post_id
#/posts
#/photos/:photo_id/comments
#/photos/:photo_id/loading
#/photos/:photo_id
#/photos/loading
#/photos
#/
#/photos/:photo_id/loading
I still have /posts/:posts_id/comments and /photos/:photo_id/comments, which is what I wanted. However, because Ember resets the namespace, I no longer have post_comments and photo_comments helpers. I have a comments route, which routes to /photos/:photo_id/comments, but I don't have any way of routing to /posts/:posts_id/comments. I realize I could fix this by doing the following, but it seems redundant:
App.Router.map () ->
#resource 'posts', ->
#resource 'post', { path: '/:post_id' }, ->
#resource 'posts.comments', { path: '/comments' }
#resource 'photos', ->
#resource 'photo', { path: '/:photo_id' }, ->
#resource 'photos.comments', { path: '/comments' }
TL/DR:
I understand that Ember resets routes for nested resources, but I don't understand why. Could somebody explain it to me?
TL;DR Resources must be unique due to the transitioning paradigm, and really you're just over-writing the comments resource.
It's because when you transition to routes you don't explicitly call out an entire path.
this.transitionTo('photo', photo);
{{#link-to 'photo' photo}} My photo{{/link-to}}
Because of this resources must be unique.
If you were at the root of your app and wanted to jump 2 levels deep, to a photo you would just use this.transitionTo('photo', photo), and would NOT use this.transitionTo('photos.photo', photo)
If you are transitioning to a resource that is multiple dynamic resources deep you just send in multiple models. this.transitionTo('foo', bar, baz).
As was implied, you could force people to state an entire path while doing transitionTo/link-to, but the authors decided to punish the smaller percent of people with duplicate resources vs punish everyone into defining the entire path while transitioning.
Additionally it's understood that foo.bar represents resource.route in Ember. I wouldn't consider this an argument for why it was architected as it was, more of a statement about it.
#resource 'posts', ->
#resource 'post', { path: '/:post_id' }
#route 'foo'
this.transitionTo('posts.foo');
i'm trying to set a property of a controller
Trying to do so
{{view Ember.Select contentBinding="App.tastingsController.names"}}
it does not work
App.tastingsController = Ember.ArrayController.extend
names: ["Velato", "Abbastanza limpido", "Limpido", "Cristallino", "Brillante"]
while this version is working correctly (but gives this warning:WARNING: The immediate parent route did not render into the main outlet and the default 'into' option may not be expected )
App.tastingsController.names = ["Velato", "Abbastanza limpido", "Limpido", "Cristallino", "Brillante"]
here's my routes:
App.Router.map ->
#route "home", { path: "/" }
#route "about"
#resource "tastings", ->
#route "new"
#resource "tasting", { path: ":tasting_id"}
Can you explain me why?
( found it here)
thank you
Marco
There are a few issues with your code:
App.tastingsController should be named App.TastingsController. Controller Classes should begin with a capital letter.
You are getting a warning because you skipped a template within the route hierarchy. I need more information on the routes to help fix this.
If you need to set a property on the controller (such as names in your case), there are two ways to do it:
Either set it in the route:
App.TastingsRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('names', names: ['list', 'of', 'names']);
}
});
Or you can set it directly when defining the controller class:
App.TastingsController = Ember.ArrayController.extend({
content: [],
names: ['list', 'of', 'names']
});
When you need to reference a controller from the view/template, don't name the entire controller class. Just use the property you want to bind to (assuming your view's controller is App.TastingsController)
{{view Ember.Select contentBinding="names"}}
Hope this helps.