SharpSpring - Prevent form from automatically appearing if user has filled out form (without relying on cookies) - forms

Ok, this is related to the question I asked a short while ago: Silverstripe/PHP/jQuery - Once form has been filled out by user, prevent it from automatically appearing for each visit
Something has changed since then. Per request of the client, the form must not automatically appear if the user has already filled it out and has thus been placed into SharpSpring. Originally, I was creating a cookie on successful form submission using JavaScript. However, the latest concern is that it's not effective enough as cookies are registered only to certain devices and browsers, and users can clear their cookies at any time.
Essentially, the desired result is to prevent the form from automatically appearing if the user has been registered in SharpSpring (a separate domain) without having to rely on cookies.
Has anyone ever attempted something like this, checking to see if a user has submitted a form to another domain?
For reference, here is the form code I have setup:
<?php
/*
Plugin Name: SharpSpring Form Plugin
Description: A custom form plugin that is SharpSpring-compatible and uses HTML, CSS, jQuery, and AJAX
Version: 1.0
*/
define('SSCFURL', WP_PLUGIN_URL . "/" . dirname(plugin_basename(__FILE__)));
define('SSCFPATH', WP_PLUGIN_DIR . "/" . dirname(plugin_basename(__FILE__)));
function sharpspringform_enqueuescripts()
{
wp_enqueue_script('jquery-src', SSCFURL . '/js/jquery.js', array('jquery'));
wp_enqueue_script('jquery-ui', SSCFURL . '/js/jquery-ui.js', array('jquery'));
wp_enqueue_script('boootstrap', SSCFURL . '/js/bootstrap.js', array('jquery'));
wp_localize_script('sharpspringform', 'sharpspringformajax', array('ajaxurl' => admin_url('admin-ajax.php')));
}
add_action('wp_enqueue_scripts', 'sharpspringform_enqueuescripts');
function sharpspringform_show_form()
{
wp_enqueue_style( 'boilerplate', SSCFURL.'/css/boilerplate.css');
wp_enqueue_style( 'bootstrapcss', SSCFURL.'/css/bootstrap.css');
wp_enqueue_style( 'bookregistration', SSCFURL.'/css/Book-Registration.css');
wp_enqueue_style( 'formstyles', SSCFURL.'/css/styles.css');
?>
<div class="mobile-view" style="right: 51px;">
<a class="mobile-btn">
<span class="glyphicon glyphicon-arrow-left icon-arrow-mobile mobile-form-btn"></span>
</a>
</div>
<div class="slider register-photo">
<div class="form-inner">
<div class="form-container">
<form method="post" enctype="multipart/form-data" class="signupForm" id="browserHangFormPV">
<a class="sidebar">
<span class="glyphicon glyphicon-arrow-left icon-arrow arrow"></span>
</a>
<a class="closeBtn">
<span class="glyphicon glyphicon-remove"></span>
</a>
<h2 class="text-center black">Sign up for our newsletter.</h2>
<p class="errors-container light">Please fill in the required fields.</p>
<div class="success">Thank you for signing up!</div>
<div class="form-field-content">
<div class="form-group">
<input class="form-control FirstNameTxt" type="text" name="first_name" placeholder="*First Name"
autofocus="">
</div>
<div class="form-group">
<input class="form-control LastNameTxt" type="text" name="last_name" placeholder="*Last Name"
autofocus="">
</div>
<div class="form-group">
<input class="form-control EmailTxt" type="email" name="email" placeholder="*Email"
autofocus="">
</div>
<div class="form-group">
<input class="form-control CompanyTxt" type="text" name="company" placeholder="*Company"
autofocus="">
</div>
<div class="form-group submit-button">
<button class="btn btn-primary btn-block button-submit" type="button">SIGN ME UP</button>
<img src="/wp-content/plugins/sharpspring-form/img/ajax-loader.gif" class="progress" alt="Submitting...">
</div>
</div>
<br/>
<div class="privacy-link">
<a href="[privacy policy link]" class="already" target="_blank"><span
class="glyphicon glyphicon-lock icon-lock"></span>We will never share your information.</a>
</div>
</form>
<input type="hidden" id="gatewayEmbedID" value="<?php echo get_option( 'pv_signup_sharpspring_ID' ); ?>" />
<script type="text/javascript">
var embedID = document.getElementById("gatewayEmbedID").value;
var __ss_noform = __ss_noform || [];
__ss_noform.push(['baseURI', 'https://app-3QNAHNE212.marketingautomation.services/webforms/receivePostback/[redacted]']);
__ss_noform.push(['form', 'browserHangFormPV', embedID]);
__ss_noform.push(['submitType', 'manual']);
</script>
<script type="text/javascript" src="https://koi-3QNAHNE212.marketingautomation.services/client/noform.js?ver=1.24" ></script>
</div>
</div>
</div>
<?php
}
function sharpspringform_shortcode_func( $atts )
{
ob_start();
sharpspringform_show_form();
$output = ob_get_contents();
ob_end_clean();
return $output;
}
add_shortcode( 'sharpspringform', 'sharpspringform_shortcode_func' );
The form submission code with generates a cookie using JS:
;
(function ($) {
$(document).ready(function () {
var successMessage = $('.success');
var error = $('.errors-container');
var sharpSpringID = $('#gatewayEmbedID').val();
var submitbtn = $('.button-submit');
var SubmitProgress = $('img.progress');
var formdata = {};
function setCookie(cname, cvalue, exdays) {
var d = new Date();
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
}
submitbtn.click(function (e) {
resetErrors();
postForm();
});
function resetErrors() {
$('.signupForm input').removeClass('error-field');
}
function postForm() {
$.each($('.signupForm input'), function (i, v) {
if (v.type !== 'submit') {
formdata[v.name] = v.value;
}
});
submitbtn.hide();
error.hide();
SubmitProgress.show();
$.ajax({
type: "POST",
data: formdata,
url: '/wp-content/plugins/sharpspring-form/sharpsring-form-submission.php',
dataType: "json"
}).done(function (response) {
submitbtn.show();
SubmitProgress.hide();
if (response.errors) {
error.show();
var errors = response.errors;
errors.forEach(function (error) {
$('input[name="' + error + '"]').addClass('error-field');
})
}
else {
__ss_noform.push(['submit', null, sharpSpringID]);
setCookie('SignupSuccess', 'NewsletterSignup', 3650);
$('#browserHangFormPV')[0].reset();
$('.form-field-content').hide();
successMessage.show();
$('.button-submit').html("Submitted");
}
});
}
});
}(jQuery));
The jQuery code that sets up the form sliding animation and popup feature, as well as checks for the existence of the JS cookie created on successful form submit:
jQuery.noConflict();
(function ($) {
$(document).ready(function () {
//This function checks if we are in mobile view or not to determine the
//UI behavior of the form.
checkCookie();
window.onload = checkWindowSize();
var arrowicon = $(".arrow");
var overlay = $("#overlay");
var slidingDiv = $(".slider");
var closeBtn = $(".closeBtn");
var mobileBtn = $(".mobile-btn");
//When the page loads, check the screen size.
//If the screen size is less than 768px, you want to get the function
//that opens the form as a popup in the center of the screen
//Otherwise, you want it to be a slide-out animation from the right side
function checkWindowSize() {
if ($(window).width() <= 768) {
//get function to open form at center of screen
if(sessionStorage["PopupShown"] != 'yes' && !checkCookie()){
setTimeout(formModal, 5000);
function formModal() {
slidingDiv.addClass("showForm")
overlay.addClass("showOverlay");
overlay.removeClass('hideOverlay');
mobileBtn.addClass("hideBtn");
}
}
}
else {
//when we aren't in mobile view, let's just have the form slide out from the right
if(sessionStorage["PopupShown"] != 'yes' && !checkCookie()){
setTimeout(slideOut, 5000);
function slideOut() {
slidingDiv.animate({'right': '-20px'}).addClass('open');
arrowicon.addClass("glyphicon-arrow-right");
arrowicon.removeClass("glyphicon-arrow-left");
overlay.addClass("showOverlay");
overlay.removeClass("hideOverlay");
}
}
}
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}
function checkCookie() {
var user = getCookie("SignupSuccess");
if (user != "") {
return true;
} else {
return false;
}
}
/*
------------------------------------------------------------
Functions to open/close form like a modal in center of screen in mobile view
------------------------------------------------------------
*/
mobileBtn.click(function () {
slidingDiv.addClass("showForm");
slidingDiv.removeClass("hideForm");
overlay.addClass("showOverlay");
overlay.removeClass('hideOverlay');
mobileBtn.addClass("hideBtn");
});
closeBtn.click(function () {
slidingDiv.addClass("hideForm");
slidingDiv.removeClass("showForm");
overlay.removeClass("showOverlay");
overlay.addClass("hideOverlay")
mobileBtn.removeClass("hideBtn");
sessionStorage["PopupShown"] = 'yes'; //Save in the sessionStorage if the modal has been shown
});
/*
------------------------------------------------------------
Function to slide the sidebar form out/in
------------------------------------------------------------
*/
arrowicon.click(function () {
if (slidingDiv.hasClass('open')) {
slidingDiv.animate({'right': '-390px'}, 200).removeClass('open');
arrowicon.addClass("glyphicon-arrow-left");
arrowicon.removeClass("glyphicon-arrow-right");
overlay.removeClass("showOverlay");
overlay.addClass("hideOverlay");
sessionStorage["PopupShown"] = 'yes'; //Save in the sessionStorage if the modal has been shown
} else {
slidingDiv.animate({'right': '-20px'}, 200).addClass('open');
arrowicon.addClass("glyphicon-arrow-right");
arrowicon.removeClass("glyphicon-arrow-left");
overlay.addClass("showOverlay");
overlay.removeClass("hideOverlay");
}
});
});
}(jQuery));

