How to dynamically reconfigure Drupal's jQuery-based autocomplete at runtime? - javascript

Drupal has a very well-architected, jQuery-based autocomplete.js. Usually, you don't have to bother with it, since it's configuration and execution is handled by the Drupal form API.
Now, I need a way to reconfigure it at runtime (with JavaScript, that is). I have a standard drop down select box with a text field next to it, and depending what option is selected in the select box, I need to call different URLs for autocompletion, and for one of the options, autocompletion should be disabled entirely. Is it possible to reconfigure the existing autocomplete instance, or will I have to somehow destroy and recreate?

Have a look at misc/autocomplete.js.
/**
* Attaches the autocomplete behavior to all required fields
*/
Drupal.behaviors.autocomplete = function (context) {
var acdb = [];
$('input.autocomplete:not(.autocomplete-processed)', context).each(function () {
var uri = this.value;
if (!acdb[uri]) {
acdb[uri] = new Drupal.ACDB(uri);
}
var input = $('#' + this.id.substr(0, this.id.length - 13))
.attr('autocomplete', 'OFF')[0];
$(input.form).submit(Drupal.autocompleteSubmit);
new Drupal.jsAC(input, acdb[uri]);
$(this).addClass('autocomplete-processed');
});
};
The input's value attribute is used to create ACDB, which is a cache of values for that autocomplete path (uri). That is used in the Drupal.jsAC function to bind the element's keydown, keyup and blur events with triggers the autocomplete ajax operation (which caches its values in the ACDB object for that element), opens popups, etc.
/**
* An AutoComplete object
*/
Drupal.jsAC = function (input, db) {
var ac = this;
this.input = input;
this.db = db;
$(this.input)
.keydown(function (event) { return ac.onkeydown(this, event); })
.keyup(function (event) { ac.onkeyup(this, event); })
.blur(function () { ac.hidePopup(); ac.db.cancel(); });
};
What you'll need to do is change the input's value and also reattach the behavior. You'll reattach the behavior by removing the '.autocomplete-processed' class on the autocomplete text field input element and then call Drupal.attachBehaviors(thatInputElement).
This may not work. Things can go very badly if you attach the same behavior to the same element over and over again. It may be more sensible to create different autocomplete fields and simply hide and show them based on the value of the select. This would still require calling Drupal.attachBehaviors when you hide and display the widget, but the same behavior would remain attached if the switch happened more than once, and you wouldn't risk attaching the same behavior to the element multiple times.

Well, for reference, I've thrown together a hack that works, but if anyone can think of a better solution, I'd be happy to hear it.
Drupal.behaviors.dingCampaignRules = function () {
$('#campaign-rules')
.find('.campaign-rule-wrap')
.each(function (i) {
var type = $(this).find('select').val();
$(this).find('.form-text')
// Remove the current autocomplete bindings.
.unbind()
// And remove the autocomplete class
.removeClass('form-autocomplete')
.end()
.find('select:not(.dingcampaignrules-processed)')
.addClass('dingcampaignrules-processed')
.change(Drupal.behaviors.dingCampaignRules)
.end();
if (type == 'page' || type == 'library' || type == 'taxonomy') {
$(this).find('input.autocomplete')
.removeClass('autocomplete-processed')
.val(Drupal.settings.dingCampaignRules.autocompleteUrl + type)
.end()
.find('.form-text')
.addClass('form-autocomplete');
Drupal.behaviors.autocomplete(this);
}
});
};
This code comes from the ding_campaign module. Feel free to check out the code if you need to do something similar. It's all GPL2.

it should be as simple as dinamically change the "value" of the "hidden" autocomplete
input element that comes aside autocomplete form fields. ie.
$('select#myelement').bind('change', function(e) {
if (/* something */) {
$('input#myelement-autocomplete').attr('value', '/mycustom/path');
}
});

Working solution for Drupal 5
/*
* Błażej Owczarczyk
* blazej.owczarczyk#gmail.com
*
* Name: Autocomplete City Taxonomy
* Description: Hierarchical city selecting (province select and city autocomplete)
*/
var Act = Act || {};
Act.init = function () {
$('select.act-province').change(Act.provinceChange); // select with top taxonomy terms
}
/*
* Change event of select element
*/
Act.provinceChange = function () {
var context = $(this).parent().parent();
var currentTid = $(this).val();
Act.rewriteURI(context, currentTid);
Act.unbind();
Drupal.autocompleteAutoAttach();
};
/*
* Changes the value of hidden autocomplete input
*/
Act.rewriteURI = function (context, newTid) {
var tempArray;
tempArray = $('.autocomplete', context).val().split('/');
tempArray.pop();
tempArray.push(newTid);
$('.autocomplete', context).val(tempArray.join('/'));
};
/*
* Prevents muliple binding of the same events
*/
Act.unbind = function () {
$('.form-autocomplete').unbind().parents('form').unbind('submit');
};
$(document).ready(Act.init);

