Override Grails <g:set> variable's value in a <g:layoutBody> - grails

I have a layout.gsp where I define some markup for a control (say, banner) that might be displayed on any page (or might not).
<g:set var="showBanner" value="${...}" scope="page|request|flash|session"/>
<!-- Some more logic that may g:set showBanner var -->
<g:layoutBody/>
<g:if test="${[flash|request|???].showBanner}">
<div id="banner">...</div>
</g:if>
The idea is to let the page, rendered by <g:layoutBody>, to decide if it wants the banner on it or not. So, one page may decide to always show banner, as following - page1.gsp:
<g:set var="showBanner" value="${true}" scope="page|request|flash|session"/>
Another page decides to never show banner on it, as following - page2.gsp:
<g:set var="showBanner" value="${false}" scope="page|request|flash|session"/>
Unfortunately, this approach doesn't work for me. I tried all different combinations of scope attribute and still cannot have it overridden in children pages.
Is this a wrong approach in general or I miss some detail?

I found what I was doing wrong. The issue is that variables set inside included page are evaluated before the layout.gsp code runs, not at the moment of <g:layoutBody/> tag insertion (or call).
In other words, dependencies are rendered before the layout, not at the moment layout directive is encountered. This may be intuitive for some, but not for others (not for me).
Another point is that you still need to use request scope to access the same var between pages (which is quite intuitive).
So, the solution becomes:
First in page1.gsp:
<g:set var="showBanner" value="${true}" scope="request"/>
Then in layout.gsp:
<!-- Doesn't matter were you put it, always evaluated first -->
<g:layoutBody/>
<g:if test="request.showBanner == null"> <!-- if not set by children page -->
<g:set var="showBanner" value="${...}" scope="request"/>
<!-- Some more logic that may g:set request.showBanner var -->
</g:if>
<g:if test="${request.showBanner}">
<div id="banner">...</div>
</g:if>

What about you set the variable inside an interceptor? Like one shown here: Accessing the model from a layout view in Grails

Related

Dereferecing in gsp inner html

I have a layout file. I want something like this in each of my pages inheriting that layout:
Just ${step} Steps Away From The Awesome!!
So in my layout I have defined a string as above with a placeholder step. I dont want to pass the value of this placeholder from controller. I wish to define it in the gsp that inherits this layout.
I was looking for something like <g:set var="step" value="1"/> (or 2 or 3 depending on the gsp). But it does not work if I define it like that.So how do I dereference the value of "step" inside each extending layout?
One of the best ways to accomplish this is to make use of content blocks and page properties. These are both features derived from Sitemesh.
I assume you only want to conditionally include this information when the page using the layout provides a value. So in my example here I have wrapped it in a quick if check.
In your layout:
<g:if test="${pageProperty(name: 'page.step')}">
Just <g:pageProperty name="page.step" /> Steps Away From The Awesome!!
</g:if>
Then in any page that uses the layout you can include the content for the variable step
<content tag="step">3</content>
Note that the value within the content tag can be whatever you like. It will be evaluated when the page is rendered.

conditional check in grails site mesh

