Alfresco Share client-side synchronous Form validation - javascript

I have an Alfresco Share client-side Form in a registration scenario and I want to validate if the chosen username is taken.
Can I add a Alfresco forms validator in a synchronous way?
So far my form goes something like this:
this.widgets.saveForm = new Alfresco.forms.Form(this.id + "-form");
this.widgets.saveForm.addValidation(Dom.get(this.id + "-username"), this.validateUsername, me, "blur", this.msg("validation-hint.username-taken"));
But this.validateUsername needs to make an Alfresco.util.Ajax.request to a repo-side web service which checks availability for the chosen username. So basically, by the time the response is here, my original validateUsername method is long-finished, returning false (likely).
My progress so far is to disable the submit button (that is why I am passing "me" to the validation handler), and have success/failure callbacks enable it if the username is fine.
What would be a decent way to make a synchronous validator?
A side question would be if there is a better way to set the scope of the validation handler to "this", as opposed to passing me (this one works too, this is just to make sure I am not missing a better way).

This is a snippet of the create-site form. It does the same and checks if the shortname is already present and shows an error if it's taken.
createSiteForm.doBeforeFormSubmit =
{
fn: function()
{
var formEl = Dom.get(this.id + "-form");
formEl.attributes.action.nodeValue = Alfresco.constants.URL_SERVICECONTEXT + "modules/create-site";
this.widgets.okButton.set("disabled", true);
this.widgets.cancelButton.set("disabled", true);
// Site access
var siteVisibility = "PUBLIC";
if (this.widgets.isPublic.checked)
{
if (this.widgets.isModerated.checked)
{
siteVisibility = "MODERATED";
}
}
else
{
siteVisibility = "PRIVATE";
}
this.widgets.siteVisibility.value = siteVisibility;
this.widgets.panel.hide();
this.widgets.feedbackMessage = Alfresco.util.PopupManager.displayMessage(
{
text: Alfresco.util.message("message.creating", this.name),
spanClass: "wait",
displayTime: 0
});
},
obj: null,
scope: this
};
// Submit as an ajax submit (not leave the page), in json format
createSiteForm.setAJAXSubmit(true,
{
successCallback:
{
fn: this.onCreateSiteSuccess,
scope: this
},
failureCallback:
{
fn: this.onCreateSiteFailure,
scope: this
}
});
So this is the client side JavaScript file create-site.js which does the ajax call just before the submit.

