jQuery settings for form validation - javascript

I'm building a basic jQuery form validator. The code below is just to validate the name. I have several functions to validate mail, password, credit card, date etc. I want the user to be able to easily edit the error messages and classes. Let's say the user want to change the name of the class "error" or "success". Now the user needs to search and replace everywhere in the code where it says "error" or "success".
Is there any way to use an array or object at the top of the code to edit the error messages for each if-statement, and apply the classes?
Something like what's shown here: http://learn.jquery.com/plugins/basic-plugin-creation/#accepting-options (but at the top of the code, outside the function). Or could I use the same method but type like this (outside the function, at the top of the file)?
var settings = $.extend({
color: "#556b2f",
errorClass: "error"
}, options );
I also want the user to be able to add ID's and classes directly to the form element and type "true" in the setting "required" so the form element will be required. Is there some good way to do this?
var name = $("[data-name]");
var nameMsg = $("#nameMsg");
name.on("blur", function() { $(this).validateName() });
$.fn.validateName = function() {
if(name.val().length < 5) {
name.removeClass("success")
.addClass("error");
nameMsg.removeClass("success")
.addClass("error")
.text("Not that short, at least 5 characters.");
return false;
} else {
name.removeClass("error")
.addClass("success");
nameMsg.removeClass("error")
.addClass("success")
.html("Correct");
return true;
}
}
Solution
var defaults = {
errorClass: "error",
successClass: "success",
successMessage: "<i class='fa fa-check-circle'></i>",
errorMessageName: "Not that short, at least 5 characters.",
};
var settings = $.extend({}, defaults);
Special thanks to Martin Adámek for his help!

$.extend just creates new object merged with values from both arguments, you can use it to extend default options object with user given (anywhere you want).
I'm not sure if I understand you correctly, is this what you want?
var defaults = { // maybe you do not need extend at all?
errorClass: "error",
successClass: "success",
messageError: "Not that short, at least 5 characters.",
messageCorrect: "Correct"
};
var name = $("[data-name]");
var nameMsg = $("#nameMsg");
name.on("blur", function() { $(this).validateName() });
$.fn.validateName = function() {
var userOpts = {...}; // you can store it eg in data attribute on the element
// extend options with user given values
options = $.extend(defauts, userOpts);
if(name.val().length < 5) {
name.removeClass(options.successClass)
.addClass(options.errorClass);
nameMsg.removeClass(options.successClass)
.addClass(options.errorClass)
.text(options.messageError);
return false;
} else {
name.removeClass(options.errorClass)
.addClass(options.successClass);
nameMsg.removeClass(options.errorClass)
.addClass(options.successClass)
.html(options.messageCorrect);
return true;
}
}
The user given options would be stored as a JSON string (to allow merging with default options), so you would need to call JSON.parse() on in first.
EDIT:
Example of options JSON object in data attribute:
HTML:
<input type="text" data-options='{"messageCorrect": "Bingo! That is the right choice!", "errorClass": "my-error-class"}' ...>
and then in $.fn.validateName() you will do simply:
var userOpts = JSON.parse($(this).data('options'));

Related

Store click value to use later on

