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 != "")
Related
On page 1, when ajax loading to page 2, I need to get the [data-role=page] element of page 2,
but on pageinit(or pagecreate), the $.mobile.activePage is still page 1.
How to get the newly loaded page instead of the current page on pageinit?
$(document).on('pageinit', function () {
console.log($.mobile.activePage); // page 1
});
If you want to retrieve it one time only, then use pagecreate because it fires once per page and it fires before navigation is commenced.
$(document).on("pagecreate", function (e) {
if ( $(e.target).hasClass("ui-dialog") ) {
var dialog = $(e.target);
}
});
To retrieve it every time you navigate to it, you can use othe pageContainer events. In case you want to alter the page you're nacigating to, use pagecobtainerbeforechange.
$(document).on("pagecintainerbeforechange", function (e, data) {
if ( typeof data.toPage == "object" && typeof data.prevPage != "undefined" && data.toPage.hasClass("ui-dialog") ) {
data.toPage.find(".foo").addClass(".bar");
}
});
For other events in case you just want to access dialog and manipulate its markup.
$(document).on("pagecontainerbeforeshow pagecontainershow", function (e, data) {
if ( data.toPage.hasClass("ui-dialog") ) {
var dialog = data.toPage;
/* do something */
});
As of now (Dojo 1.9.2) I haven't been able to find a Dojo autocomplete widget that would satisfy all of the following (typical) requirements:
Only executes a query to the server when a predefined number of characters have been entered (without this, big datasets should not be queried)
Does not require a full REST service on the server, only a URL which can be parametrized with a search term and simply returns JSON objects containing an ID and a label to display (so the data-query to the database can be limited just to the required data fields, not loading full data-entities and use only one field thereafter)
Has a configurable time-delay between the key-releases and the start of the server-query (without this excessive number of queries are fired against the server)
Capable of recognizing when there is no need for a new server-query (since the previously executed query is more generic than the current one would be).
Dropdown-stlye (has GUI elements indicating that this is a selector field)
I have created a draft solution (see below), please advise if you have a simpler, better solution to the above requirements with Dojo > 1.9.
The AutoComplete widget as a Dojo AMD module (placed into /gefc/dijit/AutoComplete.js according to AMD rules):
//
// AutoComplete style widget which works together with an ItemFileReadStore
//
// It will re-query the server whenever necessary.
//
define([
"dojo/_base/declare",
"dijit/form/FilteringSelect"
],
function(declare, _FilteringSelect) {
return declare(
[_FilteringSelect], {
// minimum number of input characters to trigger search
minKeyCount: 2,
// the term for which we have queried the server for the last time
lastServerQueryTerm: null,
// The query URL which will be set on the store when a server query
// is needed
queryURL: null,
//------------------------------------------------------------------------
postCreate: function() {
this.inherited(arguments);
// Setting defaults
if (this.searchDelay == null)
this.searchDelay = 500;
if (this.searchAttr == null)
this.searchAttr = "label";
if (this.autoComplete == null)
this.autoComplete = true;
if (this.minKeyCount == null)
this.minKeyCount = 2;
},
escapeRegExp: function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
replaceAll: function (find, replace, str) {
return str.replace(new RegExp(this.escapeRegExp(find), 'g'), replace);
},
startsWith: function (longStr, shortStr) {
return (longStr.match("^" + shortStr) == shortStr)
},
// override search method, count the input length
_startSearch: function (/*String*/ key) {
// If there is not enough text entered, we won't start querying
if (!key || key.length < this.minKeyCount) {
this.closeDropDown();
return;
}
// Deciding if the server needs to be queried
var serverQueryNeeded = false;
if (this.lastServerQueryTerm == null)
serverQueryNeeded = true;
else if (!this.startsWith(key, this.lastServerQueryTerm)) {
// the key does not start with the server queryterm
serverQueryNeeded = true;
}
if (serverQueryNeeded) {
// Creating a query url templated with the autocomplete term
var url = this.replaceAll('${autoCompleteTerm}', key, this.queryURL);
this.store.url = url
// We need to close the store in order to allow the FilteringSelect
// to re-open it with the new query term
this.store.close();
this.lastServerQueryTerm = key;
}
// Calling the super start search
this.inherited(arguments);
}
}
);
});
Notes:
I included some string functions to make it standalone, these should go to their proper places in your JS library.
The JavaScript embedded into the page which uses teh AutoComplete widget:
require([
"dojo/ready",
"dojo/data/ItemFileReadStore",
"gefc/dijit/AutoComplete",
"dojo/parser"
],
function(ready, ItemFileReadStore, AutoComplete) {
ready(function() {
// The initially displayed data (current value, possibly null)
// This makes it possible that the widget does not fire a query against
// the server immediately after initialization for getting a label for
// its current value
var dt = null;
<g:if test="${tenantInstance.technicalContact != null}">
dt = {identifier:"id", items:[
{id: "${tenantInstance.technicalContact.id}",
label:"${tenantInstance.technicalContact.name}"
}
]};
</g:if>
// If there is no current value, this will have no data
var partnerStore = new ItemFileReadStore(
{ data: dt,
urlPreventCache: true,
clearOnClose: true
}
);
var partnerSelect = new AutoComplete({
id: "technicalContactAC",
name: "technicalContact.id",
value: "${tenantInstance?.technicalContact?.id}",
displayValue: "${tenantInstance?.technicalContact?.name}",
queryURL: '<g:createLink controller="partner"
action="listForAutoComplete"
absolute="true"/>?term=\$\{autoCompleteTerm\}',
store: partnerStore,
searchAttr: "label",
autoComplete: true
},
"technicalContactAC"
);
})
})
Notes:
This is not standalone JavaScript, but generated with Grails on the server side, thus you see <g:if... and other server-side markup in the code). Replace those sections with your own markup.
<g:createLink will result in something like this after server-side page generation: /Limes/partner/listForAutoComplete?term=${autoCompleteTerm}
As of dojo 1.9, I would start by recommending that you replace your ItemFileReadStore by a store from the dojo/store package.
Then, I think dijit/form/FilteringSelect already has the features you need.
Given your requirement to avoid a server round-trip at the initial page startup, I would setup 2 different stores :
a dojo/store/Memory that would handle your initial data.
a dojo/store/JsonRest that queries your controller on subsequent requests.
Then, to avoid querying the server at each keystroke, set the FilteringSelect's intermediateChanges property to false, and implement your logic in the onChange extension point.
For the requirement of triggering the server call after a delay, implement that in the onChange as well. In the following example I did a simple setTimeout, but you should consider writing a better debounce method. See this blog post and the utility functions of dgrid.
I would do this in your GSP page :
require(["dojo/store/Memory", "dojo/store/JsonRest", "dijit/form/FilteringSelect", "dojo/_base/lang"],
function(Memory, JsonRest, FilteringSelect, lang) {
var initialPartnerStore = undefined;
<g:if test="${tenantInstance.technicalContact != null}">
dt = {identifier:"id", items:[
{id: "${tenantInstance.technicalContact.id}",
label:"${tenantInstance.technicalContact.name}"
}
]};
initialPartnerStore = new Memory({
data : dt
});
</g:if>
var partnerStore = new JsonRest({
target : '<g:createLink controller="partner" action="listForAutoComplete" absolute="true"/>',
});
var queryDelay = 500;
var select = new FilteringSelect({
id: "technicalContactAC",
name: "technicalContact.id",
value: "${tenantInstance?.technicalContact?.id}",
displayValue: "${tenantInstance?.technicalContact?.name}",
store: initialPartnerStore ? initialPartnerStore : partnerStore,
query : { term : ${autoCompleteTerm} },
searchAttr: "label",
autoComplete: true,
intermediateChanges : false,
onChange : function(newValue) {
// Change to the JsonRest store to query the server
if (this.store !== partnerStore) {
this.set("store", partnerStore);
}
// Only query after your desired delay
setTimeout(lang.hitch(this, function(){
this.set('query', { term : newValue }
}), queryDelay);
}
}).startup();
});
This code is untested, but you get the idea...
I'm loading some external html into a div in a jquery mobile app. Everything works fine, however I'm trying to make it a little bit smoother.
Here is my code:
$(document).bind('pagebeforecreate', function (event, ui) {
if (event.target.id == 'pageViewOrder') {
//get the page
$.getJSON(root_url + '/orders/view/' + window.viewOrderReference + '/?callback=?', null, function (d) {
$("#viewOrder_content").html(d.html).trigger("create");
$.mobile.loading('hide');
});
}
What's happening is the page is being displayed prior to the ajax call finishing. Is there a way of halting jquery mobile from proceeding to display the page before this call is finished? At the moment it shows the page then the content pops in.
EDIT: This is loading in single pages
Cheers,
Ben
Halting the display process is easy, you just need to call event.preventDefault().
The problem is then to make sure that you will go with the process once you retrieve your content. What I would actually do is bind to pagechange, check if you have already retrieved the data, if not, then interrupt the process, retrieve the data and start over. If yes, then proceed as planned.
var contentRetrieved = false; //will indicate wether the JSON call has already been executed
var contentToDisplay; //data from the JSON call
$(document).live('pagebeforechange', function (event, data) {
if (( typeof data.toPage === "string" ) && ($.mobile.path.parseUrl(data.toPage).hash == '#pageViewOrder')) {
if (contentRetrieved) {
contentRetrieved = false; //content is already retrieved, we proceed with the pagechange
} else {
event.preventDefault(); //prevent further page change operations
$.getJSON(root_url + '/orders/view/' + window.viewOrderReference + '/?callback=?', null, function (d) {
contentToDisplay = {"html":d.html};
contentRetrieved = true;
$.mobile.changePage("#pageViewOrder");
});
}
}
});
$(document).bind('pagebeforecreate', function (event, ui) {
if (event.target.id == 'pageViewOrder') {
$("#viewOrder_content").html(contentToDisplay.html).trigger("create");
$.mobile.loading('hide');
}
});
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 %>
I am trying to do a query everytime the user changes a page in a phonegap app. I am new to Phonegap/JQuery Mobile, but don't understand what is going on.
When I click a button, the pagebeforechange is getting called twice.
First time it works correctly. Next call, it does not run the dbshell.transaction, and no error is shown. So, if I click the overview page first, it works, but the other page does not. If I click the other page first, the overview page does not work. In both cases, re-visiting the same page does not re-do the query.
What's going on here? It must be something incorrect with the way I am calling dbshell?
//Listen for any attempts to call changePage().
$(document).bind( "pagebeforechange", function( e, data ) {
alert("pagebeforechange");
// We only want to handle changePage() calls where the caller is
// asking us to load a page by URL.
if ( typeof data.toPage === "string" ) {
// We are being asked to load a page by URL, but we only
// want to handle URLs that request the data for a specific
// category.
var u = $.mobile.path.parseUrl( data.toPage ),
reOverviewPage = /^#overviewPage/,
reViewByType = /^#viewByType/,
pageUrl=data.toPage;
var params = parseParams(pageUrl.substr(pageUrl.lastIndexOf("?") + 1));
if ( u.hash.search(reOverviewPage) !== -1 ) {
alert("overview");
dbShell.transaction(function(tx) {
alert("doing query");
tx.executeSql("select _id, description from area where _id=?",[params['id']],renderOverview,dbErrorHandler);
},dbErrorHandler);
} else if (u.hash.search(reViewByType) !== -1 ) {
alert("viewByType");
dbShell.transaction(function(tx) {
try
{
alert("!");
tx.executeSql("select trip.* from trip, trip_type, trip_type_lookup where trip_type.trip_id = trip._id and trip_type_lookup._id = trip_type.trip_type_lookup_id and lower(trip_type_lookup.type_name) = ?",[params['type']],dbErrorHandler, renderViewByType);
}
catch(e)
{
alert(e.message);
}
},dbErrorHandler);
}
}
});