I'm confused by the WordPress code here, rather than SilverStripe, it's not clear in your question which platform you're using.
Basically, if you want something more robust than cookies, you'll need to store the registration in the database and check it there (assuming the registration form is on your site). This means you handle the form submission on your site, send the data to the remote site and check the responses, and if all goes well, save the fact that the user has registered remotely into your database where you can check when deciding whether to show the form or not, next time.
If you don't have access to the registration form, or you want to also notice registrations made independently of your site, then you need to have an API you can query on the remote site in order to see if the user is registered.
I found a sharpspring API, but I'm not sure if it's relevant.

Related

doGet fails when same commands are called as a subfunction within it

Trying to learn Google Web Apps, I'm having the hardest time in 4 years of studying Google services. At this point, with help from other people, I have assembled a Web App form which successfully processes a few questions and accepts the submission of a file, sending the file to a folder on my Google Drive, appending the form answers to a Google Sheet, and sending me an email alerting me to the form submission. I think I understand how it works, but in trying to make the simplest of changes to it, it stopped working and gave me an error message I don't what to do with.
The form requires the user to upload an image of their COVID vaccination card. Since some people have more than one card, the form allows the user to upload more than file. This screen shot shows what the form displays upon successful submission.
The form fields and their answers remain in place, and below them appears the message "Uploaded successfully! You may upload another." What I want to have happen, besides a message like this appearing, is for all form fields and their answers disappear except for the Vacc Card/Choose File and Submit buttons.
So, I am trying to follow the instructions in this video from the highly popular YouTube Channel Learn Google Sheets & Excel Spreadsheets: Create Views (Pages) in Web App - Google Apps Script Web App Tutorial - Part 7
In this video, we see how to make the submission of a Web App form add to the current URL the string "?v=form" or "?v=whatever" and have the DoGet(e) function offer a different HTML file depending on whether the URL contains "&v" and if does, what follows that.
However, in following THE VERY FIRST STEP in the instructions, the App stopped working. All I did was cut what was inside the function doGet(e), place it inside a new function loadForm(), and put inside the doGet a calling of that function:
function doGet(e) {
if (e.parameters.v == 'form') {
return loadForm()
}
This one change caused the App to fail, returning nothing but a screen reading "The script completed but did not return anything."
My code:
/*
* Original doGet function; works fine. */
// function doGet(e){
// return HtmlService.createTemplateFromFile('index').evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1')
// .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
// }
/** Step that caused this to fail: */
function doGet(e) {
if (e.parameters.v == 'form') {
return loadForm()
} // else {HtmlService.createHtmlOutput('<h1>Hello<h1>')} // This would have been the next step, and the step after that making a second HTML file.
// Commenting out this step did NOT help.
}
function loadForm(){
return HtmlService.createTemplateFromFile('index').evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1')
.setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
} /** There you have it. All that follows is part of what works. */
function uploadFiles(formObject) {
var folderID = "1nvhxUoj9uRlE0KpBNjW3cosIuuRpAQly"; // Massage / VaccinationCards
try {
var folder = DriveApp.getFolderById(folderID);
var fileUrl = "";
//Upload file if exists
if (formObject.myFile.length > 0) {
var name = formObject.name;
var namePref = formObject.namePref;
var pronouns = formObject.pronouns;
var ready = formObject.ready;
var email = formObject.email;
var phone = formObject.phone;
var callTimes = formObject.callTimes;
var questions = formObject.questions;
var blob = formObject.myFile;
var file = folder.createFile(blob);
fileUrl = file.getUrl();
} else {
fileUrl = null;
}
SpreadsheetApp.openById("1S6RCLu8SKl0u8cVrEKd-3B71w5A-lvz0ikw_LPzgH50").getSheetByName("cards")
.appendRow([name, namePref, pronouns, ready, email, phone, callTimes, questions, fileUrl, new Date()]);
GmailApp.sendEmail('atiqzabinski#gmail.com', 'New Vax Card Uploaded!', 'Hey sweetie, smells like business! https://docs.google.com/spreadsheets/d/1S6RCLu8SKl0u8cVrEKd-3B71w5A-lvz0ikw_LPzgH50/edit', {name: 'Vacc Card Robot'});
return fileUrl;
} catch (error) {
return error.message;
}
}
My HTML:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body style = "background:none transparent;">
<!-- <link href="/tpm-styles.css" rel="stylesheet" type="text/css"> -->
<link href="https://transformphillymassage.com/tpm-styles.css" rel="stylesheet" type="text/css">
<link href="https://transformphillymassage.com/formstyles.css" rel="stylesheet" type="text/css">
<form id="myForm" onsubmit="handleFormSubmit(this)">
<p>
<label for="FormControlFile">Legal Name:</label>
<input name="name" class="form-control-file" type="text" size="35" required/>
<br>
<label for="FormControlFile">Preferred Name:</label>
<input name="namePref" class="form-control-file" type="text" size="30">
<br> <label for="FormControlFile">Pronouns:</label>
<input name="pronouns" class="form-control-file" type="text" size = "35">
</p>
<p>
<label for="FormControlFile">Choose One:</label>
<!-- <select id = "id_ready" name = "ready" class="form-control-file" required/> -->
<!-- <select id = "id_ready" name = "ready" required/> -->
<select name = "ready" class="form-control-file" required/>
<option value = "I'm ready to book!">I'm ready to book!</option>
<option value = "I have questions.">I have questions.</option>
</select>
</p>
<p>
<label for="FormControlFile">email:</label>
<input name="email" class="form-control-file" type="text" size = "40" required/>
<br>
<label for="FormControlFile">Phone Number:</label>
<input name="phone" class="form-control-file" type="text" size = "28" />
<br>
<label for="FormControlFile">Best Times To Call:</label><br>
<input name="callTimes" class="form-control-file" type="text" size="50"/>
</p>
<p>
<label for="FormControlFile">Vacc Card:</label>
<input name="myFile" class="form-control-file" type="file" required/>
</p>
<p>
<label for="FormControlFile">Questions / Comments:</label><br>
<textarea name ="questions" class="form-control-file" cols="45" rows="4"></textarea>
<br>
<button type="submit"><span class="charm spanlavender">Submit</span></button>
</p>
</form>
<div id="urlOutput"></div>
<script>
function preventFormSubmit() {
var forms = document.querySelectorAll('form');
for (var i = 0; i < forms.length; i++) {
forms[i].addEventListener('submit', function(event) {
event.preventDefault();
});
}
}
window.addEventListener('load', preventFormSubmit);
function handleFormSubmit(formObject){
google.script.run.withSuccessHandler(updateUrl).withFailureHandler(onFailure).uploadFiles(formObject);
}
function updateUrl(url) {
var div = document.getElementById('urlOutput');
if(isValidURL(url)){
div.innerHTML = '<div class="alert alert-success" role="alert">Uploaded successfully!<br>You may upload another.</div>';
// document.getElementById("myForm").reset();
} else {
//Show warning message if file is not uploaded or provided
div.innerHTML = '<div class="alert alert-danger" role="alert">'+ url +'!</div>';
}
}
function onFailure(error) {
var div = document.getElementById('urlOutput');
div.innerHTML = '<div class="alert alert-danger" role="alert">'+ error.message +'!</div>';
}
function isValidURL(string) {
var res = string.match(/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9#:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9#:%_\+.~#?&//=]*)/g);
return (res !== null);
}
</script>
</body>
</html>
return is missing on the else part of your if-else statement
Replace your doGet function by
function doGet(e) {
if (e.parameters.v == 'form') {
return loadForm()
} else {
return HtmlService.createHtmlOutput('<h1>Hello<h1>')
}
}
Below are a couple of function that could be used to debug your doGet function by using the Google Apps Script editor.
/**
* Test case 1: v parameter equal to 'form'
*
*/
function test_1_doGet(){
const e = {
parameter: {}
};
e.parameter.v = 'form';
doGet(e);
}
/**
* Test case 2: No v parameter
*
*/
function test_1_doGet(){
const e = {
parameter: {}
};
doGet(e);
}
Related
Google App Script how to load different pages using HTML Service?
How can I test a trigger function in GAS?
getting parameters from a url returns 'parameter is undefined"
How to get a URL string parameter passed to Google Apps Script doGet(e)
Serve separate HTML pages Google apps script not working
doGet(e) parameter undefined

angular 2, validate form if at least one input is typed

I'm quite new to Angular, and I've already searched the web, without finding a correct solution for my situation.
I have a dynamic form created by a *ngFor. I need to disabled the submit button if the inputs are all empty and show the alert div; but I need to enable the submit if at least one of those forms contains something different from ''.
Here is my html code
<form class="form-inline" #form="ngForm">
<div class="form-group" *ngFor="let meta of state.metaById; let i = index" style="margin: 5px">
<label>{{meta.nome}}</label>
<input type="text" class="form-control" #nome (blur)="inputInArray(nome.value, i);">
</div>
<button type="button" class="btn btn-primary" (click)="getCustomUnitaDocumentaliRow(this.param)" [disabled]="fieldNotCompiled">invia</button>
</form>
<div class="alert-notification" [hidden]="!fieldNotCompiled">
<div class="alert alert-danger">
<strong>Va compilato almeno un campo.</strong>
</div>
</div>
and here is my Typescript code
inputInArray(nome: string, indice) {
if (this.state.controlloMetaId = true) {
this.state.metadatoForm[indice] = nome;
}
// this.fieldNotCompiled = false;
for (const i in this.state.metaById) {
console.log(this.state.metadatoForm);
if (isUndefined(this.state.metadatoForm[i]) || this.state.metadatoForm[i] === '') {
this.fieldNotCompiled = true && this.fieldNotCompiled;
} else {
this.fieldNotCompiled = false && this.fieldNotCompiled;
}
console.log(this.fieldNotCompiled);
}
With this code I can check the first time a user type something in one input, but it fails if it empty one of them (or all of them)
Thanks for your time
UPDATE
Check if any input got a change that is different from empty or space, just by doing:
<input ... #nome (input)="fieldNotCompiled = !nome.value.trim()" ....>
DEMO
You can set a listener to the form changes:
#ViewChild('form') myForm: NgForm;
....
ngOnInit() {
this.myForm.valueChanges.subscribe((value: any) => {
console.log("One of the inputs has changed");
});
}

Error submitting form data using knockout and mvc

#model NewDemoApp.Models.DemoViewModel
#{
ViewBag.Title = "Home Page";
}
#*<script src="#Url.Content("~/Scripts/jquery-1.9.1.min.js")" type="text/javascript"></script>*#
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script src="#Url.Content("~/Scripts/knockout-3.3.0.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
<script type="text/javascript">
var viewModel;
var compViewModel, userViewModel;
$(document).ready(function () {
$(".wizard-step:visible").hide();
$(".wizard-step:first").show(); // show first step
$("#back-step").hide();
var result = #Html.Raw(Json.Encode(Model));
var viewModel = new DemoViewModel(result.userViewModel);
//viewModel.userViewModel.FirstName = result.userViewModel.FirstName;
//viewModel.userViewModel.LastName = result.userViewModel.LastName;
//viewModel.userViewModel.State = result.userViewModel.State;
//viewModel.userViewModel.City = result.userViewModel.City;
ko.applyBindings(viewModel);
});
var userVM = function(){
FirstName = ko.observable(),
LastName = ko.observable(),
State = ko.observable(),
City = ko.observable()
};
function DemoViewModel(data) {
var self = this;
self.userViewModel = function UserViewModel(data) {
userVM.FirstName = data.FirstName;
userVM.LastName = data.LastName;
userVM.State = data.State;
userVM.City = data.City;
}
self.Next = function () {
var $step = $(".wizard-step:visible"); // get current step
var form = $("#myFrm");
var validator = $("#myFrm").validate(); // obtain validator
var anyError = false;
$step.find("input").each(function () {
if (!validator.element(this)) { // validate every input element inside this step
anyError = true;
}
});
if (anyError)
return false; // exit if any error found
if ($step.next().hasClass("confirm")) { // is it confirmation?
$step.hide().prev(); // hide the current step
$step.next().show(); // show the next step
$("#back-step").show();
$("#next-step").hide();
//$("#myFrm").submit();
// show confirmation asynchronously
//$.post("/wizard/confirm", $("#myFrm").serialize(), function (r) {
// // inject response in confirmation step
// $(".wizard-step.confirm").html(r);
//});
}
else {
if ($step.next().hasClass("wizard-step")) { // is there any next step?
$step.hide().next().fadeIn(); // show it and hide current step
$("#back-step").show(); // recall to show backStep button
$("#next-step").show();
}
}
}
self.Back = function () {
var $step = $(".wizard-step:visible"); // get current step
if ($step.prev().hasClass("wizard-step")) { // is there any previous step?
$step.hide().prev().fadeIn(); // show it and hide current step
// disable backstep button?
if (!$step.prev().prev().hasClass("wizard-step")) {
$("#back-step").hide();
$("#next-step").show();
}
else {
$("#back-step").show();
$("#next-step").show();
}
}
}
self.SubmitForm = function (formElement) {
$.ajax({
url: '#Url.Content("~/Complaint/Save")',
type: "POST",
data: ko.toJS(self),
done: function (result) {
var newDiv = $(document.createElement("div"));
newDiv.html(result);
},
fail: function (err) {
alert(err);
},
always: function (data) {
alert(data);
}
});
}
self.loadData = function () {
$.get({
url: '#Url.Content("~/Complaint/ViewComplaint")',
done: function (data) {
debugger;
alert(data);
self.compViewModel(data);
self.userViewModel(data);
},
fail: function (err) {
debugger;
alert(err);
},
always: function (data) {
debugger;
alert(data);
}
});
}
}
</script>
<form class="form-horizontal" role="form" id="myFrm">
<div class="container">
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-6">
<div class="wizard-step">
</div>
<div class="wizard-step" >
<h3> Step 2</h3>
#Html.Partial("UserView", Model.userViewModel)
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="submit" id="submitButton" class="btn btn-default btn-success" value="Submit" data-bind="click: SubmitForm" />
</div>
<div class="col-md-3"></div>
</div>
<div class="wizard-step">
<h3> Step 3</h3>
</div>
<div class="wizard-step confirm">
<h3> Final Step 4</h3>
</div>
</div>
<div class="col-md-3"></div>
</div>
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6">
<input type="button" id="back-step" class="btn btn-default btn-success" value="< Back" data-bind="click: Back" />
<input type="button" id="next-step" class="btn btn-default btn-success" value="Next >" data-bind="click: Next" />
</div>
<div class="col-md-3"></div>
</div>
</div>
</form>
I am able to get the data from controller and bind it using knockout. There is a partial view that loads data from controller. But when submitting the updated data, I do not get the data that was updated, instead getting error that "FirstName" property could not be accessed from null reference. I just need to get pointers where I am going wrong especially the right way to create ViewModels and use them.
When you are submitting the form in self.SubmitForm function you are passing Json object which is converted from Knockout view model.
So make sure you are providing the data-bind attributes in all input tags properly. If you are using Razor syntax then use data_bind in Html attributes of input tags.
Check 2-way binding of KO is working fine. I can't be sure as you have not shared your partial view Razor code.
Refer-
http://knockoutjs.com/documentation/value-binding.html
In Chrome you can see what data you are submitting in Network tab of javascript developer console. The Json data that you are posting and ViewModel data structure you are expecting in controller method should match.
You can also change the parameters to expect FormCollection formCollection and check what data is coming from browser when you are posting.

Handle radio button form in Marionette js

I'm trying to construct a view in my app that will pop up polling questions in a modal dialog region. Maybe something like this for example:
What is your favorite color?
>Red
>Blue
>Green
>Yellow
>Other
Submit Vote
I've read that Marionette js doesn't support forms out of the box and that you are advised to handle on your own.
That structure above, branch and leaves (question and list of options), suggests CompositeView to me. Is that correct?
How do I trigger a model.save() to record the selection? An html form wants an action. I'm unclear on how to connect the form action to model.save().
My rough draft ItemView and CompositeView code is below. Am I in the ballpark? How should it be adjusted?
var PollOptionItemView = Marionette.ItemView.extend({
template: Handlebars.compile(
'<input type="radio" name="group{{pollNum}}" value="{{option}}">{{option}}<br>'
)
});
var PollOptionsListView = Marionette.CompositeView.extend({
template: Handlebars.compile(
//The question part
'<div id="poll">' +
'<div>{{question}}</div>' +
'</div>' +
//The list of options part
'<form name="pollQuestion" action="? what goes here ?">' +
'<div id="poll-options">' +
'</div>' +
'<input type="submit" value="Submit your vote">' +
'</form>'
),
itemView: PollOptionItemView,
appendHtml: function (compositeView, itemView, index) {
var childrenContainer = $(compositeView.$("#poll-options") || compositeView.el);
var children = childrenContainer.children();
if (children.size() === index) {
childrenContainer.append(itemView.el);
} else {
childrenContainer.children().eq(index).before(itemView.el);
}
}
});
MORE DETAILS:
My goal really is to build poll questions dynamically, meaning the questions and options are not known at runtime but rather are queried from a SQL database thereafter. If you were looking at my app I'd launch a poll on your screen via SignalR. In essence I'm telling your browser "hey, go get the contents of poll question #1 from the database and display them". My thought was that CompositeViews are best suited for this because they are data driven. The questions and corresponding options could be stored models and collections the CompositeView template could render them dynamically on demand. I have most of this wired and it looks good. My only issue seems to be the notion of what kind of template to render. A form? Or should my template just plop some radio buttons on the screen with a submit button below it and I write some javascript to try to determine what selection the user made? I'd like not to use a form at all and just use the backbone framework to handle the submission. That seems clean to me but perhaps not possible or wise? Not sure yet.
I'd use the following approach:
Create a collection of your survey questions
Create special itemviews for each type of question
In your CompositeView, choose the model itemView based on its type
Use a simple validation to see if all questions have been answered
Output an array of all questions and their results.
For an example implementation, see this fiddle: http://jsfiddle.net/Cardiff/QRdhT/
Fullscreen: http://jsfiddle.net/Cardiff/QRdhT/embedded/result/
Note:
Try it without answering all questions to see the validation at work
Check your console on success to view the results
The code
// Define data
var surveyData = [{
id: 1,
type: 'multiplechoice',
question: 'What color do you like?',
options: ["Red", "Green", "Insanely blue", "Yellow?"],
result: null,
validationmsg: "Please choose a color."
}, {
id: 2,
type: 'openquestion',
question: 'What food do you like?',
options: null,
result: null,
validationmsg: "Please explain what food you like."
}, {
id: 3,
type: 'checkbox',
question: 'What movie genres do you prefer?',
options: ["Comedy", "Action", "Awesome", "Adventure", "1D"],
result: null,
validationmsg: "Please choose at least one movie genre."
}];
// Setup models
var questionModel = Backbone.Model.extend({
defaults: {
type: null,
question: "",
options: null,
result: null,
validationmsg: "Please fill in this question."
},
validate: function () {
// Check if a result has been set, if not, invalidate
if (!this.get('result')) {
return false;
}
return true;
}
});
// Setup collection
var surveyCollection = Backbone.Collection.extend({
model: questionModel
});
var surveyCollectionInstance = new surveyCollection(surveyData);
console.log(surveyCollectionInstance);
// Define the ItemViews
/// Base itemView
var baseSurveyItemView = Marionette.ItemView.extend({
ui: {
warningmsg: '.warningmsg',
panel: '.panel'
},
events: {
'change': 'storeResult'
},
modelEvents: {
'showInvalidMessage': 'showInvalidMessage',
'hideInvalidMessage': 'hideInvalidMessage'
},
showInvalidMessage: function() {
// Show message
this.ui.warningmsg.show();
// Add warning class
this.ui.panel.addClass('panel-warningborder');
},
hideInvalidMessage: function() {
// Hide message
this.ui.warningmsg.hide();
// Remove warning class
this.ui.panel.removeClass('panel-warningborder');
}
});
/// Specific views
var multipleChoiceItemView = baseSurveyItemView.extend({
template: "#view-multiplechoice",
storeResult: function() {
var value = this.$el.find("input[type='radio']:checked").val();
this.model.set('result', value);
}
});
var openQuestionItemView = baseSurveyItemView.extend({
template: "#view-openquestion",
storeResult: function() {
var value = this.$el.find("textarea").val();
this.model.set('result', value);
}
});
var checkBoxItemView = baseSurveyItemView.extend({
template: "#view-checkbox",
storeResult: function() {
var value = $("input[type='checkbox']:checked").map(function(){
return $(this).val();
}).get();
this.model.set('result', (_.isEmpty(value)) ? null : value);
}
});
// Define a CompositeView
var surveyCompositeView = Marionette.CompositeView.extend({
template: "#survey",
ui: {
submitbutton: '.btn-primary'
},
events: {
'click #ui.submitbutton': 'submitSurvey'
},
itemViewContainer: ".questions",
itemViews: {
multiplechoice: multipleChoiceItemView,
openquestion: openQuestionItemView,
checkbox: checkBoxItemView
},
getItemView: function (item) {
// Get the view key for this item
var viewId = item.get('type');
// Get all defined views for this CompositeView
var itemViewObject = Marionette.getOption(this, "itemViews");
// Get correct view using given key
var itemView = itemViewObject[viewId];
if (!itemView) {
throwError("An `itemView` must be specified", "NoItemViewError");
}
return itemView;
},
submitSurvey: function() {
// Check if there are errors
var hasErrors = false;
_.each(this.collection.models, function(m) {
// Validate model
var modelValid = m.validate();
// If it's invalid, trigger event on model
if (!modelValid) {
m.trigger('showInvalidMessage');
hasErrors = true;
}
else {
m.trigger('hideInvalidMessage');
}
});
// Check to see if it has errors, if so, raise message, otherwise output.
if (hasErrors) {
alert('You haven\'t answered all questions yet, please check.');
}
else {
// No errors, parse results and log to console
var surveyResult = _.map(this.collection.models, function(m) {
return {
id: m.get('id'),
result: m.get('result')
}
});
// Log to console
alert('Success! Check your console for the results');
console.log(surveyResult);
// Close the survey view
rm.get('container').close();
}
}
});
// Create a region
var rm = new Marionette.RegionManager();
rm.addRegion("container", "#container");
// Create instance of composite view
var movieCompViewInstance = new surveyCompositeView({
collection: surveyCollectionInstance
});
// Show the survey
rm.get('container').show(movieCompViewInstance);
Templates
<script type="text/html" id="survey">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" > A cool survey regarding your life </h3>
</div>
<div class="panel-body">
<div class="questions"></div>
<div class="submitbutton">
<button type="button" class="btn btn-primary">Submit survey!</button>
</div>
</div>
</div >
</script>
<script type="text/template" id="view-multiplechoice">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<% _.each( options, function( option, index ){ %>
<div class="radio">
<label>
<input type="radio" name="optionsRadios" id="<%= index %>" value="<%= option %>"> <%= option %>
</label>
</div>
<% }); %>
</div>
</div>
</script>
<script type="text/template" id="view-openquestion">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<textarea class="form-control" rows="3"></textarea>
</div>
</div >
</script>
<script type="text/template" id="view-checkbox">
<div class="panel panel-success">
<div class="panel-heading">
<h4 class="panel-title" > <%= question %> </h4>
</div>
<div class="panel-body">
<div class="warningmsg"><%= validationmsg %></div>
<% _.each( options, function( option, index ){ %>
<div class="checkbox">
<label>
<input type="checkbox" value="<%= option %>"> <%= option %>
</label>
</div>
<% }); %>
</div>
</div>
</script>
<div id="container"></div>
Update: Added handlebars example
Jsfiddle using handlebars: http://jsfiddle.net/Cardiff/YrEP8/

Form serialize not working in Firefox

Form is dynamically loaded via ajax. After form is loaded I calling initialization of my small form plugin.
Serialize works only first time but if form has required fields errors second submit is triggered but serialize gives empty string.
This problem occurs only in Firefox. Works ok in Chrome, IE, Safari
My small Form plugin:
App.Forms = (function (parent, $) {
// Default config
var config = {
form : '.ajax-form',
control : null,
successCallback : function () {},
errorCallback : function () {}
}, _submitForm = function ($form) {
console.log('--------------- FORM DATA STRING -----------------');
console.log($form.serialize());
console.log('--------------------------------------------------');
$.ajax({
type : $form.attr('method'),
url : $form.attr('action'),
data : $form.serialize(),
cache : false,
success : function (response) {
if (config.control === null) {
$form.hide().html(response).fadeIn(800);
} else {
$(config.control).hide().html(response).fadeIn(800);
// console.log(response);
}
if ($(response).find('.field-validation-error')) {
App.Placeholder.init(); // Generate placeholder if browser not support
config.errorCallback.call();
} else {
config.successCallback.call();
}
}
});
};
parent.init = function (options) {
$.extend(config, options);
var $form = $(config.form);
if (!$form.length) {
return false;
}
App.Placeholder.init(); // Generate placeholder if browser not support
$form.on('click', ':submit', function (e) {
e.preventDefault();
_submitForm($form);
});
return parent;
};
return parent; }) (App.Forms || {}, jQuery);
Form:
#using N2Project #model N2Project._Essence.Models.RegisterModel #using (Html.BeginForm("Register", "LoyaltyLogin", FormMethod.Post, new { #id = "register-form" })) {
<p>
<span class="error">#ViewBag.Error</span>
<span class="success">#ViewBag.Success</span>
</p>
<p>
#Html.TextBoxFor(m=>m.Loyaltycard,new{#placeholder="Card Number", #class="size100"})
#Html.ValidationMessageFor(m=>m.Loyaltycard)
</p>
<p>
#Html.TextBoxFor(m=>m.FirstName,new{#placeholder="First Name", #class="size100"})
#Html.ValidationMessageFor(m=>m.FirstName)
</p>
<p>
#Html.TextBoxFor(m=>m.LastName,new{#placeholder="Last Name", #class="size100"})
#Html.ValidationMessageFor(m=>m.LastName)
</p>
<p>
#Html.TextBoxFor(m=>m.DOB,new{#placeholder="Date of birth", #class="size100", #id="dob"})
#Html.ValidationMessageFor(m=>m.DOB)
</p>
<p>
#Html.TextBoxFor(m=>m.Email,new{#placeholder="Email", #class="size100"})
#Html.ValidationMessageFor(m=>m.Email)
</p>
<p>
#Html.PasswordFor(m=>m.Password,new{#placeholder="Password", #class="size100"})
#Html.ValidationMessageFor(m=>m.Password)
</p>
<p class="checkbox">
<input type="checkbox" id="subscribe" name="Subscribe" value="true" />
<label for="subscribe">
By registering you agree to recieve news and promotions from Essence via email
</label>
</p>
<p>
<button href="#" type="submit" class="btn size100">Send</button>
</p> }
Problem fixed with this:
_submitForm($(this).closest('form'));
When I calling submitForm private method I'm passing closest form, and it's working.
Can someone explain why it's working? Why us not working in firefox in first situation ?