I am completely new in Grails, start learning grails from past couple of days. I am trying to add search feature by using searchable plugin in my demo grails application. I successfully added searchable plugins on user search where user can search other users and follow them. I am doing like this ..
grails install-plugin searchable
Domain Person.groovy --
package org.grails.twitter
class Person {
transient springSecurityService
String realName
String username
String password
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static hasMany = [followed:Person, status:Status]
static searchable = [only: 'realName']
static constraints = {
username blank: false, unique: true
password blank: false
}
static mapping = {
password column: '`password`'
}
Set<Authority> getAuthorities() {
PersonAuthority.findAllByPerson(this).collect { it.authority } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
view/searchable/index.gsp ---
<html>
<head>
<meta name="layout" content="main" />
<title>What Are You Doing?</title>
<g:javascript library="jquery" plugin="jquery" />
</head>
<body>
<h1>Search For People To Follow</h1>
<div class="searchForm">
<g:form controller="searchable">
<g:textField name="q" value=""/>
</g:form>
</div>
<h1>What Are You Doing?</h1>
<div class="updateStatusForm">
<g:formRemote onSuccess="document.getElementById('messageArea').value='';" url="[action: 'updateStatus']" update="messageLists" name="updateStatusForm">
<g:textArea name="message" value="" id="messageArea" /><br/>
<g:submitButton name="Update Status" />
</g:formRemote>
</div>
<div id="messageLists">
<g:render template="messages" collection="${messages}" var="message"/>
</div>
</body>
</html>
It works fine. Now My problem starts. Now I want to add this searchable in my Post domain also where user can search post items. I am doing like this ...
Domain Post.groovy --
package groovypublish
class Post {
static hasMany = [comments:Comment]
String title
String teaser
String content
Date lastUpdated
Boolean published = false
SortedSet comments
static searchable = [only: 'title']
static constraints = {
title(nullable:false, blank:false, length:1..50)
teaser(length:0..100)
content(nullable:false, blank:false)
lastUpdated(nullable:true)
published(nullable:false)
}
}
and here is form view
view/post/list.gsp --
------ some code -----
<g:form controller="searchable" class="navbar-search pull-left">
<g:textField name="q" value="" class="search-query" placeholder="Search Posts"/>
</g:form>
------ some code ------
Now when I try to search post by post title then it showing error. It overrides searchable action. How to solve this problem ?
You can implement your own search method using searchable, call a controller function from your search form and perform search in that:
Let say you have two search forms:
<g:form controller="postsController" action="postAction" class="navbar-search pull-left">
<g:textField name="q" value="" class="search-query" placeholder="Search Posts"/>
</g:form>
and
<g:form controller="searchable">
<g:textField name="q" value=""/>
</g:form>
then in the PostCOntroller you can have postAction method to perform search:
def postAction (Integer max) {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
params.sort = "id"
params.order = "desc"
if(params?.q){
def result = Post .search(params.q , order:"desc" )
return [searchResults: result.results, searchResultsCount: result.total, popup : params.popup?.toBoolean()]
}else{
[searchResults: Post .list(params), searchResultsCount: Post .count(), popup : params.popup?.toBoolean()]
}
and same you can have a different function for another search, if you use remote form then you need to have two different div's on the search page, and you can render the result page out there.
let say you have:
<g:formRemote name="postSearchForm" update="postSearchResultsDiv" url="[controller: 'post', action:'postAction' , params: [popup: false]]">
<label for="searchText">Search Post:</label>
<input name="q" type="text" id="searchText" class="input-medium search-query"/>
<input id="searchButton" type="submit" class="btn-info" value="Search"/>
</g:formRemote>
<div id="postSearchResultsDiv">--Your search result for the form will display here--</div>
This remote form will call postAction method in your controller, you can have postAction.gsp page on the controller's view folder and print the result out there.
and on your search page, postSearchResultsDiv will have the search result(postAction GSP page output)
I solved my own problem...
I have done like this ..
PostController ---
import org.compass.core.engine.SearchEngineQueryParseException
class PostController
{
def searchableService
def searchpost = {
if (!params.q?.trim()) {
return [:]
}
try {
return [searchResult: searchableService.search(params.q, params)]
} catch (SearchEngineQueryParseException ex) {
return [parseException: true]
}
render(view:'searchpost')
}
.......
}
Search form ---
<g:form controller="post" action="searchpost" class="navbar-search pull-left">
<g:textField name="q" value="" class="search-query" placeholder="Search Posts"/>
</g:form>
searchpost.gsp //for showing result
<html>
<head>
<r:require modules="bootstrap"/>
<meta name="layout" content="main"/>
</head>
<body>
<g:render template="/layouts/header" />
<div class="well">
<g:each var="post" in="${searchResult?.results}">
<div>
<h2>${post.title}</h2>
<p>${post.teaser}</p>
<p>Last Updated: ${post.lastUpdated}</p>
<g:link controller="post" action="view" id="${post.id}" class="btn btn-success">
View this post
</g:link>
<g:if test="${post.author == currentLoggedInUser }">
<g:link controller="post" action="edit" id="${post.id}" class="btn btn-danger">
Edit this post
</g:link>
<g:actionSubmit action="delete" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" class="btn btn-inverse" />
</g:if>
<g:form>
<g:hiddenField name="id" value="${post?.id}" />
</g:form>
</div>
</g:each>
</div>
</body>
</html>
And it works :)
Related
How obtain value of hotel in Controller ? now i have only null. The nested object are not allowed because of html 5 doesn't let to be nested. I have objects in template but only object knight bring the value and I need also further written controls values. I ve tried to put the object but without formater yet, but I dont think it has meaning now.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Add knight</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" th:href="#{/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css}" />
<script th:src="#{/webjars/jquery/3.2.1/jquery.min.js}"></script>
<script th:src="#{/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js}"></script>
</head>
<body>
<div class="container">
<form class="form-horizontal" th:object="${knight}" th:action="#{/assignQuest}" th:method="post">
<input type="hidden" th:field="*{id}"/>
<input type="hidden" th:field="*{name}"/>
<input type="hidden" th:field="*{age}"/>
<input type="hidden" th:field="*{level}"/>
<div class="form-group">
<label class="control-label">Wykonaj zadanie</label>
<select th:field="*{quest}">
<option th:each="quest : ${notStartedQuests}"
th:value="${quest.id}"
th:text="${quest.description}">?</option>
</select>
</div>
<input type="hidden" th:field="*{name}"/>
<input type="hidden" th:field="*{id}"/>
<ul>
<label class="control-label">Zamiesszkaj w hotelu</label>
<li th:each="hotel : ${hotels}">
<input type="radio" th:field="*{id}" th:value="${hotel.id}" />
<label th:for="${hotel.name}" th:text="${hotel.name}"></label>
</li>
</ul>
<div class="row">
<button type="submit" class="btn btn-default">Wyslij rycerza</button>
</div>
</form>
</div>
</body>
</html>
QuestController:
import java.util.List;
#Controller
public class QuestController {
#Autowired
KnightService knightService;
#Autowired
QuestService questService;
#Autowired
HotelService hotelService;
#RequestMapping("/assignQuest")
public String assignQuest(#RequestParam("knightId") Integer id, Model model) {
Knight knight = knightService.getKnight(id);
List<Quest> notStartedQuests = questService.getAllNotStartedQuests();
List<Hotel> hotels = hotelService.getAllHotels();
model.addAttribute("knight", knight);
model.addAttribute("notStartedQuests", notStartedQuests);
model.addAttribute("hotels", hotels);
return "assignQuest";
}
#RequestMapping(value = "/assignQuest", method = RequestMethod.POST)
public String assignQuest(Knight knight, Hotel hotels, BindingResult result) {
System.out.println(result);
System.out.println(hotels);
knightService.updateKnight(knight);
// Quest quest = knight.getQuest();
// questService.update(quest);
return "redirect:/knights";
}
}
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.
I'm new on Grails and I'm having trouble to do a form submit.
Here is AnimaisController:
package jogoanimais
class AnimaisController {
def index() {
def animalsTreeObj = AnimaisTreeMap.list()
render(view: "show", model: [animalList: animalsTreeObj])
}
def addNode()
{
log.info "add node"
log.info params
}
}
Here is show.gsp
<g:form controller="animais" action="addNode">
<div>Pense em um animal</div>
<g:textField name="myField" value="${myValue}" />
<g:actionSubmit value="OK, próximo" />
<g:each in="${animalList}" var="row" status="i">
<h3> ${row.nodeDescription}, ${row.yesAnswerNode}</h3>
<br/>
</g:each>
</g:form>
After clicking at the submit button, the URL that is requested is "http://localhost:8080/jogoAnimais/animais/addNode" and I get a 404 error.
I've also tried adding "action" do g:actionSubmit but in this case, Grails requested a addNode.gsp.
Does anyone has any idea?
Here's the solution:
GSP:
Add "action" parameter of g:form and a input type "submit" as shown below:
<g:form controller="animais" action="addNode">
<div>Pense em um animal</div>
<div>
<label for="questionToUser">Questão:</label>
<g:textField name="questionToUser" maxlength="50"/>
</div>
<input type="submit" value="Submit">
<g:each in="${animalList}" var="row" status="i">
<h3> ${row.nodeDescription}, ${row.yesAnswerNode}</h3>
<br/>
</g:each>
</g:form>
CONTROLLER:
As mbaird has said, my "addNode" mehtod need to return something, as "render 'ok'"
I'm trying to create simple form for saving post with it's relations.
Here is my domain models;
Post Model;
class Post {
String title
String teaser
String content
static belongsTo = [author: Person, category: Category]
static hasMany = [tags: Tag]
static constraints = {
}
}
Tag Model;
class Tag {
String name
static belongsTo = Post
static hasMany = [posts: Post]
static constraints = {
}
}
I've created a form for saving post object through html form;
<g:form controller="posts" action="save" method="post">
<div class="input">
<label for="post.title">Title:</label>
<g:textField name="post.title" />
</div>
<div class="input">
<label for="post.teaser">Teaser:</label>
<g:textField name="post.teaser" />
</div>
<div class="input">
<label for="post.content">Content:</label>
<g:textArea name="post.content" />
</div>
<div class="input">
<label for="post.content">Category:</label>
<g:select optionKey="id" optionValue="name" name="post.category" from="${categories}" noSelection="['':'-Choose Category-']"/>
</div>
<div class="input">
<label for="post.tags">Tags:</label>
<g:select optionKey="id" optionValue="name" name="post.tags" from="${tags}" noSelection="['':'-Choose Tags-']" />
</div>
<div class="input">
<g:submitButton name="Create" value="Create" />
</div>
</g:form>
And here is the controller logic;
def save() {
def post = new Post(params.post)
post.author = springSecurityService.currentUser
if(post.save()){
flash.message = "Post created successfully..."
redirect(action: "index", method: "GET")
}
else{
flash.error = "Something went wrong, please check the form again!"
[tags: Tag.list(), categories: Category.list()]
render(view: "create")
}
}
With this way i can't save tags for post object.
I solved the problem with
post.save(flush: true)
here is the documentation about
gorm save
I have problem with update foreign key in a instance of my domain class Semester. I'm new in Groovy Grails. When I create new Semester everything is ok.
Semester.groovy
class Semester {
int name
Season season
}
Season.groovy
class Season {
String name
}
SemesterController.groovy
SemesterController{
def update(){
def semester = Semester.get(params.id)
semester.name = params.semester
semester.season = params.season // Here is a problem !!
semester.save(flush: true)
redirect(uri: "/semester/index")
}
}
edit.gsp
<!DOCTYPE html>
<html>
<head>
<meta name="layout" content="bootstrap-main" />
<title>SARNA</title>
</head>
<body>
<br />
<div class="container">
<g:form class="form-horizontal" role="form"
url="[resource:semester, controller: 'Semester']">
<label for="semester" class="col-sm-2 control-label">Semestr</label>
<g:textField class="form-control" name="semester"
value="${semester.name}" />
<g:select name="season" from="${com.sarna.entity.Season.list()}" optionKey="id"
optionValue="name" value="${semester?.season?.id}"/>
<g:actionSubmit class="btn btn-primary" value="Zapisz"
action="update" />
</g:form>
</div>
</body>
</html>
When I'm trying save changes I have this Exception:
URI: /SARNA/semester/index/1
Class: java.lang.IllegalStateException
Message: Cannot convert value of type [java.lang.String] to required type [com.sarna.entity.Season] for property 'season': no matching editors or conversion strategy found
What am I doing wrong ? Could you help me? Thanks
Make the following changes to the form
<g:form class="form-horizontal" role="form"
url="[resource:semester, controller: 'Semester']">
<g:hiddenField name="id" value="${semester.id}"/>
<label for="semester" class="col-sm-2 control-label">Semestr</label>
<g:textField class="form-control" name="name" value="${semester.name}" />
<g:select name="season.id" from="${com.sarna.entity.Season.list()}" optionKey="id"
optionValue="name" value="${semester?.season?.id}"/>
<g:actionSubmit class="btn btn-primary" value="Zapisz"
action="update" />
</g:form>
You can then simplify your action to:
SemesterController{
def update(Semester semester){
semester.save(flush: true)
redirect(uri: "/semester/index")
}
}