How to show and hide buttons in grails depending on the condition? - grails

In my controller I have conditions, for example if value is 80, then I need to show certain button in my view, if value is 50 then I need to show a different button in my view. How would I do this in grails?

Its seem not to be a controller logic. You can just in the view do something like:
<g:if test="${val == 80}">
<input type="submit" value="Submit">
</g:if>
<g:else>
<input type="button" value="a button">
</g:else>
If you want to send val from the controller to the view, its something like:
class TestController {
def index = {
['val':80] //or [val: params.val] if you want to get it from parameters.
}
}

If you want to do this on the same page, you need javascript.
If you want to render page according condition, try <g:if> tag

I feel a better way to do this is to use a tag library rather than having logic in your .gsp. Also you can reuse this logic if it is needed elsewhere in your application.
// in your gsp
<lib:showButtons myValue="$val"/>
// in your tag lib
def showButtons = { attrs ->
def myValue = attrs.myValue
def value = "Submit"
def type = "submit"
if(myValue != 80) {
value = "a button"
type = "button"
}
out << '<input type="$type" value="$value" />'
}

Related

stimulus.js live update field outside of controller

On a rails 6 installation, I have the following:
Controller:
# app/controllers/foo_controller.rb
def bar
#items = [["firstname", "{{ FIRSTNAME }}"], ["lastname", "{{ LASTNAME }}"], ["company", "{{ COMPANY }}"]]
end
View:
# app/views/foo/bar.html.erb
<p>Quia <span data-field="firstname">{{ FIRSTNAME }}</span> quibusd <span data-field="firstname">{{ FIRSTNAME }}</span> am sint culpa velit necessi <span data-field="lastname">{{ LASTNAME }}</span> tatibus s impedit recusandae modi dolorem <span data-field="company">{{ COMPANY }}</span> aut illo ducimus unde quo u <span data-field="firstname">{{ FIRSTNAME }}</span> tempore voluptas.</p>
<% #items.each do |variable, placeholder| %>
<div data-controller="hello">
<input
type="text"
data-hello-target="name"
data-action="hello#greet"
data-field="<%= variable %>"
value="<%= placeholder %>">
</div>
<% end %>
and the relevant stimulus code (vanilla JS):
//app/javascript/controllers/hello_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "name" ]
greet() {
var elements = document.body.querySelectorAll('[data-field="' + this.nameTarget.dataset.field + '"]');
for (var i = 0; i < elements.length; i++) {
elements[i].innerText = this.nameTarget.value;
};
}
}
Now, as you might have guessed, the idea is to generate one <input> field per item from the #items hash, pre-filled with the relevant value and "linked" with a <span>, which it updates on value change. So far, everything works.
Here's my issue though. This part is plain old dirty vanilla js, which doesn't feel too 'stimulusy':
var elements = document.body.querySelectorAll('[data-field="' + this.nameTarget.dataset.field + '"]');
for (var i = 0; i < elements.length; i++) {
elements[i].innerText = this.nameTarget.value;
};
Surely there's some way to improve this. Any suggestion as to how to refactor this code in a more elegant way would be most welcome.
An approach would be to have two controllers, one for the 'thing that will change the content' (let's call this content) and another for the 'thing that will show any updated content somewhere else' (let's call this output).
Once you set up two controllers, it becomes a bit easier to reason about them as being discrete. One does something when a value updates from user interaction and the other should so something when it knows about an updated value.
Stimulus recommends cross controller coordination with events. JavaScript event passing is a powerful, browser native, way to communicate across elements in the DOM.
First, let's start with the simplest case in HTML only
In general, it is good to think about the HTML first, irrespective of how the content is generated on the server side as it will help you solve one problem at a time.
As an aside, I do not write Ruby and this question would be easier to parse if it only had the smallest viable HTML to reproduce the question.
Below we have two div elements, one sits above and is meant to show the name value inside the h1 tag and the email in the p tag.
The second div contains a two input tags and these are where the user will update the value.
I have hard-coded the 'initial' data as this would come from the server in the first HTML render.
<body>
<div
class="container"
data-controller="output"
data-action="content:updated#window->output#updateLabel"
>
<h1 class="title">
Hello
<span data-output-target="item" data-field="name">Joe</span>
</h1>
<p>
Email:
<span data-output-target="item" data-field="email">joe#joe.co</span>
</p>
</div>
<div data-controller="content">
<input
type="text"
data-action="content#update"
data-content-field-param="name"
value="Joe"
/>
<input
type="text"
data-action="content#update"
data-content-field-param="email"
value="joe#joe.co"
/>
</div>
</body>
Second - walk through the event flow
Once an input is updated, it will fire the conten#update event on change.
The data-content-field-param is an Action Parameter that will be available inside the event.params given to the class method update on the content controller.
This way, the one class method has knowledge of the element that has changed (via the event) and the field 'name' to give this when passing the information on.
The output controller has a separate action to 'listen' for an event called content:updated and it will listen for this event globally (at the window) and then call its own method updateLabel with the received event.
The output controller has targets with the name item and each one has the mapping of what 'field' it should referent in a simple data-field attribute.
Third - create the controllers
Below, the ContentController has a single update method that will receive any fired input element's change event.
The value can be gathered from the event's currentTarget and the field can be gathered via the event.params.field.
Then a new event is fired with the this.dispatch method, we give it a name of updated and Stimulus will automatically append the class name content giving the event name content:updated. As per docs - https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events
The OutputController has a target of name item and then a method updateLabel
updateLabel will receive the event and 'pull out' the detail given to it from the ContentController's dispatch.
Finally, updateLabel will go through each of the itemTargets and see if any have the matching field name on that element's dataset and then update the innerText when a match is found. This also means you could have multiple 'name' placeholders throughout this controller's scoped HTML.
class ContentController extends Controller {
update(event) {
const field = event.params.field;
const value = event.currentTarget.value;
this.dispatch('updated', { detail: { field, value } });
}
}
class OutputController extends Controller {
static targets = ['item'];
updateLabel(event) {
const { field, value } = event.detail;
this.itemTargets.forEach((element) => {
if (element.dataset.field === field) {
element.innerText = value;
}
});
}
}
An alternate approach is to follow the Publish-Subscribe pattern and simply have one controller that can both publish events and subscribe to them.
This leverages the recommended approach of Cross-controller coordination with events.
This approach adds a single controller that will be 'close' to the elements that need to publish/subscribe and is overall simpler to the first answer.
PubSubController - JS code example
In the controller below we have two methods, a publish which will dispatch an event, and a subscribe which will receive an event and update the contoller's element.
The value used by this controller is a key which will serve as the reference for what values matter to what subscription.
class PubSubController extends Controller {
static values = { key: String };
publish(event) {
const key = this.keyValue;
const value = event.target.value;
this.dispatch('send', { detail: { key, value } });
}
subscribe(event) {
const { key, value } = event.detail;
if (this.keyValue !== key) return;
this.element.innerText = value;
}
}
PubSubController - HTML usage example
The controller will be added to each input (to publish) and each DOM element you want to be updated (to subscribe).
Looking at the inputs you can see that they have the controller pub-sub and also an action (defaults to triggering when the input changes) to fire the publish method.
Each input also contains a reference to its key (e.g email or name).
Finally, the two spans that 'subscribe' to the content are triggered on the event pub-sub:send and pass the event to the subscribe method. These also have a key.
<body>
<div class="container">
<h1 class="title">
Hello
<span
data-controller="pub-sub"
data-action="pub-sub:send#window->pub-sub#subscribe"
data-pub-sub-key-value="name"
>Joe</span
>
</h1>
<p>
Email:
<span
data-controller="pub-sub"
data-action="pub-sub:send#window->pub-sub#subscribe"
data-pub-sub-key-value="email"
>joe#joe.co</span
>
</p>
</div>
<div>
<input
type="text"
data-controller="pub-sub"
data-action="pub-sub#publish"
data-pub-sub-key-value="name"
value="Joe"
/>
<input
type="text"
data-controller="pub-sub"
data-action="pub-sub#publish"
data-pub-sub-key-value="email"
value="joe#joe.co"
/>
</div>
</body>