My workaround solution is this:
Add a custom validation to the form, and pass the "var me = this" for the validator function to get the scope.
this.widgets.saveForm.addValidation(Dom.get(this.id + "-username"), this.validateUsername, me, "blur", this.msg("validation-hint.username-taken"));
Add a validation handler function which will a. disable the submit button and set the "checking" CSS class to the appropriate form for the user to have feedback. Code snip:
validateUsername: function Reg_validateUsername(el, me) {
var val = this.fieldId.value;
var username = trim(val);
me.widgets.registerButton.set("disabled", true);
Dom.addClass(me.id + "-username-img", "checking");
Dom.removeClass(me.id + "-username-img", "hidden");
Alfresco.util.Ajax.request({
url: "/alfresco/service/slingshot/register/username-available?username=" + username
, method: "GET"
, successCallback: {
fn: me.onUsernameAvailable
, scope: me
}
, failureCallback: {
fn: me.onUsernameTaken
, scope: me
}
});
Write a backend webscript username-available.get that will return success only if the username is free (and valid, even though I have another validator for that).
onUsernameAvailable will set the submit button to enabled again and run validation again (because of other fields) and change the CSS class to "valid".
onUsernameTaken will set the CSS class to "taken".
That's how I ended up doing this.

Related

Trigger input change on website using Angular with Chrome Extension content script

I'm creating a content-script which will populate
https://www.moneysupermarket.com/shop/car-insurance/questionset/#?step=highimpactquestions
with predefined data.
But I have problem with first step, the first field on the page, registration number.
I'm using jQuery inside my script, and I can set input's value to what I want using
$('input#regnumber').val(data['car_reg'])
But when I simulate click on find car button it seems like for website that field is empty, as 'Please enter a registration number' warning appears instead of showing car info.
I looked through the code, and saw that website uses angular, so I guess it has something to do with its binding.
I tried triggering change,keyup,input events on the $('input#regnumber') but it doesn't change anything.
EDIT:
Also tried with
$('input#regnumber').val(data['car_reg']).dispatchEvent(new Event("input", { bubbles: true }));
without success
AngularJS inputs use an ng-model directive to connect an input with the model:
<input id="regnumber" type="text" ng-model="formData.item" />
To set the model from outside the framework use:
var elem = $('input#regnumber');
var scope = angular.element(elem).scope();
scope.$apply(function() {
scope.formData.item = value;
});
For more information, see
AngularJS angular.element API Reference
AngularJS scope API Reference ($apply)
AngularJS Developer Guide - Scope (Integration with the Browser Event Loop)
You are getting validation error, because maybe the value of data['car_reg'] is undefined. If the data is an array of objects, then use like data[0].car_reg. Use console to verify the value.
I found a way finally.. this is what I did in my background script for the tab where this page is loaded:
setTimeout(function() {
chrome.tabs.executeScript(tab.id, { 'file': '/jquery-2.2.4.min.js', 'runAt': 'document_start' })
chrome.tabs.executeScript(tab.id, { 'file': '/angular.min.js' })
setTimeout(function() {
chrome.tabs.executeScript(tab.id, { 'code': 'angular.reloadWithDebugInfo();' });
setTimeout(function() {
chrome.tabs.executeScript(tab.id, { 'file': '/jquery-2.2.4.min.js' });
chrome.tabs.executeScript(tab.id, { 'file': '/formfiller.js' })
}, 5000);
}, 5000);
}, 8000)
And in my formfiller.js I do this in order to fill that field:
var scrpt = document.createElement('script');
scrpt.innerText = "var scp = angular.element($('#regnumber')).scope();scp.regnumber = '12345';scp.$apply(); $('.car-registration__button').click();"
document.head.appendChild(scrpt);
scrpt.onload = function() {
scrpt.parentNode.removeChild(scrpt);
};

$.when(ajaxCall) Versus success

I have the following code inside my asp.net MVC view:-
$('body').on("click", "#transferserver,#transfersd,#transferfirewall,#transferrouter,#transferswitch", function () {
$("#showoptions").dialog({
title: "Assign Selected Records To New Rack",
width: 'auto', // overcomes width:'auto' and maxWidth bug
maxWidth: 600,
height: 'auto',
modal: true,
fluid: true, //new option
resizable: false
});
var ajaxCall = $.ajax({
url: '#Url.Content("~/Rack/ShowTransferSelectedDialog")',
data: {
rackfrom: "#Model.Rack.ITsysRackID",
assettype: $(this).attr('id')//get the id for the clciked link, so that the submit button will call the associted action method.
},
type: 'get',
success: function (html) {
$('#showoptions').html(html);
$("#showoptions").dialog("show"); //This could also be dialog("open") depending on the version of jquery ui.
}
});
$.when(ajaxCall)
.then(function (data) { showDialog(data); });
});
I have the following questions:
What are the differences between the $when(ajaxcall) and on success ?
In my above code if I remove the $.when(ajaxCall) the dialog box will still be displayed . so is there any need to have it?
Thanks
EDIT
But one benefit i find for using $.when(ajaxCall) is that i have defined a custom authorization attribute as follow:-
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var viewResult = new JsonResult();
viewResult.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
viewResult.Data = (new { IsSuccess = "Unauthorized", description = "Sorry, you do not have the required permission to perform this action." });
filterContext.Result = viewResult;
}
}
}
currently if the user clicks on the link to display the dialog box and he is not authorize to do so , he will receive a jAlert containing the unauthorized message as follow:-
![enter image description here][1]
but if i remove the $.when(ajaxCall), then the user will not receive the unauthorization message , and instead the dialog will be blank .. so can anyone advice ?
1) This is the definition of jQuery when
Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events.
It make no sense to use it for a single ajax call, you want to use it for 2 or more so you wait for them to finish before executing some code.
2) I don't know what showDialog does but your dialog already shows because in your success handler you have $("#showoptions").dialog("show");. Again, no need at all to use when here

