I have been using Grails for last 3 weeks (learning and working). I have been working on porting a JSP/Servlet application to Grails and it has been absolute "fun" porting the application.
I have facing an issue and have been searching, reading but could not solve it yet.
In the GSP page I have a textfield and search button where user enters ProductID. I have a controller which is called from jQuery Ajax method when a search button is clicked.
// ----- Part of jQuery Ajax call ---
$.ajax({
type : "post",
url : '/${grailsApplication.metadata['app.name']}/product/checkProductAjax',
data : "pid="+proID,
// ----- Contoller code-----
class ProductController {
def scaffold = true
def checkProductAjax = {
def product= Product.findByProductId(params.pid)
if(product) {
[product: product] // model to get data in GSP page.
render(product.toString() + ":" + product.id)
} else {
render("none")
}
}
}
Ajax call and everything works fine. The problem I am facing is how to get the model (i.e. the Product data back to the GSP page i.e. [product: product] and display in GSP as for e.g. Product Name: ${product}
How can I get it working? I have read examples where it is mentioned that just setting the model [product: product] will help to get the data in GSP.
Product Name: ${product} always shows blank in the GSP page Product Name:
Please tell me what I am doing wrong.
Cheers!
Jay Chandran
[product: product] and render(product.toString() + ":" + product.id) are incompatible. When you see a controller action whose last line is a map like [product: product] this is the implicit return value since it's the last statement of the action closure - it's the equivalent of return [product: product]. But if you have a map in the middle of a method it's just created and discarded. It's pretty much equivalent to
def ignoreThisMap = [product: product]
// other code
Since you're making an Ajax call though, putting the product in the model doesn't make sense since you aren't going to re-render the GSP. You're going to render text, JSON, XML, or some other content that the client-side JavaScript will use to update some subset of the html. So you probably want something closer to
if (product) {
render product as JSON
}
else {
render "none"
}
and then you can use jQuery or Prototype to evaluate the JSON and extract the data in your JavaScript.
you probably want to use the grails tags that are created for this type of work; it wraps the AJAX code all up for you
http://www.grails.org/Ajax
Related
My Grails app is using version 2.3.6
In this app there's a Controller class:
class UserOrderController {
def index(Integer max) {
params.max = Math.min(max ?: 20, 100)
respond UserOrder.list(params), model:[userOrderInstanceCount: WorkOrder.count()]
}
}
Now in another Controller i am accessing the UserOrder objects and filtering based on product ID. Product ID is a string property in UserOrder domain class.
The other controller is:
class UserOrderFilterController {
def getUOBasedOnID () {
// Here i get a new list for **UserOrder**
// Now i want to draw the UserOrderController page with the new list
}
}
I am pretty new to Grails and not sure how to do this.
Should i be creating a new INDEX function in UserOrderController class and pass the new list?
Like shown below:
class UserOrderController {
def index(Integer max) {
params.max = Math.min(max ?: 20, 100)
respond UserOrder.list(params), model:[userOrderInstanceCount: userOrder.count()]
}
def index(List UserOrder) {
// create page???
}
}
UPDATE:
The UserOrderFilterController has it's own index.gsp file.
What am doing is: Accessing all the objects for UserOrder domain class and filter them based on a property.
Now in the index.gsp of UserOrderFilterController i will show the total number of objects/orders found. This number will be shown with a hyperlink using href and when the user clicks on it, it will go to index.gsp page of UserOrderController with only the filtered UserOder's displayed.
So what am expecting is:
<a href='${createLink(controller:'UserOrder', action:'index')}'>%s</a>
A href like shown above with a params field that will have the filtered list of UserOrder's.
I have no idea how to add the params/list to href. Is this possible?
Each action in Grails has a matching view. Duplicating actions is asking for trouble. If you have some new functionality that deserves a page of its own, then you should create a new action and a new view.
If it belongs on the home page, then put it through the same index method.
Note that in grails, you simply pass the values to the .gsp page and the .gsp page is what handles the formatting of the data. Your action should have absolutely 0 knowledge of the structure of the view. This is a core concept in MVC.
In this case, you can redirect to a controller, as per the docs. The one that should interest you the most is this:
redirect(controller: "yourController", action:"index", params=[filteredList: filteredList]);
This will redirect to the existing index action in your UserOrderController, and pass in the filtered list. From there, you can have something like..
if(params.filteredList) {
// You know this came from the filtered controller, so display this and don't
// make a DB call.
}
Point to Note
The structure of your grails application worries me. Why have you got an entirely separate controller for simply filtering data?
Edit
Taking the createLink approach can be improved a bit. Instead of..
<a href='${createLink(controller:'UserOrder', action:'index')}'>%s</a>
You can use the g:link functionality:
<g:link controller="UserOrder" action="index" params=['filteredList':filteredList]>%s</g:link>
If you are trying to create new controller for filter UserOrderFilterController, you can do
class UserOrderFilterController {
def getUOBasedOnID () {
//get filtered list
respond filteredList, [view:'userOrder/index',model:[userOrderInstanceCount: filteredListCount]]
}
}
You can see more about respond here.
How would you use jQuery to post to a different controller using Razor syntax? Below is the code I'm attempting to use. Each of the alerts pop up the exact information I am expecting except for the one containing the #Url.Action. This gives me a URL that just contains the Controller and method (i.e. /Agent/Details). The sNumber is completely missing.
Oddly enough, this code is still posting to the controller properly, with the correct sNumber parameter. However, once the controller goes through the process of grabbing the agent from the database and attempting to render the view, nothing happens. The user stays on the same page.
Here is the jQuery
alert("input: " + item + ", map: " + map[item].sNumber);
var sNumber = map[item].sNumber;
alert("variable: " + sNumber);
alert('#Url.Action("Details","Agent")', { id: sNumber });
$.post('#Url.Action("Details","Agent")', { id: sNumber });
There is a ton of code in controller, so I'll spare you by not posting it. However, this is the final line
return View(bigAgent);
The controller uses the sNumber input parameter to grab the record from our database and pass that agent's info to the Details view. I've checked the contents of the bigAgent object using both the jQuery search and our regular search (which redirects properly), and they are one and the same. For whatever reason, you are just not directed to the Details page when using the jQuery search.
It's not odd that it's posting correctly. You are using a POST request afterall, so the sNumber parameter doesn't belong in the URL as it would with a GET request (e.g. /Agent/Details/3).
You're not being redirected because when you use jQuery AJAX methods, they happen asynchronously. You need to handle the redirect yourself in the success callback of the $.post() function.
$.post('url', { data }, function(data) {
// callback
});
It sort of seems like you shouldn't be using AJAX at all and should be performing a regular form submission so you can handle the redirect in your controller.
I have the requirement to use Html.RenderAction like you would in ASP.NET MVC.
For instance I have a Home Page with News and Products on.
I would like to do for instance
#Html.RenderAction("/api/products/featured")
Which would start a new service call and output the template to the html stream.
Is this possible using ServiceStack Razor and if so how do I accomplish it?
The PartialExamples.cshtml test page shows different examples of rendering a razor view inside a page, e.g:
Using the new RenderToAction() method which lets you execute a Service and it's rendered partial view with a route and QueryString, e.g:
#Html.RenderAction("/products/1")
This also takes an optional view name if you want a different view than the default:
#Html.RenderAction("/products/1", "CustomProductView")
There's also the normal Html.Partial() to specify which view and model you want to render in the page, e.g:
#Html.Partial("GetProduct",
base.ExecuteService<ProductService>(s => s.Any(new GetProduct { Id = 1 })))
ExecuteService is simply a wrapper around the equivalent ResolveService in a using statement, i.e:
#{
Response response = null;
using (var service = base.ResolveService<ProductService>())
{
response = service.Any(new GetProduct { Id = 1 });
}
}
#Html.Partial("GetProduct", response)
The new RenderToAction() method in Razor Views was added in v4.0.34+ which is now available on MyGet.
*I may be duplicating my answer or I lost it somehow
Looking at the ServiceStack.Razor.ViewPage class there is an Html property of type ServiceStack.Html.HtmlHelper. I don't see 'RenderAction' as a method (or extension method) on this class so it doesn't appear to be available. There is a 'Partial' method that takes the ViewName and an overload that takes a ViewName and an object. Based on your above comment this doesn't appear to be a useful solution.
If I'm correct about the above, I think you'd need your 'Featured View Template' to pull in the data. Could add soemthing like
{ FeaturedResponse products = new JsonServiceClient("http://localhost").Get<FeaturedResponse>("/api/products/featured"); }
to your template. This would allow you to use the products variable like a Model.
Or, use JavaScript to pull the data into the template. You would have have to use JavaScript to get your data into the HTML elements, though.
You could then render the template using #Html.Partial('Featured')
Hope this helps.
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.
I am submitting an ajax form and returning a partialresult to the view. However, when validation fails I only want to tell the user why it failed with a js method without reloading the model for the view. How do I specify what to return in the controller?
I'd return a error text/html and check for that on success of the jQuery call and if it exists then just alert it out.
What might be nicer is if you return an error partial view and display that as a modal div giving the user a nice looking error message rather than just an alert.
for that matter you could return html/text and place that within a div on the page and simply show the div modal or otherwise.
i hate the simple alert messages from j/script. grey and ugly with a horrible ding sound. but that's just me.
EDIT
I'm going to make the assumption that you are going to return a partial view representing the error. This is I think the easiest and the best way.
Create an error object somewhere. Below is a very simple object. Add properties as required.
public class MyErrorObj
{
public string errorText{get;set;}
}
Then in your controller you might have the following code. It assumes that you have a partial view called "ErrorDisplay" which inherits from MyErrorObj.
public ActionResult jQueryCheckError()
{
MyErrorObj errObj = new MyErrorObj { errorText = "error happened" };
return PartialView("ErrorDisplay", errObj);
}
Then in your view, not your partial view but your page;
$.post("/MyViewFolder/jQueryCheckError", { PARAMETERS GO HERE }, function(newErrorHTML) {
$(".MyErrorDivContainer").html(newErrorHTML);
$(".MyErrorDivContainer").html(newErrorHTML).slideDown(300);
});
The above posts to your controller action, then when it returns it uses the html that was returned and puts it into a div with a class name of "MyErrorDivContainer".
Now, you can check to see if the returned html is empty and then choose not to display the error message.
You could return a jSON object, itterate through the error collection and render the html to the same div if you wanted to. It's a little more involved but do-able.
Let me know if you want that code as well or if this is enough.
http://devlicio.us/blogs/billy_mccafferty/archive/2009/02/07/beware-of-asp-net-mvc-javascriptresult.aspx
public ActionResult DoSomething() {
script s = "$('#some-div').html('Updated!');";
return JavaScript(s);
}
But I'd suggest returning something like "Json(new { Type = "ValidationError", Errors = ModelState.SelectMany(x => x.Errors)})" - and process it on client side. Or, at least, wrap your returned JavaScript into common class, like "return new JavaScriptError(ModelState)".