I have been struggling with trying to create/save multiple instances at once in Grails, now I am almost there with the following code but when I hit save an empty row of options is created, can anyone help me with this
please see these two questions to see what I want to achieve
How to save multiple object from one view using Grails
Grails one to many relationship view
<g:textField name="question" value="${multipleChoiceQuestionInstance?.question}"/><br/>
<div class="fieldcontain ${hasErrors(bean: multipleChoiceQuestionInstance, field: 'options', 'error')} ">
<label for="options">
<g:message code="multipleChoiceQuestion.options.label" default="Options" />
</label>
<ul class="one-to-many">
<g:set var="counter" value="${0}" />
<g:each status="i" in="${multipleChoiceQuestionInstance?.options?}" var="o">
<li>
<g:textField controller="multipleChoiceOption" name="options[${i}].answerOption" action="show" id="${o.id}" value="${o?.encodeAsHTML()}"/>
<g:checkBox name="options[${i}].correctOption" value="${o.correctOption}"/><br/>
</li>
<g:set var="counter" value="${++counter}" />
</g:each>
<li>
<g:textField name="options[${++counter}].answerOption" value=""/>
<g:checkBox name="options[${counter}].correctOption" /><br/>
</li>
<li class="add">
<g:link controller="multipleChoiceOption" action="create" params="['multipleChoiceQuestion.id': multipleChoiceQuestionInstance?.id]">${message(code: 'default.add.label', args: [message(code: 'multipleChoiceOption.label', default: 'MultipleChoiceOption')])}</g:link>
</li>
</ul>
</div>
If you prefer not to click on the link here are the domain classes
Class MultipleChoiceQuestion {
String question
static constraints = {
...
}
static hasMany = [options:MultipleChoiceOption]
class MultipleChoiceOption{
String answerOption
boolean correctOption
MultipleChoiceQuestion question
static constraints = {
...
}
}
}
I am using automatically generated code by grails for the controller, it is as bellow
def create() {
[multipleChoiceQuestionInstance: new MultipleChoiceQuestion(params)]
}
def save() {
println params
def multipleChoiceQuestionInstance = new MultipleChoiceQuestion(params)
if (!multipleChoiceQuestionInstance.save(flush: true)) {
render(view: "create", model: [multipleChoiceQuestionInstance: multipleChoiceQuestionInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'multipleChoiceQuestion.label', default: 'MultipleChoiceQuestion'), multipleChoiceQuestionInstance.id])
redirect(action: "show", id: multipleChoiceQuestionInstance.id)
}
def update() {
def multipleChoiceQuestionInstance = MultipleChoiceQuestion.get(params.id)
if (!multipleChoiceQuestionInstance) {
.... //deleted for real estate
return
}
if (params.version) {
//version checking stuff
}
}
multipleChoiceQuestionInstance.properties = params
if (!multipleChoiceQuestionInstance.save(flush: true)) {
render(view: "edit", model: [multipleChoiceQuestionInstance: multipleChoiceQuestionInstance])
return
}
flash.message = message(code: 'default.updated.message', args: [message(code: 'multipleChoiceQuestion.label', default: 'MultipleChoiceQuestion'), multipleChoiceQuestionInstance.id])
redirect(action: "show", id: multipleChoiceQuestionInstance.id)
}
The reason that you are getting an empty row of options is because you are leaving a textfield with name option[counter] blank. This empty value is passed as a parameter to the controller action and it creates a new row with these blank values.
You should remove any blank options before calling
multipleChoiceQuestionInstance.properties = params
You can use something like this :
def emptyOptions = params.options.findAll{!it.answerOption}
params.options.removeAll(emptyOptions)
Related
I have been trying to format phone numbers in a grails app by using the following taglib
package rewards
class MasksTagLib {
static defaultEncodeAs = [taglib:'html']
//static encodeAsForTags = [tagName: [taglib:'html'], otherTagName:
[taglib:'none']]
def phone334 = { attrs ->
String phone = attrs.phone
def formatted = "("+phone.substring(0,3)+") "+pho ne.substring(3,6)+"-"+phone.substring(6)
out << formatted
}
}
In addition I have used the import statement to use the taglib in my views, however the format is not changing the display in the index.gsp for the CustomerController
This works in the following view, but I can't get it to work elsewhere the view is profile.gsp
<%# page import="rewards.Customer" %>
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="main">
<g:set var="entityName" value="${message(code: 'customer.label', default: 'Customer')}" />
<title>Customer Profile</title>
</head>
<body>
<div id="edit-customer" class="content scaffold-edit" role="main">
<h1>Customer Profile</h1>
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<g:hasErrors bean="${customerInstance}">
<ul class="errors" role="alert">
<g:eachError bean="${customerInstance}" var="error">
<li <g:if test="${error in org.springframework.validation.FieldError}">data-field-id="${error.field}"</g:if>><g:message error="${error}"/></li>
</g:eachError>
</ul>
</g:hasErrors>
<g:form url="[resource:customerInstance, action:'updateProfile']" method="PUT" >
<g:hiddenField name="version" value="${customerInstance?.version}" />
<fieldset class="buttons">
<g:actionSubmit class="save" action="updateProfile" value="${message(code: 'default.button.update.label', default: 'Update')}" />
</fieldset>
<fieldset class="form">
<div class="fieldcontain ${hasErrors(bean: customerInstance, field: 'firstName', 'error')} ">
<label for="firstName">
<g:message code="customer.firstName.label" default="First Name" />
</label>
<g:textField name="firstName" value="${customerInstance?.firstName}"/>
</div>
<div class="fieldcontain ${hasErrors(bean: customerInstance, field: 'lastName', 'error')} ">
<label for="lastName">
<g:message code="customer.lastName.label" default="Last Name" />
</label>
<g:textField name="lastName" value="${customerInstance?.lastName}"/>
</div>
<div class="fieldcontain ${hasErrors(bean: customerInstance, field: 'phone', 'error')} required">
<span id="phone-label" class="property-label"><g:message code="customer.phone.label" default="Phone" /></span>
<span class="property-value" aria-labelledby="phone-label"><g:phone334 phone="${customerInstance?.phone}"/></span>
</div>
<div class="fieldcontain ${hasErrors(bean: customerInstance, field: 'email', 'error')} ">
<label for="email">
<g:message code="customer.email.label" default="Email" />
</label>
<g:textField name="email" value="${customerInstance?.email}"/>
</div>
<div class="fieldcontain ${hasErrors(bean: customerInstance, field: 'totalPoints', 'error')} required">
<span id="totalPoints-label" class="property-label"><g:message code="customer.totalPoints.label" default="Total Points" /></span>
<span class="property-value" aria-labelledby="totalPoints-label"><g:fieldValue bean="${customerInstance}" field="totalPoints"/></span>
</div>
</fieldset>
</g:form>
</div>
<div id="list-award" class="content scaffold-list" role="main">
<g:if test="${flash.message}">
<div class="message" role="status">${flash.message}</div>
</g:if>
<table>
<thead>
<tr>
<g:sortableColumn property="type" title="${message(code: 'award.type.label', default: 'Type')}" />
<g:sortableColumn property="awardDate" title="${message(code: 'award.checkinDate.label', default: 'Award Date')}" />
<th><g:message code="award.customer.label" default="Phone" /></th>
<g:sortableColumn property="points" title="${message(code: 'award.points.label', default: 'Points')}" />
</tr>
</thead>
<tbody>
<g:each in="${customerInstance.awards}" status="i" var="checkinInstance">
<tr class="${(i % 2) == 0 ? 'even' : 'odd'}">
<td>${fieldValue(bean: checkinInstance, field: "type")}</td>
<td>${fieldValue(bean: checkinInstance, field: "awardDate")}</td>
<td><g:phone334 phone="${customerInstance?.phone}"/></td>
<td>${fieldValue(bean: checkinInstance, field: "points")}</td>
</tr>
</g:each>
</tbody>
</table>
</div>
</body>
I would appreciate any help I can get.
Here is the Controller. It is called CustomerController.groovy and it is displaying the list using "Index". How can I apply the phone format to this list
package rewards
class CustomerController {
static scaffold = true
def calculationsService
def lookup() {
// def customerInstance = Customer.list(sort: "lastName", order:
"desc", max: 5, offset: 5)
// dynamic queries
// def customerInstance = Customer.findAllByLastName("Foster")
// def customerInstance = Customer.findAllByTotalPoints(5, [sort: "lastName", order: "desc"])
// def customerInstance = Customer.findAllByPhone(params.id) // for one row return findBy if rows > 1 only first
// def customerInstance = Customer.findAllByLastNameLike("H%") // Case Sensitive
// def customerInstance = Customer.findAllByLastNameIlike("b%") // Case Insensitive
// def customerInstance = Customer.findAllByTotalPointsGreaterThanEquals(3, [sort: "totalPoints"])
// def customerInstance = Customer.findAllByTotalPointsBetween(2, 4, [sort: "totalPoints"])
def customerInstance = Customer.findAllByFirstNameIlikeAndTotalPointsGreaterThanEquals("b%", 3)
[customerInstanceList: customerInstance]
}
def customerLookup(Customer lookupInstance) {
// Query customer by Phone number - service
// If no result, - controller
// create a new customer - service
// create welcome message - service
// add award reward - service
// save customer - service
// send welcome to kiosk - controller
// if customer found - controller
// calculate total ponts - service
// create welcome message - service
// add award reward - service
// save customer - controller
// send welcome to kiosk - controller
def (customerInstance, welcomeMessage) = calculationsService.processCheckin(lookupInstance)
render(view: "checkin", model:[customerInstance: customerInstance, welcomeMessage: welcomeMessage])
}
def index() {
params.max = 10
[customerInstanceList: Customer.list(params), customerInstanceCount: Customer.count()]
}
def create() {
[customerInstance: new Customer()]
}
def save(Customer customerInstance) {
customerInstance.save()
redirect(action: "show", id: customerInstance.id)
}
def show(Long id) {
def customerInstance = Customer.get(id)
customerInstance = calculationsService.getTotalPoints(customerInstance)
[customerInstance: customerInstance]
}
def edit(Long id) {
def customerInstance = Customer.get(id)
[customerInstance: customerInstance]
}
def update(Long id) {
def customerInstance = Customer.get(id)
customerInstance.properties = params
customerInstance.save()
redirect(action: "show", id: customerInstance.id)
}
def delete(Long id) {
def customerInstance = Customer.get(id)
customerInstance.delete()
redirect(action: "index")
}
def profile() {
def customerInstance = Customer.findByPhone(params.id)
[customerInstance: customerInstance]
}
def updateProfile(Customer customerInstance) {
customerInstance.save()
render(view: "profile", model:[customerInstance: customerInstance])
}
def checkin() {}
}
Customer List Display
If you want to customize your index view, your best bet in this case is to stop using the scaffolded page, yes. (There are alternatives, but they're overly complicated for this situation, and don't offer you nearly as much control over the resulting view.)
In general, it's not a good idea long-term to have half-scaffolded content. By this, I mean either scaffolded controllers, or scaffolded views, but not the other side. Long term, one or the other is likely to change, and your pages will break.
I have gsp with two textfield for firstname-lastname and reCaptcha. What I want is for every wrong captcha code the user's input for firstname and last name won't be erased.
snippet for controller:
***captcha_code****
if (result) {
def person = new Person(params)
person.save()
render "Success!"
} else {
flash.message = message(code: 'forgotPassword.captcha.wrong')
redirect(controller:'person', action:'form')
}
snipper for form.gsp
***captcha_code_here***
<g:form controller="person" action="save">
<label>First Name: </label>
<g:textField name="firstName"/><br/>
<label>Last Name: </label>
<g:textField name="lastName"/><br/>
<g:if test="${flash.message}">
<div class="message" role="status" style="font-size: medium;color: green;">${flash.message}</div>
</g:if>
***captcha_code_here***
<g:actionSubmit value="Save"/>
To repopulate the fields you can use the same flash scope you're using for the message. On error, add the first and last name to the flash scope, and then in your GSP use those values when they are available:
PersonController
class PersonController {
def save() {
...
if(/* recaptcha failed */) {
flash.firstName = params.firstName
flash.lastName = params.lastName
}
...
}
}
GSP
<label>First Name: </label>
<g:textField name="firstName" value="${flash.firstName ?: ''}"/><br/>
<label>Last Name: </label>
<g:textField name="lastName" value="${flash.lastName ?: ''}"/><br/>
In Controller Action, send back fields that you want to be repopulated.
In my Grails application I have multiple pages that list a group of data objects inside of a table. In these pages I provide a search function which when performed will adjust the table to only display the data objects that match the query. However If a user decides to delete one of these data objects the application will take them back to the default table which displays everything. I would like for query results to remain intact after performing a delete.
I will use my "Skill Evaluations" page as my example in this post.
Here is the relevant code in the domain class
SkillEval.groovy
class SkillEval {
static hasMany = [lines: SkillEvalL, courses: CourseOffering, choiceLabels: ChoiceLabel]
String name
String formVersion
static def search(params) {
def criteria = SkillEval.createCriteria()
def results = criteria.list(params) {
or {
ilike("name", params.search+'%')
}
}
return results
}
}
Relevant section of the gsp view file
list.gsp
<g:form>
<div class="search">
<label for="searchField">Search:</label> <input type="text"
id="searchField" name="search" value="${params.search}" /> <input
type="submit" value="Search" />
</div>
<br>
<table id="mainTable">
<thead>
<tr>
<th>Name</th>
<th>Version</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<g:each var="eval" in="${skillEvalList}">
<tr>
<td>
<strong>
${eval.name}
</strong>
</td>
<td>
${eval.formVersion}
</td>
<td>
<g:actionSubmit value="Delete" controller="skillEval" action="delete" onclick="setId(${eval.id});return confirm('Are you sure?');" class="delete" />
</td>
</tr>
</g:each>
</tbody>
</table>
<g:if test="${(skillEvalCount/maxCount) > 1}">
<div class="pagination">
<g:paginate action="list" total="${skillEvalCount}" />
</div>
</g:if>
<input id="evalId" type="hidden" name="id" value="" />
</g:form>
</div>
<r:script>
function setId(id)
{
$('#evalId').val(id);
}
</r:script>
Relevant code in the Controller class
SkillEvalController.groovy
def delete(Long id) {
def skillEval = SkillEval.get(id)
if (skillEval) {
def allInstances = SkillEvalI.findAllByForm(skillEval)
allInstances.each { evalInstance ->
evalInstance.lines.clear()
if(!evalInstance.delete()) {
println "Failed to delete skill eval instance"
}
else {
println "Instance successfully deleted."
}
}
try {
skillEval.delete(flush: true)
}
catch (DataIntegrityViolationException e) {
}
}
redirect(action: "list")
}
How can I make the view retain the queried results after deleting one of the queried items?
When you redirect to the list action you can provide parameters -- just capture the parameters (if any) in the delete action and pass them in the redirect call to list at the end of the controller action.
(Update: Forgot that actionSubmit does not accept params as an attribute, so cobbled together this solution from this SO answer and the Grails docs)
Example:
// View (list.gsp)
<g:actionSubmit action="deleteWithParams" value="Delete" ... />
// Controller
def list() {
def deleteWithParams = { forward(action:'delete', params:[search: params?.search]) }
render...
}
def delete(Long id) {
// Deleting the skillEval ...
redirect(action: "list", params: [search: params?.search])
}
a have a problem with my code. how can i handle an error from service to my gsp? i tried it with render from service or controller, Validation error occured during call to save(): - Field error in object 'talent.CandidateProfile' on field 'core.db_email': rejected value []; codes with full exception trace. my sources:
def create() {
[candidateProfileInstance: new CandidateProfile(params)]
}
def save() {
def candidateProfileInstance = new CandidateProfile(params)
if (!candidateProfileInstance.save(flush: true)) {
render(view: "create", model: [candidateProfileInstance: candidateProfileInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'candidateProfile.label', default: 'CandidateProfile'), candidateProfileInstance.id])
redirect(action: "show", id: candidateProfileInstance.id)
}
My .gsp page code
<div class="full-filed">
<h3>Email Address:</h3>
</div>
<div class="fieldcontain ${hasErrors(bean: candidateProfileInstance, field: 'core.db_email', 'error')} ">
<label for="core.db_email" class="error_message"> <g:message
code="candidateProfile.core.db_email.label" default="Dbemail" />
</label>
</div>
<g:textField name="core.db_email" value="" class="loginTxtBox" placeholder="Email Address" />
i want just show to user an error, but not full exception trace
form your config.grooyPage
//grails.gorm.failOnError = true
just comment to this line...SO it will display your Custom Error
I have this DomainClass:
package cm
class XXX{
String city
String website
static constraints = {
city(nullable: true)
website(nullable: true)
}
static mapping = {
id column:'xxx_id'
city column: 'xxx_city'
website column: 'xxx_website'
table "xxx_XXX"
version false
}
}
The Controller:
class ConferenceController {
static allowedMethods = [save: "POST", update: "POST", delete: "POST"]
def index = {
redirect(action: "list", params: params)
}
def list = {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
[XXXInstanceList: XXX.list(params), XXXInstanceTotal: XXX.count()]
}
def save = {
def XXXInstance= new XXX(params)
if (!XXXInstance.save(flush: true)) {
render view: 'add', model: [XXXInstance: XXXInstance]
return
}
//flash.message = "${message(code: 'default.created.message', args: [message(code: 'person.label', default: 'Person'), personInstance.id])}"
redirect(uri:"/index.gsp")
}
}
and my add.gsp page:
<head>
<title> xXXx</title>
<meta name="layout" content="main2" />
</head>
...
<g:form controller="XXX" action="save">
<g:hasErrors bean="${XXXInstance}">
<div class="errors">
<g:renderErrors bean="${XXXInstance}" as="list" />
</div>
</g:hasErrors>
year
<g:textField name="year" /><br>
website
<g:textField name="website" /><br>
<g:submitButton name="save2" value="Save" />
</g:form></div>
</div>
...
</body>
With this current code, everything works fine, but when a contraint is failed, it is showns the corresponding error but the written values are gone.. I cant figure this out - i'v tried a lot of things, but im sure the solution is as easy as my question -.- Please help.
I think you should check the *.properties file in YOUR_PROJECT\grails-app\i18n folder. They have the definition for translation. Investigate it for a time and refer to the document if need, you will understand how Grails perform translation.
And uncomment the following line in your controller:
//flash.message = "${message(code: 'default.created.message', args: [message(code: 'person.label', default: 'Person'), personInstance.id])}"