Restoring default drop down value with angularjs - javascript

Although I have got this working, the way it works seems improper. I have a drop down list (DDL) that displays a list of teams. The top and default entry is "Select Team... ". Although my DDL is tied to a model, "Select Team..." shouldn't be part of it since "Select Team..." has no meaning to the domain model.
When a user clicks "Add New" the form clears and all DDLs should revert to their default values.
Here are the related controller functions:
scope.addUser = function() {
resetToNewUser();
$scope.profileVisible = true;
$scope.oneAtATime = true;
$scope.accordionStatus = { isFirstOpen: true, isFirstDisabled: false };
}
function resetToNewUser() {
$scope.selectedUser.NtId = "";
$scope.selectedUser.UserId = -1;
$scope.selectedUser.IsActive = true;
$scope.selectedUser.FirstName = "";
$scope.selectedUser.LastName = "";
$scope.selectedUser.JobTitle = "";
$scope.selectedUser.Email = "";
$scope.selectedUser.SecondaryEmail = "";
$scope.selectedUser.PhoneNumber = "";
for(var i = 0; i < $scope.roleList.length; i++) {
if($scope.roleList[i].RoleSystemName.trim() === "BLU") {
$scope.selectedUser.Role = $scope.roleList[i];
}
}
$scope.selectedUser.SupervisorId = null;
//HACK BELOW//
document.getElementById('selTeam').selectedIndex = 0; // <-- This works, but feels like a hack.
$scope.selectedUser.IsRep = false;
for(var i = 0; i < $scope.signingAuthorityList.length; i++) {
if($scope.signingAuthorityList[i].SigningAuthoritySystemName === "SME") {
$scope.selectedUser.SigningAuthority = $scope.signingAuthorityList[i];
}
}
$scope.selectedUser.IsOutOfOfficeEnabled = false;
$scope.selectedUser.OutOfOfficeStartDate = null;
$scope.selectedUser.OutOfOfficeEndDate = null;
$scope.selectedUser.OutOfOfficeAppointedRepId = null;
}
Here's how the DDL is defined in the template:
<div class="form-group">
<label for="" class="control-label col-sm-2 required">Team</label>
<div class="col-sm-10">
<select class="form-control" id="selTeam"
ng-model="selectedUser.Team"
ng-options="team as team.TeamName for team in teamList track by team.TeamId">
<option value="">Select Team...</option>
</select>
</div>
</div>
Is there a better way to do this?

You could always just remove the ability for the user to select your placeholder option, right? Something like this:
<option value="" disabled selected hidden>Select Team...</option>