Related

Simulate Click with Vanilla Javascript

I have a SELECT element that I am replacing with a dropdown. I have successfully created the dropdown from the SELECT and child OPTION elements, but I need to add a click event.
This click event would be as such:
If LI is clicked, also click corresponding OPTION.
This is because Woocommerce must have some JS or PHP working where depending on the option, it shows stock status and variable amount. As such, I assume that the click event will also bind the OPTION value to the form for adding to cart.
I have this JS code:
window.onload = main;
function main(){
var select = document.querySelector('.turnintodropdown');
var selsOpts = document.querySelector('.turnintodropdown option');
var selsLi = document.querySelector('.selectOption');
var trigger = document.createElement('a');
var openDropdown = 'dropdownVisible';
var closeDropdown = 'dropdownHidden';
(function addDropdown() {
if(select) {
var selsCon = document.createElement('div');
var selsOuter = document.createElement('ul');
selsCon.classList.add('selectContainer');
selsOuter.classList.add('selectOuter');
select.parentNode.insertBefore(selsCon, select);
selsCon.appendChild(selsOuter);
for(var i=0; i<select.length; i++) {
if(select.childNodes[i].classList.contains('enabled') || select.childNodes[i].innerHTML == '- -'){ // Select First Child and <option> Tags with Enabled Class
// Create New Elements
var optsNew = document.createElement('li');
optsNew.innerHTML = select.childNodes[i].text;
optsNew.classList.add('selectOption');
// Set Attributes to New Elements
if(optsNew.innerHTML !== '- -') {
optsNew.setAttribute('value', select.childNodes[i].text);
}
else {
void(0);
}
optsNew.click(clickFunc);
// Add New LI <option> to UL <container>
selsOuter.appendChild(optsNew);
// Click Events
console.log(select.firstChild);
}
}
var clickFunc = function() {
select.click();
};
select.style.display = 'none';
}
})();
}
Any help would be greatly appreciated.
Regards
Michael
I was a bit long to answer, sorry.
the function was originally taken from this webpage and not modified, it is supposed to work with most old browsers. I actually tested on last versions of Firefox / Chrome / Opera / Edge with success.
The version which handles all types of events is more complicated because you have to make cases for standard events to process them by type (not all are MouseEvents).
It also supports the inline functions, with onclick= in the html tag, and works also for events set with jQuery.
Note that if you want the same support for old broswers, you'll have to differentiate cases for the setting of events too, the modern addEventListener being not supported by all.
function fireClick(node){
if ( document.createEvent ) {
var evt = document.createEvent('MouseEvents');
evt.initEvent('click', true, false);
node.dispatchEvent(evt);
} else if( document.createEventObject ) {
node.fireEvent('onclick') ;
} else if (typeof node.onclick == 'function' ) {
node.onclick();
}
}
used like this for example:
fireClick(document.getElementById("myId"));
Vanilla JS (without jQuery)
/**
* Simulate a click event.
* #public
* #param {Element} elem the element to simulate a click on
*/
var simulateClick = function (elem) {
// Create our event (with options)
var evt = new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
});
// If cancelled, don't dispatch our event
var canceled = !elem.dispatchEvent(evt);
};
To use it, call the function, passing in the element you want to simulate the click on.
var someLink = document.querySelector('a');
simulateClick(someLink);
src / full article: https://gomakethings.com/how-to-simulate-a-click-event-with-javascript/

Select2 3.5.2 mousedown jumps to top of list in IE10 & IE11