Basically, I have an appointment form which is broken down into panels.
Step 1 - if a user clicks london (#Store1) then hide Sunday and Monday from the calendar in panel 5.
Basically, I want to store this click so that when the user gets to the calendar panel, it will know not to show Sunday and Monday
$('#store1').click(function () {
var $store1 = $(this).data('clicked', true);
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});
after I have captured this in a var I then want to run it when the calendar displays.
function ReloadPanel(panel) {
return new Promise(function (resolve, reject, Store1) {
console.log(panel);
console.log("finalpanel");
panel.nextAll('.panel').find('.panel-updater').empty();
panel.nextAll('.panel').find('.panel-title').addClass('collapsed');
panel.nextAll('.panel').find('.panel-collapse').removeClass('in');
var panelUpdater = $('.panel-updater:eq(0)', panel),
panelUrl = panelUpdater.data('url');
if (panelUpdater.length) {
var formData = panelUpdater.parents("form").serializeObject();
panelUpdater.addClass('panel-updater--loading');
panelUpdater.load(panelUrl, formData, function (response, status) {
panelUpdater.removeClass('panel-updater--loading');
if (status == "error") {
reject("Panel reload failed");
} else {
resolve("Panel reloaded");
}
});
} else {
resolve("no reloader");
}
});
}
I'm not sure if this is even written right, so any help or suggestions would be great
Thanks in advance
Don't think of it as "storing a click". Instead, consider your clickable elements as having some sort of data values and you store the selected value. From this value you can derive changes to the UI.
For example, consider some clickable elements with values:
<button type="button" class="store-button" data-store-id="1">London</button>
<button type="button" class="store-button" data-store-id="2">Paris</button>
<button type="button" class="store-button" data-store-id="3">Madrid</button>
You have multiple "store" buttons. Rather than bind a click event to each individually and customize the UI for each click event, create a single generic one which captures the clicked value. Something like:
let selectedStore = -1;
$('.store-button').on('click', function () {
selectedStore = $(this).data('store-id');
});
Now anywhere that you can access the selectedStore variable can know the currently selected store. Presumably you have some data structure which can then be used to determine what "days" to show/hide? For example, suppose you have a list of "stores" each with valid "days":
let stores = [
{ id: 1, name: 'London', days: [2,3,4,5,6] },
// etc.
];
And your "days" buttons have their corresponding day ID values:
<button type="button" class="day-button" data-day-id="1">Sunday</button>
<button type="button" class="day-button" data-day-id="2">Monday</button>
<!--- etc. --->
You can now use the data you have to derive which buttons to show/hide. Perhaps something like this:
$('.day-button').hide();
for (let i in stores) {
if (stores[i].id === selectedStore) {
for (let j in stores[i].days) {
$('.day-button[data-day-id="' + stores[i].days[j] + '"]').show();
}
break;
}
}
There are a variety of ways to do it, much of which may depend on the overall structure and flow of your UX. If you need to persist the data across multiple pages (your use of the word "panels" implies more of a single-page setup, but that may not necessarily be the case) then you can also use local storage to persist things like selectedStore between page contexts.
But ultimately it just comes down to structuring your data, associating your UI elements with that data, and performing logic based on that data to manipulate those UI elements. Basically, instead of manipulating UI elements based only on UI interactions, you should update your data (even if it's just in-memory variables) based on UI interactions and then update your UI based on your data.
you can use the local storage for that and then you can get your value from anywhere.
Set your value
localStorage.setItem("store1", JSON.stringify(true))
Get you value then you can use it anywhere:
JSON.parse(localStorage.getItem("store1"))
Example:
$('#store1').click(function() {
var $store1 = $(this).data('clicked', true);
localStorage.setItem("store1", JSON.stringify(true))
console.log("store 1 clicked");
$('.Sunday').hide();
$('.Monday').hide();
});

Custom control - data binding not working

I am currently trying to extend a sap.m.Input field to be able to style and extend the label placement.
The rendering works fine, but somehow the data-binding gets lost in the process and i am unsure why that is. This is my control:
sap.ui.define([
'sap/m/Input',
], function(Input) {
'use strict';
return Input.extend('one.sj.control.BhTextInput', {
metadata: {
properties: {
label: {
type: 'string',
},
},
aggregations: {
icon: {
type: 'sap.ui.core.Icon',
multiple: false,
visibility: 'public',
},
},
},
renderer: function(oRM, oControl) {
oRM.write('<div class="formControl">');
oRM.write('<input placeholder="'+oControl.getPlaceholder()+'"');
oRM.write('type="'+oControl.getType()+'"');
oRM.write('value="'+oControl.getValue()+'"');
oRM.writeClasses();
oRM.writeControlData(oControl);
oRM.write('/>');
oRM.write('<label class="inputLabel" for="'+oControl.getId()+'"');
oRM.write('>');
oRM.renderControl(oControl.getIcon());
oRM.write('<span class="inputLabelContent">');
oRM.write(oControl.getLabel());
oRM.write('</span>');
oRM.write('</label>');
oRM.write('</div>');
},
});
});
As you can see it is quite simple.
This is how i use it:
<sj:BhTextInput
id="username" class="input textInput"
placeholder="{i18n>HINT_USERNAME}" value="{creds>/username}"
type="Text">
<sj:icon>
<core:Icon src="sap-icon://email" class="inputIcon" />
</sj:icon>
</sj:BhTextInput>
I confirmed that is not a problem of my model, as it works fine when i replace the manual <input/> construction in the renderer method above with:
sap.m.InputRenderer.render(oRM, oControl);
Can you spot anything wrong? Thanks!
EDIT: To clarify a bit on what i mean by "data-binding gets lost". I am only getting an empty string when accessing the value bound to the Input field inside my controller like this: getModel('creds').getProperty('/username');. This does work when replacing the manual construction as written above.
I'm not sure if this is what causing your problem but I believe oRM.write doesn't add spaces to your rendered HTML. It is better to use oRM.writeAttribute for writing attributes. Also class should be added using oRM.addClass.
Ok. There are couple of changes that are required to get it working.
Note 1: The InputBase API ( parent of sap.m.Input) needs your <input> tag to have an id containing "inner" to fetch its value properly. This is from INputBase API:
/**
* Returns the DOM value respect to maxLength
* When parameter is set chops the given parameter
*
* TODO: write two different functions for two different behaviour
*/
InputBase.prototype._getInputValue = function(sValue) {
sValue = (sValue === undefined) ? this.$("inner").val() || "" : sValue.toString();
if (this.getMaxLength && this.getMaxLength() > 0) {
sValue = sValue.substring(0, this.getMaxLength());
}
return sValue;
};
So, on every change, it reads the DOM value and then updates the control metadata.
/**
* Handles the change event.
*
* #protected
* #param {object} oEvent
* #returns {true|undefined} true when change event is fired
*/
InputBase.prototype.onChange = function(oEvent) {
// check the control is editable or not
if (!this.getEditable() || !this.getEnabled()) {
return;
}
// get the dom value respect to max length
var sValue = this._getInputValue();
// compare with the old known value
if (sValue !== this._lastValue) {
// save the value on change
this.setValue(sValue);
if (oEvent) {
//IE10+ fires Input event when Non-ASCII characters are used. As this is a real change
// event shouldn't be ignored.
this._bIgnoreNextInputEventNonASCII = false;
}
// get the value back maybe formatted
sValue = this.getValue();
// remember the last value on change
this._lastValue = sValue;
// fire change event
this.fireChangeEvent(sValue);
// inform change detection
return true;
} else {
// same value as before --> ignore Dom update
this._bCheckDomValue = false;
}
};
So, I changed your renderer method of the control like this:
renderer: function(oRM, oControl) {
oRM.write('<div class=formControl');
oRM.writeClasses();
oRM.writeControlData(oControl); // let div handle control metadata such as id.
oRM.write(">")
oRM.write('<input placeholder="'+oControl.getPlaceholder()+'"');
oRM.write('id="'+oControl.getId()+'-inner"'); // set id with 'inner'
// oRM.write('type="'+oControl.getType()+'"'); dont know why type is throwing error s=, so had to comment it.
oRM.write('value="'+oControl.getMyValue()+'"');
// oRM.writeClasses();
// oRM.writeControlData(oControl);
oRM.write('/>');
oRM.write('<label class="inputLabel" for="'+oControl.getId()+'"');
oRM.write('>');
oRM.renderControl(oControl.getIcon());
oRM.write('<span class="inputLabelContent">');
oRM.write(oControl.getLabel());
oRM.write('</span>');
oRM.write('</label>');
oRM.write('</div>');
}
Let me know if this works for you. :)

adding two validators to Parsley.js

What i basically want to do is have check values that user entered in an input field against two separate arrays. One is an Array of words that would create an error , other is an Array of words that would raise a warning.
I have created these 2 functions and they seem to work fine when they are bound with two different fields
, warning: function () {
var that = this;
return {
validate: function (val,arrayRange) {
var warningWords = new Array();
warningWords[0]="Cricket";
warningWords[1]="Football";
for (var i = 0; i < warningWords.length; i++) {
if(val.toLowerCase()===warningWords[i].toLowerCase())
return false;
}
return true;
}
, priority: 32
}
}
, wrong: function () {
var that = this;
return {
validate: function (val,arrayRange) {
var errorWords = new Array();
errorWords[0]="Justin";
errorWords[1]="Chester";
for (var i = 0; i < errorWords.length; i++) {
if(val.toLowerCase()===errorWords[i].toLowerCase())
return false;
}
return true;
}
, priority: 32
}
}
and the HTML
<label for="email">ERROR :</label>
<input name="message1" id="error" parsley-trigger="keyup" parsley-wrong="" ></input>
<label for="message">Warning</label>
<input name="message" parsley-trigger="keyup" parsley-warning=""></input>
<button type="submit">Submit</button>
Is it possible to bind both of these functions to the same input field?
And secondly i want that in case of warning the input fields background would turn yellow and in case of wrong it should turn red
I have over ridden the parsley-error class . But can i create another class that would be triggered when warning function invalidates the form.
Thirdly is it possible to submit the form even if warning functions causes the field to be invalidated?
It is easy to achieve through JS , so can we some how block parsley from stopping the form validation.
For Q1, you should be able to have multiple custom validators on the same input, just like you can use more than one of parsley's built-in validators. Raise or lower the priorities if you want to force one to validate first.
In response to Q2, you could add additional classes to the input to modify the parsley-error styling. Here's an example:
input.parsley-error {
background-color: #f00;
}
input.parsley-error.warning {
background-color: #ff0;
}
So if you were to have both validators on the same input field you could have the warning validator add a .warning class when it returns false and remove that class when returning true.
For Q3, you should be able to submit the form despite validation issues by overriding the 'onFormValidate' listener and have it return true, even if the 'isFormValid' parameter is false.
$('#formId').parsley({
listeners: {
onFormValidate: function(isFormValid, event, ParsleyForm) {
return true;
}
}
});

Apply background-color in ckeditor textarea

I'm having some problems to apply a background-color in the textarea of a ckeditor instance.
When the user clicks on submit without adding any text, it's shown a message telling him to fill all the required fields, and these required fields areas all with the text-fields set with background-color: #CFC183;.
As the ckeditor is created with javascript code, I was using it to try to check if there's any text entered in the text area. if there's no character, I apply the changes.
When I apply in the console this code:
CKEDITOR.instances.body.document.getBody().setStyle('background-color', '#CFC183');
It applies the background exactly like I want to.
So, I added this javascript code in my javascript file to try to manage it, but doesn't seems to be working. Here's my code:
var editorInstance = CKEDITOR.replace('body', { toolbar : 'Full' });
editorInstance.on("instanceReady", function (ev) {
var editorCKE = CKEDITOR.instances.body; readyMap[editorCKE] = true;
editorCKE.setReadOnly(true);
});
var hasText = CKEDITOR.instances.body.document.getBody().getChild(0).getText();
if (!hasText) {
CKEDITOR.on('instanceCreated', function(e) {
e.editor.document.getBody().setStyle('background-color', '#CFC183');
});
}
Firebug shows this error message:
TypeError: CKEDITOR.instances.body.document is undefined
I'm not that good at Javascript, so is there anything wrong with my code?
I already checked this question here, so I believe there's something wrong with my javascript code and I want your help, please.
I guess that you've got an error in this line:
var hasText = CKEDITOR.instances.body.document.getBody().getChild(0).getText();
This is because you're trying to get document element before it's ready (before instanceReady event).
The same error will be thrown here:
if (!hasText) {
CKEDITOR.on('instanceCreated', function(e) {
e.editor.document.getBody().setStyle('background-color', '#CFC183');
});
}
Again - instanceCreated is still too early. You have to move all that code to instanceReady listener. You'll have something like (I'm not sure if I understand what you're trying to achieve):
var editor = CKEDITOR.replace( 'body', { toolbar: 'Full' } );
editor.on( 'instanceReady', function( evt ) {
readyMap[ editor.name ] = true;
editor.setReadOnly( true );
var hasText = editor.document.getBody().getFirst().getText();
if ( !hasText ) {
editor.document.getBody().setStyle( 'background-color', '#CFC183' );
}
} );
As you can see, there is one more issue in your code:
readyMap[editorCKE] = true;
In JS there are no weak maps (yet, but they will be introduced soon). Only strings can be used as a keys of an object. In your case toString() method will be called on editorCKE, which returns [object Object]. That's why I added name property there.

ExtJS display RowExpander on condition only

I currently have a rather big Grid and am successfully using the RowExpander plugin to display complementary informations on certain rows. My problem is that it's not all rows that contain the aforementioned complementary informations and I do not wish the RowExpander to be active nor to show it's "+" icon if a particular data store's entry is empty. I tried using the conventional "renderer" property on the RowExpander object, but it did not work.
So basically, how can you have the RowExpander's icon and double click shown and activated only if a certain data store's field != ""?
Thanks in advance! =)
EDIT: I found a solution
As e-zinc stated it, part of the solution (for me at least) was to provide a custom renderer that would check my conditional field. Here is my RowExpander:
this.rowExpander = new Ext.ux.grid.RowExpander({
tpl: ...
renderer: function(v, p, record) {
if (record.get('listeRetourChaqueJour') != "") {
p.cellAttr = 'rowspan="2"';
return '<div class="x-grid3-row-expander"></div>';
} else {
p.id = '';
return ' ';
}
},
expandOnEnter: false,
expandOnDblClick: false
});
Now, the trick here is that for this particular Grid, I chose not to allow the expandOnEnter and expanOnDblClick since the RowExpander will sometimes not be rendered. Also, the CSS class of the grid cell that will hold the "+" icon is 'x-grid3-td-expander'. This is caused by the fact that the CSS class is automatically set to x-grid3-td-[id-of-column]. So, by setting the id to '' only when I'm not rendering the rowExpander, I'm also removing the gray background of the un-rendered cells. So, no double click, no enter, no icon, no gray-background. It really becomes as if there is strictly no RowExpander involved for the columns where my data store field is empty (when I want no RowExpander).
That did the trick for me. For someone that wishes to preserve the ID of the cell, or that wishes to keep the double click and enter events working, there is nothing else to do other than extending the class I guess. Hope this can help other people stuck in the position I was!
As e-zinc stated it, part of the solution (for me at least) was to provide a custom renderer that would check my conditional field. Here is my RowExpander:
this.rowExpander = new Ext.ux.grid.RowExpander({
tpl: ...
renderer: function(v, p, record) {
if (record.get('listeRetourChaqueJour') != "") {
p.cellAttr = 'rowspan="2"';
return '<div class="x-grid3-row-expander"></div>';
} else {
p.id = '';
return ' ';
}
},
expandOnEnter: false,
expandOnDblClick: false
});
Now, the trick here is that for this particular Grid, I chose not to allow the expandOnEnter and expandOnDblClick specifically since the RowExpander will sometimes not be rendered. Also, the CSS class of the grid cell that will hold the "+" icon is 'x-grid3-td-expander'. This is caused by the fact that the CSS class is automatically set to x-grid3-td-[id-of-column]. So, by setting the id to an empty string only when I'm not rendering the rowExpander, I'm also removing the gray background of the cells that won't offer any expanding. So, no double click, no enter, no icon, no gray-background. It really becomes as if there is strictly no RowExpander involved for the columns where my data store field is empty (when I want no RowExpander).
That did the trick for me. For someone that wishes to preserve the ID of the cell, or that wishes to keep the double click and enter events working, there is nothing else to do other than extending the RowExpander class in my opinion. Of course, one could also use Ext.override(), but then all instances of RowExpander would be hit by the override.
I have the same task, there is my solution
var rowExpander = new Ext.ux.grid.RowExpander({
renderer : function(v, p, record){
return record.get('relatedPageCount') > 0 ? '<div class="x-grid3-row-expander"> </div>' : ' ';
}
});
I have overridden render method which test relatedPageCount field in store and render + or white space.
I think I've found a cleaner solution.Give me a feedback pls :)
I extend the toggleRow method of RowExpander and if I match a condition avoid to toggle the row.Otherwise the standard flow continues
Ext.create('customplugins.grid.plugin.ClickRowExpander',{
pluginId : 'rowexpander',
rowBodyTpl : new Ext.XTemplate(
'<p><b>Last Modified By:</b> {usermodify}</p>',
'<p><b>User data:</b> {userdata}</p>',
'<p><b>Correlation ID:</b> {correlationid}</p>',
'<p><b>Description:</b> {descr}</p>'
),
toggleRow : function(rowIdx, record) {
if(record.get('directory')) return false;
customplugins.grid.plugin.ClickRowExpander.prototype.toggleRow.apply(this, arguments);
}
})
This version works in Ext JS 5 and 6 (classic)
One thing is to remove the +/- icon, which can be done via grid viewConfig:
getRowClass: function (record, rowIndex, rowParams, store) {
var yourFieldofChoice = record.get('yourFieldofChoice');
if (yourFieldofChoice == null) {
return 'hide-row-expander';
}
},
Define css for hide-row-expander:
.hide-row-expander .x-grid-row-expander {
visibility: hidden;
}
Now you disable expanding on enter key ('expandOnEnter' config is no longer supported in Ext JS 6) or double click by overriding toggleRow, or if you do not wish the override you create your custom rowexpander built on existing plugin:
Ext.define('RowExpander', {
extend: 'Ext.grid.plugin.RowExpander',
alias: 'plugin.myExpander',
init: function (grid) {
var me = this;
me.grid = grid;
me.callParent(arguments);
},
requiredFields: ['yourFieldofChoice'],
hasRequiredFields: function (rec) {
var valid = false;
Ext.each(this.requiredFields, function (field) {
if (!Ext.isEmpty(rec.get(field))) {
valid = true;
}
});
return valid;
},
toggleRow: function (rowIdx, record) {
var me = this, rec;
rec = Ext.isNumeric(rowIdx)? me.view.getStore().getAt(rowIdx) : me.view.getRecord(rowIdx);
if (me.hasRequiredFields(rec)) {
me.callParent(arguments);
}
}
});
I have handled the beforeexpand event inside the listeners of Ext.ux.grid.RowExpander. beforeexpand method got the whole row data injected. Checking the data conditionally we can return true or false. If we return false it wont expand otherwise it will do.
var expander = new Ext.ux.grid.RowExpander({
tpl: '<div class="ux-row-expander"></div>',
listeners: {
beforeexpand : function(expander, record, body, rowIndex){
var gpdata = record.data.GROUP_VALUES[1].COLUMN_VALUE
if(gpdata == null){
return false;
}
else{
return true;
}
}
}
});

Categories