Populate Grails g:select from function

Trying to populate a g:select from a function in my controller, is it possible?
Currently we have this:
def run(Long id) {
def reportInstance = Report.get(id)
def listPromptValues = populatePrompts(reportInstance)*.values()
if (!reportInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'report.label', default: 'Report'), id])
return
}
[reportInstance: reportInstance, listPromptValues: listPromptValues]
}
def populatePrompts(Report rp){
//for each prompt in the report, go out and get it's values
rp.prompts.collectMany {
reportService.listDatatypeValues(it.datatype)
}
}
And then the g:select in question is
<li class="fieldcontain">
<g:each var="prompt" in="${reportInstance.prompts}">
<span id="prompts-label" class="property-label">
<g:message code="report.prompts.label" default="${prompt.name}:" />
</span>
<g:if test="${prompt.datatype.type == 'DropDown'}">
<g:select id="prompt.name" from="${listPromptValues}" name="prompt.name" value="" noSelection="['':'']"/>
<br>
</g:if>
</g:each>
</li>
Which works just fine if there is only one prompt, however we need to be able to call the populatePrompt function directly from the gsp view if there is more than one prompt in the loop, possible sending the reportId and then getting back the listPromptValues. I can't seem to get the onChange(remoteFunction...) to work properly and am coming up empty handed in searching the vast Google webs.
Something like how createLink works
${createLink(controller:'report', action:'runReport', id:"${reportInstance.id}")}
But instead of createLink, it would be the from attribute of the select tag, something like:
<g:select id="prompt.name" from="{(controller:'report',action:'populatePrompt', id:"${reportInstance.id}")}" name="prompt.name" value="" noSelection="['':'']"/>
Any ideas, or direction to go?
I think #JamesKleeh proposed a viable solution in his last comment.
Given the fact that your gsp structure is quite static, it doesn't make sense to fetch the prompt select options to be dynamically loaded. Just return these options in a List package within listPromptValues from your controller and take it in the gsp directly.
Concerning your parameters like [prompt1: ['a','b','c'], prompt2: ['d','e','f']], you can get this map in your populatePrompts method and put each key-value pair into gsp select tags. Like this:
controller
{ ....
def listPromptValues = populatePrompts(reportInstance)
....
}
def populatePrompts(Report rp){
//for each prompt in the report, go out and get it's values
def promptMap = [:] //map to be returned
rp.prompts.each {
promptMap.put(it.name, reportService.listDatatypeValues(it.datatype))
}
return promptMap
}
gsp
<g:each var="prompt" in="${reportInstance.prompts}">
<span id="prompts-label" class="property-label">
<g:message code="report.prompts.label" default="${prompt.name}:" />
</span>
<g:if test="${prompt.datatype.type == 'DropDown'}">
<g:select id="prompt.name" from="${listPromptValues[prompt.name]}" name="prompt.name" value="" noSelection="['':'']"/>
<br>
</g:if>
</g:each>

