Find out if html form has changed - javascript

Using jquery I've added a change handler to a form.
This works when any input is changed BUT only if the user manually changes an input and not when some other code changes the input.
Is there any way to detect if a form has changed even if its inputs are changed by code?

Yes, there seems to be some confusion over this. In an ideal world you would expect the onchange event to happen whenever the inputs change but thats not what happens. I'm sure for good reasons to - maybe not.
One way I've overcome this obstacle is to capture the form state into a variable just after displaying it and then just before submitting it to check if the state has changed and to act accordingly.
An easy state to store is what the serialize function returns. An easy place to store the state is using the data functionality. Both serialize and data are available with jquery.
Of course you can use other different forms of state (some form of hash) or storage for this state (standard global variable for example).
Here is some prototype code:
If your form id is 'xform' then you can call the following code when the form has displayed:
$('#xform').data('serialize',$('#xform').serialize());
And then, when you need to check, for example just before a button submit you can use:
if($('#xform').serialize()!=$('#xform').data('serialize')){
// Form has changed!!!
}
You could wrap all this up into a copy & paste javascript snippet that will give you a formHasChanged() function to call wherever you need it (NOT TESTED):
$(function() {
$('#xform').data('serialize',$('#xform').serialize());
});
function formHasChanged(){
if($('#xform').serialize()!=$('#xform').data('serialize')){
return(true);
}
return(false);
}
But I'll stop here otherwise I'll create yet another jquery plugin.

Serializing the form is certainly an option, but it will not work if:
you want to know which fields have changed
it only needs to check a subset of the fields
dynamically adding or removing fields.
Fortunately, every form element has a default value associated with its object:
input, textarea : defaultValue
checkbox, radio : defaultChecked
select: defaultSelected
for ex: to ckeck if input or textarea has changed:
var changed = false;
$(":text,textarea").each(function(){
changed = this.value != this.defaultValue;
return !changed; // return if at least one control has changed value
});

This is easily achieved in JavaScript without jQuery. initChangeDetection() can be called multiple times:
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);
}
Test on JS Bin
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;
}

Not in a regular way.
You can change with input and then trigger the change event.
$('#inputId').val('foo').trigger('change');
or with this:
$('#inputId').val('foo').change();

