Concept - Creating client side validation from PHP object - javascript

I am making my own MVC framework and I was thinking of a way to do "automatic" client side validation controller.
Among other functionalities, my forms, elements and validators are objects that work together somewhat like this (inside the form object):
$this->addElement('text', 'myInput');
$this->elements['myInput']->addValidators(array
'length' => array('min' => 5, 'max' => 10),
'number' => array('decimals' => 0)
));
In the example above, I created a text input named 'myInput' whose value, according to the validators I added:
Must be >= than 5 characters
Must be <= than 10 characters
Must be a number
Must have no decimals (int only)
When I get the form submission and call a validation function, all works well on the server side. However, what bothered me was having to redo validation on the client side manually. I dislike having to duplicate the same functionality so I came up with a way to create my client side validation from the PHP form object that is already there.
It boils down to having JS validator functions that have the same functionality as the PHP validators and calling a getClientValidatiors() function on elements to create the appropriate <script> in the body which attaches the JS events.
NOTE: Please ignore JS errors, I wrote this as a concept and didn't test anything just yet.
The JS validator functions would work like this:
function lengthValidator(options, value, id){
//Validate according to the options. Return true if valid or false otherwise as well as calling printError function with the message and the id
}
function numberValidator(options, value, id){
//Validate according to the options. Return true if valid or false otherwise as well as calling printError function with the message and the id
}
function printError(error, id){
//Might add more functionality later
document.getElementById(id).innerHTML = error;
}
For example, this is what it would look like in the view:
<?php echo $this->form->elements['myInput]; //The HTML ?>
<?php echo $this->form->elements['myInput]->getClientValidators(); //The JS ?>
Prior to form submission, the result would look like:
<input type="text" name="myInput" id="myInput"/>
<span class="error" id="myInput-error"></span>
<script>
document.getElementById('myInput').addEventListener('blur', function(e){
var value = e.value;
var id = e.id + '-error';
if(lengthValidator({min:5, max:10}, value, id) != true){
return;
}
if(numberValidator({decimals:0}, value, id) != true){
return;
}
});
</script>
I'm looking for a thumbs up or suggestions on how this could work with another technique. If you have any thoughts, I'd like to hear them!

Consider writing the validation specification in such a way that you can automatically validate in JavaScript and PHP.
$input_schema = array(
"foo" => array(
"type" => "number",
"decimals" => 0,
"length" => array(
"min" => 5,
"max' => 10
)
)
);
Then in JS you can do:
var input_schema = <?php echo json_encode($input_schema);?>;
function validate_input(form_values) {
for (var key in input_schema) {
validate_property(input_schema[key], form_values[key]);
}
}
function validate_property(schema_property, value) {
if (schema_property.type === "number") {
validate_number(schema_property, value); // etc
}
}
And you can make a similar implementation in PHP.

Related

Preventing higher order array methods from throwing an error

Is there a way to prevent errors from being thrown while filtering?
The below function sometimes fails at conversationMember.Name.toLowerCase() when there is no conversationMember.
If it helps, this is also a computed property in a Vue application.
Should you need more information, please just ask!
filteredConversations() {
var self = this;
var filteredConvos = self.conversations;
filteredConvos = filteredConvos.filter(conversation => {
return conversation.MembershipData.some(conversationMember => {
return conversationMember.Name.toLowerCase().includes(
self.conversationSearchTerm.toLowerCase()
);
});
});
return filteredConvos;
},
This doesn't seem to have anything to do with arrays.
From your code I understand conversationMember.Name is supposed to be a string (because you're calling .toLowerCase() on it), which means incudes here is not Array.prototype.includes, but String.prototype.includes, especially since self.conversationSearchTerm seems to also be a string (you're also calling .toLowerCase() on it).
So, the problem is you're using includes on something that should be a string but is not. The simple fix is to default it to an empty string when it's falsy:
return (conversationMember.Name || '').toLowerCase().includes(
(self.conversationSearchTerm || '').toLowerCase()
);
As a side note, you don't need the var self = this;. this is available inside your filter since the filter is an arrow function. So your function (I'm guessing it's a computed but it can as well be a method) could look like this:
filteredConversations() {
return this.conversations.filter(c =>
c.MembershipData.some(md =>
(md.Name || '').toLowerCase().includes(
(this.conversationSearchTerm || '').toLowerCase()
)
)
);
}
One last note: this will still fail if any of your conversations does not have a MembershipData holding an array. To get around that, you could default it to an empty array on the fly:
...
(c.MembershipData || []).some(md =>
...
As expected, any conversation without an array in MembershipData will be filtered out by the function (not included in the result) - because .some(condition) will return false when called on an empty array.

How to Combine Multiple Return Functions (JavaScript)

I am learning JavaScript so that I can implement Google Tag Manager. I have a list of paths that I would like GTM to rewrite to something friendlier like so:
function() {
return document.location.pathname.indexOf('/l/138281/2016-06-07/dy383') > -1 ? 'Test Success' : undefined;
}
function() {
return document.location.pathname.indexOf('/l/138281/2016-04-03/55z63') > -1 ? 'SPP Contact Success' : undefined;
I'm just not sure how to combine these returns into one function (I currently have about 30 URLs to rewrite). I imagine I can use if/else, but advice would be quite lovely.
--edit--
URL Path Rewrite To
/test-638-jsj /test-success
/spp-zxcv-765 /spp-contact-success
/foo-asdf-123 /foo
/foo-bar-987 /foo-bar
The return function mentioned above does this beautifully for an individual link. I just want to be able to rewrite a series of URLs in one function (or however it makes sense to do this most specifically). Hopefully that helps clarify.
Thanks!
It is always a great idea to structure your code: separate abstract functionality from the specific problem.
What you are actually doing is scannins strings for occurences of keywords and returning specific values if such a keyword has been found.
Therefore, you need a function performing the above computation and a JavaScript datastructure holding your keywords and their values (= Object):
// Return patterns[key] if any key is found in string, else return string:
function match(string, patterns) {
for (key of Object.keys(patterns)) {
if (string.indexOf(key) > -1) return patterns[key];
}
return string;
}
var patterns = {
'/l/138281/2016-06-07/dy383': 'Test Success',
'/l/138281/2016-04-03/55z63': 'SPP Contact Success'
}
console.log(match('/l/138281/2016-06-07/dy383', patterns)); // "Test Success"
console.log(match('/doesnotexist', patterns)); // "/doesnotexist"
console.log(match(document.location.pathname, patterns));

Javascript: dictionary method(property) mapping

tl;dr; Is there a way to circumvent the cross browser implementations of functions by storing them in dictionary by same name?
I got this idea from this snippet:
function styleThis ( element, styleType, styleTypeValue) {
// styleType, styleTypeValue must be strings
element.style[styleType] = styleTypeValue;
}
So basic idea is like this :
function _dasMethod ( element, method, methodValue){
//method is String, methodValue is function stored in variable.
element[method] = methodValue;
}
The idea is to use dictionary style mapping to map specific methods according to the Browser type and input parameters.
So instead of :
function IE_ver8( element, argument1, argument 2 ... argument n ){
element.property = some_IE_function(argument 1 ... argument n ); //
}
You could preload specific methods to elements before hand, and call them by their name:
function IE_check_size(){}
function CHROME_check_size(){}
function OPERA_check_size(){}
. . .
function _dasMethod( element, MethodName, method_var ){
element[MethodName] = method_var;
}
//Which could be used like:
If( Opera){ _dasMethod(div, "check_size", OPERA_check_size); }
If( Chrome){ _dasMethod(div, "check_size", CHROME_check_size); }
If( IE ){ _dasMethod(div, "check_size", E_check_size); }
div["check_size"](a,b,c,d);
Is there a way to implement similar action as c++ preprocessor directives in javascript for each element via some sort of dictionary?
I am using raw coding with no external libraries, so i would be grateful if you don't opt-out by recommending external library.

Form validation using filter_var

I am working on an assignment using javascript. I am attempting to use filter_var to test if my form has empty fields but I cannot get it to run.
I'm not confident that my Syntax is right.
function validate_reg_form(form)
{
var form = "";
if (filter_var(form, FILTER_VALIDATE_BOOLEAN))
{
alert("validate_reg_form() not yet implemented");//remove this
}
}
filter_var is a PHP function. It does not exist in JavaScript. The exact equivalent is:
var truthyBooleanStrings = ['1', 'true', 'on', 'yes'];
function validateRegForm(form) {
var form = '';
if (truthyBooleanStrings.indexOf(form) !== -1) {
// …
}
}
Maybe that’s not the right approach for
test if my form has empty fields
, though. If you want to test that something is not an empty string, just use !== '' or the implicit truthiness of non-empty strings, as in
if (form) {
// Not empty!
}

Getting form controls from FormController

I need a way to loop through the registered controls of an AngularJS form. Essentially, I'm trying to get all the $dirty controls, but there's no array of the controls (the FormController has a number of different properties/functions in addition to containing the controls themselves - each as its' own object).
I've been looking at the source code, and I see that there is a controls array in the FormController that is exactly the array I'm looking for. Is there a way to get access to this value, or extend the FormController to include a function that returns this controls array?
Edit: Plnkr demo
Also, I realized that technically I could check the first character in the key string for "$", but I'd like to avoid that in case the FormController/directive changes in a future version of Angular.
Edit 2: Another clarification: My goal in all of this is to be able to determine which specific fields are $dirty, whether by looping through the entire list of controls (not including the $dirty, $invalid, $error, $name, and other properties that live in the Form object as it is) or by extending the FormController and creating a function that returns only the controls which are currently dirty (and not equal to their starting values)
Edit 3: The solution I'm looking for needs to be applicable to forms/models of different structures. The models on the scope are generated via AJAX, so their structure is already set (I'd like to avoid having to hardcode a new structure for all the data I'm already receiving via AJAX). Also, I'm looking to use this form submission process across multiple forms/models, and each of them have differing JSON structures - as they apply to different entities in our Object Model. This is why I've chosen to ask for a way to get access to the controls object in the FormController (I'll post the code from FormController below), because it's the only place where I can get a flat array of all of my fields.
function FormController(element, attrs) {
var form = this,
parentForm = element.parent().controller('form') || nullFormCtrl,
invalidCount = 0, // used to easily determine if we are valid
errors = form.$error = {},
controls = [];
// init state
form.$name = attrs.name || attrs.ngForm;
form.$dirty = false;
form.$pristine = true;
form.$valid = true;
form.$invalid = false;
parentForm.$addControl(form);
// Setup initial state of the control
element.addClass(PRISTINE_CLASS);
toggleValidCss(true);
// convenience method for easy toggling of classes
function toggleValidCss(isValid, validationErrorKey) {
validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
element.
removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
}
/**
* #ngdoc function
* #name ng.directive:form.FormController#$addControl
* #methodOf ng.directive:form.FormController
*
* #description
* Register a control with the form.
*
* Input elements using ngModelController do this automatically when they are linked.
*/
form.$addControl = function(control) {
controls.push(control);
if (control.$name && !form.hasOwnProperty(control.$name)) {
form[control.$name] = control;
}
};
/**
* #ngdoc function
* #name ng.directive:form.FormController#$removeControl
* #methodOf ng.directive:form.FormController
*
* #description
* Deregister a control from the form.
*
* Input elements using ngModelController do this automatically when they are destroyed.
*/
form.$removeControl = function(control) {
if (control.$name && form[control.$name] === control) {
delete form[control.$name];
}
forEach(errors, function(queue, validationToken) {
form.$setValidity(validationToken, true, control);
});
arrayRemove(controls, control);
};
// Removed extra code
}
As you can see, the form itself has the controls array privately available. I'm wondering if there's a way for me to extend the FormController so I can make that object public? Or create a public function so I can at least view the private array?
For a direct solution to the question, modify #lombardo's answer like so;
var dirtyFormControls = [];
var myForm = $scope.myForm;
angular.forEach(myForm, function(value, key) {
if (typeof value === 'object' && value.hasOwnProperty('$modelValue') && value.$dirty)
dirtyFormControls.push(value)
});
The array 'dirtyFormControls' will then contain the form controls that are dirty.
You can also use this trick to show error messages on form submission for 'Required' validations and all others. In your submit() function you will do something like;
if (form.$invalid) {
form.$setDirty();
angular.forEach(form, function(value, key) {
if (typeof value === 'object' && value.hasOwnProperty('$modelValue'))
value.$setDirty();
});
//show user error summary at top of form.
$('html, body').animate({
scrollTop: $("#myForm").offset().top
}, 1000);
return;
}
And in your form you will show error messages with
<span ng-messages="myForm['subject-' + $index].$error" ng-show="myForm['subject-' + $index].$dirty" class="has-error">
<span ng-message="required">Course subject is required.</span>
</span>
The above solution is useful when you have dynamically generated controls using 'ng-repeat' or something similar.
You can use the following code to iterate the controls:
var data = {};
angular.forEach(myForm, function (value, key) {
if (value.hasOwnProperty('$modelValue'))
data[key] = value.$modelValue;
});
try simply with from within your controller:
$scope.checkForm = function(myFormName){
console.log(myFormName.$invalid);
}
UPDATE:
<div ng-controller="MyController">
<form name="form" class="css-form" novalidate>
<input type="text" ng-model="user.uname" name="uname" required /><br />
<input type="text" ng-model="user.usurname" name="usurname" required /><br />
<button ng-click="update(form)">SAVE</button>
</form>
</div>
app.controller('MyController',function($scope){
$scope.user = {};
$scope.update = function (form){
var editedFields = [];
angular.forEach($scope.user, function(value, key){
if(form[key].$dirty){
this.push(key + ': ' + value);
}
}, editedFields);
console.log(editedFields);
}
});
I've come up with a somewhat decent solution, but it still isn't what I was looking for. I've salvaged some code from another problem involving creating JSON objects from strings, and come up with the following:
Essentially I'm naming my fields in the same way they're tied to the model, and then building a new object for submission when the form_submit is called.
Plnkr demo
In the demo, if you change either of the form fields, then hit submit, you'll see the object pop up with only the dirty values.

Categories