:remote form with multiple submit buttons - ajax

I can't seem to get a :remote form with multiple submit controls to work under Rails 3. The following code:
<%= form_tag({:action => 'debug'}, {:remote => true}) do %>
<%= submit_tag "Foo" %>
<%= submit_tag "Bar" %>
<% end %>
Produces a form with two buttons, but the resulting AJAX POST doesn't contain a commit parameter to say which one was pressed. If I leave :remote => true out, the normal POST does contain the commit parameter.
Is there any way to make this work, or is it just a bug?

After some playing about, I think I've found a solution.
The problem is that rails.js uses serializeArray() on the form element containing the clicked submit control; but the form's serialized data doesn't contain that control. However, JQuery or Javascript is keeping track of the original event in the call-chain, which was technically a "submit" event on the appropriate control.
So I've edited rails.js as follows:
callRemote: function (e) { /* Note - new parameter e */
var el = this,
method = el.attr('method') || el.attr('data-method') || 'GET',
url = el.attr('action') || el.attr('href'),
dataType = el.attr('data-type') || 'script';
if (url === undefined) {
throw "No URL specified for remote call (action or href must be present).";
} else {
if (el.triggerAndReturn('ajax:before')) {
var data = el.is('form') ? el.serializeArray() : [];
/********************/
/* Note new if-test */
/********************/
if (e)
{
data.push({name: e.originalEvent.explicitOriginalTarget.name,
value: e.originalEvent.explicitOriginalTarget.value})
}
/* Function continues as before */
... and further down ...
$('form[data-remote]').live('submit', function (e) {
$(this).callRemote(e);
e.preventDefault();
});
This has the effect of adding in the name-value pair of the clicked button before firing off the AJAX.
I'm a bit new to Javascript, so do let me know if any of this is horrendously wrong!

I tried your solution and it worked for Firefox, but then the application did not work any more for IE and Safari. Now I found another solution: Simply putting the value of the submit button into a hidden input field by a small javascript.
<input id="selected_button" type="hidden" name="commit" value=""/>
<script>
function identify_button( el ) {
window.document.getElementById( 'selected_button' ).value = el.value;
}
</script>
<input class="button"
type="submit"
name="commit"
value="Save"
onclick="identify_button( this )" );"
/>
<input class="button"
type="submit"
name="commit"
value="OK"
onclick="identify_button( this )" );"
/>

Related

Reloading main view after button in PartialView is clicked

I have a partial view that the user can preform a search in, and the search results are shown in a select box. In my main view I have a section that is supposed to show the search results after a select button is pressed. Right now when I click the select button is loads the correct information into the correct model for my main view, but the main view doesn't change. When I click refresh, the page updates correctly. How do I make the page update automatically when a button is clicked in the plugin view?
My section in the main view (Index.vbhtml) in my main app:
#Section CUInfo
Credit Union Name: #Model.CUInfo.CUName
end section
Here is my controller method in my Plugin:
Function ChangeCUInfo(strCUName As String) As ActionResult
m_hostApp.CUInfo.CUName = strCUName
m_hostApp.blnPluginRefreshButtonPressed = True
Return View("Index", m_hostApp)
End Function
I've tried to set a boolean value in the hostApp object and then in my main razor view call this function if it is true:
#code
If Model.blnPluginRefreshButtonPressed = True Then
#<script type="text/javascript">
$(function () {
window.location.reload();
});
</script>
End If
Model.blnPluginRefreshButtonPressed = False
End Code
EDIT:
JS function called when the select button is clicked:
function loadCU(CUInfo) {
strCU = CUInfo.split('|');
strCUName = strCU[0];
$.ajax({
type: "POST",
url: "/CUContractNumberPlugin/ChangeCUInfo",
data: { "strCUName": strCUName }
});
}
Form that is used in the plugin view:
#Using (Html.BeginForm("ChangeCUInfo", "CUContractNumberPlugin"))
#<div id="LogoSigSearch" style="height:300px;width:500px;position:relative;">
<span style="display:inline-block;height:20px;width:166px;position:absolute;top:35px;left:5px;">Credit Union Name</span>
<br />
#Html.TextBox("strCUName")
<input type="submit" name="LogoSigSearch$ctl02" value="Search" id="LogoSigSearch_ctl02" tabindex="3" style="width:60px;position:absolute;top:5px;left:352px;" />
<input name="LogoSigSearch$ctl05" type="button" onclick="javascript:clearSearch()" value="Clear" style="position:absolute;top:35px;left:352px;width:60px;" />
<select size="4" name="LogoSigSearch$ctl06" id="LogoSigSearch_ctl06" tabindex="5" style="height:230px;width:342px;position:absolute;top:65px;left:5px;"></select>
<input type="button" name="SelectCU" value="Select" onclick="javascript:loadCU(LogoSigSearch_ctl06.options[LogoSigSearch_ctl06.selectedIndex].value)" tabindex="4" style="width:60px;position:absolute;top:65px;left:352px;" />
</div>
End Using
Are both buttons part of a form? A button won't invoke an action without you attaching it to script or making it part of a form with an associated action.
Use a partial view to render the results of the query, even on the main page load. This simplifies your development.
Add a jQuery event handler (jQuery.on()) to watch for the button click on your main page, or if the button is returned in the partial view, just use an on ready handler in your partial and attach a button.click() event, again using jQuery.
The jQuery event handler can take care of submitting the values of the query, posting to your controller, and displaying the results. I have a number of older articles here but they are still relevant to your question and demonstrate submitting data and fetching partials.
Your client-side code will end up looking something like this:
$("#your-button").click(function () {
var fetchUrl = '#Url.Action("ActionName", "Controller")';
$.post(fetchUrl, { searchParams: $("#your-search-box").val() })
.success(function (data) {
// replace the contents of the DIV with the results. 'data'
// here has whatever you sent back from your partial view
})
.error(function (data) {
// handle the error, use a DIV with some kind of alert message etc
});
});
Hope this helps some.

Multiple dropdownlist postback in MVC3

I have the following code on a view:
#using (Html.BeginForm()) {
<div class="select-box form">
<p>
<strong>Select transcript name to view:</strong>
#Html.DropDownList("license", (SelectList)ViewBag.Licenses, new { onchange = "this.form.submit();" })
</p>
</div>
<div class="select-box form">
<p>
<strong>You may have multiple periods for each transcript name:</strong>
#Html.DropDownList("period", (SelectList)ViewBag.Periods, new { onchange = "this.form.submit();" })
</p>
</div>
}
I need to implement some logic depending on which dropdown cause the postback. I'm thinking to add a hidden input and set value of the control name by jQuery before submit the form, but I'm wondering if there is a 'native' way to do this.
This is my controller signature:
public ActionResult Checklist(int license, int period)
Thanks in advance!
I would apply a class to the dropdown so that my jQuery can use that as the selector criteria
#Html.DropDownList("license", (SelectList)ViewBag.Licenses, new { #class="mySelect"})
#Html.DropDownList("period", (SelectList)ViewBag.Periods, new { #class="mySelect"})
<input type="hidden" id="source" name="source" value="" />
And the script is
$(function(){
$(".mySelect").change(function(){
var itemName=$(this).attr("name");
$("#source").val(itemName);
$("form").submit()
});
});
Use something like this. (Here you are calling the action method instead of submitting the form)
Which ever dropdown caused the change will pass non zero value to the action method and the other will pass 0.
#Html.DropDownList("license", (SelectList)ViewBag.Licenses, new { onchange = "document.location.href = '/ControllerName/Checklist?license=' + this.options[this.selectedIndex].value + '&period=0'" })
#Html.DropDownList("period", (SelectList)ViewBag.Periods, new { onchange = "document.location.href = '/ControllerName/Checklist?license=0&period=' + this.options[this.selectedIndex].value;" })
Hope this helps!