Here is what i did (i found my solution using zaf's answer)
$("form").change(function() {
$(this).data("changed","true");
});
$("input[type='submit']").click(function() {
if($("form").data("changed") == "true") {
var discard = confirm("Some unsaved changes. Discard them ?");
if(!discard) return false;
}
});

Try onchange attribute
According to W3c it should trigger anytime the content of an element, the selection, or the checked state have changed.

Related

Using a javascript class method to reset the values of a previously submitted form

I am attempting to use a class method to clear a form that may have been previously submitted using vanilla Javascript. While the functionality works outside of a class, inside has been a nightmare to figure out, and using .reset() returns the previously submitted values. Ideally I'd be able to call this methods inside of other methods to keep things DRY as clearing the form is used quite a lot.
The result of what I have below is no console errors, and otherwise expected functionality, just the previously submitted data still populating the form. Console logging the elements (input) in the clearForm() method returns the expected DOM element.
There seems to be some sort of knowledge gap on my part and I'd like to fill it.
Currently I have this for clearing:
[class and constructor]...
/* in my constructor this._form = document.querySelector(target); where target is a param
containing the target element's id or class */
clearForm() {
[...this._form.elements].forEach((input) => {
if (input.type == "checkbox") {
input.checked = false
input.setAttribute("value", false);
return;
}
input.value = "";
input.setAttribute("value", "");
});
}
And I put it to use in other methods such as:
async populateForm(id) {
try {
await this.clearForm();
[additional functionality]...
} catch (e) {
...
}
}
Thanks for any help!

MAking a section on CRM 2011 form required

I want to make a 'section' on the form required. Basically the sections has checkboxes. The user is supposed to check atleast one. How can this be done?
Inside your OnSave event you need to check if at least one checkbox is checked, if all are not selected you can stop the save event.
To stop the save event you need to pass the context to your onsave event:
The function will looks like:
function onSave(executionObj)
{
// stop the save event
executionObj.getEventArgs().preventDefault();
}
to check the values you have several ways, the simplest one is to keep an array of them:
function onSave(executionObj)
{
var canSave = false;
var fields = ["new_checkbox1", "new_checkbox2", "new_checkbox3"];
for (index = 0; index < fields.length; index++)
{
var checkboxValue = Xrm.Page.getAttribute(fields[index]).getValue();
if (checkboxValue == true)
{
canSave = true;
break;
}
}
if (canSave == false)
{
alert("At least one checkbox must be selected!");
executionObj.getEventArgs().preventDefault();
}
}
Guido's answer is entirely correct, but I wanted to offer a different solution. Instead of performing the validation during the onSave Event, you can wire up OnChangeEvents for your checkboxes, that perform the same basic logic as Guido's except marking them as not required or required:
Xrm.Page.getAttribute(controlName).setRequiredLevel("required");
Xrm.Page.getAttribute(controlName).setRequiredLevel("none");
So for example, lets assume you have CheckBoxes 1, 2, & 3, and by default, they are marked as required.
When a user checks a checkbox, mark it as required, and mark the other checkboxes that are not checked as not required. If the user unchecks all, mark them all as required.
Doing this is harder in code, but provides a better user experience since they can see what is required before they update the form, and you don't have to worry about changing the OnSave default behavior.

Trigger action on programmatic change to an input value

My objective is to observe an input value and trigger a handler when its value gets changed programmatically. I only need it for modern browsers.
I have tried many combinations using defineProperty and this is my latest iteration:
var myInput=document.getElementById("myInput");
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
// handle value change here
this.setAttribute("value",val);
}
});
myInput.value="new value"; // should trigger console.log and handler
This seems to do what I expect, but it feels like a hack as I am overriding the existing value property and playing with the dual status of value (attribute and property). It also breaks the change event that doesn't seem to like the modified property.
My other attempts:
a setTimeout/setInterval loop, but this is not clean either
various watch and observe polyfills, but they break for an input value property
What would be a proper way to achieve the same result?
Live demo: http://jsfiddle.net/L7Emx/4/
[Edit] To clarify: My code is watching an input element where other applications can push updates (as a result of ajax calls for example, or as a result of changes in other fields). I have no control on how the other applications push updates, I am just an observer.
[Edit 2] To clarify what I mean by "modern browser", I'd be very happy with a solution that works on IE 11 and Chrome 30.
[Update] Updated demo based on the accepted answer: http://jsfiddle.net/L7Emx/10/
The trick suggested by #mohit-jain is to add a second input for user interaction.
if the only problem with your solution is breaking of change event on value set. thn you can fire that event manually on set. (But this wont monitor set in case a user makes a change to the input via browser -- see edit bellow)
<html>
<body>
<input type='hidden' id='myInput' />
<input type='text' id='myInputVisible' />
<input type='button' value='Test' onclick='return testSet();'/>
<script>
//hidden input which your API will be changing
var myInput=document.getElementById("myInput");
//visible input for the users
var myInputVisible=document.getElementById("myInputVisible");
//property mutation for hidden input
Object.defineProperty(myInput,"value",{
get:function(){
return this.getAttribute("value");
},
set:function(val){
console.log("set");
//update value of myInputVisible on myInput set
myInputVisible.value = val;
// handle value change here
this.setAttribute("value",val);
//fire the event
if ("createEvent" in document) { // Modern browsers
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", true, false);
myInput.dispatchEvent(evt);
}
else { // IE 8 and below
var evt = document.createEventObject();
myInput.fireEvent("onchange", evt);
}
}
});
//listen for visible input changes and update hidden
myInputVisible.onchange = function(e){
myInput.value = myInputVisible.value;
};
//this is whatever custom event handler you wish to use
//it will catch both the programmatic changes (done on myInput directly)
//and user's changes (done on myInputVisible)
myInput.onchange = function(e){
console.log(myInput.value);
};
//test method to demonstrate programmatic changes
function testSet(){
myInput.value=Math.floor((Math.random()*100000)+1);
}
</script>
</body>
</html>
more on firing events manually
EDIT:
The problem with manual event firing and the mutator approach is that the value property won't change when user changes the field value from browser. the work around is to use two fields. one hidden with which we can have programmatic interaction. Another is visible with which user can interact. After this consideration approach is simple enough.
mutate value property on hidden input-field to observe the changes and fire manual onchange event. on set value change the value of visible field to give user feedback.
on visible field value change update the value of hidden for observer.
The following works everywhere I've tried it, including IE11 (even down to IE9 emulation mode).
It takes your defineProperty idea a bit further by finding the object in the input element prototype chain that defines the .value setter and modifying this setter to trigger an event (I've called it modified in the example), while still keeping the old behavior.
When you run the snippet below, you can type / paste / whatnot in the text input box, or you can click the button that appends " more" to the input element's .value. In either case, the <span>'s content is synchronously updated.
The only thing that's not handled here is an update caused by setting the attribute. You could handle that with a MutationObserver if you want, but note that there's not a one-to-one relationship between .value and the value attribute (the latter is just the default value for the former).
// make all input elements trigger an event when programmatically setting .value
monkeyPatchAllTheThings();
var input = document.querySelector("input");
var span = document.querySelector("span");
function updateSpan() {
span.textContent = input.value;
}
// handle user-initiated changes to the value
input.addEventListener("input", updateSpan);
// handle programmatic changes to the value
input.addEventListener("modified", updateSpan);
// handle initial content
updateSpan();
document.querySelector("button").addEventListener("click", function () {
input.value += " more";
});
function monkeyPatchAllTheThings() {
// create an input element
var inp = document.createElement("input");
// walk up its prototype chain until we find the object on which .value is defined
var valuePropObj = Object.getPrototypeOf(inp);
var descriptor;
while (valuePropObj && !descriptor) {
descriptor = Object.getOwnPropertyDescriptor(valuePropObj, "value");
if (!descriptor)
valuePropObj = Object.getPrototypeOf(valuePropObj);
}
if (!descriptor) {
console.log("couldn't find .value anywhere in the prototype chain :(");
} else {
console.log(".value descriptor found on", "" + valuePropObj);
}
// remember the original .value setter ...
var oldSetter = descriptor.set;
// ... and replace it with a new one that a) calls the original,
// and b) triggers a custom event
descriptor.set = function () {
oldSetter.apply(this, arguments);
// for simplicity I'm using the old IE-compatible way of creating events
var evt = document.createEvent("Event");
evt.initEvent("modified", true, true);
this.dispatchEvent(evt);
};
// re-apply the modified descriptor
Object.defineProperty(valuePropObj, "value", descriptor);
}
<input><br><br>
The input contains "<span></span>"<br><br>
<button>update input programmatically</button>
I only need it for modern browsers.
How modern would you like to go? Ecma Script 7 (6 will be made final in December) might contain Object.observe. This would allow you to create native observables. And yes, you can run it! How?
To experiment with this feature, you need to enable the Enable
Experimental JavaScript flag in Chrome Canary and restart the browser.
The flag can be found under 'about:flags’
More info: read this.
So yeah, this is highly experimental and not ready in the current set of browsers. Also, it's still not fully ready and not 100% if it's coming to ES7, and the final date for ES7 isn't even set yet. Still, I wanted to let you know for future use.
Since you are already using polyfills for watch/observe, etc, let me take the opportunity to suggest to you Angularjs.
It offers exactly this functionality in the form of it's ng-models. You can put watchers on the model's value, and when it changes, you can then call other functions.
Here is a very simple, but working solution to what you want:
http://jsfiddle.net/RedDevil/jv8pK/
Basically, make a text input and bind it to a model:
<input type="text" data-ng-model="variable">
then put a watcher on the angularjs model on this input in the controller.
$scope.$watch(function() {
return $scope.variable
}, function(newVal, oldVal) {
if(newVal !== null) {
window.alert('programmatically changed');
}
});
There is a way to do this.
There is no DOM event for this, however there is a javascript event that triggers on an object property change.
document.form1.textfield.watch("value", function(object, oldval, newval){})
^ Object watched ^ ^ ^
|_ property watched | |
|________|____ old and new value
In the callback you can do whatever.
In this example, we can see this effect (Check the jsFiddle) :
var obj = { prop: 123 };
obj.watch('prop', function(propertyName, oldValue, newValue){
console.log('Old value is '+oldValue); // 123
console.log('New value is '+newValue); // 456
});
obj.prop = 456;
When obj change, it activates the watch listener.
You have more information in this link : http://james.padolsey.com/javascript/monitoring-dom-properties/
I wrote the following Gist a little while ago, which allows to listen for custom events cross browser (including IE8+).
Have a look at how I'm listening for onpropertychange on IE8.
util.listenToCustomEvents = function (event_name, callback) {
if (document.addEventListener) {
document.addEventListener(event_name, callback, false);
} else {
document.documentElement.attachEvent('onpropertychange', function (e) {
if(e.propertyName == event_name) {
callback();
}
}
};
I'm not sure the IE8 solution works cross browser, but you could set a fake eventlistener on the property value of your input and run a callback once the value of value changes triggered by onpropertychange.
This is an old question, but with the newish JS Proxy object, triggering an event on a value change is pretty easy:
let proxyInput = new Proxy(input, {
set(obj, prop, value) {
obj[prop] = value;
if(prop === 'value'){
let event = new InputEvent('input', {data: value})
obj.dispatchEvent(event);
}
return true;
}
})
input.addEventListener('input', $event => {
output.value = `Input changed, new value: ${$event.data}`;
});
proxyInput.value = 'thing'
window.setTimeout(() => proxyInput.value = 'another thing', 1500);
<input id="input">
<output id="output">
This creates a JS proxy object based on the original input DOM object. You can interact with the proxy object in the same way you'd interact with the origin DOM object. The difference is that we've overridden the setter. When the 'value' prop is changed, we carry out the normal, expected operation, obj[prop] = value, but then if the prop's value is 'value' we fire a custom InputEvent.
Note that you can fire whatever kind of event you'd like with new CustomEvent or new Event. "change" might be more appropriate.
Read more on proxies and custom events here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
Caniuse:
https://caniuse.com/#search=proxy

Simple jQuery function not picking up click function

I am having a go at my very first jQuery function. What needs to happen is that a form will be assigned to the function. When a user clicks on the submit button it needs to first do validation on each :input field within the the function.
I am assigning a form as the selector for my function:
$("form[name='form_name']").validator();
This then gets passed into the function and is assigned to a function property called "selected_form". Within my jQuery function I have another function which needs to wait for the submit button to be clicked, and after that it needs to iterate through all of the :input form elements and handle simple validation.
The issue now is:
a) Within my jQuery function I am making a call to return validate(); Validate then checks to see whether the button as clicked. I suspect that because the function is run and there was no button clicked, it continues and basically returns the validate() function back as false without doing any checks. I assume I will need to customize this to almost wait until the button is pressed, but I really have no idea how to do that.
b) $(selected_form+" :input").each(function() { ... } does not work as it appears to not be able to use that as a selected. I need to iterate through the "selected_form" property and select only the :input fields. Any assistance?
c) If you have any other suggestions or best practices for me regarding building a proper jQuery function please assist, this is my first attempt and I would rather learn the best practices now as appose to following my own ideas.
(function($) {
$.fn.validator = function(user_options) {
var default_options = {
exclusions: false,
rules: false,
submit_btn_name: 'event_form_submit'
};
var selected_form = this;
var selected_element = false;
var element_type = false;
var options = $.extend(default_options,user_options);
function validate() {
// Run only if form has been submitted: (this does not appear to work now, the form gets submitted without the return false;
$("input[type='"+options.submit_name+"']").click(function(event) {
event.preventDefault();
// The selector here appears to be wrong?
$(selected_form+" :input").each(function() {
// Individual element validation will happen here, for now I am just looking to log the element types :)
selected_element = this;
console.log(get_element_type());
});
return false;
});
return false;
};
function get_element_type() {
return element_type = element.type;
}
return validate();
}
})(jQuery);
// Run the validator:
$(document).ready(function() {
$("form[name='tepg_form_processor']").validator();
});
As jalynn2 and others have mentioned, my selector was incorrect. Fixed and working :)