You html part looks good, but I think on js side you make a lot of logic. What happens if there will be added new options on the server? Better get state of the new user from the backend, customize it with the select and other widgets and keep it before it will be submitted. On pseudo code it will be looks like
$scope.addUser = function() {
//create empty user on the scope
$scope.selectedUser = {};
//get the new user state from the backend
UserService.resetToNewUser($scope.selectedUser);
//setup view options
$scope.accordionStatus = {isFirstOpen: true, isFirstDisabled: false}
};
app.service('UserService', function(){
this.resetToNewUser = function(user){
$http({
method: 'GET',
url: '/default_user/'
}).then(function successCallback(response) {
user = response;
);
};
});

Related

How do I change this Contact Form's Javascript so that IF a key is something else, perform different action?

I have a Contact Form that utilizes Google Scripts. It successfully sends the email and formats it decently to my inbox, but there are 2 problems:
-I need it so that IF var key is equal to 'Action', then do not display it in the email it sends. Because right now, "Action send_message" is getting included in the email and I don't like that.
For this, I have unsuccessfully tried things like:
for (var idx in order) {
var key = order[idx];
//Skip this entry into the email output if it is the Action
if( key === 'Action') {
continue
}
It seems to not react to this code at all.
-I also need it so that if a city is selected, e.g. Alachua, that the email says 'Alachua' instead of 'Florida_Alachua'. But I can't add a NAME to an option since apparently options don't have that property. I also can't do the quick fix of changing the VALUE of the <option> to resolve this step, because of other code I have that conflicts with this route.
Google Scripts Code:
/******************************************************************************
* This tutorial is based on the work of Martin Hawksey twitter.com/mhawksey *
* But has been simplified and cleaned up to make it more beginner friendly *
* All credit still goes to Martin and any issues/complaints/questions to me. *
******************************************************************************/
// if you want to store your email server-side (hidden), uncomment the next line
var TO_ADDRESS = "myemail#email.com";
// spit out all the keys/values from the form in HTML for email
// uses an array of keys if provided or the object to determine field order
function formatMailBody(obj, order) {
var result = "";
if (!order) {
order = Object.keys(obj);
}
// loop over all keys in the ordered form data
for (var idx in order) {
var key = order[idx];
result += "<h4 style='text-transform: capitalize; margin-bottom: 0'>" + key + "</h4><div>" + sanitizeInput(obj[key]) + "</div>";
// for every key, concatenate an `<h4 />`/`<div />` pairing of the key name and its value,
// and append it to the `result` string created at the start.
}
return result; // once the looping is done, `result` will be one long string to put in the email body
}
// sanitize content from the user - trust no one
// ref: https://developers.google.com/apps-script/reference/html/html-output#appendUntrusted(String)
function sanitizeInput(rawInput) {
var placeholder = HtmlService.createHtmlOutput(" ");
placeholder.appendUntrusted(rawInput);
return placeholder.getContent();
}
function doPost(e) {
try {
Logger.log(e); // the Google Script version of console.log see: Class Logger
record_data(e);
// shorter name for form data
var mailData = e.parameters;
// names and order of form elements (if set)
var orderParameter = e.parameters.formDataNameOrder;
var dataOrder;
if (orderParameter) {
dataOrder = JSON.parse(orderParameter);
}
// determine recepient of the email
// if you have your email uncommented above, it uses that `TO_ADDRESS`
// otherwise, it defaults to the email provided by the form's data attribute
var sendEmailTo = (typeof TO_ADDRESS !== "undefined") ? TO_ADDRESS : mailData.formGoogleSendEmail;
// send email if to address is set
if (sendEmailTo) {
MailApp.sendEmail({
to: String(sendEmailTo),
subject: "Contact form submitted",
// replyTo: String(mailData.email), // This is optional and reliant on your form actually collecting a field named `email`
htmlBody: formatMailBody(mailData, dataOrder)
});
}
return ContentService // return json success results
.createTextOutput(
JSON.stringify({"result":"success",
"data": JSON.stringify(e.parameters) }))
.setMimeType(ContentService.MimeType.JSON);
} catch(error) { // if error return this
Logger.log(error);
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": error}))
.setMimeType(ContentService.MimeType.JSON);
}
}
/**
* record_data inserts the data received from the html form submission
* e is the data received from the POST
*/
function record_data(e) {
var lock = LockService.getDocumentLock();
lock.waitLock(30000); // hold off up to 30 sec to avoid concurrent writing
try {
Logger.log(JSON.stringify(e)); // log the POST data in case we need to debug it
// select the 'responses' sheet by default
var doc = SpreadsheetApp.getActiveSpreadsheet();
var sheetName = e.parameters.formGoogleSheetName || "responses";
var sheet = doc.getSheetByName(sheetName);
var oldHeader = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var newHeader = oldHeader.slice();
var fieldsFromForm = getDataColumns(e.parameters);
var row = [new Date()]; // first element in the row should always be a timestamp
// loop through the header columns
for (var i = 1; i < oldHeader.length; i++) { // start at 1 to avoid Timestamp column
var field = oldHeader[i];
var output = getFieldFromData(field, e.parameters);
row.push(output);
// mark as stored by removing from form fields
var formIndex = fieldsFromForm.indexOf(field);
if (formIndex > -1) {
fieldsFromForm.splice(formIndex, 1);
}
}
// set any new fields in our form
for (var i = 0; i < fieldsFromForm.length; i++) {
var field = fieldsFromForm[i];
var output = getFieldFromData(field, e.parameters);
row.push(output);
newHeader.push(field);
}
// more efficient to set values as [][] array than individually
var nextRow = sheet.getLastRow() + 1; // get next row
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// update header row with any new data
if (newHeader.length > oldHeader.length) {
sheet.getRange(1, 1, 1, newHeader.length).setValues([newHeader]);
}
}
catch(error) {
Logger.log(error);
}
finally {
lock.releaseLock();
return;
}
}
function getDataColumns(data) {
return Object.keys(data).filter(function(column) {
return !(column === 'formDataNameOrder' || column === 'formGoogleSheetName' || column === 'formGoogleSendEmail' || column === 'honeypot');
});
}
function getFieldFromData(field, data) {
var values = data[field] || '';
var output = values.join ? values.join(', ') : values;
return output;
}
Contact Form HTML
<section id="contact-form">
<form id="gform"
class="contact-form" method="post"
action="(Google Scripts URL)"
enctype="text/plain">
<p>
<label for="name">Your Name <font face="Arial" color="red">*</font></label>
<input type="text" style="height:35px;" class="heighttext required" name="name" id="name" class="required" title="* Please provide your name">
</p>
<p>
<label>Your Location <font face="Arial" color="red">*</font></label>
<select name="Location" id="column_select" style="height:35px;" class="required" title=" * Please provide your location">
<option selected value="col00">-- State --</option>
<option value="Alabama">Alabama</option>
<option value="California">California</option>
<option value="Florida">Florida</option>
</select>
<select name="City" id="layout_select" style="height:35px;">
<option disabled selected value="Florida">-- City --</option>
<option name="Alachua" value="Florida_Alachua">Alachua</option>
<option name="Alford" value="Florida_Alford">Alford</option>
</select>
</p>
<p>
<input type="submit" value="Send Message" id="submit" class="pp-btn special">
<img src="images/ajax-loader.gif" id="contact-loader" alt="Loading...">
<input type="hidden" name="action" value="send_message">
</p>
</form>
</section><!-- #contact-form -->
Form Handler Javascript
(function() {
function validEmail(email) { // see:
var re = /^([\w-]+(?:\.[\w-]+)*)#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
return re.test(email);
}
function validateHuman(honeypot) {
if (honeypot) { //if hidden form filled up
console.log("Robot Detected!");
return true;
} else {
console.log("Welcome Human!");
}
}
// get all data in form and return object
function getFormData() {
var form = document.getElementById("gform");
var elements = form.elements;
var fields = Object.keys(elements).filter(function(k) {
return (elements[k].name !== "honeypot");
}).map(function(k) {
if(elements[k].name !== undefined) {
return elements[k].name;
// special case for Edge's html collection
}else if(elements[k].length > 0){
return elements[k].item(0).name;
}
}).filter(function(item, pos, self) {
return self.indexOf(item) == pos && item;
});
var formData = {};
fields.forEach(function(name){
var element = elements[name];
// singular form elements just have one value
formData[name] = element.value;
// when our element has multiple items, get their values
if (element.length) {
var data = [];
for (var i = 0; i < element.length; i++) {
var item = element.item(i);
if (item.checked || item.selected) {
data.push(item.value);
}
}
formData[name] = data.join(', ');
}
});
// add form-specific values into the data
formData.formDataNameOrder = JSON.stringify(fields);
formData.formGoogleSheetName = form.dataset.sheet || "responses"; // default sheet name
formData.formGoogleSendEmail = form.dataset.email || ""; // no email by default
console.log(formData);
return formData;
}
function handleFormSubmit(event) { // handles form submit without any jquery
event.preventDefault(); // we are submitting via xhr below
var data = getFormData(); // get the values submitted in the form
/* OPTION: Remove this comment to enable SPAM prevention, see README.md
if (validateHuman(data.honeypot)) { //if form is filled, form will not be submitted
return false;
}
*/
if( data.email && !validEmail(data.email) ) { // if email is not valid show error
var invalidEmail = document.getElementById("email-invalid");
if (invalidEmail) {
invalidEmail.style.display = "block";
return false;
}
} else {
disableAllButtons(event.target);
var url = event.target.action; //
var xhr = new XMLHttpRequest();
xhr.open('POST', url);
// xhr.withCredentials = true;
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
console.log( xhr.status, xhr.statusText )
console.log(xhr.responseText);
//document.getElementById("gform").style.display = "none"; // hide form
/*
var thankYouMessage = document.getElementById("thankyou_message");
if (thankYouMessage) {
thankYouMessage.style.display = "block";
}
*/
return;
};
// url encode form data for sending as post data
var encoded = Object.keys(data).map(function(k) {
return encodeURIComponent(k) + "=" + encodeURIComponent(data[k])
}).join('&')
xhr.send(encoded);
}
}
function loaded() {
console.log("Contact form submission handler loaded successfully.");
// bind to the submit event of our form
var form = document.getElementById("gform");
form.addEventListener("submit", handleFormSubmit, false);
};
document.addEventListener("DOMContentLoaded", loaded, false);
function disableAllButtons(form) {
var buttons = form.querySelectorAll("button");
for (var i = 0; i < buttons.length; i++) {
buttons[i].disabled = true;
}
}
})();
finally, this is the extra code that would break if I simply tried changing the value of option to, e.g., 'Alachua' instead of 'Flordia_Alachua'. https://jsfiddle.net/hmatt843/504dgmqy/19/
Thanks for any and all help.
Try console.log(key) before if( key === 'Action'). I think you'll find that key never equals 'Action', exactly. Looks like you'll need if( key === 'action'), instead.
If you wish to remove part of string value, try the replace method: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
It also looks like you're trying to work with elements[k].name when you mean to be working with elements[k].value.
I believe your code should look something like...
function(k) {
if(elements[k].value !== undefined) {
return elements[k].value.replace('Florida_', '');
// special case for Edge's html collection
} else if(elements[k].length > 0){
return elements[k].item(0).value.replace('Florida_', '');
}
}
... or something to that effect.
In the future, you may want to make it easier for folks trying to help you by posting only the portions of code your having trouble with, and breaking your questions into different posts. A lot to sift through up there.
Hope this helped.
The split() method splits a String object into an array of strings by separating the string into substrings, using a specified separator string to determine where to make each split.
Var splitValue = elements[k].item(0).value.split("");
splitValue[1] will give you a string of characters after the delimeter () in this case.

ng-dirty is not working as expected

I am having tough time in understanding why my element shows ng-dirty after updating the model.
I have a collection of bridges which need to be rendered on UI. On each tab click, I am changing the index and rendering the data.
If my first tab data has changed and moved to second tab why are input elements still dirty on second tab. (Function - $scope.changeIndex)
After executing calculate, the model gets updated but still the input elements are still dirty
UI
<td style="padding:10px;text-align:center">
<label>Length:</label>
<input type="text" class="currencyLabel-editable" ng-model="bridgeModel.bridges[currentIndex].length" />
</td>
<td style="padding:10px;text-align:center">
<label>Width:</label>
<input type="text" class="currencyLabel-editable" ng-model="bridgeModel.bridges[currentIndex].width" />
</td>
<td style="padding:10px;text-align:center">
<label> Skew:</label>
<input type="text" class="currencyLabel-editable" ng-model="bridgeModel.bridges[currentIndex].skew" />
</td>
Controller
(function () {
var bridgeCtrl = function ($scope, $bootstrapBridgeData, $crudService,$log) {
$scope.bridgeModel = $bootstrapBridgeData.bridgeModel;
var onCalculateComplete = function (data) {
$scope.bridgeModel.bridges[$scope.currentIndex] = angular.copy(angular.fromJson(data));
}
var onCalculateError = function (reason){
$scope.error = "Unable to perform calculation";
$log.error(reason);
}
var onError = function (reason) {
$scope.error = "Unable to fetch data";
}
//function to null the values which needs to be re-computed
var removeCalculatedValues = function () {
$scope.bridgeModel.bridges[$scope.currentIndex].foundation_PreBoringCalculated = null;
$scope.bridgeModel.bridges[$scope.currentIndex].foundation_DrilledShaftsCalculated = null;
}
//function to compute the bridge values
$scope.calculate = function (url) {
if (!preValidation()) {
return false;
}
removeCalculatedValues();
$crudService.postAndGetData(url, $scope.bridgeModel.bridges[$scope.currentIndex])
.then(onCalculateComplete, onCalculateError)
}
//function to select the bridge and change the index of the bridge
$scope.changeIndex = function (bridgeName,index) {
$scope.selectedBridge = bridgeName;
$scope.currentIndex = index;
}
$scope.save = function (index, url) {
$scope.currentIndex = index;
crudService.postAndGetData(url, $scope.bridges[index])
.then(onUserComplete, onError);
}
//$scope.enableSave = function isFormDirty() {
// if ($(".ng-dirty").length) {
// return false;
// }
// else { return true; }
//}
//Behaviour Changes
//function which changes the css
$scope.isBridgeSelected = function (bridge) {
return $scope.selectedBridge === bridge;
}
var preValidation = function () {
if ($(".ng-invalid").length) {
alert("Please correct the errors.")
return false;
}
else { return true;}
}
}
//Get the module and add a controller to it
var module = angular.module("bridgeModule");
module.controller("bridgeCtrl", bridgeCtrl);
}());
From the documentation
ng-dirty is set if the form is dirty.
This is a check for whether the form itself has been interacted with in any way. It doesn't care what the underlying object binding is. So this is the expected behavior, since you are using the same form but changing the ng-model behind the scenes.
Dunno if this is the problem or not, but the line $scope.$setPristine; is not doing anything. It should be: $scope.$setPristine();

Conditional drop down menu in javascript, problems with the selector

I have a conditional dropdown menu which works but it depends on where I put it on the page. If I don't put it in a specific position the positions in the menu don't fill at all. The problem is also that I want to add a second conditional dropdown, and that one doesn't work regardless of where I put it. So my question is: do I need to add the script twice on the page to correspond the two forms I have? I actually want both drop downs to fill with the exact same words.
document.forms[0]['List'+i].length = 1;
document.forms[0]['List'+i].selectedIndex = 0;
I'm guessing it's something related with the [0] there. I tried adding an id to the form and then writing the the second script document.forms('myId')[] but that didn't work either. How should I go about doing this?
<script type="text/javascript">
var categories = [];
categories["startList"] = ["Programming","Science","History","Business and Economics","Software","Languages","Do it Yourself","Others"];
categories["Programming"] = ["Java","C++","C.","Python","Html","Php","Mysql","ObjectiveC","Android","Others"];
categories["Science"] = ["Mathematics","Physics","Biology","Chemistry","Medicine","Astronomy","Statistics","Others"];
categories["Others"] = ["All"]
var nLists = 2; // number of lists in the set
function fillSelect(currCat,currList){
var step = Number(currList.name.replace(/\D/g,""));
for (i=step; i<nLists+1; i++) {
document.forms[0]['List'+i].length = 1;
document.forms[0]['List'+i].selectedIndex = 0;
}
var nCat = categories[currCat];
for (each in nCat) {
var nOption = document.createElement('option');
var nData = document.createTextNode(nCat[each]);
nOption.setAttribute('value',nCat[each]);
nOption.appendChild(nData);
currList.appendChild(nOption);
}
}
function getValue( L2, L1) {
$.post( "", { List1: L1, List2: L2 } );
}
function init() {
fillSelect('startList',document.forms[0]['List1'])
}
navigator.appName == "Microsoft Internet Explorer" ? attachEvent('onload', init, false) : addEventListener('load', init, false);
</script>
<form action="" method="post">
<select name='List1' onchange="fillSelect(this.value,this.form['List2'])">
<option selected>Category</option>
</select>
<select name='List2' onchange="getValue(this.value,this.form['List1'].value)">
<option selected >Subcategory</option>
</select>
<input type="Submit">
Fixed it. I just had to add either a selector or 1 there to correspond to my second form.
document.forms[1]['List'+i].length = 1;
document.forms[1]['List'+i].selectedIndex = 0;
}
var nCat = categories[currCat];
for (each in nCat) {
var nOption = document.createElement('option');
var nData = document.createTextNode(nCat[each]);
nOption.setAttribute('value',nCat[each]);
nOption.appendChild(nData);
currList.appendChild(nOption);
}
}
function getValue( L2, L1) {
$.post( "", { List1: L1, List2: L2 } );
}
function init() {
fillSelect('startList',document.forms[1]['List1'])

AngularJS - Save as multiple rows - data coming from ng-repeat

Hello new to AngularJS and I am trying to wrap my head around something...
Basically I have a form that can accept 1 to many "Guests" for an event. Using ng-repeat I display the fields like so:
<div ng-repeat="guest in guests">
<input type="text" ng-model="guests[$index].first_name" />
<input type="text" ng-model="guests[$index].last_name" />
<select ng-model="guests[$index].meal" ng-options="meal.id as meal.name for meal in meals"></select>
<select ng-model="guests[$index].rsvp">
<option value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
<div class="controls"><button ng-click="submit()" class="btn btn-success">Save</button></div>
And in the controller:
//DETERMINE TOTAL IN PARTY
var totalInParty = 2;
$scope.meals = RSVPRes.Meals.query();
//EDIT
if ($scope.rsvpId) {
}
//NEW RSVP SUBMISSION
else {
$scope.rsvp = new RSVPRes.RSVP();
//INITIALIZE EMPTY GUESTS
$scope.guests = [];
for (var i = 0; i < totalInParty; i++) {
$scope.guests[i] = {
first_name: '',
last_name: '',
meal: 1,
rsvp: 0
};
}
}
And my Resource
.factory( 'RSVPRes', function ( $resource ) {
return {
RSVP: $resource("../reservations/:id.json", {id:'#id'}, {'update': {method:'PUT'}, 'remove': {method: 'DELETE', headers: {'Content-Type': 'application/json'}}}),
Meals: $resource('../meals.json')
};
})
Now all this works really well - I am just having trouble saving the data. I would like to save each Guest (First Name, Last Name, Meal & RSVP) as it's own row.
If I try this:
$scope.submit = function() {
for(var i = 0; i < $scope.guests.length; i++){
$scope.rsvp.first_name = $scope.guests[i].first_name;
$scope.rsvp.last_name = $scope.guests[i].last_name;
$scope.rsvp.meal_id = $scope.guests[i].meal;
$scope.rsvp.rsvp = $scope.guests[i].rsvp;
$scope.rsvp.$save();
}
$state.transitionTo('rsvps');
};
It creates two rows (total_in_party set to 2) but it's always the 2nd persons data.
Feel like I am close, I looked a quite a few ng-repeat examples, but couldn't find one that deals with my specific case!
Any help is appreciated.
SOLVED
I totally duffed my thinking about the Resource, creating a new RSVP object now everytime in the loop.
$scope.submit = function() {
for(var i = 0; i < $scope.guests.length; i++){
$scope.rsvp = new RSVPRes.RSVP();
$scope.rsvp.first_name = $scope.guests[i].first_name;
$scope.rsvp.last_name = $scope.guests[i].last_name;
$scope.rsvp.meal_id = $scope.guests[i].meal;
$scope.rsvp.rsvp = $scope.guests[i].rsvp;
$scope.rsvp.$save();
}
$state.transitionTo('rsvps');
};
Move $scope.rsvp.$save(); outside the loop

Generic way to detect if html form is edited

I have a tabbed html form. Upon navigating from one tab to the other, the current tab's data is persisted (on the DB) even if there is no change to the data.
I would like to make the persistence call only if the form is edited. The form can contain any kind of control. Dirtying the form need not be by typing some text but choosing a date in a calendar control would also qualify.
One way to achieve this would be to display the form in read-only mode by default and have an 'Edit' button and if the user clicks the edit button then the call to DB is made (once again, irrespective of whether data is modified. This is a better improvement to what is currently existing).
I would like to know how to write a generic javascript function that would check if any of the controls value has been modified ?
In pure javascript, this would not be an easy task, but jQuery makes it very easy to do:
$("#myform :input").change(function() {
$("#myform").data("changed",true);
});
Then before saving, you can check if it was changed:
if ($("#myform").data("changed")) {
// submit the form
}
In the example above, the form has an id equal to "myform".
If you need this in many forms, you can easily turn it into a plugin:
$.fn.extend({
trackChanges: function() {
$(":input",this).change(function() {
$(this.form).data("changed", true);
});
}
,
isChanged: function() {
return this.data("changed");
}
});
Then you can simply say:
$("#myform").trackChanges();
and check if a form has changed:
if ($("#myform").isChanged()) {
// ...
}
I am not sure if I get your question right, but what about addEventListener? If you don't care too much about IE8 support this should be fine. The following code is working for me:
var form = document.getElementById("myForm");
form.addEventListener("input", function () {
console.log("Form has changed!");
});
In case JQuery is out of the question. A quick search on Google found Javascript implementations of MD5 and SHA1 hash algorithms. If you wanted, you could concatenate all form inputs and hash them, then store that value in memory. When the user is done. Concatenate all the values and hash again. Compare the 2 hashes. If they are the same, the user did not change any form fields. If they are different, something has been edited, and you need to call your persistence code.
Another way to achieve this is serialize the form:
$(function() {
var $form = $('form');
var initialState = $form.serialize();
$form.submit(function (e) {
if (initialState === $form.serialize()) {
console.log('Form is unchanged!');
} else {
console.log('Form has changed!');
}
e.preventDefault();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form>
Field 1: <input type="text" name="field_1" value="My value 1"> <br>
Field 2: <input type="text" name="field_2" value="My value 2"> <br>
Check: <input type="checkbox" name="field_3" value="1"><br>
<input type="submit">
</form>
Form changes can easily be detected in native JavaScript without jQuery:
function initChangeDetection(form) {
Array.from(form).forEach(el => el.dataset.origValue = el.value);
}
function formHasChanges(form) {
return Array.from(form).some(el => 'origValue' in el.dataset && el.dataset.origValue !== el.value);
}
initChangeDetection() can safely be called multiple times throughout your page's lifecycle: See Test on JSBin
For older browsers that don't support newer arrow/array functions:
function initChangeDetection(form) {
for (var i=0; i<form.length; i++) {
var el = form[i];
el.dataset.origValue = el.value;
}
}
function formHasChanges(form) {
for (var i=0; i<form.length; i++) {
var el = form[i];
if ('origValue' in el.dataset && el.dataset.origValue !== el.value) {
return true;
}
}
return false;
}
Here's how I did it (without using jQuery).
In my case, I wanted one particular form element not to be counted, because it was the element that triggered the check and so will always have changed. The exceptional element is named 'reporting_period' and is hard-coded in the function 'hasFormChanged()'.
To test, make an element call the function "changeReportingPeriod()", which you'll probably want to name something else.
IMPORTANT: You must call setInitialValues() when the values have been set to their original values (typically at page load, but not in my case).
NOTE: I do not claim that this is an elegant solution, in fact I don't believe in elegant JavaScript solutions. My personal emphasis in JavaScript is on readability, not structural elegance (as if that were possible in JavaScript). I do not concern myself with file size at all when writing JavaScript because that's what gzip is for, and trying to write more compact JavaScript code invariably leads to intolerable problems with maintenance. I offer no apologies, express no remorse and refuse to debate it. It's JavaScript. Sorry, I had to make this clear in order to convince myself that I should bother posting. Be happy! :)
var initial_values = new Array();
// Gets all form elements from the entire document.
function getAllFormElements() {
// Return variable.
var all_form_elements = Array();
// The form.
var form_activity_report = document.getElementById('form_activity_report');
// Different types of form elements.
var inputs = form_activity_report.getElementsByTagName('input');
var textareas = form_activity_report.getElementsByTagName('textarea');
var selects = form_activity_report.getElementsByTagName('select');
// We do it this way because we want to return an Array, not a NodeList.
var i;
for (i = 0; i < inputs.length; i++) {
all_form_elements.push(inputs[i]);
}
for (i = 0; i < textareas.length; i++) {
all_form_elements.push(textareas[i]);
}
for (i = 0; i < selects.length; i++) {
all_form_elements.push(selects[i]);
}
return all_form_elements;
}
// Sets the initial values of every form element.
function setInitialFormValues() {
var inputs = getAllFormElements();
for (var i = 0; i < inputs.length; i++) {
initial_values.push(inputs[i].value);
}
}
function hasFormChanged() {
var has_changed = false;
var elements = getAllFormElements();
for (var i = 0; i < elements.length; i++) {
if (elements[i].id != 'reporting_period' && elements[i].value != initial_values[i]) {
has_changed = true;
break;
}
}
return has_changed;
}
function changeReportingPeriod() {
alert(hasFormChanged());
}
Here's a polyfill method demo in native JavaScript that uses the FormData() API to detect created, updated, and deleted form entries. You can check if anything was changed using HTMLFormElement#isChanged and get an object containing the differences from a reset form using HTMLFormElement#changes (assuming they're not masked by an input name):
Object.defineProperties(HTMLFormElement.prototype, {
isChanged: {
configurable: true,
get: function isChanged () {
'use strict'
var thisData = new FormData(this)
var that = this.cloneNode(true)
// avoid masking: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
HTMLFormElement.prototype.reset.call(that)
var thatData = new FormData(that)
const theseKeys = Array.from(thisData.keys())
const thoseKeys = Array.from(thatData.keys())
if (theseKeys.length !== thoseKeys.length) {
return true
}
const allKeys = new Set(theseKeys.concat(thoseKeys))
function unequal (value, index) {
return value !== this[index]
}
for (const key of theseKeys) {
const theseValues = thisData.getAll(key)
const thoseValues = thatData.getAll(key)
if (theseValues.length !== thoseValues.length) {
return true
}
if (theseValues.some(unequal, thoseValues)) {
return true
}
}
return false
}
},
changes: {
configurable: true,
get: function changes () {
'use strict'
var thisData = new FormData(this)
var that = this.cloneNode(true)
// avoid masking: https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
HTMLFormElement.prototype.reset.call(that)
var thatData = new FormData(that)
const theseKeys = Array.from(thisData.keys())
const thoseKeys = Array.from(thatData.keys())
const created = new FormData()
const deleted = new FormData()
const updated = new FormData()
const allKeys = new Set(theseKeys.concat(thoseKeys))
function unequal (value, index) {
return value !== this[index]
}
for (const key of allKeys) {
const theseValues = thisData.getAll(key)
const thoseValues = thatData.getAll(key)
const createdValues = theseValues.slice(thoseValues.length)
const deletedValues = thoseValues.slice(theseValues.length)
const minLength = Math.min(theseValues.length, thoseValues.length)
const updatedValues = theseValues.slice(0, minLength).filter(unequal, thoseValues)
function append (value) {
this.append(key, value)
}
createdValues.forEach(append, created)
deletedValues.forEach(append, deleted)
updatedValues.forEach(append, updated)
}
return {
created: Array.from(created),
deleted: Array.from(deleted),
updated: Array.from(updated)
}
}
}
})
document.querySelector('[value="Check"]').addEventListener('click', function () {
if (this.form.isChanged) {
console.log(this.form.changes)
} else {
console.log('unchanged')
}
})
<form>
<div>
<label for="name">Text Input:</label>
<input type="text" name="name" id="name" value="" tabindex="1" />
</div>
<div>
<h4>Radio Button Choice</h4>
<label for="radio-choice-1">Choice 1</label>
<input type="radio" name="radio-choice-1" id="radio-choice-1" tabindex="2" value="choice-1" />
<label for="radio-choice-2">Choice 2</label>
<input type="radio" name="radio-choice-2" id="radio-choice-2" tabindex="3" value="choice-2" />
</div>
<div>
<label for="select-choice">Select Dropdown Choice:</label>
<select name="select-choice" id="select-choice">
<option value="Choice 1">Choice 1</option>
<option value="Choice 2">Choice 2</option>
<option value="Choice 3">Choice 3</option>
</select>
</div>
<div>
<label for="textarea">Textarea:</label>
<textarea cols="40" rows="8" name="textarea" id="textarea"></textarea>
</div>
<div>
<label for="checkbox">Checkbox:</label>
<input type="checkbox" name="checkbox" id="checkbox" />
</div>
<div>
<input type="button" value="Check" />
</div>
</form>
I really like the contribution from Teekin above, and have implemented it.
However, I have expanded it to allow for checkboxes too using code like this:
// Gets all form elements from the entire document.
function getAllFormElements() {
// Return variable.
var all_form_elements = Array();
// The form.
var Form = document.getElementById('frmCompDetls');
// Different types of form elements.
var inputs = Form.getElementsByTagName('input');
var textareas = Form.getElementsByTagName('textarea');
var selects = Form.getElementsByTagName('select');
var checkboxes = Form.getElementsByTagName('CheckBox');
// We do it this way because we want to return an Array, not a NodeList.
var i;
for (i = 0; i < inputs.length; i++) {
all_form_elements.push(inputs[i]);
}
for (i = 0; i < textareas.length; i++) {
all_form_elements.push(textareas[i]);
}
for (i = 0; i < selects.length; i++) {
all_form_elements.push(selects[i]);
}
for (i = 0; i < checkboxes.length; i++) {
all_form_elements.push(checkboxes[i]);
}
return all_form_elements;
}
// Sets the initial values of every form element.
function setInitialFormValues() {
var inputs = getAllFormElements();
for (var i = 0; i < inputs.length; i++) {
if(inputs[i].type != "checkbox"){
initial_values.push(inputs[i].value);
}
else
{
initial_values.push(inputs[i].checked);
}
}
}
function hasFormChanged() {
var has_changed = false;
var elements = getAllFormElements();
var diffstring = ""
for (var i = 0; i < elements.length; i++) {
if (elements[i].type != "checkbox"){
if (elements[i].value != initial_values[i]) {
has_changed = true;
//diffstring = diffstring + elements[i].value+" Was "+initial_values[i]+"\n";
break;
}
}
else
{
if (elements[i].checked != initial_values[i]) {
has_changed = true;
//diffstring = diffstring + elements[i].value+" Was "+initial_values[i]+"\n";
break;
}
}
}
//alert(diffstring);
return has_changed;
}
The diffstring is just a debugging tool

Categories