I have my gsp pages all applying the layout from main.gsp. Main.gsp is using the bootstrap layout and a container to make the pages responsive. In a few of the pages on my site I'm trying to use a liquid layout and don't want to have a container wrapping all of the content in each of the sub pages.
Is there a way to pass a variable or set something in a page that applies the layout of main.gsp, and have a conditional piece of code execute in the main layout?
Neither of these attempts below worked. In one I'm setting a variable in the sub page, and in another attempt I'm passing in a variable in the model. In both cases the container is being rendered in profile.gsp.
main.gsp:
<body>
<g:if test="${fluid != true}">
<div class="container">
</g:if>
...
profile.gsp:
<g:set var="fluid" value="true"/>
<g:applyLayout name="main" model="[fluid:'true']">
<html>
<head>
...
You are Setting a String value to var fluid in:
<g:set var="fluid" value="true"/>
In case <g:if test="${fluid != true}"> it is checking a boolean not string comparison so you need to define either a boolean or you need to check for sstring comparison.
To set boolean you can do <g:set var="fluid" value="${true}"/> cause "true" will make it just a string with value true not a boolean true.
in addtion to Sachin Vermas response, you can use two additional approaches:
not a clean solution, but it should work: in the layout, there is the params-map and the session available, so you could store your flag in one of those objects.
another solution would be to not only do a conditional check in your layout, but use a whole new layout:
'<g:applyLayout name="${fluid?'fluid':'main'" >'
but as Sachin Verma already replyed, the fluid variable has to be of the right type. As I do understand, in your case you even wouldn't have to dynamicalle switch between two layouts, but you could just use `
<g:applyLayout name="fluid" >`
for your fluid pages and `
<g:applyLayout name="main" >`
otherwise. This would make your code even cleaner in case that you not only have to hide a div.

Render scripts for use in Partial View?

I've seen numerous sources stating that it's incorrect / bad practice to put scripts in Partial Views, but this raises a huge question...
How are you supposed to run scripts that interact with Partial Views? I've tried using #section scripts { } or something like that, but it doesn't work. At all. The section contains the scripts just fine, but they don't get rendered when the Partial is loaded into the full View.
In addition, I can't render the scripts my Partial View needs on my full View because the scripts pull values from the Model, which is only rendered on the Partial View, since it's the piece of the puzzle that actually works with data.
I hope that doesn't sound too complicated... How can I effectively, efficiently, and correctly render scripts for use with my Partial View's elements and Model?
#keyCrumbs I will not give you a direct answer, but something for you to analyze.
One of the most biggest problems in you call scripts for you partial view is replication of code.
Think you'll use ajax to get the partial view and you will continue doing this for a while. In every call you'll download the script code, and you put it in html. Every time you reset the script block in html, the functions are reset, the variables are reset. It's can be a big problem depending on your js code.
Other point is the size of the response, you can say, but js has a small size, and I'll say multiply this size for every call of a user and next multiply for ever user connected.
So finally, a solution for you, in this case is: create function in the page and call the function in partial view like this:
Your page: Page.cshtml
<script type="text/javascript">
function myPartialView_Load() {
$("birth").datepicker();
$("phone").mask("000-0000");
}
</script>
<!-- Some other code here -->
<div>
<!-- Or you can call from a ajax or other way... -->
#Html.Action("MyActionPartialView")
</div>
Your partial view: MyPartialView.cshtml
<script type="text/javascript">
$(function () { myPartialView_Load(); });
</script>
<form>
<input type="text" name="birth" id="birth" />
<input type="text" name="phone" id="phone" />
</form>
So as you see the problem lies not in putting js in partial view, but the way as you do it. All rules of good practice has a "why" behind, if you understand the "why" you can determine how far the rule is a help and not a burden. And decide break it or not.
Like a said, I'm not given you a ultimate answer but something you thing about. You can use other ways to contour the problem.

Only display first error in gsp

I'm performing inline validation on fields as the user tabs between them.
A problem occurs when there is more than one error against a field i.e both errors are shown.
I only want to show one error (The first one for arguments sake).
Is there are different tag to deal with this?
<jqvalui:renderError for="title">
<g:eachError bean="${objInstance}" field="title"><g:message error="${it}" /></g:eachError>
</jqvalui:renderError>
Thanks
So essentially you just have to use the errors themselves instead of using the tags provided for you.
<g:hasErrors bean="${objInstance}" field="title">
<g:message error="${objInstance.errors.getFieldErrors("title")[0]}" />
</g:hasErrors>
I know it's like a hack but if no exact solutions...
Consider adding a flag or a counter and set/test it inside the loop:
<g:set var="isErrorShown" value=""/>
<g:eachError bean="${objInstance}" field="title">
<g:if test="${!isErrorShown}">
<g:message error="${it}"/>
<g:set var="isErrorShown" value="TRUE"/>
</g:if>
</g:eachError>

p:commandlink inside ui:repeat

In our application we are displaying the menus dynamically. We have the menu object(menuitems in below code) populated with all the menu items (read from an xml). The home page then generates the menus by usinng ui:repeat. Insdie ui:repeat there are p:commandlink.
Below is the code
<h:form id="mainMenu">
<h:panelGroup id="MMPanel" layout="block" styleClass="left_menu">
<ui:repeat var="node" value="#{menuitems.level1menus}">
<p:commandLink immediate="true" styleClass="menu"
action="#{menuitems.onLevel1MenuChange(node.id)}"
update=":pageTitle :menuLevel2 :menuLevel3" title="#{node.name}"
rendered="#{menuitems.selectedLevel1.id!=node.id}" onclick="menuSelect(this)">
<span class="icon icon_#{node.name}"></span>
<span class="text">#{node.name}</span>
</p:commandLink>
<p:commandLink immediate="true" styleClass="menu activelink"
action="#{menuitems.onLevel1MenuChange(node.id)}"
update=":pageTitle :menuLevel2 :menuLevel3" title="#{node.name}"
rendered="#{menuitems.selectedLevel1.id==node.id}" onclick="menuSelect(this)">
<span class="icon icon_#{node.name}"></span>
<span class="text">#{node.name}</span>
</p:commandLink>
</ui:repeat>
</h:panelGroup>
</h:form>
The menuitems bean is at Session level.
There are two diffeent p:commandlink inside ui:repeat. The only difference between the 2 is in the styleclass and rendered attribute. This is done to identify the default menu item when the user logs for the first time and give it an extra css of "activeLink".
The java script called on onclick is given below (in case it is required)
function menuSelect(selectOne){
$(".left_menu>a").removeClass("activelink");
$(selectOne).addClass("activelink");
removeAllChannels();
}
function removeAllChannels(){
$.atmosphere.unsubscribe();
}
The removeAllChannels is to remove primepush autorefresh channels we have.
The issue i am facing is this.
All the links are getting rendered correctly and in Firebug i see all of them have the same html.
But the one which is generated with the extra css "activeLink" (through rendered condition -- menuitems.selectedLevel1.id==node.id) is not working. Nothing happens when i click this default link. All other links work fine.
So when i click on a different link, it takes me to the required page. But when i click back on the menu which was default, nothing happens
I added a Custom phase listener and saw that all the lifecyle stages are called when this link is clicked but the action method is not.
I went through the links this and this but could not figure out the issue.
Please help.
Do tell me if something is not clear
As part of safeguard against tampered/hacked requests, the JSF component's rendered attribute is re-evaluated during processing the form submit. In case of a command link/button, if it evaluates false, then its action won't be queued/invoked. This matches the symptoms you're seeing.
This can in turn happen if the managed bean #{menuitems} is request scoped and/or when the properties behind #{menuitems.level1menus} or #{menuitems.selectedLevel1} are incompatibly changed during the postback request.
Putting the bean in the view scope and ensuring that the getters do not do any business job should fix this problem.
See also:
How to choose the right bean scope?
commandButton/commandLink/ajax action/listener method not invoked or input value not updated - point 5 applies to you

Resources