Manually create a computed property

Here is the problem. I'm currently creating a mixin to handle form validation. The problem is that I want to create in the init method a computed property isFormValid which is a 'computed.and' property of the 'is<fieldName>Valid' other properties.
I can create it but then it never updates. I guess I need to add observers but maybe someone will have a better solution ?
EDIT
Here is some clarification.
My controller got that property:
App.FormViewController = Ember.Controller.extend(App.ValidatorMixin, {
validations: {
field1: {
errLvl: App.Validation.ErrLvl.ERROR,
type: App.Validation.Type.TEXT,
pattern: /^\d{6}$/,
message: 'Error message'
},
field2: {
//somecode
}
}
});
The mixin is define as follow:
App.ValidatorMixin = Ember.Mixin.create({
init: function() {
this._super();
var self = this;
Ember.keys(this.validations).forEach(function(prop) {
self.set('is' + prop.capitalize() + 'Valid', false); //Is changed when the field is valid
});
}
});
The isFormValid property should be an Ember.computed.and of all these is<fieldName>Valid
EDIT2
Every input on the form are defined by the input helper and this mixin:
App.Mixin.ValidatableInput = Ember.Mixin.create({
focusOut: function() {
this.validate();
},
//Do the validation
validate: function() {
//I'm currently moving that part to the controller because it's part
//of the logic but it was easier for a start to write it here
//We update the is<fieldName>Valid property
this.get('parentView.controller').set('is' + this.get('name').capitalize() + 'Valid', !hasError);
//Then some DOM manipulation to attach the error message
}
});
And finally my view looks like that
{{view App.CustomTextField name="field1" value=field1}}
{{view view.buttonCreate disabled=isFormInvalid}}
From within your init method you can setup a computed property using defineProperty.
Ember.defineProperty(myObject, 'myProperty', Ember.computed(function computed() {
return 'myValue';
}).property('myObserver'));
Would this work for you? (expanding off #Wildhoney, I can't comment). You can specify multiple properties if you have several:
Ember.defineProperty(myObject, 'myProperty', Ember.computed(function computed() {
return 'myValue';
}).property('myObserver.#each.myObservedProperty'));
I've had a bit of luck using jQuery Validate in Ember by triggering the .validate() call and rules on didInsertElement for your form view, and then a .valid() check when you focus out of each Ember.TextField. I would highly recommend it for form validation.

How to destroy jQuery validation and any further attempts at submission?

I have the following code for the jQuery validation plugin.... basically on submit, I slide everything up & fade it out... the only problem is, if you're quick enough, you can submit the form multiple times. How can I make sure that any presses of the enter key on the input (or clicks on that submit button) will not submit further?
Basically what happens, is that the form will load up the url in the action attribute when no javascript is there, so purely unbinding doesn't work... (even if it did, I can always press enter / click fast enough to get it to do a couple more....)
jQuery('.desired').validate({
debug: true,
rules: {
email: {
required: true,
email: true
}
},
wrapper: "div",
messages: {
email: "Please enter a valid email address."
},
errorPlacement: function(error, element) {
error.hide().appendTo(element.parent()).hide().slideDown();
},
errorClass: 'help-text',
submitHandler: function(form) {
var $ = jQuery;
var url = $(form).attr('action');
var query = $(form).serialize();
$.ajax({
url: url,
type: "POST",
data: query,
success: function() {
$("<p class='help-jquery'><b>Thanks</b>")
.insertAfter(jQuery(form))
.css('height', function(i,h) {
$(this).hide()
return h;
});
// $(form).css('height', $(form).height());
$(form).slideUp('slow');
$(form).fadeOut({ queue: false, duration: 'slow' });
// $('.help-jquery').fadeIn('slow');
$('.help-jquery')
.css('opacity', 0)
.slideDown('fast')
.animate(
{ opacity: 1 },
{ queue: false, duration: 'slow' }
);
//$('.desired submit').click(function(){
//return false;
//});
},
error: function() {
console.log('Error: did not submit properly');
},
complete: function(e) {
//$('.desired').unbind('submit');
//e.preventDefault();
//return false;
}
});
},
success: function(error,element){
},
highlight: function(error){
// This empty function needs to be here for this to work
}
});
You're on the right track with unbind(), it solves half of your problem because it will effectively suppress validation on form submission.
To solve the second half, you only have to neuter the form's submit event after unbinding, by registering the appropriate handler:
$(form).unbind("submit").submit(function() {
return false;
});
Use a boolean variable, like this:
var didValidate = false;
if(!didValidate) {
jQuery('.desired').validate({
//... your code here
});
}
In your AJAX success function, set didValidate to true.
Have a variable that is 1 or 0. before you do any validation, check that the variable is equal to 0. If it isn't 0, do nothing. If it is, continue. Once the validation passes, set the variable to 1 so that the validation cannot occur again.
Use event namespaces:
The name following the '.' let's you target handlers more specifically.
This at the top of the submit handler:
$(form).bind('submit.temp_submit_hold', function(e){
e.preventDefault();
e.stopPropagation(); //added this in case the plugin's handler func uses bubbling
return false;
} );
This at the top of the complete callback for the ajax call:
$('.desired').unbind('submit.temp_submit_hold');
A little more explanation after seeing your comments in complete. The time to preventDefault is immediately after your onsubmit handler starts working. On complete is when you want to enable it again. So we bind a func that stops it with prevent default and then unbind it to toggle behavior. I also added stopPropagation in case the plugin uses delegation/bubbling.
Probably the simplest is to add something like this to your submithandler
submitHandler: function(form) {
var $ = jQuery;
if ( $.data(form, "submit") ) > "" return false;
$.data(form, "submit", "in progress");
// .. the rest of your handler
}
If you want to allow the form to submitted again later, remove the .data() on success.
Use the .destroy() method.
This question and its answers are quite old. So since that time, the developer has added a .destroy() method to detach the validation plugin from the form.
Destroys this instance of validator freeing up resources and unregistering events.
// Initialize the validation plugin on your form
var validator = $("#myform").validate();
// Remove validation from your form
validator.destroy();
// After this point `#myform` is back to its original state without validation.
https://jqueryvalidation.org/Validator.destroy/
To stop multiple submissions, disable the submit button within the plugin's submitHandler function. (The submitHandler only fires when the form is valid and you've already clicked the submit button.)
submitHandler: function(form) {
// validation & submit success, so disable submit button
$("#yourSubmitButton").prop('disabled', true);
// your ajax code here
return false;
}

