This is the html:
<span ng-repeat="category in categories">
<label class="checkbox-inline" for="{{category.id}}">
<input type="checkbox" ng-checked="check(category.id)" ng-click="saveRoleMenu($event,category.id)" name="group" id="{{category.id}}" />
{{category.name}}
</label>
</span>
this is my controller:
//get all the menus
authorityService.getMenus().then(function(response) {
$scope.categories = response.data.data; //get all the menus success
})
//get the user's menus
var roleId = sessionStorage.getItem("roleId");
authorityService.getMenusByRoleId(roleId).then(function(response) {
$scope.userMenu = response.data.data; //get the user's menu success
})
$scope.saveRoleMenu = function($event, id) {
var checkbox = $event.target;
if (checkbox.checked) {
var roleId = $location.search().id;
authorityService.saveRoleMenu(roleId, id).then(function(response) {
if (response.data.code == 0) {
alert("success!");
}
})
}
}
$scope.check = function(value) {
//error caused here
for (var i = 0; i < $scope.userMenu.length; i++) {
if(value == $scope.userMenu[i].id){
return true;
}else{
return false;
}
}
}
I want to set checkbox checked if the value in $scope.userMenu,but in the check function,caused the error "Cannot read property 'length' of undefined",what's the reason about this error?
Cannot read property 'length' of undefined
means $scope.userMenu is not defined. Define $scope.userMenu before assigning value to it.
$scope.userMenu = [];
var roleId = sessionStorage.getItem("roleId");
Its because your userMenu are loaded from API and the check function execute before they are loaded.
define the userMenu in top something like this -
$scope.userMenu = [];
This would stop check from throwing errors. Once menu are loaded from API view will re-render with it.
Related
So I have a function that displays a list from an API:
displayEventTicketDetails() {
this.Service
.getEventTicketDetails().subscribe((data: any) => {
this.eventTicketDetails = data.map(ticket => ticket.ticket_name);
console.log(this.eventTicketDetails);
});
}
This is the result from the function above:
["Regular", "VIP", "Table", "Testing", "Cabana"]
Here is how the form array is declared:
ngOnInit() {
this.composeMessage = this.fb.group({
ticket: new FormArray([])
});
Then I use this function below to track if the check boxes are checked
onChange(event: any, isChecked: boolean){
const control = <FormArray>this.composeMessage.controls.ticket;
if(isChecked){
control.push(new FormControl(event))
} else{
const index = control.controls.findIndex(x => x.value === event);
control.removeAt(index)
}
}
Then finally in my ts file, here is my onsubmit function that submits the data on the form:
submitComposeMessage() {
this.submitted = true;
if (this.composeMessage.invalid) {
return;
}
const ticket = this.f.ticket.value;
this.Service
.createMessage(
ticket)
.subscribe(
(res: any) => {
if (res.err) {
this.toaster.errorToastr(res.message, null, { toastTimeout: 5000 });
return false;
}
this.toaster.successToastr(res.message, null, { toastTimeout: 5000 });
console.log("Message successfully created");
},
err => {
console.log(err);
}
);
}
So in my Html file here is my input field:
<ng-container *ngFor="let event of eventTicketDetails; let i = index" >
<label class="w-checkbox checkbox-field" >
<input
type="checkbox"
id="{{i}}"
name="checkbox-9"
class="w-checkbox-input checkbox"
(change)="onChange(event, $event.target.checked)"
[checked]="composeMessage.controls.ticket.value.indexOf(event)>=0">
<span class="no-margin w-form-label">{{event}}</span>
</label>
</ng-container>
With that loop, I'm able to get this result
So, I need help with two things:
1). I want all the checkbox to be checked by default when the page loads at first instance.
2). I want to validate the checkbox to ensure at least one checkbox is checked on submission.
I'll appreciate any help I can get.
If you want to only show validation message after submit, I would suggest the following, where we instead iterate the formarray in template, initially set all checked (as that is what you wish). We would listen to valueChanges of the formarray, but filter out as long as form is not submitted. We would introduce a new variable, for example isEmpty, which based on we would show/hide validation message. So all in all....
TS:
isEmpty = false;
submitted = false;
constructor(private fb: FormBuilder) {
const ctrls = this.eventTicketDetails.map(control => this.fb.control(true));
this.composeMessage = this.fb.group({
ticket: this.fb.array(ctrls)
});
this.tickets.valueChanges.pipe(
filter(() => !!this.submitted)
).subscribe((value) => {
value.some(x => x === true) ? this.isEmpty = false : this.isEmpty = true;
})
}
get tickets() {
return this.composeMessage.get("ticket") as FormArray;
}
onSubmit() {
this.submitted = true;
const selectedTickets = this.tickets.value
.map((checked, i) => (checked ? this.eventTicketDetails[i] : null))
.filter(value => !!value);
selectedTickets.length ? this.isEmpty = false : this.isEmpty = true
}
HTML:
<label formArrayName="ticket" *ngFor="let t of tickets.controls; index as i">
<input type="checkbox" [formControlName]="i">
{{eventTicketDetails[i]}}
</label>
<small *ngIf="isEmpty">Choose at least one checkbox</small>
STACKBLITZ
change Id to something like this
id="ticket{{i}}"
In this method write like this and call displayEventTicketDetails on ngOnit. This will check all the values:
displayEventTicketDetails() {
this.Service
.getEventTicketDetails().subscribe((data: any) => {
this.eventTicketDetails = data.map(ticket =>ticket.ticket_name);
setTimeout(() => {
for(var i= 0;i < this.evenTicketDetails.length ; i++){
var id = "ticket" + i;
(<HTMLInputElement>document.getElementById(id)).checked = true;
console.log(this.eventTicketDetails);
}, 500);
});
}
2. In submit method write something like this
submitComposeMessage() {
for(var i= 0;i < this.evenTicketDetails.length ; i++){
var id = "ticket" + i;
var resval = (<HTMLInputElement>document.getElementById(id)).value;
if(resval){
// this will check atleast one value is checked and if it true we are coming
out of the loop and performing other operations..
i = this.evenTicketDetails.length;
}
else{
// show error message or block from going forward..
}
});
}
This will solve your issues.
I am saving all data into localStorage. When a checkbox is checked function is called to change items state. It works fine. However after page refresh, last checked item gets unchecked (or if it was unchecked, it gets checked) while others are working just fine. Why does that 1 last action gets ignored after page is refreshed?
Here is codepen: http://codepen.io/kunokdev/pen/vGeEoY?editors=1010
(add few items and click on "click me" for all of them and then refresh page, last action will be ignored)
The view:
<div ng-app="TaskApp" ng-controller="ToDoCtrl">
<form>
<input type="text" ng-model="toDoItem">
<input type="submit" ng-click="addToDoItem()">
</form>
<div>
<ul>
<div
ng-repeat="item in toDoItems |
orderBy: 'createdAt'
track by item.createdAt">
<b>Content:</b> {{item.content}} <br>
<b>Completed?</b> {{item.completed}}
<md-checkbox ng-model="item.completed" ng-click="toggleToDoItem(item.completed)" aria-label="todo-checkbox">
CLICK ME
</md-checkbox>
</div>
</ul>
</div>
</div>
And JS:
var ls = {};
ls.get = function(key) {
return JSON.parse(localStorage.getItem(key));
};
// sets or updates a value for a key
ls.set = function(key, val) {
localStorage.setItem(key, JSON.stringify(val));
};
// returns true if value is set, else false
ls.isSet = function(key) {
var val = ls.get(key);
return ( null === val || 'undefined' === typeof val) ? false : true;
};
// removes a set item
ls.remove = function(key) {
localStorage.removeItem(key)
};
var TaskApp = angular.module('TaskApp', [
'ngMaterial',
'taskAppControllers'
]);
var taskAppControllers = angular.module('taskAppControllers',[]);
taskAppControllers.controller('ToDoCtrl', ['$scope',
function($scope){
//
loadToDoItems = function(){
var data = ls.get("toDoData");
if (data == null) data = [];
return data;
};
//
$scope.toDoItems = loadToDoItems();
//
$scope.addToDoItem = function(){
var toDoItems = $scope.toDoItems;
var newToDoItem = {
"content" : $scope.toDoItem,
"createdAt" : Date.now(),
"completed" : false
}
toDoItems.push(newToDoItem);
ls.set("toDoData", toDoItems);
$scope.toDoItem = "";
};
//
$scope.toggleToDoItem = function(item){
console.log('test');
var toDoItems = $scope.toDoItems;
for (var i = 0; i < toDoItems.length; i++)
if (toDoItems[i].createdAt === item){
if (toDoItems[i].completed == true)
toDoItems[i].completed = false;
else
toDoItems[i].completed = true;
}
ls.set('toDoData', toDoItems);
};
//
}]);
md-checkbox is designed to toggle whatever you put in ng-model so with your code, md-checkbox was toggling the completed property and then you were changing it back again in your $scope.toggleToDoItem function. Why this worked for all the items except the last clicked I am unsure.
So I changed the ng-click to only save the items to local storage and still got the same problem which leads to me believe the problem is caused by using ng-click on an md-checkbox.
<md-checkbox ng-model="item.completed" ng-click="saveToLocalStorage()" aria-label="todo-checkbox">
CLICK ME
</md-checkbox>
$scope.saveToLocalStorage = function() {
ls.set('toDoData', $scope.toDoItems);
};
So I removed the ng-click and set up a watch on $scope.toDoItems.
<md-checkbox ng-model="item.completed" aria-label="todo-checkbox">
$scope.$watch("toDoItems", function() {
ls.set("toDoData", $scope.toDoItems);
}, true);
Codepen
-- EDIT --
Just read the documentation and feel like an idiot, you should use ng-change instead of ng-click. From the docs regarding ng-change:
Angular expression to be executed when input changes due to user interaction with the input element.
That being said, the above about not needing to toggle the completed property yourself still stands.
You are passing item.completed (in the HTML) to your toggleToDoItem(item) method. In your array loop, you then compare the item.Created field to the item.completed parameter. This is comparing a Date type to a Bool. How is that supposed to work?
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;
);
};
});
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();
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