jQuery form validation questions?

I am new to jQuery and javascript and I am used mainly to php. I am upgrading my site so it contains a little ajax to improve usability and to keep me busy!
I am using this simple little script I threw together for the login page. All works well at the minute but I have a couple of questions I'd like to ask!
$('#loginForm .submit').click(function(event) {
$('.error').hide();
event.preventDefault();
var errors = 0;
var loginEmail = $('#loginEmail').val();
var loginPassword = $('#loginPassword').val();
if (loginEmail.length == 0) {
$('#loginEmail').after('<div class="error"></div>');
errors++;
}
if (loginPassword.length == 0) {
$('#loginPassword').after('<div class="error"></div>');
errors++;
}
if (!errors) {
$('.submit').submit();
}
});
You will notice that the first line of code within the function is;
$('.error').hide();
Now in php I would normally use;
if (isset(........)) {
Is there a simliar way to do this in javascript as when the user first activates the function there will be no html with the class .error?
Also I am trying to add a new parameter to the function as well as the event parameter, how would I do this? I have tried the following?
$('#loginForm .submit').click(function(event, var) {
$('#loginForm .submit').click(function(event var) {
$('#loginForm .submit').click(function('event', 'var') {
And all seem not to work. Then again I am going by php, jQuery/javascript is not my strong point!
Thanks
Before performing an action, you can check if the function $() selected element by using the following syntax:
if ($('.error').length)
{
// actions
}
If the .error - div exists in the DOM by default and is just hidden, you can't just check the length of $('.error'), because even if it's empty, length will return 1.
You could do something like this:
if($('.error').html().length !== 0){
//do something
}
This will check the containing string of the error-div, so if it's empty, length will return 0.
Still I would recommend setting a boolean var. If errors occur, it gets set to false and you can check the var and you do not have to query for DOM-elements for such a simple task.
To your second question, try something like this:
$('#loginForm .submit').bind("click", {variable1: var}, handleSubmit);
function handleSubmit(event){
var passedVar = event.data.variable1;
}

Categories

Resources