How to place a jQuery snippet into a global file

I have a JavaScript file here http://www.problemio.com/js/problemio.js and I am trying to place some jQuery code into it that looks like this:
$(document).ready(function()
{
queue = new Object;
queue.login = false;
var $dialog = $('#loginpopup')
.dialog({
autoOpen: false,
title: 'Login Dialog'
});
var $problemId = $('#theProblemId', '#loginpopup');
$("#newprofile").click(function ()
{
$("#login_div").hide();
$("#newprofileform").show();
});
// Called right away after someone clicks on the vote up link
$('.vote_up').click(function()
{
var problem_id = $(this).attr("data-problem_id");
queue.voteUp = $(this).attr('problem_id');
voteUp(problem_id);
//Return false to prevent page navigation
return false;
});
var voteUp = function(problem_id)
{
alert ("In vote up function, problem_id: " + problem_id );
queue.voteUp = problem_id;
var dataString = 'problem_id=' + problem_id + '&vote=+';
if ( queue.login = false)
{
// Call the ajax to try to log in...or the dialog box to log in. requireLogin()
}
else
{
// The person is actually logged in so lets have him vote
$.ajax({
type: "POST",
url: "/problems/vote.php",
dataType: "json",
data: dataString,
success: function(data)
{
alert ("vote success, data: " + data);
// Try to update the vote count on the page
//$('p').each(function()
//{
//on each paragraph in the page:
// $(this).find('span').each()
// {
//find each span within the paragraph being iterated over
// }
//}
},
error : function(data)
{
alert ("vote error");
errorMessage = data.responseText;
if ( errorMessage == "not_logged_in" )
{
//set the current problem id to the one within the dialog
$problemId.val(problem_id);
// Try to create the popup that asks user to log in.
$dialog.dialog('open');
alert ("after dialog was open");
// prevent the default action, e.g., following a link
return false;
}
else
{
alert ("not");
}
} // End of error case
}
}); // Closing AJAX call.
};
$('.vote_down').click(function()
{
alert("down");
problem_id = $(this).attr("data-problem_id");
var dataString = 'problem_id='+ problem_id + '&vote=-';
//Return false to prevent page navigation
return false;
});
$('#loginButton', '#loginpopup').click(function()
{
alert("in login button fnction");
$.ajax({
url:'url to do the login',
success:function() {
//now call cote up
voteUp($problemId.val());
}
});
});
});
</script>
There are two reasons why I am trying to do that:
1) I am guessing this is just good practice (hopefully it will be easier to keep track of my global variables, etc.
2) More importantly, I am trying to call the voteUp(someId) function in the original code from the problemio.js file, and I am getting an error that it is an undefined function, so I figured I'd have better luck calling that function if it was in a global scope. Am I correct in my approach?
So can I just copy/paste the code I placed into this question into the problemio.js file, or do I have to remove certain parts of it like the opening/closing tags? What about the document.ready() function? Should I just have one of those in the global file? Or should I have multiple of them and that won't hurt?
Thanks!!
1) I am guessing this is just good practice (hopefully it will be
easier to keep track of my global variables, etc.
Yes and no, you now have your 'global' variables in one spot but the chances that you're going to collide with 'Global' variables (ie those defined by the browser) have increased 100% :)
For example say you decided to have a variable called location, as soon as you give that variable a value the browser decides to fly off to another URL because location is a reserved word for redirecting.
The solution to this is to use namespacing, as described here
2) More importantly, I am trying to call the voteUp(someId) function
in the original code from the problemio.js file, and I am getting an
error that it is an undefined function, so I figured I'd have better
luck calling that function if it was in a global scope. Am I correct
in my approach?
Here's an example using namespacing that will call the voteUp function:
(function($) {
var myApp = {};
$('.vote_up').click(function(e) {
e.preventDefault();
myApp.voteUp();
});
myApp.voteUp = function() {
console.log("vote!");
}
})(jQuery);
What about the document.ready() function? Should I just have one of
those in the global file? Or should I have multiple of them and that
won't hurt?
You can have as many document.ready listeners as you need, you are not overriding document.ready you are listening for that event to fire and then defining what will happen. You could even have them in separate javascript files.
Be sure your page is finding the jquery file BEFORE this file is included in the page. If jquery is not there first you will get function not defined. Otherwise, you might have other things conflicting with your jquery, I would look into jquery noConflict.
var j = jQuery.noConflict();
as seen here:
http://api.jquery.com/jQuery.noConflict/
Happy haxin
_wryteowl
Extending what KreeK has already provided: there's no need to define your "myApp" within the document ready function. Without testing, I don't know off the top of my head if doing so is a potential source for scope issues. However, I CAN say that the pattern below will not have scope problems. If this doesn't work, the undefined is possibly a script-loading issue (loading in the right order, for example) rather than scope.
var myApp = myApp || {}; // just adds extra insurance, making sure "myApp" isn't taken
myApp.voteUp = function() {
console.log("vote!");
}
$(function() { // or whatever syntax you prefer for document ready
$('.vote_up').click(function(e) {
e.preventDefault();
myApp.voteUp();
});
});

Categories