Using Select2 v3.5.2 to create a dynamic option list. The input allows users to select from the list, type to search through options and if the option doesn't exist it is created. The function which created this data is called within an angular controller. (ui-select was not used for this particular input as I did not see how to implement the search + custom input when this was implemented)
var dataQuant = {results: []};
for (var key in response.defaultQuantities) {
var objQuant = response.defaultQuantities[key];
dataQuant.results.push({id:key, text:key + 'otherText'});
}
$('.customClass').select2({
createSearchChoice:function(term, data) {
if ($(data).filter(function() {return this.text.localeCompare(term)===0; }).length===0) {
return {id:term, text:term};
}
},
matcher: function(term, text) {
return text.toUpperCase().indexOf(term.toUpperCase())==0;
},
data: dataQuant,
dropdownCssClass: "customClass2",
selectOnBlur: true,
initSelection : function (element, callback) {
var data = {id: response.defaultQuantity , text: response.defaultQuantity};
callback(data);
}
});
$('.customClass').on('change',function(){
var newQuantityData = $('.customClass').select2('data');
if ($scope.formData["quantity"] != newQuantityData.id){
$scope.formData["quantity"] = newQuantityData.id;
$scope.updateFunction();
}
});
This works perfectly fine in chrome/firefox/opera/safari & IE9 and below. In IE10 and 11 any options seen initially can be clicked and work fine. Any options in the option list hidden initially (user has to scroll to) mousedown jumps back up to the top of the option list. If the mouse is held down and you then scroll back down the options when released the correct option is selected.
After some searching I have found that within the select.js under
// single
postprocessResults: function (data, initial, noHighlightUpdate) {
the culprit was
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
this.highlight(0);
}
All other browsers have the 'initial' value as true passed into the function. IE10/11 has an object passed in which fails at the if statement resulting in the first option being highlighted. I'm not sure why an object is being passed in rather than true/false which it seems is what it's expecting. Anyone with more understanding of Select2 able to weigh in?
EDIT:
After removing this.highlight(0) I have now found that custom inputs that didn't exist before are not selected, so clicking the enter key does not select them. For now I'm just going to add a conditional to ignore this line if in IE.
I solved this with the following:
var scrollTop;
$('#mySelect2').on("select2:selecting", function( event ){
var $pr = $( '#'+event.params.args.data._resultId ).parent();
scrollTop = $pr.prop('scrollTop');
});
$('#mySelect2').on("select2:select", function( event ){
var $pr = $( '#'+event.params.data._resultId ).parent();
$pr.prop('scrollTop', scrollTop );
});
Perhaps not the most elegant solution, but essentially we listen for the selecting event and then grab the current scrollTop of the selectable options panel. On the actual selection, we reset the panel's scrollTop back to its original. This happens so fast that you should see no jump in the control window. Only test with Select2 v. 4.x.
The nice thing about this solution is that you don't have to hack the component or include anything in your config functions.
Using and seems to function correctly in all browsers.
Changing
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
this.highlight(0);
}
to
if (initial === true && selected >= 0) {
this.highlight(selected);
} else {
var docMode = document.documentMode,
hasDocumentMode = (docMode !== undefined),
isIE10 = (docMode === 10),
isIE11 = (docMode === 11);
if(hasDocumentMode && (isIE11 || isIE10)){
if(initial.target.value == data.results[0].id || data.results.length == 1){
this.highlight(0);
}
}else{
this.highlight(0);
}
}
Option list no longer jumps to the top in IE 10/11, but still allows users to enter custom values and the 'enter' key selects the typed values.

apply a jQuery effect in ASP based on validation result

