Has any one used client_side_validations gem with Chosen.js dropdown? - ruby-on-rails

I am using chosen.js (http://harvesthq.github.com/chosen/). I was wondering if anyone has been able to use chosen select boxes and client_side_validations together.
The issue is that when we use chosen it hides the original select element and renders its own dropdown instead, and when we focus out the validation isn't called and also when the validation message is shown it is shown with the original select element so positioning of the error isnt also correct.
What could be a good way to handle this, My be we can change some code inside ActionView::Base.field_error_proc which currently looks something like
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
unless html_tag =~ /^<label/
%{<div class="field_with_errors">#{html_tag}<label for="#{instance.send(:tag_id)}" class="message">#{instance.error_message.first}</label></div>}.html_safe
else
%{<div class="field_with_errors">#{html_tag}</div>}.html_safe
end
end
Any ideas ?
Edit 1:
I have the following solution that is working for me now.
applied a class "chzn-dropdown" to all my selects that were being displayed by chosen
used the following callback provided by client_side_validations Gem
clientSideValidations.callbacks.element.fail = function(element, message, callback) {
if (element.data('valid') !== false) {
if(element.hasClass('dropdown')){
chzn_element = $('#'+element.attr('id')+'_chzn');
console.log(chzn_element);
chzn_element.append("<label class='message-chzn'>"+message+"</label>");
}
else{
callback();
}
}
}

About the issue with the validation not running at all when the chosen selection changes you can try this:
selectbox.change(function() { setTimeout(function() { selectbox.focusout() }) });
I'm still looking for a solution about the validation message positioning because I'd really love to avoid tampering with the client side validations javascript.
EDIT:
It seems this is a good solution after all, to keep closer to the client side validations api I came up with the following (for whom it may concern):
var settings = {
type: 'ActionView::Helpers::FormBuilder',
input_tag: '<div class="field_with_errors"><span id="input_tag" /><label class="message validationError" /></div>'
};
clientSideValidations.callbacks.element.fail = function (element, message, addError) {
if ($(element).data('chosen') != null) {
var chosen = $('#' + element.attr('id') + '_chzn');
clientSideValidations.formBuilders[settings.type].add(chosen, settings, message);
// Point the label back to the select box
$('label[for="' + chosen.attr('id') + '"]').attr('for', element.attr('id'));
// Mark it as invalid
chosen.data('valid', false);
} else {
addError(element, message);
}
};
clientSideValidations.callbacks.element.pass = function (element, removeError) {
if (element.data('chosen') != null) {
var chosen = $('#' + element.attr('id') + '_chzn');
clientSideValidations.formBuilders[settings.type].remove(chosen, settings);
// Un-mark it from invalid
chosen.data('valid', null);
} else {
removeError(element);
}
};
Note that I use a data attribute (data-chosen) for initialising select-boxes with chosen. Also for a label click to open chosen to work, I explicitly open the list box on label click:
// element is the select box
// Delegate click event from related labels to element (this is already done on "good" browsers)
$('label[for="' + element.attr('id') + '"]').click(function() { element.click() });
// Delegate click event on original element and any related labels to open the list
$(element).click(function() { setTimeout(function() { element.trigger('liszt:open'); }, 0); });

clientSideValidations.callbacks.element.fail = function(element, message, callback){
if(element.data("valid") !== false) {
callback();
if(element.is("select") && $("#" + element.attr("id") + "_chzn").length > 0){
element.parent().prepend($("#" + element.attr("id") + "_chzn"));
}
}
}

Try this:
Tell the gem that you want to validate select tags:
ClientSideValidations.selectors.inputs += ', select';
Force validation when Chosen's dropbox changes:
$('#form_field').chosen().change(function(e) {
var settings = window.ClientSideValidations.forms[this.form.id];
$(this).isValid(settings.validators);
});
Since Chosen hides the field, you will need to enable validation for that field explicitly in the view:
<%= form_for ..., :validate => true do |f| %>
...
<%= f.validate :form_field %>
...
<% end %>

Related

How can i use data outside of the submit function

Trying to use data outside of the submit function to use my own validation
onBlur = {
() = > {
setNameError(nameValidation(data.name));
}
}
You should not handle validation with onBlur function first of all. You can listen to events on form using these approaches so that you capture both form input and button click.
I have not covered styling of form . You can look into more info link to do that one but yeah, just add or remove a class as shown in code below which either shows validation text or not show based on input changes.
// create form with id/class
// There are many ways to pick a DOM node; here we get the form itself and the email
// input box, as well as the span element into which we will place the error message.
const form = document.getElementsByTagName('form')[0];
const email = document.getElementById('mail');
const emailError = document.querySelector('#mail + span.error');
// add an event listener to listen to both form and button click which are defined by id "email" and "submit" as button events.
email.addEventListener('input', function(event) {
// Each time the user types something, we check if the
// form fields are valid.
if (email.validity.valid) {
// In case there is an error message visible, if the field
// is valid, we remove the error message.
emailError.textContent = ''; // Reset the content of the message
emailError.className = 'error'; // Reset the visual state of the message
} else {
// If there is still an error, show the correct error
showError();
}
});
form.addEventListener('submit', function(event) {
// if the email field is valid, we let the form submit
if (!email.validity.valid) {
// If it isn't, we display an appropriate error message
showError();
// Then we prevent the form from being sent by canceling the event
event.preventDefault();
}
});
// finally kick off the error that validates input or button click both and displays error if needed.
function showError() {
if (email.validity.valueMissing) {
// If the field is empty,
// display the following error message.
emailError.textContent = 'You need to enter an e-mail address.';
} else if (email.validity.typeMismatch) {
// If the field doesn't contain an email address,
// display the following error message.
emailError.textContent = 'Entered value needs to be an e-mail address.';
} else if (email.validity.tooShort) {
// If the data is too short,
// display the following error message.
emailError.textContent = `Email should be at least ${ email.minLength } characters; you entered ${ email.value.length }.`;
}
// Set the styling appropriately
emailError.className = 'error active';
}
More info
// On react way
handleOnChange(ev): React.MouseEvent {
this.validateUserInput(ev):
}
const validateUserInput = (ev) => {
if (ev.target.textContent.indexOf(Regular expression logic goes here)) {
this.state.setState({
formValidationText: "Character must be 3 at least"
})
}
}
const renderValidationText = () => { <
h1 > {
this.state.formValidationText
} < /h1>
}
render() {
return <>
<input placeholder="Enter your name" onChange = {
(ev) => this.handleOnChange.bind(this)
}/>
{this.renderValidatorText}
/>
}

Select2 - Show/Hide div based on selection

I am trying to have a div show if an option is selected using the Select2 plug in.
I tried using the following code but it does not work:
$(document).ready(function() {
var h = $(".shipment-details--other-description");
$("#shipment-details-select-pacakage-types").change(function() {
if (this.checked && this.value == "Other") {
h.show();
} else {
h.hide();
}
}).change()
});
The div is being hid but not being shown if the Other option is selected.
Is there a different way to do this with Select2?
Edit: Forgot to mention that this a multiple select field. Also, adjusted the code and took out the this.checked as that was for a check field.
try to create a new css class, is-hidden{ display:none;}
$(document).ready(function() {
var h = $(".shipment-details--other-description");
$("#shipment-details-select-pacakage-types").change(function() {
if (this.checked && this.value == "Other") {
h.addClass('is-hidden');
} else {
h.removeClass('is-hidden');
}
}).change()
});

my checkbox pass value even unchecked - Grails

I have lots of checkbox element on my Grails form, one is this:
<g:checkBox id="consolidate" name="consolidate" value="${true}" checked="${false}" />
Then on the receiving controller, I verify the value of the checkbox using this code:
println params?.consolidate
And it displays:
on
Regardless whether I've tick my checkbox or not. In other language, if the checkbox is not ticked, its value on the controller will be null or undefined. What should be its value when unchecked, and what is the right code to access its value on the grails controller?
Temporary Solution:
The following code (on JavaScript) is what I had used temporarily to accommodate my requirement. Though what I want is an explanation or maybe correction about this behavior.
var serialized_string = "";
$("#form input").each(function(i, j) {
var o = $(j);
if(o.val() !== undefined && o.val() !== "undefined" && o.val() !== "") {
if(serialized_string === "") {
serialized_string += o.attr("name") + "=";
}
else {
if(o.attr("name") === "consolidate") {
var val = "false";
if(o.prop("checked")) {
val = "true";
}
data += val;
}
else {
data += "&" + o.attr("name") + "=";
}
}
}
});
If checked, you see the on you're seeing; otherwise you see null, which is your false.

Use jQuery to check and see if div element is empty

Below is the code I'm using to place a blur event on a text box in my ASP MVC 3 view. The code works fine if #MailingState is empty, however it cannot tell if #channelName is empty.
For example, if #channelName is empty but #MailingState is not, then when I place a value in #MailingZip the getDrmTerritory Ajax call is fired off every time.
Here is the jQuery
$('#MailingZip').blur(function () {
if ($('#AlignmentM').is(':checked')) {
if ($('#MailingState').val() != "" && $('#channelName').html() != "") {
getDrmTerritory($('#MailingZip').val(), $('#MailingState').val(), $('#channelName').html());
}
}
});
and here is the HTML for the #channelName segment it is checking
<div id="channelName" class="M-display-field">
#*this will be updated via ajax from Market Segment *#
#Html.DisplayFor(model => model.Channel, new { style = "margin-left: 300px;" } )
</div>
The section mentioned in the comments is updated via another jQuery method that looks like this
function ChangeChannel() {
//this function called if Market Segment changes, to update the channel
var pendistcode = document.getElementById('Pendist');
if (pendistcode == null) alert('Error: Cannot find Market Segment control');
//alert('!pendistcode value is ' + pendistcode.value);
$.ajax({
type: 'POST',
url: '/AgentTransmission/GetChannel/',
data: { pendist: pendistcode.value },
success: function (data) {
// alert("success: " + data);
$('#channelName').html(data);
$('#Channel').val(data);
},
error: function (data) {
alert("failure to obtain Channel name");
}
});
CheckTerritory('channel');
} //end ChangeChannel
That jQuery method (ChangeChannel) appends text to the channelName div which, when rendered with a value in it, looks like this
Here is the HTML you get when you inspect the Life Sales from that picture
You can check if #channelName is empty or not like this:
if ( $('#channelName').is(':empty') )
and combine with your code like this:
if ($('#MailingState').val() != "" && !$('#channelName').is(':empty'))
I put in an alert statment to find out channelName's length and it came back with 35, so there must obviously be some white space that gets rendered each time. I had to change the statement to the following to trim out the whitespace and add the variable to the condition.
var channel = $.trim($('#channelName').html());
if ($('#MailingState').val() != "" && channel != "")

Latency issue with Primefaces overlayPanel - loads to lazy

I am using Primefaces 3.2 with jsf 2 and glassfish 3.1.2.
I have a p:dataTable of users containing avatars of the user. Whenever the user moves the mouse over the avatar a p:overlayPanel appears with more information (lazy loaded) on the user, and disappears when the user moves the cursor away - like this:
<p:overlayPanel for="avatar" dynamic="true" showEvent="mouseover" hideEvent="mouseout" ...>
This works very well - as long as the user is "slowhanded". Whenever an user moves the cursor fast above many avatars many of the overlayPanels stay visible.
For example when the user has the cursor over the position where user avatars are displayed and uses the scroll wheel of his mouse to scroll the usertable down or up.
I believe that the overlaypanel starts to load the information dynamically (dynamic="true") from the server when showEvent="mouseover" is dispatched and displays the overlaypanel after the response from the server arrives.
This way it is not possible to detect whether the cursor is already away when the overlaypanel becomes visible - so the hideEvent="mouseout" is never dispatched.
Is there a way to make the primefaces overlaypanel appear directly on mousover, showing a loading gif and update the content into the overlaypanel when the response comes from the server.
Is this a good appraoch or does anyone know any other way to solve this nasty problem?
Thanks Pete
As my first answer is already very long and contains valid information, I decided to open a new answer presenting my final approach.
Im now using Primefaces inheritance pattern making the code alot cleaner. Also I noticed that replacing/overwriting the whole bindEvents function isnt necessary, as we can remove the old event handlers. Finally this code fixs the latest issue experienced: A hide event before ajax arrival.
PrimeFaces.widget.OverlayPanel = PrimeFaces.widget.OverlayPanel
.extend({
bindEvents : function() {
this._super();
var showEvent = this.cfg.showEvent + '.ui-overlay', hideEvent = this.cfg.hideEvent
+ '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(
showEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.timer = setTimeout(function() {
_self.hidden = false;
_self.show();
}, 300);
}).on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hidden = true;
_self.hide();
});
},
_show : function() {
if (!this.cfg.dynamic || !this.hidden) {
this._super();
}
}
});
Im sorry for the poor formatting: Eclipses fault ;)
Wow, finally after a long debuging session and testing various approaches i recognized that the problem isnt the ajax request but the event handlers itself:
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
if(_self.isVisible()) {
_self.hide();
}
});
As you can see, the widget is just hidden if its visible before. If your moving your mouse out too fast, now two things can happen:
The widget isnt visible at all
The animation is still going on
In this case the event is discarded and the panel stays visible. As animations are queued, one simply has to remove the if statement to fix the issue. I did this by replacing the whole bindEvents method:
PrimeFaces.widget.OverlayPanel.prototype.bindEvents = function() {
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent == this.cfg.hideEvent) {
var event = this.cfg.showEvent;
$(document).off(event, this.targetId).on(event, this.targetId, this, function(e) {
e.data.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlay',
hideEvent = this.cfg.hideEvent + '.ui-overlay';
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
if(!_self.isVisible()) {
_self.show();
}
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.hide();
});
}
//enter key support for mousedown event
this.bindKeyEvents();
var _self = this;
//hide overlay when mousedown is at outside of overlay
$(document.body).bind('mousedown.ui-overlay', function (e) {
if(_self.jq.hasClass('ui-overlay-hidden')) {
return;
}
//do nothing on target mousedown
var target = $(e.target);
if(_self.target.is(target)||_self.target.has(target).length > 0) {
return;
}
//hide overlay if mousedown is on outside
var offset = _self.jq.offset();
if(e.pageX < offset.left ||
e.pageX > offset.left + _self.jq.outerWidth() ||
e.pageY < offset.top ||
e.pageY > offset.top + _self.jq.outerHeight()) {
_self.hide();
}
});
//Hide overlay on resize
var resizeNS = 'resize.' + this.id;
$(window).unbind(resizeNS).bind(resizeNS, function() {
if(_self.jq.hasClass('ui-overlay-visible')) {
_self.hide();
}
});
};
Execute this code on load and the issue should be gone.
As your replacing the js code nevertheless, you can use this oppurtunity to implement quite a nice feature. By using timeouts in the event handlers one can easily implement a little delay not just improving usability (no more thousands of popups appear) but also reducing network traffic:
$(document).off(showEvent + ' ' + hideEvent, this.targetId).on(showEvent, this.targetId, this, function(e) {
var _self = e.data;
_self.timer = setTimeout( function(){
if(!_self.isVisible()) {
_self.show();
}
}, 300);
})
.on(hideEvent, this.targetId, this, function(e) {
var _self = e.data;
clearTimeout(_self.timer);
_self.hide();
});
Ofcourse you can use a global variable to control the delay time. If you want a more flexible approach youll have to overwrite the encodeScript method in the OverlayPanelRender to transmit an additional property. You could access it then with _self.cfg.delay. Notice though that youll have to replace the component model OverlayPanel too providing it with an extra attribute.
At the same time I thank you for this brilliant solution I take the opportunity to update it for Primefaces 5.2. In our application the code broke after that upgrade.
Follows the updated code for Primefaces 5.2:
PrimeFaces.widget.OverlayPanel.prototype.bindTargetEvents = function() {
var $this = this;
//mark target and descandants of target as a trigger for a primefaces overlay
this.target.data('primefaces-overlay-target', this.id).find('*').data('primefaces-overlay-target', this.id);
//show and hide events for target
if(this.cfg.showEvent === this.cfg.hideEvent) {
var event = this.cfg.showEvent;
this.target.on(event, function(e) {
$this.toggle();
});
}
else {
var showEvent = this.cfg.showEvent + '.ui-overlaypanel',
hideEvent = this.cfg.hideEvent + '.ui-overlaypanel';
this.target
.off(showEvent + ' ' + hideEvent)
.on(showEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
$('.ui-overlaypanel').hide();
$this.hidden = false;
$this.show();
}, 500);
})
.on(hideEvent, function(e) {
clearTimeout($this.timer);
$this.timer = setTimeout(function() {
// don't hide if hovering overlay
if(! $this.jq.is(":hover")) {
$this.hide();
}
}, 100);
});
}
$this.target.off('keydown.ui-overlaypanel keyup.ui-overlaypanel').on('keydown.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
e.preventDefault();
}
})
.on('keyup.ui-overlaypanel', function(e) {
var keyCode = $.ui.keyCode, key = e.which;
if(key === keyCode.ENTER||key === keyCode.NUMPAD_ENTER) {
$this.toggle();
e.preventDefault();
}
});
};
I also added an extra feature which allows the user to move the mouse over the overlay without hiding it. It should hide when you move the mouse out of it then which I accomplished through:
<p:overlayPanel .... onShow="onShowOverlayPanel(this)" ...>
function onShowOverlayPanel(ovr) {
ovr.jq.on("mouseleave", function(e) {
ovr.jq.hide();
});
}
Hope you enjoy!
It's been a long time, but in case anyone bumps into this problem, a showDelay attribute was added to the overlayPanel to solve this problem starting from Primefaces 6.2. However, it is not in the official documentation for some reason.

Resources