MVC In Html.Begin form need two buttons call different actions with all the values of the form

Frnds i want to submit data to two different controllers using two different buttons on one form and i want that both buttons call different actions and also provide all the values of the form to their respected controller.
You can do this with JavaScript like so:
HTML
<form method="POST" action="/">
... The Rest Of Your Form ...
<input type="Submit" id="btn-1" name="btn-1" />
<input type="Submit" id="btn-2" name="btn-2" />
</form>
JavaScript
(function(d) {
var form = d.getElementsByTagName('form')[0],
targetBtn;
form.onsubmit = function(e) {
e.preventDefault();
targetBtn = e.explicitOriginalTarget.id;
//Set the form action based on the id of the button that submitted it
if(targetBtn === 'btn-1') {
form.action = '/foo';
} else {
form.action = '/bar'
}
//Submit your form
form.submit();
};
}(document));
All you need to do is make sure this code is placed after the from in the HTML markup or add it to a jQuery document.ready function.
here is a good article that can help you accomplish what you want
http://blog.ashmind.com/2010/03/15/multiple-submit-buttons-with-asp-net-mvc-final-solution/

Update Drop Down Value in Grails using <g:submitToRemote>

I have just entered into the Grails arena.
I have a requirement where I want to put one drop down and one button on one page, and by clicking on button only that drop down values should be changed, other controls on the page should remain unchanged.
My code is as follows :
_connType.gsp
<div id="mappedDeviceDiv">
<select id="mappedDevice" name="mappedDevice" value="" style="width:200px">
<g:each in="${deviceList}" status="i" var="dl">
<option value="${dl}">${dl}</option>
</g:each>
</select>
<g:submitToRemote class="blackButton" update="mappedDeviceDiv"
url="${[controller:'resource',action:'getDeviceList']}"
value="Get Devices"/>
</div>
ResourceController.groovy
def getDeviceList = {
println "Getting NV devices.. + Nirmal" + params
def model = buildAccessRequestModel()
List<NVDeviceBean> deviceList = NVUtil.getDevices(params.datasource, null);
Collections.sort(deviceList);
List<String> devices = []
for(NVDeviceBean deviceBean : deviceList) {
devices.add(deviceBean.getName())
}
println "list = "+devices
model.putAt('deviceList', devices)
render (template:'config/connType',model:model)
}
So, in above scenario, it's setting the values in the devices perfectly, but at the view side in a drop down m getting whole connType page, instead of only list values which is in devices variable of controller.
Any help would be highly appreciated..
You might want to create/modify one of the controller actions to return a list of HTML options. You could even have this action render a template, too.
def getDeviceOptions = {
def options = []
// code that creates the option list goes here.
render(template:'config/optionList', model: [optionList: options ])
}
And the template...
<!-- config/_optionList.gsp -->
<g:each in="${optionList}" status="i" var="dl">
<option value="${dl}">${dl}</option>
</g:each>
Then, tell the g:submitToRemote tag to update the select object.
<g:submitToRemote class="blackButton" update="mappedDevice"
url="${[controller:'resource',action:'getDeviceOptions']}"
value="Get Devices"/>
That should get you started in the right direction.