I have a webform with a control panel (#pnlStepOne). The panel includes two textfields "txtFname" and "txtLname". I have a validator setup for each textfield. I have tested the form and all works as desired.
My questions is how do I add a jQuery effect to the panel onclick event only if one (or both) of the textfields ("txtFname" and "txtLname") don't validate. (this effect would "shake" the panel).
And I would like to add another jQuery effect to "flip" the control panel and switch the current one (#pnlStepOne) for another one (#pnlStepTwo) if both fields are validated by the asp:RequiredFieldValidators.
Just a sample code that I will tweak once I have the right If condition.
$(document).ready(function () {
$("#btnStepOne").click(function (event) {
if (**this is the condition that I am missing**)
{
$('#pnlStepOne').css({
background: 'red',
});
}
});
});
You can modify your code to be like this:
$(document).ready(function () {
$("#btnStepOne").click(function (event) {
var fvFname = document.getElementById('client-id-of-your-fvFname-validator');
var fvLname = document.getElementById('client-id-of-your-fvLname-validator');
ValidatorValidate(fvFname);
ValidatorValidate(fvLname);
if (!fvFname.isvalid || !fvLname.isvalid) {
$('#pnlStepOne').css({
background: 'red',
});
}
});
});
Have a rad of my answer to a similar question here:
Enable/Disable asp:validators using jquery
Which has the MSDN link here: http://msdn.microsoft.com/en-us/library/aa479045.aspx
In one of my projects I use a prettifyValidation function, so you could have something like:
$(document).ready(function () {
$("#btnStepOne").click(function (event) {
prettifyValidation();
});
});
function prettifyValidation() {
var allValid = true;
if (typeof Page_Validators != 'undefined') {
// Loop through from high to low to capture the base level of error
for (i = Page_Validators.length; i >= 0; i--) {
if (Page_Validators[i] != null) {
if (!Page_Validators[i].isvalid) { // The Control is NOT Valid
$("#" + Page_Validators[i].controltovalidate).removeClass("makeMeGreen").addClass("makeMeRed");
allValid = false;
} else { // Control is valid
$("#" + Page_Validators[i].controltovalidate).removeClass("makeMeRed").addClass("makeMeGreen");
};
};
};
};
}
This will loop through all controls on the page that have an ASP.NET validator attached, and then add or remove a class depending if they are valid or not.
Obviously from here you can limit the function to a specific control by matching the controlToValidate property, and you can restyle, add controls, change classes but this should hopefully provide you a decent base to work from.

jquery autocomplete enter key

Currently the autocomplete action when pressing the Enter key on a selected element is to put that elements value into the input box.
How can I modify the behavior so that pressing the Enter key on an element triggers that elements embedded url. To simplify I'd like to make the Enter key have the same behavior as a mouse click on a returned element.
Thanks
Easy !
$(".quicksearch-input").autocomplete({
minLength: 4, // minimum length to trigger suggestions
// ... + other configuration options
select: function(e, ui) { // define select handler
var uri = ui.item.link; // where ui.item.link itself is defined in your object which you composing when retrieving JSON from endpoint
window.location = uri; // forwarding to the URL obtained
}, // select //
});
But everything depends on your way of implementation.
This is the function that is called when you select an item and hit enter. The hacky code you see in this function extracts the href from the link and redirects to that url.
$("#searchBox").result(function(event, data, formatted) {
var p = data.toString();
p = p.replace('<a href="', '');
var url = p.substr(0, p.indexOf('"'));
p = p.replace('</a>', '');
p = p.substr(p.indexOf('>') + 1, p.length - p.indexOf('>') - 1);
window.location = "" + url;
return false;
});
Did you try keypress event
have a look on the events
http://api.jquery.com/keypress/
http://api.jquery.com/category/events/keyboard-events/
http://api.jquery.com/category/events/mouse-events/

Trigger event of auto popup list selection via javascript

I have previously entered value 1111, 1222, and 1333 into a HTML text input. Now if I enter 1 to the text input, it should popup a list with value 1111, 1222, and 1333 as available options. How do you trigger an event when any one of these options is selected?
I have a javascript function that performs a calculation on values entered into the text input via "onkeyup" event. This works very well if the user just enter value via keyboard. However, it does not work if the user is selecting a previously entered value from the auto popup list.
I know we can turn off the auto popup list by adding autocomplete="off" to the form/text input. But is there any solution to make it work with the auto popup list? I have tried all available event options including "onchange", but none of those works.
The HTML code is very simple:
<input id="elem_id_1" name="output" type="text" value="0" onkeyup="update();"/>
The js function is very simple too:
function update() {
var a = $('elem_id_1').value;
$('elem_id_2').value = a / 100;
}
Have you tried the onchange event? I'm not sure if it fires for auto-complete selection, but you could also try the onpropertychange event and check for the value property:
textInput.onpropertychange = function ()
{
if (event.propertyName == "value")
doCalculation();
}
This would also work on right-click->paste or right-click->cut, which wouldn't fire your calculation using your current method.
EDIT
It looks like you might have to use a combination of events and timers. Set an interval on focus of the edit and clear it on blur. I'd also use the onpropertychange for IE to make it more efficient and keep the keyup event to make it rather quick too.
//- If it's IE, use the more efficient onpropertychange event
if (window.navigator.appName == "Microsoft Internet Explorer")
{
textInput.onpropertychange = function ()
{
if (event.propertyName == "value")
doCalculation();
}
}
else
{
var valueCheck, lastValue = "";
textInput.onkeyup = function ()
{
doCalculation();
}
textInput.onfocus = function ()
{
valueCheck = window.setInterval(function ()
{
// Check the previous value against (and set to) the new value
if (lastValue != (lastValue = textInput.value))
doCalculation();
}, 100);
}
textInput.onblur = function ()
{
window.clearInterval(valueCheck);
}
}
If your calculation routine is tiny (like a simple mathematical equation), I would just leave out the previous value check and run the calculation every 100ms.

Categories