/** * @file better_exposed_filters.js * * Provides some client-side functionality for the Better Exposed Filters module */ (function ($) { Drupal.behaviors.betterExposedFilters = { attach: function(context) { // Add highlight class to checked checkboxes for better theming $('.bef-tree input[type=checkbox], .bef-checkboxes input[type=checkbox]') // Highlight newly selected checkboxes .change(function() { _bef_highlight(this, context); }) .filter(':checked').closest('.form-item', context).addClass('highlight') ; } }; Drupal.behaviors.betterExposedFiltersSelectAllNone = { attach: function(context) { /* * Add Select all/none links to specified checkboxes */ var selected = $('.form-checkboxes.bef-select-all-none:not(.bef-processed)'); if (selected.length) { var selAll = Drupal.t('Select All'); var selNone = Drupal.t('Select None'); // Set up a prototype link and event handlers var link = $(''+ selAll +'') link.click(function(event) { // Don't actually follow the link... event.preventDefault(); event.stopPropagation(); if (selAll == $(this).text()) { // Select all the checkboxes $(this) .html(selNone) .siblings('.bef-checkboxes, .bef-tree') .find('.form-item input:checkbox').each(function() { $(this).attr('checked', true); _bef_highlight(this, context); }) .end() // attr() doesn't trigger a change event, so we do it ourselves. But just on // one checkbox otherwise we have many spinning cursors .find('input[type=checkbox]:first').change() ; } else { // Unselect all the checkboxes $(this) .html(selAll) .siblings('.bef-checkboxes, .bef-tree') .find('.form-item input:checkbox').each(function() { $(this).attr('checked', false); _bef_highlight(this, context); }) .end() // attr() doesn't trigger a change event, so we do it ourselves. But just on // one checkbox otherwise we have many spinning cursors .find('input[type=checkbox]:first').change() ; } }); // Add link to the page for each set of checkboxes. selected .addClass('bef-processed') .each(function(index) { // Clone the link prototype and insert into the DOM var newLink = link.clone(true); newLink.insertBefore($('.bef-checkboxes, .bef-tree', this)); // If all checkboxes are already checked by default then switch to Select None if ($('input:checkbox:checked', this).length == $('input:checkbox', this).length) { newLink.click(); } }) ; } // Check for and initialize datepickers if (Drupal.settings.better_exposed_filters.datepicker && // This setting is undefined when "bef_datepicker" setting is array of // false values and as a result "ui.datepicker" library does not get // included. typeof Drupal.settings.better_exposed_filters.datepicker_options.dateformat !== 'undefined') { // Note: JavaScript does not treat "" as null if (Drupal.settings.better_exposed_filters.datepicker_options.dateformat) { $('.bef-datepicker').datepicker({ dateFormat: Drupal.settings.better_exposed_filters.datepicker_options.dateformat }); } else { $('.bef-datepicker').datepicker(); } } } // attach: function() { }; // Drupal.behaviors.better_exposed_filters = { Drupal.behaviors.betterExposedFiltersAllNoneNested = { attach:function (context, settings) { $('.form-checkboxes.bef-select-all-none-nested li').has('ul').once('bef-all-none-nested', function () { $(this) // To respect term depth, check/uncheck child term checkboxes. .find('input.form-checkboxes:first') .click(function() { $(this).parents('li:first').find('ul input.form-checkboxes').attr('checked', $(this).attr('checked')); }) .end() // When a child term is checked or unchecked, set the parent term's // status. .find('ul input.form-checkboxes') .click(function() { var checked = $(this).attr('checked'); // Determine the number of unchecked sibling checkboxes. var ct = $(this).parents('ul:first').find('input.form-checkboxes:not(:checked)').size(); // If the child term is unchecked, uncheck the parent. // If all sibling terms are checked, check the parent. if (!checked || !ct) { $(this).parents('li:first').parents('li:first').find('input.form-checkboxes:first').attr('checked', checked); } }); }); } } Drupal.behaviors.better_exposed_filters_slider = { attach: function(context, settings) { if (Drupal.settings.better_exposed_filters.slider) { $.each(Drupal.settings.better_exposed_filters.slider_options, function(i, sliderOptions) { // Only make one slider per filter. $("#" + sliderOptions.viewId + " #edit-" + sliderOptions.id + "-wrapper").once('slider-filter', function() { var $input = $(this).find('input[type=text]'); // This is a "between" or "not between" filter with two values. if ($input.length == 2) { var $min = $input.parent().find('input#edit-' + sliderOptions.id + '-min'), $max = $input.parent().find('input#edit-' + sliderOptions.id + '-max'), default_min, default_max; if (!$min.length || !$max.length) { return; } // Get the default values. // We use slider min & max if there are no defaults. default_min = parseFloat(($min.val() == '') ? sliderOptions.min : $min.val(), 10); default_max = parseFloat(($max.val() == '') ? sliderOptions.max : $max.val(), 10); // Set the element value in case we are using the slider min & max. $min.val(default_min); $max.val(default_max); $min.parents('div.views-widget').after( $('
').slider({ range: true, min: parseFloat(sliderOptions.min, 10), max: parseFloat(sliderOptions.max, 10), step: parseFloat(sliderOptions.step, 10), animate: sliderOptions.animate ? sliderOptions.animate : false, orientation: sliderOptions.orientation, values: [default_min, default_max], // Update the textfields as the sliders are moved slide: function (event, ui) { $min.val(ui.values[0]); $max.val(ui.values[1]); }, // This fires when the value is set programmatically or the // stop event fires. // This takes care of the case that a user enters a value // into the text field that is not a valid step of the slider. // In that case the slider will go to the nearest step and // this change event will update the text area. change: function (event, ui) { $min.val(ui.values[0]); $max.val(ui.values[1]); }, // Attach stop listeners. stop: function(event, ui) { // Click the auto submit button. $(this).parents('form').find('.ctools-auto-submit-click').click(); } }) ); // Update the slider when the fields are updated. $min.blur(function() { befUpdateSlider($(this), 0, sliderOptions); }); $max.blur(function() { befUpdateSlider($(this), 1, sliderOptions); }); } // This is single value filter. else if ($input.length == 1) { if ($input.attr('id') != 'edit-' + sliderOptions.id) { return; } // Get the default value. We use slider min if there is no default. var default_value = parseFloat(($input.val() == '') ? sliderOptions.min : $input.val(), 10); // Set the element value in case we are using the slider min. $input.val(default_value); $input.parents('div.views-widget').after( $('
').slider({ min: parseFloat(sliderOptions.min, 10), max: parseFloat(sliderOptions.max, 10), step: parseFloat(sliderOptions.step, 10), animate: sliderOptions.animate ? sliderOptions.animate : false, orientation: sliderOptions.orientation, value: default_value, // Update the textfields as the sliders are moved. slide: function (event, ui) { $input.val(ui.value); }, // This fires when the value is set programmatically or the // stop event fires. // This takes care of the case that a user enters a value // into the text field that is not a valid step of the slider. // In that case the slider will go to the nearest step and // this change event will update the text area. change: function (event, ui) { $input.val(ui.value); }, // Attach stop listeners. stop: function(event, ui) { // Click the auto submit button. $(this).parents('form').find('.ctools-auto-submit-click').click(); } }) ); // Update the slider when the field is updated. $input.blur(function() { befUpdateSlider($(this), null, sliderOptions); }); } else { return; } }) }); } } }; /* * Helper functions */ /** * Adds/Removes the highlight class from the form-item div as appropriate */ function _bef_highlight(elem, context) { $elem = $(elem, context); $elem.attr('checked') ? $elem.closest('.form-item', context).addClass('highlight') : $elem.closest('.form-item', context).removeClass('highlight'); } /** * Update a slider when a related input element is changed. * * We don't need to check whether the new value is valid based on slider min, * max, and step because the slider will do that automatically and then we * update the textfield on the slider's change event. * * We still have to make sure that the min & max values of a range slider * don't pass each other though, however once this jQuery UI bug is fixed we * won't have to. - http://bugs.jqueryui.com/ticket/3762 * * @param $el * A jQuery object of the updated element. * @param valIndex * The index of the value for a range slider or null for a non-range slider. * @param sliderOptions * The options for the current slider. */ function befUpdateSlider($el, valIndex, sliderOptions) { var val = parseFloat($el.val(), 10), currentMin = $el.parents('div.views-widget').next('.bef-slider').slider('values', 0), currentMax = $el.parents('div.views-widget').next('.bef-slider').slider('values', 1); // If we have a range slider. if (valIndex != null) { // Make sure the min is not more than the current max value. if (valIndex == 0 && val > currentMax) { val = currentMax; } // Make sure the max is not more than the current max value. if (valIndex == 1 && val < currentMin) { val = currentMin; } // If the number is invalid, go back to the last value. if (isNaN(val)) { val = $el.parents('div.views-widget').next('.bef-slider').slider('values', valIndex); } } else { // If the number is invalid, go back to the last value. if (isNaN(val)) { val = $el.parents('div.views-widget').next('.bef-slider').slider('value'); } } // Make sure we are a number again. val = parseFloat(val, 10); // Set the slider to the new value. // The slider's change event will then update the textfield again so that // they both have the same value. if (valIndex != null) { $el.parents('div.views-widget').next('.bef-slider').slider('values', valIndex, val); } else { $el.parents('div.views-widget').next('.bef-slider').slider('value', val); } } }) (jQuery);