Solved. I really don't know what I did, but it worked. One of those moments...
My domain object is "Actiune". I want to add a new entry with 4 preset fields from another entry that has the given id to "createNewEtapa" method. So I query that entry (with the id) and get it's values, preset them in the form and the hit save but it does not work. The save will not work.
What am I doing wrong ? I've been going around this for hours. Please help again stackOverflow :)
If you don't feel like checking all the code, please just let me know how is this done or link me a example :)
Thx!
These are the relevalt parts of my code
This is my GSP form:
<g:form action="save" enctype="multipart/form-data">
<fieldset class="form">
<g:applyLayout name="form">
<tmpl:/templates/form />
</g:applyLayout>
</fieldset>
<fieldset class="buttons">
<g:submitButton name="create" class="save"
value="${message(code: 'default.button.adauga.label', default: 'Adauga')}" />
<g:actionSubmit name="create" class="save" action="savenew" value="Adauga2" />
</fieldset>
</g:form>
This is my controller:
def create(Long id){
def actiuneInstance = Actiune.findById(id)
def c = Actiune.createCriteria()
def instanceList = c {
eq("idActiune", actiuneInstance.idActiune)
order("termenPornire", "asc")
}
params.idActiune = actiuneInstance.idActiune
params.tema = actiuneInstance.tema
params.firma = actiuneInstance.firma
params.user = User.findById(springSecurityService.currentUser.id)
[instance: new Actiune(params)]
}
def save() {
def actiuneInstance = new Actiune(params)
actiuneInstance.user = User.findById(springSecurityService.currentUser.id)
actiuneInstance.termenPornire = new Date()
def actiuneBD = Actiune.findByIdActiune(params.idActiune)
actiuneInstance.idActiune = actiuneBD.idActiune
actiuneInstance.tema = actiuneBD.tema
actiuneInstance.firma = actiuneBD.firma
print "in params " + params
print "\\"
print "in save... " + actiuneInstance.properties
if (!actiuneInstance.save(flush: true)) {
render(view: "create", model: [firInstance: actiuneInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'actiune.label', default: 'Actiune'), actiuneInstance.idActiune])
redirect(action: "completeShow", id: actiuneInstance.id)
}
This is the output from the print statements in the save() method
*...*
in params [termenLimita_month:11, termenPornire_month:11, termenPornire_day:1, documentPath:, tema:Promotie A, termenLimita_day:1, _action_savenew:Adauga2, id:, observatii:uuu, termenLimita:date.struct, termenPornire_year:2012, etapa.id:1, etapa:[id:1], contact.id:1, contact:[id:1], termenPornire:date.struct, firma:Google, idActiune:zt8h, termenLimita_year:2012, action:save, controller:actiune]
\
....
in save... [user:Flavian, termenPornire:Thu Nov 01 22:57:45 EET 2012, contact:null, documentPath:null, temaId:1, etapaId:null, firmaId:3, firma:Google, contactId:null, termenLimita:null, idActiune:zt8h, observatii:null, userId:null, etapa:null, tema:Promotie A]
Call actiuneInstance.validate() and then look at the actiuneInstance.errors object to see the result of any validation errors on your object.
Related
In my app, people can comment on pets' images. I am using the react example from here, although I changed quite a few stuff.
Right now, it is successful in displaying the existing comments. Now, when a user is creating a comment, I have to pass the comment body, user id, and pet id. I was trying to do the following:
var CommentForm = React.createClass({
handleSubmit:function()
{
var user=this.refs.user_id.getDOMNode().value.trim();
var comment=this.refs.body.getDOMNode().value.trim();
var pet_id=this.refs.pet_id.getDOMNode().value.trim();
this.props.onCommentSubmit({comment:comment, user:user, pet:pet_id});
if(!user||!comment||!pet_id)
return false;
var formData = $( this.refs.form.getDOMNode() ).serialize();
this.props.onCommentSubmit( formData, this.props.form.action );
// reset form
this.refs.body.getDOMNode().value = "";
},
render: function () {
return (
<form ref="form" className="comment-form" action={ this.props.form.action } accept-charset="UTF-8" method="post" onSubmit={ this.handleSubmit }>
<p><input type="hidden" name={ this.props.form.csrf_param } value={ this.props.form.csrf_token } /></p>
<p><input type="hidden" ref="user" value={ this.props.user_id } /></p>
<p><input type="hidden" ref="pet_id" value={ this.props.pet_id } /></p>
<p><textarea ref="body" name="comment[text]" placeholder="Say something..." /></p>
<p><button type="submit">Post comment</button></p>
</form>
)
}
});
And apparently, it doesn't look like it is passing the pet_id correctly, because I am getting the error message
ActiveRecord::RecordNotFound in CommentsController#create
Couldn't find Pet with 'id'=
My CommentsController looks like
def create
#pet = Pet.find(params[:pet_id])
#comment = #pet.comments.new(comment_params)
#comment.user = current_user
For further clarification, I have three models, Pets, Users and Comments, and when users make comments, the comment gets the user_id, and pet_id as its parameters.
edit:
My react component looks like
<%= react_component('CommentBox',
{:presenter => #presenter.to_json},
{:prerender => true}) %>
and my PetController looks like
def show
#comments = #pet.comments
#user = current_user
#presenter = {
:comments => #comments,
:user => current_user,
:pet_id => #pet,
:form => {
:action => comments_path,
:csrf_param => request_forgery_protection_token,
:csrf_token => form_authenticity_token
}
So there are a few issues I can see. Firstly your using ref where you should be name.
<input type="hidden" ref="pet_id" value={ this.props.pet_id } />
should be
<input type="hidden" name="pet_id" value={ this.props.pet_id } />
Your setting both an action and an onSubmit. Is there a reason to do this? Why not just read it from the props when you perform the ajax request? This is most likely causing your form to be submitted and the browser to load another page. The form submitting has nothing to do with what is on the server. The issue is in your client side code.
I would also consider putting your model values in to their own array. This is generally what rails expects back from the server. In your case it should be params[:pet][:id] not params[:pet_id]. Many of the rails active record methods such as update attributes can then be directly called giving you less verbose code.
#pet = Pet.find(params[:pet][:id])
#pet.update_attributes(prams[:pet])
#pet.save
I'm running into a complex scenario where we are using a legacy database with composite keys and the client wants to be able alter two of the databases pks, "expenseDate" and "adjustmentNumber". In order to be able to change the pks, I've have to use a HQL query. Now of course this has caused another slew of issues with validation. My work around was to populate a domain so that I could validate against it.
So far everything works fine until we have a validation error that I'd like to return to the UI.
I have the following URL which uses the recoveryDetail controller and the edit action to render the page.
http://localhost:8080/pisr/recoveryDetail/edit?division=ALBANY&peid=PI0003&orgkey=14046701&expenseDate=07-22-2015&adjustmentNumber=1
Edit action
def edit() {
//Parse clean url expense date
params.expenseDate = new SimpleDateFormat('MM-dd-yyyy').parse(params.expenseDate)
def recoveryDetailInstance = RecoveryDetail.get(new RecoveryDetail(params))
if(recoveryDetailInstance == null) {
redirect(uri:'/')
return
}
[recoveryDetailInstance: recoveryDetailInstance, disabled: isdisabled(recoveryDetailInstance.batchOverride)]
}
And the following update action.
Update action
#Transactional
def update() {
params.pk_expenseDate = getDateParser(params.pk_expenseDate)
params.expenseDate = getDateParser(params.expenseDate)
params.adjustmentNumber = getAdjustementNumber(params)
RecoveryDetail recoveryDetailInstance = new RecoveryDetail(params);
recoveryDetailInstance.division = params.pk_division
recoveryDetailInstance.peid = params.pk_peid
recoveryDetailInstance.orgkey = params.pk_orgkey
recoveryDetailInstance .validate()
if(recoveryDetailInstance .hasErrors()) {
flash.message = "test"
respond view: "edit", model:[recoveryDetailInstance:recoveryDetailInstance]
return
} else {
def sqlParams = [
pk_division:params.pk_division,
pk_peid:params.pk_peid,
pk_orgkey:params.pk_orgkey,
pk_expenseDate:params.pk_expenseDate,
pk_adjustmentNumber:params.int('pk_adjustmentNumber'),
approved:YesNoTypes.valueOf(params.approved),
batchOverride:YesNoTypes.valueOf(params.batchOverride),
adjustmentFlag:params.adjustmentFlag,
adjustmentNumber:params.adjustmentNumber,
projectHours:new BigDecimal(params.projectHours),
percentEffort:new BigDecimal(params.percentEffort),
totalHours:new BigDecimal(params.totalHours),
expenseDate:params.expenseDate
]
RecoveryDetail.executeUpdate(recoveryDetailQuery, sqlParams)
}
Edit gsp
<g:form class="form-horizontal" url="[resource:recoveryDetailInstance, action:'update']" method="PUT">
<!-- hidden fields contain params from url (composite key)-->
<g:hiddenField name="pk_division" value="${recoveryDetailInstance?.division}"/>
<g:hiddenField name="pk_peid" value="${recoveryDetailInstance?.peid}"/>
<g:hiddenField name="pk_orgkey" value="${recoveryDetailInstance?.orgkey}"/>
<g:hiddenField name="pk_expenseDate" value="${formatDate(format:'MM/dd/yyyy',date:recoveryDetailInstance?.expenseDate)}" />
<g:hiddenField name="pk_adjustmentNumber" value="${recoveryDetailInstance?.adjustmentNumber}"/>
<div class="row">
<div class="col-md-6">
<g:render template="form" model="[recoveryDetailInstance: recoveryDetailInstance, 'mode':'edit']"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<g:actionSubmit class="btn btn-primary" action="update" disabled="${disabled}" value="${message(code: 'default.button.update.label', default: 'Update')}"/>
</div>
</div>
</g:form>
The problem
When a user causes a validation error that triggers a server side response, I run into the following problems with the different return types.
redirect - this is returning the flash message, but redirects the gsp to the edit action which in turn fires the initializer query and replaces all the form data with the original data.
Example -
redirect (controller:"recoveryDetail", action:"edit", params:["division":params.pk_division, "peid":params.pk_peid, "orgkey": params.pk_orgkey, "expenseDate":params.expenseDate.format("MM-dd-yyyy"), "adjustmentNumber":params.adjustmentNumber])
respond - So I assumed I needed to just use respond, well it resulted in the following.
URL was changed to http://localhost:8080/pisr/recoveryDetail/update dropping all parameters and a 404 page was returned.
Example
flash.message = "test"
respond view: "edit", model:[recoverDetailInstance:recoverDetailInstance]
return
So my question
How do I throw a server side validation error and return it to the page with the data entered by the user?
You may add the parameters back into the redirect or render call.
redirect (..., params: params)
https://grails.github.io/grails-doc/latest/ref/Controllers/redirect.html
Also, I'd suggest using a service instead of the controller method. Services are already transactional. I would also throw an exception if the hasErrors is true. The exception message or object can be your payload back to the user.
Solution was to use render without the use of params like so.
if(recoveryDetailInstance.hasErrors()) {
render view: "edit", model:[recoveryDetailInstance:recoveryDetailInstance]
return
}
I am running a query to check if an entity id exists for multiple account ids. If the result set is not null, then I need to either throw an error or display a flash message.
The code for the method is as below:
def save() {
def SAMLInfoInstance = new SAMLInfo(params)
def account = Account.get(params?.accountId)
SAMLInfoInstance.setAccount(account)
def samlInfoInstanceList = SAMLInfo.executeQuery("from SAMLInfo " +
" where account.id <> ? " +
" and entityId = ?", [SAMLInfoInstance.accountId, SAMLInfoInstance.entityId])
if (samlInfoInstanceList?.size > 0){
flash.message = message(code: 'test.not.created.message', args: [message(code: 'SAMLInfo.label', default: 'SAMLInfo'), SAMLInfoInstance.entityId])
/*flash.message = "default.not.created.message"
flash.args = ["SAMLInfo", SAMLInfoInstance.entityId]
flash.default = "SAMLInfo cannot be created"
*/
render(view: "create", model: [SAMLInfoInstance: SAMLInfoInstance])
return
}
if (!SAMLInfoInstance.save(flush: true)) {
render(view: "create", model: [SAMLInfoInstance: SAMLInfoInstance])
return
}
flash.message = message(code: 'default.created.message', args: [message(code: 'SAMLInfo.label', default: 'SAMLInfo'), SAMLInfoInstance.entityId])
redirect(action: "list", id: SAMLInfoInstance.account.id)
}
In my view I render the flash message and the errors in the following manner:
<g:if test="${flash.message}">
<br/>
<div class="message" role="status">
<g:message code="${flash.message}" args="${flash.args}" default="${flash.default}"/>
</div>
<br/>
</g:if>
<br/>
<g:renderErrors bean="${SAMLInfoInstance}" as="list" />
In my message.properties file, I have the following line:
test.not.created.message=The SP url {1} is not allowed for this account. Please enter a different value.
When I run this code, the flash message displays as the string I pass as message i.e. "test.not.created.message". Also, this string is passed on to display whenever I navigate to any other page which displays flash.message.
I am new to grails and groovy and would appreciate any help with this.
Thanks!
2 problems then:
1 - the message is not being retrieved from your message.properties:
You must have other message.properties files in your project. Give it a check. Because if it's not found, grails shows the code itself instead of the message, since it did not find one. Maybe it's looking for your message in some other properties file, like the one specific for your Locale (ex: pt_BR or en_US). Other than that, you're doing it right using the message(code:...) construct.
2 - Your flash message doesn't disappear:
Instead of flash.message, use request.message.
I think the example you want to follow is here.
You only need to resolve the message once ie in the controller or in the view.
So in the controller:
flash.message = "test.not.created.message"
flash.args = ["SAMLInfo"]
flash.default = "<default text>"
And in the view:
<g:message code="${flash.message}" args="${flash.args}"
default="${flash.default}"/>
The flash scope is cleared at the end of the next request which may explain why you still see the message on the next page.
I have a domain object User that
static hasMany = [following:User]
When I create a new User I can select the users I want to follow in my multi select and it works fine but if I then update my User and change the 'following' list it errors on binding the parameters with
java.lang.IllegalStateException: Cannot convert value of type
[org.codehaus.groovy.grails.web.binding.ListOrderedSet] to required
type [java.util.Set] for property 'following': no matching editors or
conversion strategy found
at UserController.update(UserController.groovy:76) at
java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:68
gsp code
<div class="clearfix ${hasErrors(bean: userInstance, field: 'following', 'error')} ">
<label for="following">
<g:message code="user.following.label" default="Following" />
</label>
<div class="input">
<g:select name="following" from="${com.social.User.list()}" multiple="multiple"
optionKey="id" size="5" value="${userInstance?.following*.id}" class="many-to-many" />
</div>
</div>
Here is the controller code for the update:
def update() {
def userInstance = User.get(params.id)
if (!userInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'user.label', default: 'User'), params.id])
redirect(action: "list")
return
}
if (params.version) {
def version = params.version.toLong()
if (userInstance.version > version) {
userInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
[message(code: 'user.label', default: 'User')] as Object[],
"Another user has updated this User while you were editing")
render(view: "edit", model: [userInstance: userInstance])
return
}
}
userInstance.properties = params
// It will work if I blacklist the following param as below
//bindData(userInstance, params, ['following'])
if (!userInstance.save(flush: true)) {
render(view: "edit", model: [userInstance: userInstance])
return
}
flash.message = message(code: 'default.updated.message', args: [message(code: 'user.label', default: 'User'), userInstance.id])
redirect(action: "show", id: userInstance.id)
}
I currently have a workaround of
// Auto saving of following seems to fail so blacklist following param
// then set the list manually...
bindData(userInstance, params, ['following'])
userInstance.following = User.getAll(params.get("following") as List<BigInteger>)
in the update within the user controller but seems like there could be a better way???
I had this problem, and it was ultimately caused by the json-rest-api plugin that didn't handle correctly hibernate proxies obtained from the database.
See https://github.com/padcom/grails-json-rest-api/issues/14
This is in the gsp
<g:if test="${hasError}">
<div class="errors">
<g:renderErrors bean="${eventInstance}" />
</div>
</g:if>
<g:else >
<div id="messageBox" class="message" style="display:none;">
<g:message code="legalevent.save.success" args="[entityName]" default="Event saved successfully" />
</div>
</g:else>
<g:formRemote name="eventForm" id="eventForm" url="[controller : 'search', action : 'saveLegalEvent']"
update="eventFormDiv" action="${createLink(controller: 'search', action: 'saveLegalEvent')}" method="POST"
onSuccess="jQuery('#messageBox').show()">
I am rendering a page for update with this :
def saveLegalEvent = {
def paramsView = params
def eventPattern = /(event\.).*/
def event = LegalEvent.findByLevId(params["levId"])
def corrTxt = params["corrTxt"] as CorrectionText
if(corrTxt.getCorrId()){
corrTxt = CorrectionText.findByCorrId(corrTxt.getCorrId())
}
event.setCorrTxt(corrTxt)
event.properties = params["event"]
def dataList = []
def hasError = false
def validated = event.validate()
validated &= event.validateHistoryParams()
if(validated)
event.save(flush:true)
else
hasError = true
def errorsView = event.errors
render(view:'leform', model:[attributeDataInstanceList:event.tags, lecInstance:event.leCode, eventInstance:event, hasError: hasError])
}
validateHistoryParams validate some more params that are usually not needed in the domain class.
def validateHistoryParams = { ->
if(!changeRef || !changeRef.trim()) {
this.errors.rejectValue('changeRef', 'event.changeRef.blank')
}
if(!corrTxt || !(corrTxt.corrTxt.trim() || corrTxt.corrId )) {
this.errors.rejectValue('corrTxt', 'event.corrTxt.null')
}
!(this.hasErrors())
}
The problem with all this is that the errors are not rendered in the gsp.
All other tags are rendered fine, when debugging I can see that the errors are actually in the error stack. But in the end, the tag isn't rendering them.
As you can see, there is no redirection, so I can't understand why the errors would somehow be erased between the response creation and the rendering ...
In your Groovy code, parameter returned is named hasError, and GSP checks for hasErrors. I'd recommend not to use extra variables, and just query the bean itself in GSP.
I also believe that you need to have that errors div inside the formRemote element in order to re-render after form submission.