I am writing a Grails app where I would like to setup a form that allows a user to type in an image ID number and this value will be passed to a controller / action that retrieves the image from S3 for the given image ID.
The desired url would be of the format example.com/results/1234. I have setup the following URL mappings:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
// apply constraints here
}
}
"/results/$id?" {
controller = "s3Image"
action = "getS3Image"
}
"/"(view:"/index")
"500"(view:'/error')
}
}
The following is how I have setup a form:
<g:form controller="results" method="get">
<input type="text" name="id" class="input-xxlarge" placeholder="http://www.example.com">
<button class="btn btn-inverse">Submit</button>
</g:form>
However this seems to submit the form to example.com/results?id=12345.
How would I alter my form or mappings such that I can produce the desired url after form submission?
Thanks!
<g:form controller="results" method="get">
will generate an HTML form whose action URL is /results (the reverse URL mapping for a controller named "results" with no action or id). When this form is submitted, the browser will add ?id=1234 to the end of this URL because the form method is GET. This is not something you can influence in your URL mappings on the server side.
Instead, you should have your form POST to a different controller action that redirects to the getS3Image action. The redirect will have access to the ID on the server side, so can generate the friendly-looking URL for the redirect.
UrlMappings:
"/results/$id?" {
controller = "s3Image"
action = "getS3Image"
}
"/findImage" {
controller = "s3Image"
action = "find"
}
S3ImageController:
def find() {
redirect(action:"getS3Image", id:params.id)
}
def getS3Image() {
// as before
}
GSP:
<g:form controller="s3Image" action="find" method="post">
<input type="text" name="id" class="input-xxlarge" placeholder="http://www.example.com">
<button class="btn btn-inverse">Submit</button>
</g:form>
Derek,
what you are seeing is correct behaviour. For the GET request you have two things
URL of request
Parameters
Parameters are appended after urls using urlencode and separated by &, so, when you have form with URL http://mydomain.com/controller/action/, and in this form you have two fields: id, name, then, upon submission, they will be passed like this: http://mydomain.com/controller/action/?id=3&name=someName
URLMappings are mapping only URL part of it. So, in your example UrlMapping are matched only to /results/ and id is not passed.
But that is ok, since you can still access the id parameter in your controller, just do it like this (inside s3Image controller):
def getS3Image() {
def id = params.id
}
Try very simple change in UrlMapping:
"/results/$id?" {
controller = "s3Image"
action = "getS3Image"
id = $id
}
and then for sure you have the id accessible in the s3Image controller via:
def id = params.id
I think you have two problems here. First, UrlMappings rules are matched in order of appearance, from top to bottom. First rule that grails matches is "/$controller/$action?/$id?". Move your rule "/results/$id?" above and it should take precedence. - check comment below post.
Your second mistake is declaration of form. I think you've meant:
<g:form controller="s3Image" method="getS3Image">
Related
I'm trying to understand if I can achieve with vue.js the same handy process I use to follow with vanilla/jquery to collect simultaneously all (or only some of) the form fields I need.
When dealing with forms usually I never submit them the old school way, instead, I add a class my-update to the form fields I want to send and then I loop them this way:
let objectToUpdate = {};
$(".my-update").each(function(index, element) {
objectToUpdate[$(this).data("db-field")] = $(this).val();
});
Then I just pass the object to an Ajax POST call and send it to the backend API.
With Vue, it's way simple to get data since we have the property (usually called data) already available within the Vue instance but the problem is if I just send the this.$data it will catch not only the properties (all of them without choice) but also all methods included in the object (getter/setter, ...).
Do you have any best practice or suggestions to share to achieve the same I usually do with a couple of lines of jquery but in Vue?
Usually in Vue form controls are binded to data via v-model. Let's say my Vue instance/component is like:
new Vue({
data: {
user: {
name: '',
surname: '',
phone: '',
}
},
methods: {
send() {
// send this.user through http
}
}
});
And my template is like:
<form #submit="send">
<input name="username" v-model="user.name" />
<input name="surname" v-model="user.surname" />
<input name="phone" v-model="user.phone" />
<button> send </button>
</form>
In such scenario, you have all the user information in your component/instance via this.user. You don't need to send this.$data at all if you create an object to manage your form fields (like this.user in this example).
I'm having a fundamental problem in understanding the concept of MVC and displaying more than one form at a time. I've tried a variety of methods but I'm still stuck - and that's because I don't think I'm understanding CI and MVC correctly.
I tried using 2 different views for the two different forms. Didn't work. I tried using one function per form in my controller. That didn't work either. I don't know what to do.
Should I be doing this;
Create a controller and have an index() function in it.
Build up my form elements for each form within this index()
Create 1 view that displays both forms and call it from within index()
Use form_open to direct the submit action to another function - call it validate()
Validate everything that comes in, send back errors
Somehow, and this is the main bit I don't get, complete an action if the form has been filled in correctly.
6 Is my biggest problem. Don't know how to do that. For example, on successful completion of the form I want my user to have created a directory at a chosen location - so I'm using mkdir() - so do I need an if statement within the validate() function or what??
UPDATE
Here is the code I have created so far;
Controller:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
// Forms CodeIgniter controller
class Admin extends CI_Controller {
// Controller constructor
public function __construct()
{
parent::__construct();
// Load form helper required to validate forms
$this->load->helper(array('form', 'url'));
$this->load->library('form_validation');
}
//*************************************************//
// Prepare data for the view to output the forms
public function index()
{
//*****************************************************//
//returns a drop down list of radio buttons, one for each directory
$map_one = $this->recursive_model->iterate_add_folder_names();
$data['folder_list_add'] = $map_one;
//****************************************************//
//*****************************************************//
//also returns a drop down list of radio buttons (slightly different), one for each directory
$map_two = $this->recursive_model->iterate_folder_names();
$data['folder_list_select'] = $map_two;
//****************************************************//
//load the views and the forms
$this->load->view('templates/header.php');
$this->load->view('admin/add_new_folder.php', $data);
$this->load->view('admin/add_new_file.php', $data);
$this->load->view('templates/small_footer.php');
}
//*************************************************//
//function if adding a new directory to the current structure
public function add_folder()
{
//need to select a directory for it to go under
$this->form_validation->set_rules('new_folder', 'New Folder', 'required');
//and name the new directory
$this->form_validation->set_rules('new_folder_name', 'New Folder Name', 'required');
if ($this->form_validation->run() === FALSE)
{
$this->index();
}
else
{
if($this->input->post())
{
$new_folder = $this->input->post('new_folder');
$new_folder_name = $this->input->post('new_folder_name');
$folder_path = "/var/www/html/mike/content".$new_folder."/".$new_folder_name;
mkdir($folder_path, 0777);
$this->index();
}
}
}
//*************************************************//
public function add_file()
{
//folder location and name of file
$folder_name = $this->input->post('folder_name');
$new_folder_name = $this->input->post('file_name');
//validation rules
$this->form_validation->set_rules('folder_name', 'Folder Name', 'required');
$this->form_validation->set_rules('file_name', 'File Name', 'required');
//if there is an error with validation
if ($this->form_validation->run() === FALSE)
{
//gets stuck here every time when trying to upload a new folder :(
$this->index();
}
//if there is not an error with validation
else
{
//$folder_name will be something like "http://www.example.com/publications/people/reports"
$config['upload_path'] = $folder_name;
$config['allowed_types'] = 'gif|jpg|png|html|pdf|xls';
$this->load->library('upload', $config);
//if file cannot be loaded (due to $config perhaps?)
if ( ! $this->upload->do_upload())
{
$error = array('error' => $this->upload->display_errors());
$this->index();
}
else
{
$data = array('upload_data' => $this->upload->data());
$this->index();
}
}
}
//*************************************************//
}
Here is one view (add_new_file.php);
<div id="container">
<h1>Upload A File/Publication</h1>
<div id="body">
<?php //echo $error;?>
<?php echo form_open_multipart('admin/add_file');?>
<?php echo $folder_list_select; ?>
<input type="file" name="file_name" size="20" />
<input type="submit" value="upload" />
</form>
</div>
Here is the other (add_new_folder.php)
div id="container">
<h1>Add A New Folder</h1>
<div id="body">
<?php echo validation_errors(); ?>
<?php echo form_open('admin/add_folder');?>
<?php echo $folder_list_add; ?>
New Folder Name: <input type="text" name="new_folder_name">
<input type="submit" value="upload" />
</form>
</div>
I hope this helps answer this thread.
Basically, I can get the first section to work - adding a folder - but I cannot get the adding a file to work. This is because if ($this->form_validation->run() === FALSE) is always returning false. I think it might be looking at the form elements in the other form - which it shouldn't do. What am I missing?
Should I be doing this;
1 . Create a controller and have an index() function in it.
[let's, for the sake of conversation, call this controller Users thx -ed]
Sure. That's cool. You could also have a function in that Controller called edit, or banana or whatever; either way works. With using just the index method (function), the url might look like http://example.com/index.php/users whereas if you add another method to the controller like banana, the url might look like http://example.com/index.php/users/banana.
2 . Build up my form elements for each form within this index()
Well, typically form elements are not created in the controllers. This is where the V in MVC comes in -- stuff you view goes into a view.
So, one might do something like
// Users Controller
class Users extends CI_Controller{
function index(){
//index method
}
function banana(){
$this->load->view('banana_view');
}
}
then in application/views/banana_view.php, you create your form. When you visit http://example.com/users/banana, you will see the form you created in banana_view.php.
3 . Create 1 view that displays both forms and call it from within index()
Sure, that'd work just fine. But remember that each <form></form> needs its own <input type="submit" name="Lets GO"> inside and thusly needs somewhere to send each forms data. This is the action="". You can leave it out, but beware that it will then send the form to whatever page you are currently on (in our case here, http://example.com/index.php/users/banana), so you have to have something in the banana() method to handle the form data. But, typically, it will be set via form_open(). Something like form_open('index.php/users/eat_banana'); will generate <form action="index.php/users/eat_banana"...
4 . Use form_open to direct the submit action to another function - call it validate()
Just don't call it late_for_dinner. But seriously, validate is a bit broad -- validate what? Validate why? As to validation, https://www.codeigniter.com/user_guide/libraries/form_validation.html. But you should cross that bridge after you grok the fundamentals of CodeIgniter (won't take long).
5 . Validate everything that comes in, send back errors
See last question.
6 . Somehow, and this is the main bit I don't get, complete an action if the form has been filled in correctly.
Many times people will display a success message
class Users extends CI_Controller{
function index(){
//index method
}
function banana(){
$this->load->view('banana_view');
}
// assuming form_open('index.php/users/eat_banana'); in banana_view
function eat_banana(){
//make sure that this is a POST
if($this->input->post()){
// do things with the data
// typically it gets saved to a database
// via a model (the M in MVC)
// http://ellislab.com/codeigniter/user-guide/general/models.html
if($saved_to_db){
// set message to send to the view
$data['message'] = "Everything went OK";
}else{
$data['message'] = "but who was database? data didn't save :(";
}
// load the view and send the data
$this->load->view('eat_banana', $data);
}
}
application/views/eat_banana.php:
<!DOCTYPE html>
<html>
<head></head>
<body>
<div>
<b>Form submitted.</b><br />
The message is: <?php echo $message; ?>
</div>
</html>
other times, one might instead prefer to redirect
class Users extends CI_Controller{
function index(){
//index method
}
function banana(){
$this->load->view('banana_view');
}
// assuming form_open('index.php/users/eat_banana'); in banana_view
function eat_banana(){
//make sure that this is a POST
if($this->input->post()){
// do things with the data
if($saved_to_db){
// just send them to the homepage
redirect('/');
}else{
// send them back to the form
redirect('index.php/users/banana');
}
}
}
So,
M is for model. Models are used to talk to the database.
V is for Vend view. Views render the text, forms, pictures, gifs, whatever to the screen. That's the idea anyway. There's nothing stopping you from echo'ing out an enormous unminimized javascript application from your controller. That would totally not be MVC tho.
C is for controller. Controllers call and send data to the views, receive data sent from views, take that data and send it to a model to be saved in the database (although CodeIgniter doesn't enforce this in any way either; you could if you wanted to save the data to a database directly from the controller, but this obviously defeats the MVC principal as well), retrieves data from the database and sends it to a view for display. Those are the basics anyway.
What is the best way to change data of, for example, User?
I got lots of properties like 'username', 'city', 'phone' and when I want to edit just one field ('password'), I have to do this:
<form:form action="editUser.htm?id=${user.id}" commandname="user">
<form:hidden path='username' />
<form:hidden path='city' />
<form:hidden path='phone' />
<form:input path='password' />
....
In my controller action is defined:
#RequestMapping(value = "/editUser.htm", method=RequestMethod.POST)
public ModelAndView ordertypeedit(#ModelAttribute("user") User user,
BindingResult result, HttpServletRequest request)throws Exception{
userTypeValidator.validate(orderType, result);
if(result.hasErrors()){
(...)
return new ModelAndView(...);
}
orderTypeDAO.update(orderType);
return new ModelAndView(...);
I don't like to set all the to my action form... If I miss it, it shows error 'column 'username' cannot be null".
It depends if your properties are validated or mandatory on that form:
If not, then you don't need to submit them with your form:
<form:form action="editUser.htm?id=${user.id}" commandname="user">
<form:input path='password' />
and in your controller you will get user object with only user parameter.
Alternative way is to pass regular (not Spring MVC) form and create inside user object with only id and password properties.
you can use simple (without spring tags) form with one input and get it in controller using #RequestParam
If there's a form, and has a textbox and a button, how do you erase the content of the textbox after you submit the form?
<h:inputText id="name" value="#{bean.name}" />
<h:commandButton id="submit" value="Add Name" action="#{bean.submit}" />
After I enter a value in the textbox and submit, the value still appears in the textbox. I need to clear the content of the textbox once its been submitted. How can I achieve this?
Introduction
There are several ways to achieve this. The naive way is to simply null out the fields in backing bean. The insane way is to grab JS/jQuery for the job which does that after submit or even during page load. Those ways only introduces unnecessary code and indicates a thinking/design problem. All you want is just starting with a fresh request/page/view/bean. Like as you would get with a GET request.
POST-Redirect-GET
The best way is thus to just send a redirect after submit. You probably already ever heard of it: POST-Redirect-GET. It gives you a fresh new GET request after a POST request (a form submit), exactly as you intended. This has the additional benefit that the previously submitted data isn't re-submitted when the enduser ignorantly presses F5 afterwards and ignores the browser warning.
There are several ways to perform PRG in JSF.
Just return to same view with faces-redirect=true query string. Assuming a /page.xhtml, you could do so in action method:
public String submit() {
// ...
return "/page.xhtml?faces-redirect=true";
}
If you're still fiddling around with navigation cases the JSF 1.x way, then it's a matter of adding <redirect/> to the navigation case in question. See also How to make redirect using navigation-rule.
To make it more reusable, you can obtain the view ID programmatically:
public String submit() {
// ...
UIViewRoot view = FacesContext.getCurrentInstance().getViewRoot();
return view.getViewId() + "?faces-redirect=true";
}
Either way, if you've view parameters which needs to be retained in the request URL as well, then append &includeViewParams=true to the outcome. See also Retaining GET request query string parameters on JSF form submit.
If you're making use of some URL rewriting solution which runs outside JSF context, then you'd best grab the current request URL (with query string) and use ExternalContext#redirect() to redirect to exactly that.
public void submit() throws IOException {
// ...
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
StringBuffer requestURL = ((HttpServletRequest) ec.getRequest()).getRequestURL();
String queryString = ((HttpServletRequest) ec.getRequest()).getQueryString();
ec.redirect((queryString == null) ? requestURL.toString() : requestURL.append('?').append(queryString).toString());
}
It's only a mess which should really be refactored to some utility class.
Request/View scoped bean
Note that this all works only nicely in combination with request or view scoped beans. If you've a session scoped bean tied to the form, then the bean wouldn't be recreated from scratch. You've then another problem which needs to be solved as well. Split it into a smaller session scoped one for the session scoped data and a view scoped one for the view scoped data. See also How to choose the right bean scope?
Faces Messages
If you've a faces message to be shown as result of successful action, then just make it a flash message. See also How to show faces message in the redirected page.
public String submit() {
// ...
FacesContext context = FacesContext.getCurrentInstance();
context.addMessage(clientId, message);
context.getExternalContext().getFlash().setKeepMessages(true);
return "/page.xhtml?faces-redirect=true";
}
Ajax
Only if you happen to have an ajax-only page on which a F5 would always trigger a fresh new GET request, then simply nulling out the model field(s) in action method shouldn't harm that much.
See also:
How to navigate in JSF? How to make URL reflect current page (and not previous one)
Pure Java/JSF implementation for double submit prevention
You can blank out the property of the managed bean that should not be repainted when you render the response. This can be done done using code similar to the snippet posted below:
private String name;
public String getName(){return name;}
public void setName(String name){this.name=name};
public String submit()
{
//do some processing
...
// blank out the value of the name property
name = null;
// send the user back to the same page.
return null;
}
The reason for the current behavior can be found in how the JSF runtime processes requests. All JSF requests to a view are processed in accordance with the JSF standard request-response lifecyle. In accordance with the lifecyle, the managed bean contents are updated with the value from request (i.e. the value of DataForm.Name is set) before the application event (DataForm.submit) is executed. When the page is rendered in the Render Response phase, the current value of the bean is used to render the view back to the user. Unless the value is changed in an application event, the value will always be one that is applied from the request.
You can clear the form from the Bean method that gets called when the form is submitted;`
private String name;
private String description;
private BigDecimal price;
/*----------Properties ------------*/
/*-----Getter and Setter Methods---*/
public void save()throws SQLException{
String sql = "INSERT INTO tableName(name,description,price) VALUES (?,?,?)";
Connection conn = ds.getConnection();
try {
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, getName());
pstmt.setString(2, getDescription());
pstmt.setBigDecimal(3, getPrice());
pstmt.executeUpdate();
} catch (SQLException e) {
e.getMessage();
e.toString();
}finally{
conn.close();
clear();
}
}//End Save Method
public void clear(){
setName(null);
setDescription(null);
setPrice(null);
}//end clear`
Notice that the clear() method is called from the save method after all the operations of the save method is complete. As an option you could perform the clearing only if the methods operation was successful...The method below is placed in the ProductController Class...
public String saveProduct(){
try {
product.save();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
The method call from the view/jsp would look like the Following:
<h:commandButton value="Save" action="#{productController.saveProduct}"/>
You can do it with jQuery.
I had the similar problem. I needed to clear popup window form.
<rich:popupPanel id="newProjectDialog" autosized="true"
header="Create new project">
<h:form id="newProjectForm">
<h:panelGrid columns="2">
<h:outputText value="Project name:" />
<h:inputText id="newProjectDialogProjectName"
value="#{userMain.newProject.projectName}" required="true" />
<h:outputText value="Project description:" />
<h:inputText id="newProjectDialogProjectDescription"
value="#{userMain.newProject.projectDescription}" required="true" />
</h:panelGrid>
<a4j:commandButton id="newProjectDialogSubmit" value="Submit"
oncomplete="#{rich:component('newProjectDialog')}.hide(); return false;"
render="projects" action="#{userMain.addNewProject}" />
<a4j:commandButton id="newProjectDialogCancel" value="Cancel"
onclick="#{rich:component('newProjectDialog')}.hide(); return false;" />
</h:form>
</rich:popupPanel>
jQuery code:
$('#newProjectForm').children('input').on('click', function(){$('#newProjectForm').find('table').find('input').val('');});
I added a code snippet how to reset all values for the current ViewRoot recursively for JSF 2 here:
Reset all fields in form
This works for submitted forms showing validation errors as well as for newly entered values in a form.
I have an object MainObject with a list of objects, SubObjects, among other things. I am trying to have the user click a link on the View to add a new SubObject to the page. However, I am unable to pass the MainObject I am working with into the Action method. The MainObject I currently receive is empty, with all its values set to null. How do I send my controller action the MainObject that was used to render the View originally?
The relevant section of the view looks like this:
<div class="editor-list" id="subObjectsList">
<%: Html.EditorFor(model => model.SubObjects, "~/Views/MainObject/EditorTemplates/SubObjectsList.ascx")%>
</div>
<%: Ajax.ActionLink("Add Ajax subObject", "AddBlanksubObjectToSubObjectsList", new AjaxOptions { UpdateTargetId = "subObjectsList", InsertionMode = InsertionMode.Replace })%>
The relevant function from the controller looks like this:
public ActionResult AddBlanksubObjectToSubObjectsList(MainObject mainobject)
{
mainobject.SubObjects.Add(new SubObject());
return PartialView("~/Views/MainObject/EditorTemplates/SubObjectsList.acsx", mainobject.SubObjects);
}
I ended up with the following:
View:
<div class="editor-list" id="subObjectsList">
<%: Html.EditorFor(model => model.SubObjects, "~/Views/MainObject/EditorTemplates/SubObjectsList.ascx")%>
</div>
<input type="button" name="addSubObject" value="Add New SubObject" onclick="AddNewSubObject('#SubObjectList')" />
Control:
public ActionResult GetNewSubObject()
{
SubObject subObject= new SubObject();
return PartialView("~/Views/TestCase/EditorTemplates/SubObject.ascx", subObject);
}
And, finally, I added this JQuery script:
function AddNewSubObject(subObjectListDiv) {
$.get("/TestCase/GetNewSubObject", function (data) {
//there is one fieldset per SubObject already in the list,
//so this is the index of the new SubObject
var index = $(subObjectListDiv + " > fieldset").size();
//the returned SubObject prefixes its field namess with "[0]."
//but MVC expects a prefix like "SubObjects[0]" -
//plus the index might not be 0, so need to fix that, too
data = data.replace(/name="\[0\]/g, 'name="SubObject[' + index + "]");
//now append the new SubObject to the list
$(subObjectListDiv).append(data);
});
}
If someone has a better way to do this than kludging the MVC syntax for nested objects onto a returned View using JQuery, please post it; I'd love to believe that there is a better way to do this. For now, I'm accepting my answer.