Grails noob here. I am build a simple grails app where I have the default create/ edit / list / show pages for a Product domain object. I also have the ProductController. The create method by default does this:
def create() {
[productInstance: new Product(params)]
}
My understanding is this will take me to the create page. And there will be nothing in the params object so everything will be blank.
I want to change the behaviour so that the when the create() method is invoked, a pop up is returned to the User: Sorry you are not allowed to create new data.
The user will stay on the same page and the only thing that will happen is the pop up.
How do I do this without using JavaScript?
Thanks
If the create method is not allowed, why not just remove the create() method from the controller, delete the create.gsp page and remove the 'New' button from the list page?
If you do want to offer a 'New' button and display a popup message that says you can't use the button, that is probably easiest done in javascript.
If you can just display the message in the default grails message panel rather than in a popup, then change the controller method to
def create() {
flash.message = "Sorry you are not allowed to create new data."
redirect(action: "list")
}
Prior to sending the user to the GSP view, you should make a decision in the controller, if the user is allowed to execute the create action. In your controller this could be something simple like:
params.allowed = false
The result of this decision is then passed to you GSP where you can evaluate it inside the GSP using something like:
<g:if test="${params.allowed == false}">
Alert: you are not allowed...
</g:if>
For the alerts, there are good looking alternatives to javascript like: http://getbootstrap.com/components/#alerts
Related
I have some partial actions that I render with the Asp.Net Futures RenderAction method. Some of these perform redirects after the forms in them have been processed.
Now that I upgraded to Asp.Net MVC 2 RC it gives me an error "Child actions are not allowed to perform redirect actions".
I checked out the source code and I found the line that throws the exception. To Get around it I can make a custom RedirectResult, But before I do I want to understand why the framework doesn't allow it in the first place. There must be a good reason and maybe I Shouldn't do either.
Any one know the reason for this limitation?
Thanks
The limitation exists because MVC has already started rendering a view to the client. The effect of redirecting from this point is undefined. It could work perfectly, it could continue rendering the original view without redirecting, it could throw a different exception, etc.
Since the result of performing this action is undefined, the framework blocks it. In practice, RenderAction should never be used to render anything other than a view (or view-like content) for similar reasons.
In your particular case, the outer action should redirect. If you're just going to end up redirecting from within the view anyway without showing anything to the user, then there was really no purpose to going through the view in the first place, as the outer action could have delegated the work appropriately on its own.
Try to use something like this in Child Action:
ControllerContext.HttpContext.Response.Redirect(ControllerContext.HttpContext.Request.Url.ToString());
My solution.
Action method:
return View("Redirect", model);
View:
<script type="text/javascript" language="javascript">
document.location = '<%: Url.Action("Index", "Album", new { id = Model.Id }) %>';</script>
In my case, the form being rendered is a "configure" panel on an extension to a website I'm building. I'd like the extension's own controller to be able to handle the form processing and then redirect back to the admin page listing all configured extensions. I don't think it's appropriate or practical here to ask the parent page's controller to process the form for the extension. What would you suggest I do instead?
In my unusual case, I had a custom AuthorizeAttribute attached to my controllers which was attempting to redirect on a child action, which is (as mentioned above) not allowed.
To resolve the issue, I removed authorisation checking redirection on all child actions:
Public Overrides Sub OnAuthorization(filterContext As AuthorizationContext)
//Child actions cannot redirect anyway, so no need to check permissions.
If filterContext.IsChildAction Then Exit Sub
.. parent authorisation checks ..
Sometimes this error occured when you try to render an action of base action result.
Example:
ActionResult X
Return View
View X
RenderAction Y
ActionResult Y
// Bla bla
return View
// else
return RedirectToAction X
In that case just point the partial view form's submit url to action that was the target of your problematic redirection and let it perform itself redirection to its GET version.
I'm working with the domain class Alojamiento, and its generated controller and views. The next code works:
I have included in the form of a view another form:
<g:render template="../caracteristicas/form" bean="${params.caracteristicasInstance}" />
Now, the edit action of the controller has:
def alojamientoInstance = Alojamiento.get(id)
def caracteristicasInstance = alojamientoInstance.caracteristicas
[caracteristicasInstance: caracteristicasInstance,
And to the update action of the controller:
def caracteristicasInstance = Caracteristicas.get(id)
caracteristicasInstance.properties = params
caracteristicasInstance.save(flush: true)
As I said, the above code works, but it is not protected against errors, so I'm trying to use the update action of CaracteristicasController (I'm following this approach: http://stuff4j.blogspot.com.es/2011/04/calling-controller-method-from-another.html). The next code does NOT work, but I think it explain itself what I'm trying:
CaracteristicasController caracteristicasController = new CaracteristicasController()
CaracteristicasController.properties = params
CaracteristicasController.params.doNotRedirect = 'true' // See: http://stuff4j.blogspot.com.es/2011/04/calling-controller-method-from-another.html
CaracteristicasController.update()
By the way, the error of Grails is: "Cannot set read-only property: properties"
UPDATE 1
I think I didn't explain something well. I have in _form.gsp 3 embedded _form.gsp (I said in my question 1 to simplify). So when I edit _form.gsp, the others must be updated too. I want to call the update action of the "child" controllers to update the forms, but not move to them. I want to keep being in the "parent" controller so when everything updates, the show.gsp of the "parent" will appear. Do I explain it better now?
Why don't you redirect or chain with all need params?
My question is similar to this following post
Render a view of another controller
I have a TestConfigController my question is what can I do in case validation fails and I want to render controller:test and view:edit rather then controller:testCOnfig and view:edit
def save() {
def testConfigInstance = new TestConfig(params)
if (!testConfigInstance.save(flush: true)) {
/*Rather then view:"edit" i want view:"/test/edit" which does not work */
render(view:"edit", model: [testConfigInstance: testConfigInstance],id:params.test.id)
return
}
println "+++++++++++++++++++++++++"
flash.message = message(code: 'Data successfully saved', args: [message(code: 'testConfig.label', default: 'Successfully saved')])
redirect(action: "edit", controller:"test", id:params.test.id)
}
Any pointers? I have already looked into grails redirect which does not have "model" param and thus can not pass the validation errors to the view
Also I have looked in to grails render which does not have controller param so that I can go back to different controller!
Please let me know if more detail/code is needed
EDIT
Following happens while using one of the two things
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance],id:params['test.id'])
The code above renders the page /test/edit with no reference to testid eventually erroring out saying "test.id" can not be null.. (means its rendering /test/edit and not /test/edit/1)
render(view:"/test/edit/"+params['test.id'], model: [testConfigInstance: testConfigInstance],id:params['test.id'])
The code above leads to following error
The requested resource (/EasyTha/WEB-INF/grails-app/views/test/edit/1.jsp) is not available.
Either one of the above code renders just "/test/edit" no id at the end, thus eventually erroring out saying test.id can not be null.
The value of id that you are trying to append in the view path should be a part of the model map. The values that you provide in the model map are available in the view that is rendered.
In the first option that you tried, the id parameter doesn't make any difference as the render method doesn't use any 'id' parameter (the redirect method uses the id parameter to generate the redirect url).
Your code snippet should be something like this:
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance, id:params['test.id']])
The render method that you are using here doesn't redirect you to some other action. render just prints the parsed viewName to the output stream. Eg. render(view:"/test/edit") just renders the edit.gsp view. It isn't actually redirecting you to the edit action of test controller. Therefore, just passing the id in the model map won't give you access to the testInstance on the view. You will have to get the testInstance By id and pass it to the view in the model map
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance, testInstance: Test.get(params['test.id'] as Long)])
Anuj Arora is right:
If you just want to render an arbitrary view you can use the full path to the view related to the grails-app/view folder:
In your case:
render(view:"/test/edit", model: [testConfigInstance: testConfigInstance],id:params.test.id)
should work.
If you are only wanting to render the view /test/edit, then the call render(view:'/test/edit',...) should be all you need.
If instead, you also want to include some of the processing from the TestController and edit action, then look at the chain() call. It has a model parameter where you can pass the validation errors and controller/action parameters to redirect to the other controller.
Controller for a Bug:
this is the create method for a bug, I printed out bugInstance.activities and it had my activity object in it
def create = {
def bugInstance = new Bug()
def activity = new Activity(description:"created")
bugInstance.properties = params
bugInstance.addToActivities(activity)
return [bugInstance: bugInstance]
}
Then I looked at the save method, and printed the exact same thing, and the result is null, so somehow it's lost the activity I created, and I have no idea why. Is this really the default behavior? Am I doing something really basic wrong, because there doesn't seem to be any reason such a simple piece of code wouldn't work.
def save = {
def bugInstance = new Bug(params)
println bugInstance.activities
if (bugInstance.save(flush: true)) {
flash.message = "${message(code: 'default.created.message', args: [message(code: 'bug.label', default: 'Bug'), bugInstance.id])}"
redirect(action: "show", id: bugInstance.id)
}
else {
render(view: "create", model: [bugInstance: bugInstance])
}
}
I know I can work around this by adding the activity in the save method, but why do I lose the activity from create() -> save()
You never call save() on the new instance:
def create = {
def bugInstance = new Bug()
def activity = new Activity(description:"created")
bugInstance.properties = params
bugInstance.addToActivities(activity)
bugInstance.save()
return [bugInstance: bugInstance]
}
You don't need to save the Activity because it'll be transitively saved since it's in the activities collection.
might sound like a stupid question but are you setting a hidden parameter or anything in the create.gsp with the Bug Instance instantiated in the create?? I mean, I don't see anything wrong with what your doing here. What does your create.gsp look like?
If you are using the auto-generated create.gsp the activities set will not be included in the form. It is of course there in the model, but there will be no activities field rendered on the client side. When it comes back to save, it is clear that the activity is lost. Depending on what you want to achieve you could add some activity selector to the create.gsp or (to start with) a hidden field with your activities description, but then in the save action I guess you have to handle the the activities param in any case, as the magic of grails doesn't go as far as instanciating the Activity for you. The same way as you instantiate the Bug itself in the save action, you have to instantiate the Activity and even save it, if you want it to be persisted.
Edit: If you really want to pass around the whole activities list, you could make use of indexed properties.
In create.gsp add this:
<g:each status="i" var="activity" in="${bugInstance.activities}">
<!-- one hidden field for each property of each attached activity -->
<g:hiddenField
name="activities[${i}].description"
value="${activity.description}" />
</g:each>
And in the save method this:
params.activities.each{ activity ->
bugInstance.addToActivities(new Activity(activity))
}
But in your case it might be sufficient to instantiate the one activity from a single field.
How can I do this: I have on page named "Schedule" and it can be accessed through 2 differente ways:
URL 1- www.bla.com/Admin/Schedule
URL 2- www.bla.com/Schedule
"URL 1" will be accessed by users with Admin previlegies and this View will show some Admin stuff, and users must be LoggedOn.
In the otherhand, "URL 2" will be accessed by users NOT LoggedOn and it will NOT show the admin stuff.
But, they are the same page, just with some differences depending on user's access.
I already have AdminController and I intend to put this "Schedule" View as part of this controller. As result, I know if I type "URL 1" it will work. But, if I type "URL 2"? Will I have to create a "ScheduleController" just to handle this?
I wonder if there is a way to resolve this by Global.asax, configuring the routing... I don't know...
Thanks!!!
You can map the /Schedule route to the /Admin/Schedule action from the Global.asax.cs like this:
routes.MapRoute(
"Schedule",
"schedule",
new { controller = "Admin", action = "Schedule" }
);
This will solve your immediate problem of wanting two separate routes resulting in the same action/view.
However, this will not solve your scenario properly. The main issue is that the identity of the logged on user is orthogonal to the route the request takes. In other words, you can't force the admin user to always hit the /Admin/Schedule route, they could just as well hit the /Schedule route and still would expect the same end result. Not only that, but doing it this way will prevent you from using the [Authorize] attribute on the Admin controller or the action to force the user to login and will have to implement custom logic checking which route the action was hit through and decide whether you want to force login or let the user through.
Thus, you have to make a decision:
you share the controller, action and the view and determine whether to show the additional information in the view based on the identity and the role membership of the logged on user (if any). You will have to change the name of the controller then, as /Admin will not reflect the new role this class has;
you share only the view and have two separate controllers and actions - Admin.Schedule and User.Schedule. You will have to put the view in the /views/shared folder and return the same view fromboth actions, potentially passing different model. You'll end up with two routes - /Admin/Schedule and /User/Schedule;
you have two separate controllers, actions and views.
In all three cases, you can still have the rule above pointing to the appropriate controller, if you want to have also the shortest /Schedule route.
Make the View shared and just render it from both controller actions. Pass the appropriate data via the model (or ViewData) so the View knows not to render the admin stuff when rendered from the non-admin controller action.
And, yes, create the Schedule controller. Make the routing simple and handle sharing the generation code on the back end.
AdminController
public ActionResult Schedule( ... )
{
Schedule sched = ... get model ...
return View("Schedule", new SchedViewModel {
Schedule = sched,
Admin = true
} );
}
ScheduleController
public ActionResult Index( ... )
{
Schedule sched = ... get model ...
return View("Schedule", new SchedViewModel {
Schedule = sched,
Admin = false
} ); }
Sounds like you don't really need a different URL if it's the same page. However if for some reason you still want to use 2 different URLs...
Url 1:
routes.MapRoute("ScheduleAdmin", "Admin/Schedule",
new
{
controller = "AdminController",
action = "Schedule"
});
Url 2:
routes.MapRoute("Schedule", "Schedule",
new
{
controller = "ScheduleController",
action = "Index"
});
You didn't make it clear what action you were using for the schedule controller so feel free to change that.