I have a collection of text editors in a form, validating them with knockout-validation. When I submit the form, only a few of them show validation errors, although all of them should pass. This issue only occurs with these two fields shown in error, however I have checked and double checked and all of the wiring for all of these is set up correctly, e.g. there is no reason I can see that these two fields should be different from the others that do not exhibit this issue.
I'm hoping that someone with more experience than I in the internals of knockout-validation could point me to something that may be alternately tripping the validation of these. I have it set up to validate the text when a user clicks outside of the editor, and that works fine. The issue below only appears when a user clicks submit, and other fields on the page (besides the editors shown here) have an issue. In other words, if every single field is valid, the form is submitted. If any fields OTHER than those shown below have issues, the false positive is shown.
Adding Code examples:
Creating the validator and extending the observables. Note the ones depicted in the image are configured identically to those not being marked invalid:
ko.validation.rules['notIn'] = {
validator: function (value, params) {
var thisValidator = baseCompare.slice();
if (Array.isArray(params))
params.forEach(function (element, index, array) {
thisValidator.push(element);
});
else
thisValidator.push(params);
return thisValidator.indexOf(value) == -1;
},
message: 'Please enter a unique value'
};
ko.validation.registerExtenders();
self.setupValidation = function () {
self.title.extend({
required: true,
notIn: {
message: 'Please enter a unique title',
params: self.froalaBasify('New Change Request')
}
});
self.domain.extend({ required: true, notEqual: '0' }).extend({ notify: 'always' });
self.priority.extend({ required: true, notEqual: '0' }).extend({ notify: 'always' });
self.securityClassification.extend({ required: true, notEqual: '0' }).extend({ notify: 'always' });
self.impact.extend({ required: true, notEqual: '0' }).extend({ notify: 'always' });
self.customersAffected.extend({
required: true,
notIn: {
message: 'Please describe the customers affected',
params: self.froalaBasify(CRHelper.ValidationPlaceholders.customersAffected)
}
});
//False Positive
self.justification.extend({
required: true,
notIn: {
message: 'Please provide a justification for the request',
params: self.froalaBasify(CRHelper.ValidationPlaceholders.justification)
}
});
self.scope.extend({
required: true,
notIn: {
message: 'Please describe the systems affected by this change',
params: self.froalaBasify(CRHelper.ValidationPlaceholders.scope)
}
});
//False Positive
self.validationProcess.extend({
required: true,
notIn: {
message: 'Please enter a validation plan for this change',
params: self.froalaBasify(CRHelper.ValidationPlaceholders.validationProcess)
}
});
self.description.extend({
required: true,
notIn: {
message: 'Please enter a description',
params: self.froalaBasify(CRHelper.ValidationPlaceholders.description)
}
});
self.deadlineRequested.extend({
required: true,
dateGreaterThanToday: {
onlyIf: function () {
//var isTrue = $('#ddTransition option:selected').text() == "Delete Request";
//return !(isTrue);
return self.id === 0;
}
}
});
//self.deadlineRequested.extend({
// required: true}).extend({ notify: 'always' });
};
And then here is the submit handler:
self.submitChangeRequest = function () {
var validated = ko.validatedObservable(self.ChangeRequest());
var transition = "";
transition = $('#ddTransition option:selected').text();
var disableValidation = false;
if (transition == "Delete Request")
disableValidation = true;
var formIsValid = validated.isValid();
if (CRHelper.ConfirmSubmitIfDeleteChosen() && (disableValidation || formIsValid)) {
$('.submitter').prop('disabled', true);
var formUsage = $("[id*='hdnFormUsage']")[0].value;
var ajax = new utils.AjaxFormatter(
{
type: "POST",
data: ko.toJSON(self.ChangeRequest()),
url: DomainUtil.baseUrl + "api/ChangeRequests/",
contentType: "application/json",
headers: { 'formUsage': formUsage }
},
function (updatedModel, status, request) {
var updateMessage = formUsage == 'execute' ? 'executed' : (formUsage == 'new' ? 'saved new' : transition == 'Delete Request' ? 'deleted' : 'updated');
var $msg = $("[id*='StatusMessage']");
$msg.html('Successfully ' + updateMessage + ' Change Request ' + updatedModel.id);
$msg.addClass('alertGo').show().fadeOut({ duration: 8000, easing: 'easeInQuint', complete: function () { $msg.removeClass('alertGo'); } });
if (formUsage == 'new') {
changeRequestViewModel.SetChangeRequest(new ChangeRequestModel());
CRHelper.SetEditors('new');
} else {
changeRequestViewModel.SetChangeRequest(new ChangeRequestModel(updatedModel));
CRHelper.SetEditors('edit');
}
if ((formUsage == 'editState' || formUsage == 'edit') && updatedModel.activeStateId === 8)
$('#approvedByItems').toggle();
if (formUsage === 'execute' || (formUsage === 'editState' && updatedModel.activeStateName == 'Approved')) {
$('#divExecuted').toggle();
$('#divChangeState').hide();
}
try {
$('#dtpDeadlineRequested').datepicker();
$('#dtpDeadlineRequested').datepicker('refresh');
} catch (e) {
$('#dtpDeadlineRequested').datepicker('destroy');
$('#dtpDeadlineRequested').datepicker();
}
if (transition == "Delete Request") {
$('.submitter').prop('disabled', true);
$('#divSubmitButtons #buttonSet').toggle();
$('#divSubmitButtons #returnLink').toggle();
$('#formFields').prop('disabled', true);
}
if (updatedModel.activeStateName == 'Executed' || updatedModel.activeStateName == 'Deleted' || updatedModel.activeStateName == 'Approved') {
$('#formFields').prop('disabled', true);
$('.editor').unbind('dblclick');
$('#divSubmitButtons #buttonSet').toggle();
$('#divSubmitButtons #returnLink').toggle();
}
},
function (request, status, error) {
//update message text
var $msg = $("[id*='StatusMessage']");
var updateMessage = formUsage == 'execute' ? 'executing' : (formUsage == 'new' ? 'saving' : 'updating');
$msg.html('Failure ' + updateMessage + ': ' + error);
$msg.addClass('alertStop').show().fadeOut({ duration: 8000, easing: 'easeInQuint', complete: function () { $msg.removeClass('alertStop'); } });
},
function () {
if (pwChooser)
pwChooser.selectedPW([]);
//hide spinny
}
);
utils.tokenizedAjax(ajax);
} else {
ko.validation.group(self.ChangeRequest()).showAllMessages();
var $msg = $("[id*='StatusMessage']");
$msg.html('Check your entries and try again')
.addClass('alertCaution')
.show()
.fadeOut({
duration: 2000,
easing: 'easeInQuint',
complete: function () {
$msg.removeClass('alertCaution');
}
});
}
}
And then here is where I declare the observables on the model - all pretty straightforward:
self.description = ko.observable(crData.description || '');
self.domain = ko.observable(crData.domain || '');
self.id = crData.id || 0;
self.impact = ko.observable(crData.impact || '');
self.includesPolicyWaiver = ko.observable(crData.includesPolicyWaiver || false);
self.justification = ko.observable(crData.justification || '');
self.priority = ko.observable(crData.priority || '');
self.scope = ko.observable(crData.scope || '');
self.securityClassification = ko.observable(crData.securityClassification || '');
self.title = ko.observable(crData.title || 'New Change Request');
self.validationProcess = ko.observable(crData.validationProcess || '');
I've triple-checked the code and there is nowhere else that the values behind these two form fields are being explicitly changed. This looks to me like a global-type value is getting toggled then not reset or something.
This is interesting: The only change I made was the order of the elements in the html. Doing the same test/submission, now the other fields that are in the 2nd and 4th position are showing the false positive. The ones that showed it originally validate as expected.
Related
I have this script, which in some cases may require user input. It works, but the script continues regardless of the result of the Sweet Alert input.
$('#cart').on('click', '.button', function() {
var code = $(this).data('code')
if ((code !== '') && (code !== null) && (code !== undefined)) {
var repeated = 0
var secondary = null
if (code.startsWith('ABC')) {
swalBs.fire({
text: 'Enter secondary code',
showCancelButton: true,
cancelButtonText: 'Cancel',
showConfirmButton: true,
confirmButtonText: 'Confirm',
reverseButtons: true,
input: 'text',
inputValidator: (value) => {
if (!value) {
return 'Code required'
}
}
})
.then((result) => {
console.log(result)
secondary = result.value
})
}
if (repeated < 1) {
$.post(serviceUrl, {
c : code,
s : secondary
}, function(response) {
...
})
.fail(function() {
...
})
} else {
swalBs.fire('Error', 'Code already entered', 'error')
}
} else {
swalBs.fire('Enter code', 'Field must not be blank.')
}
})
How can I make this script wait for the Sweet Alert input while still allowing the $.post to happen when the if (code.startsWith('ABC')) condition is not met (and Sweet Alert is not needed)?
I am facing the following issue:
ActionController::RoutingError (uninitialized constant Spree::Api::AramexAddressController::AramexAddressValidator):
app/controllers/spree/api/aramex_address_controller.rb:2:in <class:AramexAddressController>
app/controllers/spree/api/aramex_address_controller.rb:1:in <top (required)>
I included the following in my controllers/spree/api/aramex_address_controller.rb:
class Spree::Api::AramexAddressController < ApplicationController
include AramexAddressValidator
# fetch cities from aramex api
def fetch_cities_from_aramex_address
response = []
zones = Spree::ShippingMethod.where(['LOWER(admin_name) like ?', '%aramex%']).map(&:zones).flatten
if zones.map(&:countries).flatten.map(&:iso).include?(params['country_code'])
response = JSON.parse(fetch_cities(params['country_code']))['Cities']
end
respond_to do |format|
format.json { render :json => response, :status => 200 }
end
end
# Validate address for aramex shipping
def validate_address_with_aramex
begin
zones = Spree::ShippingMethod.where(['LOWER(admin_name) like ?', '%aramex%']).map(&:zones).flatten
final_response = {}
if zones.map(&:countries).flatten.map(&:iso).include?(params[:b_country])
final_response[:b_errors] = confirm_address_validity(params[:b_city], params[:b_zipcode], params[:b_country])
end
if zones.map(&:countries).flatten.map(&:iso).include?(params[:s_country]) && params[:use_bill_address] == "false"
final_response[:s_errors] = confirm_address_validity(params[:s_city], params[:s_zipcode], params[:s_country])
end
rescue
return true
end
respond_to do |format|
format.json { render :json => final_response, :status => 200 }
end
end
# Confirm address validity with Aramex address validatio API
def confirm_address_validity(city, zipcode, country)
response = JSON.parse(validate_address(city, zipcode, country))
if response['HasErrors'] == true
if response['SuggestedAddresses'].present?
response['Notifications'].map{|data| data['Message']}.join(', ') + ', Suggested city name is - ' + response['SuggestedAddresses'].map{|data| data['City']}.join(', ')
else
if response['Notifications'].first['Code'] == 'ERR06'
response['Notifications'].map{|data| data['Message']}.join(', ')
else
cities_response = JSON.parse(fetch_cities(country, city[0..2]))
cities_response['Notifications'].map{|data| data['Message']}.join(', ') + ', Suggested city name is - ' + cities_response['Cities'].join(' ,')
end
end
end
end
end
In my route file I mentioned:
get 'validate_address_with_aramex', to: 'aramex_address#validate_address_with_aramex'
get 'fetch_cities_from_aramex_address', to: 'aramex_address#fetch_cities_from_aramex_address'
I included the following JS call for the submitted Aramex Ajax validation in assets/javascripts/spree/frontend/checkout/address.js:
Spree.ready(function($) {
Spree.onAddress = function() {
var call_aramex = true;
$("#checkout_form_address").on('submit', function(e) {
if ($('#checkout_form_address').valid()) {
var s_country = $("#order_ship_address_attributes_country_id").find('option:selected').attr('iso_code');
var s_zipcode = $("#order_ship_address_attributes_zipcode").val();
var s_city = $("#order_ship_address_attributes_city").val();
var b_country = $("#order_bill_address_attributes_country_id").find('option:selected').attr('iso_code');
var b_zipcode = $("#order_bill_address_attributes_zipcode").val();
var b_city = $("#order_bill_address_attributes_city").val();
if (call_aramex == true && (typeof aramex_countries !== 'undefined') && (aramex_countries.includes(b_country) || aramex_countries.includes(s_country))) {
e.preventDefault();
var error_id = $('#errorExplanation').is(':visible') ? '#errorExplanation' : '#manual_error'
$(error_id).html("").hide()
$.blockUI({
message: '<img src="/assets/ajax-loader.gif" />'
});
$.ajax({
url: "/api/validate_address_with_aramex",
type: 'GET',
dataType: "json",
data: {
s_country: s_country,
s_zipcode: s_zipcode,
s_city: s_city,
b_country: b_country,
b_zipcode: b_zipcode,
b_city: b_city,
use_bill_address: ($("#order_use_billing").is(":checked"))
},
success: function(result) {
$.unblockUI()
if (result.b_errors || result.s_errors) {
if (result.b_errors) {
$(error_id).append('<div>Billing Address ' + result.b_errors + ' </div>')
}
if (result.s_errors) {
$(error_id).append('<div>Shipping Address ' + result.s_errors + ' </div>')
}
if ((result.b_errors && !result.s_errors) && ($("#order_use_billing").is(":unchecked"))) {
$(".js-summary-edit").trigger("click")
}
$(error_id).show();
$('html, body').animate({
scrollTop: '0px'
}, 300);
} else {
call_aramex = false;
$('#checkout_form_address').submit()
}
},
error: function(xhr, status, error) {
$(error_id).append('Oops Something Went Wrong but you can process order')
$(error_id).show();
$.unblockUI()
$('html, body').animate({
scrollTop: '0px'
}, 300);
$('#checkout_form_address').submit()
}
});
}
}
})
var getCountryId, order_use_billing, update_shipping_form_state;
if (($('#checkout_form_address')).is('*')) {
($('#checkout_form_address')).validate({
rules: {
"order[bill_address_attributes][city]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_bill_address_attributes_country_id").val(), 22)
}
},
"order[bill_address_attributes][firstname]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_bill_address_attributes_country_id").val(), 15)
}
},
"order[bill_address_attributes][lastname]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_bill_address_attributes_country_id").val(), 17)
}
},
"order[bill_address_attributes][address1]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_bill_address_attributes_country_id").val(), 32)
}
},
"order[bill_address_attributes][address2]": {
maxlength: function(element) {
return maxCharLimit($("#order_bill_address_attributes_country_id").val(), 32)
}
},
"order[bill_address_attributes][zipcode]": {
maxlength: function(element) {
return maxCharLimit($("#order_bill_address_attributes_country_id").val(), 10)
}
},
"order[ship_address_attributes][city]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_ship_address_attributes_country_id").val(), 22)
}
},
"order[ship_address_attributes][firstname]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_ship_address_attributes_country_id").val(), 15)
}
},
"order[ship_address_attributes][lastname]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_ship_address_attributes_country_id").val(), 17)
}
},
"order[ship_address_attributes][address1]": {
required: true,
maxlength: function(element) {
return maxCharLimit($("#order_ship_address_attributes_country_id").val(), 32)
}
},
"order[ship_address_attributes][address2]": {
maxlength: function(element) {
return maxCharLimit($("#order_ship_address_attributes_country_id").val(), 32)
}
},
"order[ship_address_attributes][zipcode]": {
maxlength: function(element) {
return maxCharLimit($("#order_ship_address_attributes_country_id").val(), 10)
}
}
}
});
getCountryId = function(region) {
return $('#' + region + 'country select').val();
};
isCountryUsOrCa = function(country_id) {
return ["38", "232"].includes(country_id)
}
maxCharLimit = function(country_id, limit) {
if (["38", "232"].includes(country_id)) {
return limit;
} else {
return 255;
}
};
Spree.updateState = function(region) {
var countryId;
var cityId
countryId = getCountryId(region);
if (countryId != null) {
if (region == 'b') {
cityId = '#order_bill_address_attributes_city'
countryInputId = "#order_bill_address_attributes_country_id"
} else {
cityId = '#order_ship_address_attributes_city'
countryInputId = "#order_ship_address_attributes_country_id"
}
fill_cities($(countryInputId).find('option:selected').attr('iso_code'), cityId)
if (Spree.Checkout[countryId] == null) {
return $.get(Spree.routes.states_search, {
country_id: countryId
}, function(data) {
Spree.Checkout[countryId] = {
states: data.states,
states_required: data.states_required
};
return Spree.fillStates(Spree.Checkout[countryId], region);
});
} else {
return Spree.fillStates(Spree.Checkout[countryId], region);
}
}
};
fill_cities = function(country_code, cityId) {
$.ajax({
url: "/api/fetch_cities_from_aramex_address",
type: 'GET',
dataType: "json",
data: {
country_code: country_code
},
success: function(data) {
$(cityId).autocomplete({
source: data,
});
},
error: function() {}
});
}
Spree.fillStates = function(data, region) {
var selected, stateInput, statePara, stateSelect, stateSpanRequired, states, statesRequired, statesWithBlank;
statesRequired = data.states_required;
states = data.states;
statePara = $('#' + region + 'state');
stateSelect = statePara.find('select');
stateInput = statePara.find('input');
stateSpanRequired = statePara.find('[id$="state-required"]');
if (states.length > 0) {
selected = parseInt(stateSelect.val());
stateSelect.html('');
statesWithBlank = [{
name: '',
id: ''
}].concat(states);
$.each(statesWithBlank, function(idx, state) {
var opt;
opt = ($(document.createElement('option'))).attr('value', state.id).html(state.name);
if (selected === state.id) {
opt.prop('selected', true);
}
return stateSelect.append(opt);
});
stateSelect.prop('disabled', false).show();
stateInput.hide().prop('disabled', true);
statePara.show();
stateSpanRequired.show();
if (statesRequired) {
stateSelect.addClass('required');
}
stateSelect.removeClass('hidden');
return stateInput.removeClass('required');
} else {
stateSelect.hide().prop('disabled', true);
stateInput.show();
if (statesRequired) {
stateSpanRequired.show();
stateInput.addClass('required');
} else {
stateInput.val('');
stateSpanRequired.hide();
stateInput.removeClass('required');
}
statePara.toggle(!!statesRequired);
stateInput.prop('disabled', !statesRequired);
stateInput.removeClass('hidden');
return stateSelect.removeClass('required');
}
};
($('#bcountry select')).change(function() {
$('label.error').hide()
if (isCountryUsOrCa($("#order_bill_address_attributes_country_id").val())) {
$('#checkout_form_address').valid();
}
return Spree.updateState('b');
});
($('#scountry select')).change(function() {
$('label.error').hide()
if (isCountryUsOrCa($("#order_bill_address_attributes_country_id").val())) {
$('#checkout_form_address').valid();
}
return Spree.updateState('s');
});
Spree.updateState('b');
order_use_billing = $('input#order_use_billing');
order_use_billing.change(function() {
return update_shipping_form_state(order_use_billing);
});
update_shipping_form_state = function(order_use_billing) {
if (order_use_billing.is(':checked')) {
($('#shipping .inner')).hide();
return ($('#shipping .inner input, #shipping .inner select')).prop('disabled', true);
} else {
($('#shipping .inner')).show();
($('#shipping .inner input, #shipping .inner select')).prop('disabled', false);
return Spree.updateState('s');
}
};
return update_shipping_form_state(order_use_billing);
}
};
return Spree.onAddress();
});
Why am I am facing the issue mentioned at the top?
maybe you can try this:
namespace :spree do
namespace :api do
resources :aramex_address, only: [] do
get :validate_address_with_aramex
get :fetch_cities_from_aramex_address
end
end
end
words of advice I think it's better if you rename the fetch_cities_from_aramex_address with show method, so it still follow rails convenience
well here is the issue of constant lookup. Your constant that is AramexAddressValidator is missing because of the way you wrote class Spree::Api::AramexAddressController < ApplicationController
So if your module AramexAddressValidator is inside some scope use that scope also while including this module
For Ex. if its inside spree/aramex_address_validator use
include Spree::AramexAddressValidator
In my UI, I have an EditorGridPanel and a three buttons namely: 'add','save' and 'cancel'. Currently, whenever I want to edit the existing data in my grid and click the save button, automatically it will update the data and that is my problem. I want is to add an alert message box saying 'Do you want to save changes?' If YES, proceed in executing save function. If NO, return the data on its original form before updating the data in the Grid.
Could someone help me about this?
This are my codes:
test.js
var grid = new Ext.grid.EditorGridPanel({
id: 'maingrid',
store: store,
cm: cm,
width: 785.5,
anchor: '100%',
height: 700,
frame: true,
loadMask: true,
waitMsg: 'Loading...',
clicksToEdit: 2,
tbar: [
'->',
{
text: 'Add',
iconCls: 'add',
id: 'b_add',
disabled: true,
handler : function(){
var Put = grid.getStore().recordType;
var p = new Put({
action_take: 'add',
is_active: '',
allowBlank: false
});
Ext.getCmp('b_save').enable();
Ext.getCmp('b_cancel').enable();
grid.stopEditing();
store.insert(0, p);
grid.startEditing(0, 1);
}
},'-',{
text: 'Save',
iconCls: 'save',
id: 'b_save',
disabled: true,
handler : function(){
var objectStore = Ext.getCmp("maingrid").getStore();
var objectModified = objectStore.getModifiedRecords();
var customer_id = Ext.getCmp("maingrid").getStore().baseParams['customer_id'];
var objectData = new Array();
var dont_include;
if(objectModified.length > 0)
{
for(var i = 0; i < objectModified.length; i++)
{
dont_include = false;
if(objectModified[i].data.id
&&
(
(objectModified[i].data.firstname == undefined || objectModified[i].data.firstname == null|| objectModified[i].data.firstname == '')
||
(objectModified[i].data.lastname == undefined || objectModified[i].data.lastname == null|| objectModified[i].data.lastname == '')
||
(objectModified[i].data.email_address == undefined || objectModified[i].data.email_address == null|| objectModified[i].data.email_address == '')
)
)
{
Ext.Msg.show({
title: 'Warning',
msg: "Input value required.",
icon: Ext.Msg.WARNING,
buttons: Ext.Msg.OK
});
return;
}
else//no id, means new record
{
//all fields are not filled-in, just do nothing
if((objectModified[i].data.firstname == undefined || objectModified[i].data.firstname == null|| objectModified[i].data.firstname == '')&&
(objectModified[i].data.lastname == undefined || objectModified[i].data.lastname == null|| objectModified[i].data.lastname == '')&&
(objectModified[i].data.email_address == undefined || objectModified[i].data.email_address == null|| objectModified[i].data.email_address == ''))
{
//boolean flag to determine whether to include this in submission
dont_include = true;
}
//either one field is not filled-in prompt to fill in all fields
else if((objectModified[i].data.firstname == undefined || objectModified[i].data.firstname == null|| objectModified[i].data.firstname == '')||
(objectModified[i].data.lastname == undefined || objectModified[i].data.lastname == null|| objectModified[i].data.lastname == '')||
(objectModified[i].data.email_address == undefined || objectModified[i].data.email_address == null|| objectModified[i].data.email_address == ''))
{
Ext.Msg.show({
title: 'Warning',
msg: "Input value required.",
icon: Ext.Msg.WARNING,
buttons: Ext.Msg.OK
});
return;
}
}
//the data for submission
if(!dont_include)
{
objectData.push({
firstname: objectModified[i].data.firstname,
lastname: objectModified[i].data.lastname,
email_address: objectModified[i].data.email_address,
id: objectModified[i].data.id,
customer_id: customer_id
});
}
}
// console.log(objectData)
// return;
//check if data to submit is not empty
if(objectData.length < 1)//empty
{
Ext.Msg.show({
title: 'Warning',
msg: "No records to be saved",
icon: Ext.Msg.WARNING,
buttons: Ext.Msg.OK
});
Ext.getCmp('maingrid').getStore().reload();
return;
}
// return;
Ext.Msg.wait('Saving Records...');
Ext.Ajax.request({
timeout:900000,
params: {objdata: Ext.encode(objectData)},
url: '/index.php/SaveContent',
success: function(resp){
var response = Ext.decode(resp.responseText);
Ext.Msg.hide();
if(response.success == true){
Ext.Msg.show({
title: "Information",
msg: response.msg,
buttons: Ext.Msg.OK,
icon: Ext.Msg.INFO,
fn: function(btn){
Ext.getCmp('maingrid').getStore().reload();
Ext.getCmp('b_save').disable();
Ext.getCmp('b_cancel').disable();
}
});
}else{
Ext.Msg.show({
title: "Warning",
msg: response.msg,
buttons: Ext.Msg.OK,
icon: Ext.Msg.WARNING
});
}
},
failure: function(resp){
Ext.Msg.hide();
Ext.Msg.show({
title: "Warning1",
msg: response.msg,
buttons: Ext.Msg.OK,
icon: Ext.Msg.WARNING
});
}
});
}
else{
Ext.Msg.show({
title: 'Warning',
msg: "No changes made.",
icon: Ext.Msg.WARNING,
buttons: Ext.Msg.OK
});
}
}
},'-',
{
text: 'Cancel',
iconCls: 'cancel',
id: 'b_cancel',
disabled: true,
handler : function(){
var store = Ext.getCmp('maingrid').getStore();
var modified = store.getModifiedRecords();
if (modified.length) {
Ext.MessageBox.confirm('Cancel', 'There are records not yet saved. Are you sure you want to cancel the changes?', function(btnId) {
if (btnId == 'yes') {
store.reload();
Ext.getCmp('b_save').disable();
Ext.getCmp('b_cancel').disable();
}
});
}
}
}
],
bbar: pager
});
actions.class.php
public function executeSaveContent(sfWebRequest $request){
$save_data = json_decode($request->getParameter("objdata"));
$count_array = count($save_data);
$id_insession = $_SESSION['employee_id'];
if($count_array > 0){
foreach($save_data as $k => $v){
$id = strip_tags($v->id);
$firstname = preg_replace("/\s+/", " ", $v->firstname);
$firstname = ltrim(addslashes(strip_tags($firstname)));
$firstname = rtrim($firstname);
$lastname = preg_replace("/\s+/", " ", $v->lastname);
$lastname = ltrim(addslashes(strip_tags($lastname)));
$lastname = rtrim($lastname);
$email_address = preg_replace("/\s+/", " ", $v->email_address);
$email_address = ltrim(addslashes(strip_tags($email_address)));
$email_adsress = rtrim($email_address);
$customer_id = $v->customer_id;
$action = ''; //strip_tags($v->action);
if(empty($id))
{
$action='add';
}
else {
$action='edit';
}
if(!empty($firstname)||($lastname)||($email_address)){
$sql_check = "select firstname,lastname,email_address
from
customer_saver
";
if($action == "edit"){
$sql_check .= " where
firstname = '$firstname'
and
id not in ($id)";
}
if($action == "add"){
$sql_check .= " where
firstname = '$firstname'
";
}
}
$setpath = _appContract::SchemaChange('contract_arbill');
$this->conn->execute($setpath);
$result_check = $this->conn->fetchAll($sql_check);
foreach($result_check as $v){
$fetch_firstname = $this->formatString($v['firstname']);
$fetch_lastname = $this->formatString($v['lastname']);
$fetch_email_address= $v['email_address'];
}
$result_check_count = count($result_check);
if($action == "edit"){
if($result_check_count == 0){
$sql = "update
customer_saver
set
firstname = '$firstname',
lastname = '$lastname',
email_address = '$email_address'
where
id = $id";
}
else{
$resp['success'] = false;
$resp['msg'] = "Duplicate entry found. <br>
First name: <b>".$firstname."</b> Last name: <b>".$lastname."</b> Email Address: <b>".$email_address."</b>";
die(json_encode($resp));
}
}
elseif($action == "add"){
if(empty($id) && $result_check_count == 0){
$sql = "insert into
customer_saver
(firstname, lastname, email_address,customer_id)
values
('$firstname', '$lastname', '$email_address', $customer_id)";
}
else{
$resp['success'] = false;
$resp['msg'] = "Duplicate entry found. <br>
First Name: <b>".$firstname."</b> Last Name: <b>".$lastname."</b> Email Address: <b>".$email_address."</b>";
die(json_encode($resp));
}
}
try{
$this->conn->execute($setpath);
$this->conn->execute($sql);
}catch(Exception $e){
die($e->getMessage());
}
}
$resp['success'] = true;
$resp['msg'] = "Successfully saved the record.";
die(json_encode($resp));
}
}
For displaying confirmation dialog you can use Ext.MessageBox.confirm() method.
Then if user click on Yes button you can call store.sync() method or process your own changes saving.
If user click on No button you can call Ext.data.Store rejectChanges() method. This method rejects outstanding changes on all modified records and re-insert any records that were removed locally.
{
text: 'Save',
iconCls: 'save',
id: 'b_save',
disabled: true,
handler : function(){
var store = Ext.getCmp("maingrid").getStore();
Ext.MessageBox.confirm('Save changes', 'Do you want save changes?', function(btnId) {
if (btnId == 'yes') {
// Your current save button handler code
} else {
store.rejectChanges();
}
});
}
}
Ok, I know that the first thought here for everyone is to simply create another validation method and message. But here is the deal. We have a form that has a field that is used as Employer Name and Company Name, dependent on what is selected in a dropdown.
What I need to be able to do is when the dropdown shows that they own the company and they didn't fill out the field then it shows one message. If they don't own the company and it is empty then it shows another message.
How would I accomplish this? This is using the latest version of jQuery and the Validate plugin (http://jqueryvalidation.org/).
UPDATE:
I present to you the code that I ended up with after the selected answer below was selected...
http://pastebin.com/iG3Z4BbJ
Thanks and enjoy!
Code In Detail:
/**
* Reference: http://jqueryvalidation.org/rules
*/
$(document).ready(function() {
var rules = {
IncomeSource: 'required',
NetIncome: {
required: true,
usDollar: true,
minDollars: 1
},
//Begin - Fields that don't always show
JobTitle: {
//required: true,
nameInput: true
},
Employer: {
//required: true,
nameInput: true
},
EmployerPhone: {
//required: true,
phoneUS: true
},
BenefitSource: {
//required: true,
nameInput: true
},
//SemiMonthlySpecifics: 'required', //Select one when Semi-Monthly or Monthly are selected
//End - Fields that don't always show
PayFrequency: 'required',
LastPayDate: {
required: true,
pastDate: true,
date: true
},
NextPayDate: {
required: true,
futureDate: true,
date: true
},
DirectDeposit: 'required',
EmploymentLength: 'required',
ActiveMilitary: 'required',
RoutingNumber: {
required: true,
digits: true,
rangelength: [9, 9]
},
AccountNumber: {
required: true,
digits: true,
rangelength: [4, 17]
},
AccountType: 'required'
};
//And field specific (and even validation type specific) error messages
var messages = {
IncomeSource: ss.i18n._t('IncomeSourceRequiredError'),
NetIncome: {
required: ss.i18n._t('NetIncomeRequiredError')
},
//Begin - Fields that don't always show
JobTitle: {
required: ss.i18n._t('JobTitleRequiredError')
},
Employer: {
required: ss.i18n._t('EmployerRequiredError')
},
EmployerPhone: {
required: ss.i18n._t('EmployerPhoneRequiredError')
},
BenefitSource: {
required: ss.i18n._t('BenefitSourceRequiredError')
},
SemiMonthlySpecifics: ss.i18n._t('SemiMonthlySpecificsRequiredError'), //Select one when Semi-Monthly or Monthly are selected
//End - Fields that don't always show
PayFrequency: ss.i18n._t('PayFrequencyRequiredError'),
LastPayDate: {
required: ss.i18n._t('LastPayDateRequiredError')
},
NextPayDate: {
required: ss.i18n._t('NextPayDateRequiredError')
},
DirectDeposit: ss.i18n._t('DirectDepositRequiredError'),
EmploymentLength: ss.i18n._t('EmploymentLengthRequiredError'),
ActiveMilitary: ss.i18n._t('ActiveMilitaryRequiredError'),
RoutingNumber: {
required: ss.i18n._t('RoutingNumberRequiredError')
},
AccountNumber: {
required: ss.i18n._t('AccountNumberRequiredError')
},
AccountType: ss.i18n._t('AccountTypeRequiredError')
};
$('#applicationForm').validate({
//debug: true,
rules: rules,
messages: messages,
errorElement: 'span',
ignore: '.ignore',
onfocusout: function( element, event ) {
$(element).valid();
},
invalidHandler: function(event, validator) {
kclHelpers.openErrorModal(event, validator);
},
errorPlacement: function(error, element) {
var insertLocation = kclHelpers.getInsertLocation(element);
error.appendTo( insertLocation );
},
success: function(label, element) {
element = $(element);
var insertLocation = kclHelpers.getInsertLocation(element);
insertLocation.hide();
kclHelpers.parentShowSuccess(element, '.control-group');
},
//Had to use this for show/hide of errors because error placement doesn't get called on every error.
showErrors: function(errorMap, errorList) {
if (this.numberOfInvalids() > 0) {
//We want to make sure that we show/hide things appropriately on error
$.each(errorList, function(index, item) {
var domElement = item['element'];
var message = item['message'];
var element = $(domElement);
var insertLocation = kclHelpers.getInsertLocation(element);
insertLocation.show();
kclHelpers.parentShowError(element, '.control-group');
});
}
this.defaultShowErrors();
}
});
$('[rel=tooltip]').tooltip();
// When the income source is changed, change the form respectively
$('#IncomeSource').on('change', kclFinancialInfo.incomeSourceChanged);
$('#PayFrequency').on('change', kclFinancialInfo.payFrequencyChanged);
});
/**
* This is where we specify functions only for this form
*/
var kclFinancialInfo = function()
{
return {
incomeSourceChanged: function() {
// Store and convert the income source to lowercase
var val = $(this).val().toLocaleLowerCase();
// Switch on the income source
switch (val) {
// Case for 'benefits'
case 'benefits':
// Do not display the employment section
kclFinancialInfo.toggleEmploymentSection(false);
// Display the benefit source section
kclFinancialInfo.toggleBenefitSource(true);
break;
// Case for 'selfemployment'
case 'selfemployment':
// Display the benefit source section
kclFinancialInfo.toggleBenefitSource(false);
// Display the employment section
kclFinancialInfo.toggleEmploymentSection(true, "Please enter your company's name");
// Call to update the labels for the employment section based on income source of 'selfemployment'
kclFinancialInfo.updateEmploymentSectionLabels('selfemployment');
// Hide the employer phone field
kclFinancialInfo.toggleEmployerPhone(true);
break;
// Case for 'job' and default
case 'job':
default:
// Do not display the benefit source
kclFinancialInfo.toggleBenefitSource(false);
// Display the employment section
kclFinancialInfo.toggleEmploymentSection(true, "Please enter your employer's name");
// Call to update the labels for the employment section based on income source of 'job'
kclFinancialInfo.updateEmploymentSectionLabels('job');
// Do not hide the employer phone field
kclFinancialInfo.toggleEmployerPhone(false);
break;
}
},
payFrequencyChanged: function() {
var val = $(this).val().toLocaleLowerCase();
switch (val) {
case 'semi_monthly':
// Display the Specific SemiMonthly section
kclFinancialInfo.toggleSpecificSemiMonthly(true, val);
case 'monthly':
// Display the Specific SemiMonthly section BUT change the labels
kclFinancialInfo.toggleSpecificSemiMonthly(true, val);
break;
default:
// Do not display the Specific SemiMonthly section
kclFinancialInfo.toggleSpecificSemiMonthly(false);
break;
}
},
toggleSpecificSemiMonthly: function(show, value) {
if (show) {
if (value == 'semimonthly') {
$('#specifics-label-for-SemiMonthlySpecifics_day').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.SEMI_MONTHLY_SPECIFICS_DAY'));
$('#specifics-label-for-SemiMonthlySpecifics_date').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.SEMI_MONTHLY_SPECIFICS_DATE'));
}
else {
$('#specifics-label-for-SemiMonthlySpecifics_day').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.MONTHLY_SPECIFICS_DAY'));
$('#specifics-label-for-SemiMonthlySpecifics_date').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.MONTHLY_SPECIFICS_DATE'));
}
$('#semi-monthly-specifics').show().removeClass('hidden');
$('input[name="SemiMonthlySpecifics"]').rules('add', 'required');
}
else {
kclHelpers.hideMessage('SemiMonthlySpecifics');
$('#semi-monthly-specifics').hide().addClass('hidden');
$('input[name="SemiMonthlySpecifics"]').rules('remove', 'required');
}
},
toggleEmployerPhone: function(hide) {
// If you want to hide the employer phone field
if (hide) {
// Hide the employer phone
$('#employer_phone').hide().addClass('hidden');
$('employer_phone').rules('remove', 'required');
}
// Else (you don't want to hide the employer phone field)
else {
// Display the employer phone field
$('#employer_phone').show().removeClass('hidden');
$('employer_phone').rules('add', 'required');
}
},
toggleBenefitSource: function(show) {
// If you want to show the benefit sources section
if (show) {
// Show the benefit source section
$('#benefit_source').show().removeClass('hidden');
// Since the benefit source section is now visible, make it required
$('#BenefitSource').rules('add', 'required');
/**
* Hide the WorkPhone validation message if its displayed (it remains on the screen if the user
* selects benefits)
*/
kclHelpers.hideMessage('WorkPhone');
}
// Else (you don't want to show the benefit source section, display the default employment section)
else {
// Hide the benefit source section
$('#benefit_source').hide().addClass('hidden');
// Since the benefit source is now hidden, it is no longer required
$('#BenefitSource').rules('remove', 'required');
}
},
toggleEmploymentSection: function(show, employerMessage = '') {
// If you want to show the employment section
if (show) {
// Show the employment section
$('#employment_section').show();
$('#JobTitle').rules('add', 'required');
$('#Employer').rules('add', {
required: true,
messages: {
required: employerMessage
}
});
$('#EmployerPhone').rules('add', 'required');
}
// Else (you don't want to show the employment section)
else {
// Hide the employment section
$('#employment_section').hide();
$('#JobTitle').rules('remove', 'required');
$('#Employer').rules('remove', 'required');
$('#EmployerPhone').rules('remove', 'required');
}
},
updateEmploymentSectionLabels: function(source) {
// Switch on the income source (now lowercase)
switch (source.toLocaleLowerCase()) {
// Case for 'job'
case 'job':
/**
* Make sure the job title and employer labels are correct (this is needed in case the labels
* need to be changed back to default
*/
$('#JobTitle_label').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.JOB_TITLE'));
$('#Employer_label').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.EMPLOYER'));
$('#Employer').attr('placeholder', 'Enter Employer Name');
break;
// Case for 'selfemployment'
case 'selfemployment':
// Change the employer label to correspond to self employment (Employer -> Your Company Name)
$('#Employer_label').text(ss.i18n._t('FINANCIAL_INFORMATION_PAGE.SELF_EMPLOYER'));
$('#Employer').attr('placeholder', 'Enter Company Name');
break;
}
}
}
}();
var kclHelpers = function()
{
var firstErrorID = '';
var errorCount = 0;
return {
//Expects jQuery element
getInsertLocation: function(element) {
var fieldName = element.attr('name');
if (fieldName == 'Reference_1[Relationship]') {
fieldName = 'Reference_1-Relationship';
}
else if (fieldName == 'Reference_2[Relationship]') {
fieldName = 'Reference_2-Relationship';
}
var insertTo = "#" + fieldName + "-error";
return $(insertTo);
},
//Expects jQuery element and a selector string
parentShowError: function(element, parentSelector) {
var parentElem = element.parents(parentSelector);
if (!parentElem.hasClass('has-error')) {
parentElem.addClass('has-error');
}
parentElem.removeClass('has-success');
},
//Expects jQuery element and a selector string
parentShowSuccess: function(element, parentSelector) {
var parentElem = element.parents(parentSelector);
if (!parentElem.hasClass('has-success')) {
parentElem.addClass('has-success');
}
parentElem.removeClass('has-error');
},
hideMessage: function(fieldName) {
if (fieldName == 'CreatePassword') {
// Clear the Create-Password error message as well as the Confirm-Password error message
$('#ConfirmPassword-error').hide();
}
if (fieldName == 'CellPhone') {
// Clear the Cell-Phone error message as well as the Home-Phone error message
$('#HomePhone-error').hide();
}
/**
* Find the id in the form styling file corresponding to field we wish to "clear"
* And set its "display" value to "none", effectively deleting it
* This is for the desktop version
*/
$("#" + fieldName + "-error").hide();
},
openErrorModal: function(event, validator) {
var errorCnt = validator.numberOfInvalids();
var errors = '';
var hasSetErrorID = false;
kclHelpers.errorCount = errorCnt;
if (errorCnt) {
var message = "";
for (var i in validator.errorMap) {
if (!hasSetErrorID) {
hasSetErrorID = true;
kclHelpers.firstErrorID = i;
}
var str = i;
var label = '';
if (i == 'AgreeTerms') {
label = 'Website terms';
}
else if (i == 'ContactTerms') {
label = 'Contact terms';
}
else if (i == 'HomePhone') {
label = 'Home Phone';
}
else if (i == 'RoutingNumber') {
label = 'Routing Number';
}
else if (i == 'AccountNumber') {
label = 'Account Number';
}
else if (i == 'SemiMonthlySpecifics') {
label = 'Pay Frequency Specifics';
}
else if (/Reference_1/i.test(str)) {
label = 'Reference 1 ' + $("label[for='" + i + "']").text();
}
else if (/Reference_2/i.test(str)) {
label = 'Reference 2 ' + $("label[for='" + i + "']").text();
}
else {
label = $("label[for='" + i + "']").text();
}
errors += '<li>' + label + ': ' + validator.errorMap[i] + '</li>';
}
message += '<ol>' + errors + '</ol>';
$('#form-error-modal .modal-body .error-count').text(errorCnt);
$('#form-error-modal ul.error-list').html(message);
$('#form-error-modal').modal('show');
}
}
}
}();
Here's a much cleaner (and probably the correct) way
messages: {
RoutingNumber: {
required:"Message1",
digits: "Message2",
rangelength: "Message 3"
}
}
For more validation methods: List of built in validation methods
Use the .rules('add') method to dynamically change the message on change of the select. Then use the .valid() method to trigger a new test on the field in order to instantly update the message.
$(document).ready(function () {
$('#myform').validate({
rules: {
type: {
required: true
},
theName: {
required: true
}
}
});
$('select[name="type"]').on('change', function () {
var theMessage,
theType = $(this).val();
if (theType == 'employer') {
theMessage = "Required Message 1";
} else if (theType == 'company') {
theMessage = "Required Message 2";
} else {
theMessage = "This field is required.";
}
// dynamically change the message
$('input[name="theName"]').rules('add', {
messages: {
required: theMessage
}
});
// trigger immediate validation to update message
$('input[name="theName"]').valid();
});
});
Working DEMO: http://jsfiddle.net/CLFY9/
I tried to include a conditional in the unHappy function; While the form processes, I don't get the message indicating success! Here's my code.
var dd= $.noConflict();
dd(document).ready(function () {
dd('.success').hide();
dd('#frmContact').isHappy({
fields: {
// reference the field you're talking about, probably by `id`
// but you could certainly do $('[name=name]') as well.
'#yourName': {
required: true,
message: 'Might we inquire your name'
},
'#email': {
required: true,
message: 'How are we to reach you sans email??',
test: happy.email // this can be *any* function that returns true or false
},
'#comments': {
required: true,
message: 'Please leave a message!',
}
},
unHappy: function () {
var yourName = dd('#yourName').val();
var email = dd('#email').val();
var comments = dd('#comments').val();
if (yourName && email && comments == true){
dd('.success').show();
return;
}
},
});
});