I tried to use refactor my show.html.erb using:
<%= render #question.answers %>
into
_answer.html.erb
My understanding is that the partial here will be rendered once for EACH answer that is in the #question.answers collection.
As the render method iterates over the #question.answers collection, it assigns each answer to a local variable named the same as the partial, i.e. answer
However, I realized that every time when I refresh the page, the order of the content inside answer will be random. It is not listed in descending/ascending order, just like #question.answers.each |answer| does.
Any way to fix?
I kinda of solve it temporarily by putting
default_scope {
order("created_at ASC")
}
into my model.
Now when I refresh the page, the order of answer content will be the same order.
Related
I am looking for the way to refresh a template inside a view rendered from another controller than the template's controller, I mean:
I got two controllers AdminController & UserController. And two gsps /admin/listUsers & /user/_searchResult.
Then a want to render view listUsers who have inside the template _searchResult and all right.
Now, i want to refresh the template _searchResult, but cant find how. I tryed calling render(view:"/admin/listUsers", template:"/user/_searchResult", model:[searchResult:result])
AdminController.groovy
#Secured(['ROLE_ADMIN'])
def listUsers(){
//...
}
UserController.groovy
#Secured(['ROLE_ADMIN'])
def search(){
//search users for the givven params and send result by chain if there's an action or update a template if it's needed
//in my case this method need to update the template _searchResult
}
#Secured(['ROLE_ADMIN'])
def searchResult(){
//...
[searchResult:result]
}
listUsers.gsp
//...
<formRemote name="searchForm" url="[action:"search", controller:"user"]">
//Some fields for the search
//I need to place here some hidden inputs to send which
//template i want to update or action to redirect
</formRemote>
<g:render template="/user/_searchResult"/>
//...
_searchResult.gsp
//Just itterate and print the search result in a table
I hope I have explained the problem correctly, thanks!
I don't think I entirely understand your question, but I think the source of your confusion is that the way you are naming things doesn't follow regular conventions and you're not using the right tools for the job. Let me explain...
The methods on Controllers are called Actions. They send some data (the Model) to a View to be rendered into HTML. Views can be composed from smaller, reusable fragments called Templates. (sorry if I sound like I'm being condescending here, but I'm just trying to make sure we're all on the same page).
Now, what you've called templateA is actually a View, not a Template. You're correct that templateA (your View) can call templateB to render some markup, but then having the templateB try to call a method on another Controller doesn't make sense. That's not how things flow.
If you have some logic that needs to be executed after you've sent your Model to the View, you want to use a Tag Library (http://grails.org/doc/latest/guide/theWebLayer.html#taglibs).
To summarise, here's a quick recap.
A request should only call one Action, which sends the model to only one view.
If you need to reuse logic between Controllers, move that code to a Service.
If you need to reuse markup between Views, move that markup to a Template.
If you have logic that you want to have executed after you've sent the Model to the View, use a Tag Library.
Hopefully this will point you in the right direction.
--- UPDATE ---
OK, with the real code I can see better what you're trying to achieve. Firstly, as you're using the <g:formRemote> tag, you should have a good read of the docs at http://grails.org/doc/latest/ref/Tags/formRemote.html to understand what it does.
What you will have here is 2 separate requests. The first will be a regular page load by your browser, which is handled by the listUsers() action. Once the page is then finished loading, the user will enter a search term and hit the submit button. This will fire off a second ajax request, which will be handled by the search() action. This action could use the _searchResult.gsp template to render a HTML table to display the search results. When the browser get this, it will insert it into the DOM where you've told it to put it using the "update" attribute of the <g:formRemote> tag.
The important thing here is that from the server's perspective, these are 2 separate requests that are completely independent. They both first call an action, then send a model (a Map containing some data) to a view, which renders/merges the data with HTML and sends it back to the browser.
The difference between the 2 is that the first is a complete page load by the browser, whereas for the second request, the browser only loads a small chunk of HTML (the search results table) and updates the page content without reloading it.
So your code would look more like this...
AdminController.groovy
#Secured(['ROLE_ADMIN'])
def listUsers() {
render(view:"/admin/listUsers")
}
listUsers.gsp
<g:formRemote name="searchForm" update="insertSearchResultsHere"
url="[controller: 'user', action:'search']">
<input name="searchTerm" type="text" />
</g:formRemote>
<div id="insertSearchResultsHere"></div>
UserController.groovy
#Secured(['ROLE_ADMIN'])
def search() {
// use the search term to get a List<User>
render(template: "/user/searchResult", model: [users: users])
}
_searchResult.gsp
<table>
<g:each var="user" in="${users}">
%{-- Iterate through your search results --}%
</g:each>
</table>
I solved it by placing the attribute update and rendering the template alone:
gsp:
<formRemote name="searchForm" url="[action:"search", controller:"user"]" update="divToUpdate">
//Some fields for the search
</formRemote>
<div id="divToUpdate">
<g:render template="/user/_searchResult"/>
</div>
Controller:
def search(){
render(template:"/user/_searchResult", model:[searchResult:result])
}
When i asked this question, i was new on Grails community and i was confused with the use of remoteFunction and tags that use it like remoteForm. But i had this confusion because of i had not read the documentation. So in my case, the solution was search for documentation about how to use remote tags and render. Thanks to #AndrewW for show me the way.
I am not sure if this is the best approach but I have a controller that originally I intended to control a show index that renders many partials on it (a header partial and then, has some if else magic to render different partials based on the step the user is in in filling out a form... a form has many sections across several pages). I think ultimately ajax is the way to go but I am not even to that point yet. I am not sure this is the right way to do it, so I guess that is what I am asking... is the many different partials to one controller the way ? or does each "page" of form data have to be broken out into its own controller? allowing the user to fill out form (check boxes, comment section) and click "next" passing the model of the data they are filling out along the way and saving that model in each next?
U may not need several controllers, but 1 controller with some actions may be a good start. =)
Then each action should load only the partial it needs. like u can give the action name to the partial, making easy to know which partial to render.
Or maybe u can try to use wicked.
There is a railscasts for it.
Well, you could use a method to decide which partial to render.
Use this example or do some meta programming.
class YourController < ApplicationController
def index
render :partial => partial_selector(param)
end
private
def partial_selector param
#logic to decide what partial do render
#returns the partial name
end
end
I'm adding unobtrusive ajax to my site, so I need to set the layout depending on the request type :
# app/controllers/application_controller.rb
layout Proc.new { |controller| controller.request.xhr? ? 'ajax' : 'application' }
# app/views/layout/ajax.html.erb
<%= render :partial => "shared/flash", :object => flash %>
<%= yield %>
But I also want to use nested layout and have my javascript deal with where to insert ajax retrieved content. According to the rails api layout nil force default layout behavior with inheritance, so I tried this :
layout Proc.new { |controller| controller.request.xhr? ? 'ajax' : nil }
Which doesn't work, I get no layout at all. The only piece of information I found is an old chat log saying that this, indeed, doesn't work due to the way Rails handle layout.
Is there a way to achieve this ?
Since a proc returning nil doesn't work, is their a way to set the layout on a condition beeing met (request.xhr? here). Something like layout_if.
Or should I just add
layout Proc.new { |controller| controller.request.xhr? ? 'ajax' : 'controller_name' }
to every controllers with a different layout ?
My ajax request actually return html, should I cheat a little and return those html elements in .xml files and use respond_to to set the layout ?
(This may be great, for another reason, having differents urls to access with and without layout content could allow for page cache, but there are other way to achieve this.)
Or maybe there is a way better solution.
Rails can do this automatically. For example you create 2 layouts(application.html.erb and application.js.erb). When request is xhr? it will use js(application.js.erb) layout, if standard request comes to the server rails will render application.html.erb.
Regardless of the language or MVC framework used, how should I handle different views based on roles?
For example (pseudo code):
views/post/show:
<% show post here %>
if (role.isAdmin or role.isModerator) {
<% show moderation tools %>
}
<% show rest of content %>
I don't quite like the idea of putting too much business logic into the view, but it doesn't seem like there're other options. Are there?
This gets messier and messier once you have more roles, or different levels of permissions. Take this site for example. Users with more than 200 rep see less ads. Users with more than 500 rep have a retag button. Then you get an edit button at 2000, a close button at 3000, moderation tools at 10k, and more functions if you are a "star" moderator.
You can make this a little neater by having a custom ViewModel with a boolean property called ShowModerationTools. In your controller you perform the checks to see whether the current user, based on their roles, can see the post and set ShowModerationTools to either true or false. You then return the custom ViewModel to the view. That way you can then just do this in your view:
<% show post here %>
if (Model.ShowModerationTools) {
<% show moderation tools %>
}
<% show rest of content %>
It also means if your business rules change (for instance you need to introduce another condition) you just change the controller and don't need to alter your view.
There is nothing wrong with this. It's not business logic. It's presentation logic.
Question that this if answers is: should we show moderation tools?
It's not like: 'should regular user be able to delete whole history of payments' or something.
I agree with Arnis L. - but will add the following.
You could do this instead...
<% show post here %>
<% Html.RenderAction<MyControllerName>(m => m.ModerationTools()); %>
<% show rest of content %>
This has several benefits.
It encapsulates the Model and View required for moderation tools in a single Action.
It will probably remove the need for you to even have the role on the Model in the page you've posted as an example
It can be re-used on other pages.
Personally, I think what's more important here is the difference between good and good enough...
While technically I think that classifies as business logic, for such a simple case I don't see a problem including it in the view.
Now, if you had more complex logic, I'd suggest creating a new view for each "logic branch", and choosing between them in the controller.
I'm trying to pass a list of a few items to a view via the ViewData to create a drop down list. This shouldn't be too difficult, but I'm new to MVC, so I'm probably missing something obvious.
The controller assigns the list to the ViewData:
ViewData["ImageLocatons"] = new SelectList(gvr.ImageLocations);
and the view tries to render it into a drop down list:
<%= Html.DropDownList("Location", ViewData["ImageLocations"] as SelectList) %>
However, when I run it, I get this error:
There is no ViewData item of type 'IEnumerable' that has the key 'Location'.
Any ideas why this isn't working? Also, shouldn't it be looking for the key "ImageLocations" rather than location?
If you use:
ViewData["Location"] = new SelectList(gvr.ImageLocations);
and
<%= Html.DropDownList("Location") %>
Your life will be a lot easier.
Also check out the typo (missing i) when setting the ViewData in your example (ImageLocatons => ImageLocations). This causes the second parameter you pass to DropDownList to be null. This will cause the MVC engine to search for Location.
Is it possible that your ViewData was reset?
Try putting a break point in your View on the line where you emit the drop down list.
Then do a quick watch on ViewData["ImageLocations"].
Make sure that there is a value here when the view tries to use it.