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();
Related
When the user clicks on the button, you need to compare the values in certain fields and display the values according to the template.
I know this is a very stupid question, but I can't make it so that all values are "true" and the function is considered executed and the button works out the correct values
I try like this
function serviceCheck() {
function serviceCheck1() {
var CasePageServiceSelector = document.getElementById('CasePageServicePact00d7746a-0675-49ac-8608-323407985e07ComboBoxEdit-el').value;
var ServiceDocument = 'Case';
if (CasePageServiceSelector === ServiceDocument){
console.log('1')
}
}
function serviceCheck2() {
var CasePageServiceCategSelector = document.getElementById('CasePageServiceCategoryComboBoxEdit-el').value;
var ServiceCateg = 'Talker';
if (CasePageServiceCategSelector === ServiceCateg){
console.log('2!')
}
}
function serviceCheck3() {
var CasePageServiceItemSelector = document.getElementById('CasePageServiceItemComboBoxEdit-el').value;
var ServiceItem = '4. Problem';
if (CasePageServiceItemSelector === ServiceItem){
console.log('3')
}
}
}
function ChangeSecondTA() {
let Theme = $('#CasePageSubjectTextEdit-el').val('template');
let Symptoms = $('#CasePageSymptomsHtmlEdit-el').val('template2 ');
}
function onButtonClick(event) {
try {
alert(serviceCheck)
} catch (error) {
console.error("error: ", error);
}
};
At first, you write the function serviceCheck() and you call it using: alert(serviceCheck)
Why do you use alert, if your function does not return anything?
Secondly, you write three functions serviceCheck1-3() however you newer call these functions.
you need write something like this:
function serviceCheck() {
let in1 = document.getElementById('input1').value;
let in2 = document.getElementById('input2').value;
if (in1 == in2) {
alert('true');
} else {
alert('false');
}
}
function onButtonClick() {
serviceCheck();
};
<html>
<head></head>
<body>
<button onclick="onButtonClick()">input1 == input2 ?</button>
<br><input id='input1' type="text" value="55">
<input id='input2' type="text" value="55">
</body>
</html>
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.
I have found this very short yet handy two way binding code written in pure JavaScript. The data binding works fine, but what I want is to take the value from the first input and multiply it by a desired number and bind the outcome to the next input. I will appreciate any kind of help.
This is my HTML Code:
<input class="age" type="number">
<input class="age" type="number">
and the JavaScript Code:
var $scope = {};
(function () {
var bindClasses = ["age"];
var attachEvent = function (classNames) {
classNames.forEach(function (className) {
var elements = document.getElementsByClassName(className);
for (var index in elements) {
elements[index].onkeyup = function () {
for (var index in elements) {
elements[index].value = this.value;
}
}
}
Object.defineProperty($scope, className, {
set: function (newValue) {
for (var index in elements) {
elements[index].value = newValue;
}
}
});
});
};
attachEvent(bindClasses);
})();
If the desired results is take first input value, do something with it, put it to second input then why so serious?
(function () {
var bindClasses = ["age"];
var attachEvent = function (classNames) {
classNames.forEach( function( className ) {
var els = document.getElementsByClassName( className ),
from, to;
if ( 2 > els.length ) {
return;
}
from = els[0];
to = els[els.length - 1];
from.addEventListener( 'keyup', function() {
var v = this.value;
// Do whatever you want with that value
// Then assign it to last el
if ( isNaN( v ) ) {
return;
}
v = v / 2;
to.value = v;
});
});
};
attachEvent(bindClasses);
})();
Another simple approach to two-way binding in JS could be something like this:
<!-- index.html -->
<form action="#" onsubmit="vm.onSubmit(event, this)">
<input onchange="vm.username=this.value" type="text" id="Username">
<input type="submit" id="Submit">
</form>
<script src="vm.js"></script>
// vm.js - vanialla JS
let vm = {
_username: "",
get username() {
return this._username;
},
set username(value) {
this._username = value;
},
onSubmit: function (event, element) {
console.log(this.username);
}
}
JS Getters and Setters are quite nice for this - especially when you look at the browser support for this.
I have a TS code like this:
class MicrositeRequest {
micrositeName: string;
micrositeTemplate: string;
constructor() {
this.micrositeName = $("#micrositeNameId").val();
this.micrositeTemplate = $("#templateId option:selected").text();
}
public IsMicrositeRequestValid() {
if (this.checkForName() && this.checkForTemplate()) {
return true;
}
else {
return false;
}
}
checkForName() {
if (this.micrositeName != null && this.micrositeName.length != 0) {
return true;
}
else {
return false;
}
}
checkForTemplate() {
if (this.micrositeTemplate != null && this.micrositeTemplate.length != 0) {
return true;
}
else {
return false;
}
}
}
Here's the converted JS:
/// <reference path="scripts/typings/jquery/jquery.d.ts" />
var MicrositeRequest = (function () {
function MicrositeRequest() {
this.micrositeName = $("#micrositeNameId").val();
this.micrositeTemplate = $("#templateId option:selected").text();
}
MicrositeRequest.prototype.IsMicrositeRequestValid = function () {
if (this.checkForName() && this.checkForTemplate()) {
return true;
}
else {
return false;
}
};
MicrositeRequest.prototype.checkForName = function () {
if (this.micrositeName != null && this.micrositeName.length != 0) {
return true;
}
else {
return false;
}
};
MicrositeRequest.prototype.checkForTemplate = function () {
if (this.micrositeTemplate != null && this.micrositeTemplate.length != 0) {
return true;
}
else {
return false;
}
};
return MicrositeRequest;
})();
//# sourceMappingURL=Microsite.js.map
On Click of a button I want to call the IsMicrositeRequestValid() method.
Here's the HTML:
<div>
<input type="submit" name="submit" value="Get" onclick="IsMicrositeRequestValid()" />
</div>
The Console says IsMicrositeRequestValid is not defined.
Any clues why this is happening and how I can fix it?
The call to IsMicrositeRequestValid in the onclick attribute requires that the function be part of the global namespace (window). Further, I'm pretty sure you'll need to instantiate MicrositeRequest object before the call to IsMicrositeRequestValid work (because it relies on this).
function validateRequest() { // declare a function in the global namespace
var mr = new MicrositeRequest();
return mr.IsMicrositeRequestValid();
}
<input type="submit" name="sumbit" value="Get" onclick="validateRequest()" />
is the quick & dirty way which should get it working.
You could also do this:
window.validateRequest = function () { // declare a function in the global namespace
var mr = new MicrositeRequest();
return mr.IsMicrositeRequestValid();
}
which I think is more readable.
Also, look into the Element.addEventListener method. It allows much more flexibility.
var submit = document.getElementById('my-submit');
submit.addEventListener('click', function () {
var mr = new MicrositeRequest();
return mr.IsMicrositeRequestValid();
});
<input type="submit" id="my-submit" name="sumbit" value="Get" />
In addition to pete's answer here an object oriented solution:
Alex works with jQuery, so i feel free to use it in this answer.
1.
You can't call methods of a class but only methods of objects, so you have to instantiate a object of class MicrositeRequest somewhere. Usually you do this after the DOM is fully populated, so you write:
$(() => {
new MicrositeRequest();
});
You may place this code beneath your class definition so that it starts after the browser loaded your script.
2.
Give your input element an id:
<div>
<input id="myInputElement" type="submit" name="submit" value="Get" onclick="IsMicrositeRequestValid()" />
</div>
Register method IsMicrositeRequestValid of the MicrositeRequest-object as onClick-handler for your input-element. To achieve this add these lines to the constructor method of class MicrositeRequest:
let that = this;
$('#myInputElement').on('click', () => {
that.IsMicrositeRequestValid();
});
You need variable that because inside the event handler this points to the input element.
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