Liftweb: create a form that can be submitted both traditionally and with AJAX

Is it possible in Lift web framework to create forms (and links) that react via AJAX, but also work without Javascript support? If so, how?
When I build the form using <lift:form.ajax>, the form's action is set to javascript:// so that it no longer submits without JS. If I build the form without explicit AJAX support, I don't know how to insert the AJAX functionality.
I suppose I could build a RESTful interface (we'll have to build that anyway) and write custom Javascript to submit the form through that. I would like to avoid code duplication, though: if it is possible to handle all three inputs (RESTful, traditional HTTP POST, AJAX) with the same code, that would be best.
Take a look at http://demo.liftweb.net/form_ajax
FormWithAjax.scala
class FormWithAjax extends StatefulSnippet {
private var firstName = ""
private var lastName = ""
private val from = S.referer openOr "/"
def dispatch = {
case _ => render _
}
def render(xhtml: NodeSeq): NodeSeq =
{
def validate() {
(firstName.length, lastName.length) match {
case (f, n) if f < 2 && n < 2 => S.error("First and last names too short")
case (f, _) if f < 2 => S.error("First name too short")
case (_, n) if n < 2 => S.error("Last name too short")
case _ => S.notice("Thanks!"); S.redirectTo(from)
}
}
bind( "form", xhtml,
"first" -> textAjaxTest(firstName, s => firstName = s, s => {S.notice("First name "+s); Noop}),
"last" -> textAjaxTest(lastName, s => lastName = s, s => {S.notice("Last name "+s); Noop}),
"submit" -> submit("Send", validate _)
)
}
form_ajax.html
<lift:surround with="default" at="content">
Enter your first and last name:<br>
<form class="lift:FormWithAjax?form=post">
First Name: <form:first></form:first>
Last Name: <form:last></form:last>
<form:submit></form:submit>
</form>
</lift:surround>
And this will work without javascript:
<form action="/form_ajax" method="post">
<input name="F1069091373793VHXH01" type="hidden" value="true">
First Name: <input value="" type="text" name="F1069091373788OVAAWQ" onblur="liftAjax.lift_ajaxHandler('F1069091373789N2AO0C=' + encodeURIComponent(this.value), null, null, null)">
Last Name: <input value="" type="text" name="F1069091373790VANYVT" onblur="liftAjax.lift_ajaxHandler('F1069091373791CJMQDY=' + encodeURIComponent(this.value), null, null, null)">
<input name="F1069091383792JGBYWE" type="submit" value="Send">
</form>
I dont know a lot about Lift so my answer focuses on alternate way to do it.
This is jQuery based and will do with AJAX when Javascript is usable and traditional POST if there is no Javascript support enabled.
Form:
<form id="ajaxform" action="formhandler.php" method="post" enctype="multipart/form-data" >
<input name="firstname" type="text" />
<input name="email" type="email" />
<input name="accept" type="submit" value="Send" />
</form>
<div id="result"></div>
JS:
note: jQuery $.ajax() sends as application/x-www-form-urlencoded by default, it may be good to set form enctype="application/x-www-form-urlencoded" too.
$("#ajaxform").submit(function(e){
// Alternative way to prevent default action:
e.preventDefault();
$.ajax({
type: 'POST',
url: 'formhandler.php',
// Add method=ajax so in server side we can check if ajax is used instead of traditional post:
data: $("#ajaxform").serialize()+"&method=ajax",
success: function(data){ // formhandler.php returned some data:
// Place returned data <div id="result">here</div>
$("#result").html(data);
}
});
// Prevent default action (reposting form without ajax):
return false;
});
Server side (PHP)
<?php
if (isset($_POST['method']) && $_POST['method'] == 'ajax') {
// AJAX is used this time, only #result div is updating in this case.
} else {
// Traditional POST is used to send data, whole page is reloading. Maybe send <html><head>... etc.
}
?>
What About REST then?
This is something you should decide to use or to not use, it is not something to support as alternate to other methods (ajax, traditional) but more something integrate within other methods.
Of course you can always enable or disable REST feature.
You can always make form method="POST/GET/PUT/DELETE" and ajax call RESTful:
...
$.ajax({
type: 'PUT',
url: 'formhandler.php',
...
...
$.ajax({
type: 'DELETE',
url: 'formhandler.php',
...
But REST asks us to use XML, JSON, ... for requests too
Well, that is not well supported by browsers (without Javascript) but $.ajax() uses application/x-www-form-urlencoded as default encoding.
Ofcourse, with Javascript one can always convert data container to XML or JSON ...
Here's how it can be done with jQuery, JSON object:
/* This is function that converts elements to JSON object,
* $.fn. is used to add new jQuery plugin serializeObject() */
$.fn.serializeObject = function()
{
var o = {};
var a = this.serializeArray();
$.each(a, function() {
if (o[this.name]) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
But I want one AJAX call that does everything:
You are right, computers should do our work. It's what they are designed for.
So, another thing that needs to be done is to check what http method our original html form wants to use and adapt it to send ajax requests with same method that would be used without javascript support.
This is modified version from under JS: heading used earlier:
...
// Alternative way to prevent default action:
e.preventDefault();
// Find out what is method that form wants to use and clone it:
var restmethod = $('#ajaxform').attr('method');
// Put form data inside JSON object:
var data = $('#orderform').serializeObject();
// Add method=ajax so in server side we can check if ajax is used instead of traditional post:
data.method = 'ajax';
$.ajax({
type: restmethod, // Use method="delete" for ajax if so defined in <form ...>
url: 'formhandler.php',
data: data, // data is already serialized as JSON object
...
Now, our AJAX handler sends data as JSON object using method (post|get|put|delete) that is defined at <form method="put" ...>, if form method changes then our ajax handler will adapt changes too.
That's all, some code tested and is actually in use, some is not tested at all but should work.

MVC 3: Why is jquery form.serialize not picking up all the controls in my form?

I am trying to create a situation where if a user clicks on an "edit" button in a list of text items, she can edit that item. I am trying to make the "edit" button post back using ajax.
Here's my ajax code:
$(function () {
// post back edit request
$('input[name^="editItem"]').live("click", (function () {
var id = $(this).attr('id');
var sections = id.split('_');
if (sections.length == 2) {
var itemID = sections[1];
var divID = "message_" + itemID;
var form = $("#newsForm");
$.post(
form.attr("action"),
form.serialize(),
function (data) {
$("#" + divID).html(data);
}
);
}
return false;
}));
});
But the form.serialize() command is not picking up all the form controls in the form. It's ONLY picking up a hidden form field that appears for each item in the list.
Here's the code in the view, inside a loop that displays all the items:
**** this is the only control being picked up: ******
#Html.Hidden(indexItemID, j.ToString())
****
<div class="datetext" style="float: right; margin-bottom: 5px;">
#Model.newsItems[j].datePosted.Value.ToLongDateString()
</div>
#if (Model.newsItems[j].showEdit)
{
// *********** show the editor ************
<div id="#divID">
#Html.EditorFor(model => model.newsItems[j])
</div>
}
else
{
// *********** show the normal display, plus the following edit/delete buttons ***********
if (Model.newsItems[j].canEdit)
{
string editID = "editItem_" + Model.newsItems[j].itemID.ToString();
string deleteID = "deleteItem_" + Model.newsItems[j].itemID.ToString();
<div class="buttonblock">
<div style="float: right">
<input id="#editID" name="#editID" type="submit" class="smallsubmittext cancel" title="edit this item" value="Edit" />
</div>
<div style="float: right">
<input id="#deleteID" name="#deleteID" type="submit" class="smallsubmittext cancel" title="delete this item" value="Delete" />
</div>
</div>
<div class="clear"></div>
}
It's not picking up anything but the series of hidden form fields (indexItemID). Why would it not be picking up the button controls?
(The ID's of the edit button controls, by the way, are in the form "editItem_x" where x is the ID of the item. Thus the button controls are central to the whole process -- that's how I figure out which item the user wants to edit.)
UPDATE
The answer seems to be in the jquery API itself, http://api.jquery.com/serialize/:
"No submit button value is serialized since the form was not submitted using a button."
I don't know how my action is supposed to know which button was clicked, so I am manually adding the button to the serialized string, and it does seem to work, as inelegant as it seems.
UPDATE 2
I spoke too soon -- the ajax is not working to update my partial view. It's giving me an exception because one of the sections in my layout page is undefined. I give up -- I can't waste any more time on this. No Ajax for this project.
You could try:
var form = $('#newsForm *'); // note the '*'
Update
Did you change the argument to $.post() as well? I think I may have been a little too simple in my answer. Just change the second argument within $.post() while continuing to use form.attr('action')
New post should look like this:
$.post(
form.attr("action"),
$('#newsForm *').serialize(), // this line changed
function (data) {
$("#" + divID).html(data);
}
);

Trying to check each form input and blank its default value in jquery ajaxform()

I am using the ajaxform() plugin, which so far is working well. However, my input fields have default values, and if the user just submits the untouched form, I need to blank them out before the form is submitted using the beforeSubmit: callback.
In nutshell, I don't know the syntax to check the forms input fields and stop the submit if necessary. I have an idea its using the each() method and this.defaultValue, and maybe a return false? but I'm not sure of the details.
Could anyone perhaps give me an idea? Thanks. Heres my code so far, its the checkValues() function that I'm stuck with.
$(document).ready(function(){
//========= Functions =========
function styleForm() {
$('.quickcontact label').hide();
$('input[type="text"],textarea').addClass("idleField");
$('input[type="text"],textarea').focus(function() {
$(this).removeClass("idleField").addClass("focusField");
if (this.value == this.defaultValue){
this.value = '';
}
if(this.value != this.defaultValue){
this.select();
}
});
$('input[type="text"],textarea').blur(function() {
$(this).removeClass("focusField").addClass("idleField");
if ($.trim(this.value) == ''){
this.value = (this.defaultValue ? this.defaultValue : '');
}
});
}
//options for ajaxform() function
var options = {
target: '.quickcontactDisplay', // target element(s) to be updated with server response
beforeSubmit: checkValues, // pre-submit callback
success: reBind // post-submit callback
// other available options:
//url: url // override for form's 'action' attribute
//type: type // 'get' or 'post', override for form's 'method' attribute
//dataType: null // 'xml', 'script', or 'json' (expected server response type)
//clearForm: true // clear all form fields after successful submit
//resetForm: true // reset the form after successful submit
// $.ajax options can be used here too, for example:
//timeout: 3000
};
//rebinds the ajax functionality to updated form html
function reBind() {
// re-do the form, as it has just been replaced
$('form.quickcontact').ajaxForm(options);
styleForm();
}
//checks for default values of form on submit to prevent them being submitted
function checkValues(){
}
// ==== logic =====
$('form.quickcontact').ajaxForm(options);
styleForm();
});
And my form html:
<form action="/enquiries/add" method="post" id="EnquiryAddForm" class="quickcontact">
<input type="hidden" value="POST" name="_method"/>
<input type="hidden" id="EnquiryVisitorId" value="276" name="data[Enquiry][visitor_id]"/>
<input type="text" id="EnquiryName" maxlength="200" value="Your name" name="data[Enquiry][name]"/>
<input type="text" id="EnquiryEmailAddress" maxlength="200" value="Your Email" name="data[Enquiry][emailAddress]"/>
<textarea id="EnquiryEnquiry" rows="6" cols="30" name="data[Enquiry][enquiry]">Your Email Address</textarea>
<input type="submit" value="Ok, I'm done"/>
</form>
You are abusing the default value as a label. This is causing you problems. Rather then trying to work around those problems, I suggest fixing the cause instead.
When setting default values — set default values. Don't use the default value as a pseudo-label. Use a <label> element instead.
Haven't you looked at the documentation?
beforeSubmit:
Callback function to be invoked before the form is submitted. The
'beforeSubmit' callback can be
provided as a hook for running
pre-submit logic or for validating the
form data. If the 'beforeSubmit'
callback returns false then the form
will not be submitted. The
'beforeSubmit' callback is invoked
with three arguments: the form data in
array format, the jQuery object for
the form, and the Options Object
passed into ajaxForm/ajaxSubmit. The
array of form data takes the following
form:
[ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
Default value: null
Here the idea, didn't check it yet.
function checkValues(formData, jqForm, options)
{
for( var i in formData)
if ( formData[i].value == "")
return false;
return true;
}
sounds as if you need to:
run through all the inputs / textarea at the start and grab the default values, then stick it into an associative array with the element id as key
within checkValues, iterate through inputs once again and compare the pre-submit value against your array - when finding a match, you can set the value to "".

Resources