Grails 1.2.1 with Webflow Plugin (1.2.1) - params not being returned

I am using the webflow plugin for Grails for the first time and am having some difficulties.
To summarize, once within the Webflow, no information appears to be returning to the controller from the form. All examples that I have looked at indicate that the params are returned to the controller action normally and then you can put objects into the flow scope as necessary. Unfortunately, the illustrated printlns are both outputting null, and any programatic output of the params shows that the expected 'testField1' and 'testField2' are not in the params object. Excuse the non-uniform text boxes and ways of submitting - they were the result of experimentation.
A simplified version of the controller action flow:
def generateProductVariantsFlow = {
start() {
action {
[productInstance:Product.get(params.id)] //the entry params contains the expected id
}
on ("success").to("selectAttributeValues")
}
selectAttributeValues() {
on("next"){TestCommand tc -> //params does not have testField1 or testField2
println "TEST COMMAND"
println "${tc.testField1}"
println "${tc.testField2}"
}.to("selectProductVariants")
on("cancel").to("finishBeforeStart")
}
selectProductVariants {
on("cancel").to("finish")
on("previous").to("selectAttributeValues")
on("next").to("confirmNewVariants")
}
//other states here
finish {
redirect(action:"list")
}
finishBeforeStart { //somewhat misleading state name, but shouldn't be relevant
redirect(controller:"product",action:"show")
}
}
The GSP and Command are equally simple -
selectAttributeValues GSP:
<%# page import="com.castaway.rigging.Product" %>
<g:form action="generateProductVariants">
<input type="integer" id="testField1" name="testField1" value="test1" />
<g:textField name="testField2" value="test2"/>
<div class="buttons">
<span class="button"><g:actionSubmit class="cancel" name="cancel" value="Cancel"/></span>
<g:link action="generateProductVariants" event="next" >Next</g:link>
</div>
</g:form>
</div>
</body>
Command:
class TestCommand implements Serializable {
def testField1
def testField2
}
Why do you use a link instead of a submit button to trigger the next event?
Clicking that link will do a GET request which will not include the form fields.
You need to use a submit button to trigger the next event.
cheers
Lee

Resources