3 * Some basic behaviors and utility functions for Views UI.
7 Drupal
.behaviors
.viewsUiEditView
= {};
10 * Improve the user experience of the views edit interface.
12 Drupal
.behaviors
.viewsUiEditView
.attach = function (context
, settings
) {
13 // Only show the SQL rewrite warning when the user has chosen the
14 // corresponding checkbox.
15 jQuery('#edit-query-options-disable-sql-rewrite').click(function () {
16 jQuery('.sql-rewrite-warning').toggleClass('js-hide');
20 Drupal
.behaviors
.viewsUiAddView
= {};
23 * In the add view wizard, use the view name to prepopulate form fields such as
24 * page title and menu link.
26 Drupal
.behaviors
.viewsUiAddView
.attach = function (context
, settings
) {
28 var exclude
, replace
, suffix
;
29 // Set up regular expressions to allow only numbers, letters, and dashes.
30 exclude
= new RegExp('[^a-z0-9\\-]+', 'g');
33 // The page title, block title, and menu link fields can all be prepopulated
34 // with the view name - no regular expression needed.
35 var $fields
= $(context
).find('[id^="edit-page-title"], [id^="edit-block-title"], [id^="edit-page-link-properties-title"]');
37 if (!this.fieldsFiller
) {
38 this.fieldsFiller
= new Drupal
.viewsUi
.FormFieldFiller($fields
);
41 // After an AJAX response, this.fieldsFiller will still have event
42 // handlers bound to the old version of the form fields (which don't exist
43 // anymore). The event handlers need to be unbound and then rebound to the
44 // new markup. Note that jQuery.live is difficult to make work in this
45 // case because the IDs of the form fields change on every AJAX response.
46 this.fieldsFiller
.rebind($fields
);
50 // Prepopulate the path field with a URLified version of the view name.
51 var $pathField
= $(context
).find('[id^="edit-page-path"]');
52 if ($pathField
.length
) {
53 if (!this.pathFiller
) {
54 this.pathFiller
= new Drupal
.viewsUi
.FormFieldFiller($pathField
, exclude
, replace
);
57 this.pathFiller
.rebind($pathField
);
61 // Populate the RSS feed field with a URLified version of the view name, and
62 // an .xml suffix (to make it unique).
63 var $feedField
= $(context
).find('[id^="edit-page-feed-properties-path"]');
64 if ($feedField
.length
) {
65 if (!this.feedFiller
) {
67 this.feedFiller
= new Drupal
.viewsUi
.FormFieldFiller($feedField
, exclude
, replace
, suffix
);
70 this.feedFiller
.rebind($feedField
);
76 * Constructor for the Drupal.viewsUi.FormFieldFiller object.
78 * Prepopulates a form field based on the view name.
81 * A jQuery object representing the form field to prepopulate.
83 * Optional. A regular expression representing characters to exclude from the
86 * Optional. A string to use as the replacement value for disallowed
89 * Optional. A suffix to append at the end of the target field content.
91 Drupal
.viewsUi
.FormFieldFiller = function ($target
, exclude
, replace
, suffix
) {
93 this.source
= $('#edit-human-name');
94 this.target
= $target
;
95 this.exclude
= exclude
|| false;
96 this.replace
= replace
|| '';
97 this.suffix
= suffix
|| '';
99 // Create bound versions of this instance's object methods to use as event
100 // handlers. This will let us easily unbind those specific handlers later on.
101 // NOTE: jQuery.proxy will not work for this because it assumes we want only
102 // one bound version of an object method, whereas we need one version per
105 this.populate = function () {return self
._populate
.call(self
);};
106 this.unbind = function () {return self
._unbind
.call(self
);};
109 // Object constructor; no return value.
113 * Bind the form-filling behavior.
115 Drupal
.viewsUi
.FormFieldFiller
.prototype.bind = function () {
117 // Populate the form field when the source changes.
118 this.source
.bind('keyup.viewsUi change.viewsUi', this.populate
);
119 // Quit populating the field as soon as it gets focus.
120 this.target
.bind('focus.viewsUi', this.unbind
);
124 * Get the source form field value as altered by the passed-in parameters.
126 Drupal
.viewsUi
.FormFieldFiller
.prototype.getTransliterated = function () {
127 var from = this.source
.val();
129 from = from.toLowerCase().replace(this.exclude
, this.replace
);
131 return from + this.suffix
;
135 * Populate the target form field with the altered source field value.
137 Drupal
.viewsUi
.FormFieldFiller
.prototype._populate = function () {
138 var transliterated
= this.getTransliterated();
139 this.target
.val(transliterated
);
143 * Stop prepopulating the form fields.
145 Drupal
.viewsUi
.FormFieldFiller
.prototype._unbind = function () {
146 this.source
.unbind('keyup.viewsUi change.viewsUi', this.populate
);
147 this.target
.unbind('focus.viewsUi', this.unbind
);
151 * Bind event handlers to the new form fields, after they're replaced via AJAX.
153 Drupal
.viewsUi
.FormFieldFiller
.prototype.rebind = function ($fields
) {
154 this.target
= $fields
;
158 Drupal
.behaviors
.addItemForm
= {};
159 Drupal
.behaviors
.addItemForm
.attach = function (context
) {
161 // The add item form may have an id of views-ui-add-item-form--n.
162 var $form
= $(context
).find('form[id^="views-ui-add-item-form"]').first();
163 // Make sure we don't add more than one event handler to the same form.
164 $form
= $form
.once('views-ui-add-item-form');
166 new Drupal
.viewsUi
.addItemForm($form
);
170 Drupal
.viewsUi
.addItemForm = function($form
) {
172 this.$form
.find('.views-filterable-options :checkbox').click(jQuery
.proxy(this.handleCheck
, this));
173 // Find the wrapper of the displayed text.
174 this.$selected_div
= this.$form
.find('.views-selected-options').parent();
175 this.$selected_div
.hide();
176 this.checkedItems
= [];
179 Drupal
.viewsUi
.addItemForm
.prototype.handleCheck = function (event
) {
180 var $target
= jQuery(event
.target
);
181 var label
= jQuery
.trim($target
.next().text());
182 // Add/remove the checked item to the list.
183 if ($target
.is(':checked')) {
184 this.$selected_div
.show();
185 this.checkedItems
.push(label
);
188 var length
= this.checkedItems
.length
;
189 var position
= jQuery
.inArray(label
, this.checkedItems
);
190 // Delete the item from the list and take sure that the list doesn't have undefined items left.
191 for (var i
= 0; i
< this.checkedItems
.length
; i
++) {
193 this.checkedItems
.splice(i
, 1);
198 // Hide it again if none item is selected.
199 if (this.checkedItems
.length
== 0) {
200 this.$selected_div
.hide();
203 this.refreshCheckedItems();
208 * Refresh the display of the checked items.
210 Drupal
.viewsUi
.addItemForm
.prototype.refreshCheckedItems = function() {
211 // Perhaps we should precache the text div, too.
212 this.$selected_div
.find('.views-selected-options').html(Drupal
.checkPlain(this.checkedItems
.join(', ')));
213 Drupal
.viewsUi
.resizeModal('', true);
218 * The input field items that add displays must be rendered as <input> elements.
219 * The following behavior detaches the <input> elements from the DOM, wraps them
220 * in an unordered list, then appends them to the list of tabs.
222 Drupal
.behaviors
.viewsUiRenderAddViewButton
= {};
224 Drupal
.behaviors
.viewsUiRenderAddViewButton
.attach = function (context
, settings
) {
226 // Build the add display menu and pull the display input buttons into it.
227 var $menu
= $('#views-display-menu-tabs', context
).once('views-ui-render-add-view-button-processed');
232 var $addDisplayDropdown
= $('<li class="add"><a href="#"><span class="icon add"></span>' + Drupal
.t('Add') + '</a><ul class="action-list" style="display:none;"></ul></li>');
233 var $displayButtons
= $menu
.nextAll('input.add-display').detach();
234 $displayButtons
.appendTo($addDisplayDropdown
.find('.action-list')).wrap('<li>')
235 .parent().first().addClass('first').end().last().addClass('last');
236 // Remove the 'Add ' prefix from the button labels since they're being palced
237 // in an 'Add' dropdown.
238 // @todo This assumes English, but so does $addDisplayDropdown above. Add
239 // support for translation.
240 $displayButtons
.each(function () {
241 var label
= $(this).val();
242 if (label
.substr(0, 4) == 'Add ') {
243 $(this).val(label
.substr(4));
246 $addDisplayDropdown
.appendTo($menu
);
248 // Add the click handler for the add display button
249 $('li.add > a', $menu
).bind('click', function (event
) {
250 event
.preventDefault();
251 var $trigger
= $(this);
252 Drupal
.behaviors
.viewsUiRenderAddViewButton
.toggleMenu($trigger
);
254 // Add a mouseleave handler to close the dropdown when the user mouses
255 // away from the item. We use mouseleave instead of mouseout because
256 // the user is going to trigger mouseout when she moves from the trigger
257 // link to the sub menu items.
259 // We use the 'li.add' selector because the open class on this item will be
260 // toggled on and off and we want the handler to take effect in the cases
261 // that the class is present, but not when it isn't.
262 $menu
.delegate('li.add', 'mouseleave', function (event
) {
264 var $trigger
= $this.children('a[href="#"]');
265 if ($this.children('.action-list').is(':visible')) {
266 Drupal
.behaviors
.viewsUiRenderAddViewButton
.toggleMenu($trigger
);
272 * @note [@jessebeach] I feel like the following should be a more generic function and
273 * not written specifically for this UI, but I'm not sure where to put it.
275 Drupal
.behaviors
.viewsUiRenderAddViewButton
.toggleMenu = function ($trigger
) {
276 $trigger
.parent().toggleClass('open');
277 $trigger
.next().slideToggle('fast');
281 Drupal
.behaviors
.viewsUiSearchOptions
= {};
283 Drupal
.behaviors
.viewsUiSearchOptions
.attach = function (context
) {
285 // The add item form may have an id of views-ui-add-item-form--n.
286 var $form
= $(context
).find('form[id^="views-ui-add-item-form"]').first();
287 // Make sure we don't add more than one event handler to the same form.
288 $form
= $form
.once('views-ui-filter-options');
290 new Drupal
.viewsUi
.OptionsSearch($form
);
295 * Constructor for the viewsUi.OptionsSearch object.
297 * The OptionsSearch object filters the available options on a form according
298 * to the user's search term. Typing in "taxonomy" will show only those options
299 * containing "taxonomy" in their label.
301 Drupal
.viewsUi
.OptionsSearch = function ($form
) {
303 // Add a keyup handler to the search box.
304 this.$searchBox
= this.$form
.find('#edit-options-search');
305 this.$searchBox
.keyup(jQuery
.proxy(this.handleKeyup
, this));
306 // Get a list of option labels and their corresponding divs and maintain it
307 // in memory, so we have as little overhead as possible at keyup time.
308 this.options
= this.getOptions(this.$form
.find('.filterable-option'));
309 // Restripe on initial loading.
311 // Trap the ENTER key in the search box so that it doesn't submit the form.
312 this.$searchBox
.keypress(function(event
) {
313 if (event
.which
== 13) {
314 event
.preventDefault();
320 * Assemble a list of all the filterable options on the form.
323 * A jQuery object representing the rows of filterable options to be
324 * shown and hidden depending on the user's search terms.
326 Drupal
.viewsUi
.OptionsSearch
.prototype.getOptions = function ($allOptions
) {
328 var i
, $label
, $description
, $option
;
330 var length
= $allOptions
.length
;
331 for (i
= 0; i
< length
; i
++) {
332 $option
= $($allOptions
[i
]);
333 $label
= $option
.find('label');
334 $description
= $option
.find('div.description');
336 // Search on the lowercase version of the label text + description.
337 'searchText': $label
.text().toLowerCase() + " " + $description
.text().toLowerCase(),
338 // Maintain a reference to the jQuery object for each row, so we don't
339 // have to create a new object inside the performance-sensitive keyup
348 * Keyup handler for the search box that hides or shows the relevant options.
350 Drupal
.viewsUi
.OptionsSearch
.prototype.handleKeyup = function (event
) {
351 var found
, i
, j
, option
, search
, words
, wordsLength
, zebraClass
, zebraCounter
;
353 // Determine the user's search query. The search text has been converted to
355 search
= (this.$searchBox
.val() || '').toLowerCase();
356 words
= search
.split(' ');
357 wordsLength
= words
.length
;
359 // Start the counter for restriping rows.
362 // Search through the search texts in the form for matching text.
363 var length
= this.options
.length
;
364 for (i
= 0; i
< length
; i
++) {
365 // Use a local variable for the option being searched, for performance.
366 option
= this.options
[i
];
368 // Each word in the search string has to match the item in order for the
370 for (j
= 0; j
< wordsLength
; j
++) {
371 if (option
.searchText
.indexOf(words
[j
]) === -1) {
376 // Show the checkbox row, and restripe it.
377 zebraClass
= (zebraCounter
% 2) ? 'odd' : 'even';
379 option
.$div
.removeClass('even odd');
380 option
.$div
.addClass(zebraClass
);
384 // The search string wasn't found; hide this item.
391 Drupal
.behaviors
.viewsUiPreview
= {};
392 Drupal
.behaviors
.viewsUiPreview
.attach = function (context
, settings
) {
395 // Only act on the edit view form.
396 var contextualFiltersBucket
= $('.views-display-column .views-ui-display-tab-bucket.contextual-filters', context
);
397 if (contextualFiltersBucket
.length
== 0) {
401 // If the display has no contextual filters, hide the form where you enter
402 // the contextual filters for the live preview. If it has contextual filters,
404 var contextualFilters
= $('.views-display-setting a', contextualFiltersBucket
);
405 if (contextualFilters
.length
) {
406 $('#preview-args').parent().show();
409 $('#preview-args').parent().hide();
412 // Executes an initial preview.
413 if ($('#edit-displays-live-preview').once('edit-displays-live-preview').is(':checked')) {
414 $('#preview-submit').once('edit-displays-live-preview').click();
419 Drupal
.behaviors
.viewsUiRearrangeFilter
= {};
420 Drupal
.behaviors
.viewsUiRearrangeFilter
.attach = function (context
, settings
) {
422 // Only act on the rearrange filter form.
423 if (typeof Drupal
.tableDrag
== 'undefined' || typeof Drupal
.tableDrag
['views-rearrange-filters'] == 'undefined') {
427 var table
= $('#views-rearrange-filters', context
).once('views-rearrange-filters');
428 var operator
= $('.form-item-filter-groups-operator', context
).once('views-rearrange-filters');
430 new Drupal
.viewsUi
.rearrangeFilterHandler(table
, operator
);
435 * Improve the UI of the rearrange filters dialog box.
437 Drupal
.viewsUi
.rearrangeFilterHandler = function (table
, operator
) {
439 // Keep a reference to the <table> being altered and to the div containing
440 // the filter groups operator dropdown (if it exists).
442 this.operator
= operator
;
443 this.hasGroupOperator
= this.operator
.length
> 0;
445 // Keep a reference to all draggable rows within the table.
446 this.draggableRows
= $('.draggable', table
);
448 // Keep a reference to the buttons for adding and removing filter groups.
449 this.addGroupButton
= $('input#views-add-group');
450 this.removeGroupButtons
= $('input.views-remove-group', table
);
452 // Add links that duplicate the functionality of the (hidden) add and remove
454 this.insertAddRemoveFilterGroupLinks();
456 // When there is a filter groups operator dropdown on the page, create
457 // duplicates of the dropdown between each pair of filter groups.
458 if (this.hasGroupOperator
) {
459 this.dropdowns
= this.duplicateGroupsOperator();
460 this.syncGroupsOperators();
463 // Add methods to the tableDrag instance to account for operator cells (which
464 // span multiple rows), the operator labels next to each filter (e.g., "And"
465 // or "Or"), the filter groups, and other special aspects of this tableDrag
467 this.modifyTableDrag();
469 // Initialize the operator labels (e.g., "And" or "Or") that are displayed
470 // next to the filters in each group, and bind a handler so that they change
471 // based on the values of the operator dropdown within that group.
472 this.redrawOperatorLabels();
473 $('.views-group-title select', table
)
474 .once('views-rearrange-filter-handler')
475 .bind('change.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
477 // Bind handlers so that when a "Remove" link is clicked, we:
478 // - Update the rowspans of cells containing an operator dropdown (since they
479 // need to change to reflect the number of rows in each group).
480 // - Redraw the operator labels next to the filters in the group (since the
481 // filter that is currently displayed last in each group is not supposed to
482 // have a label display next to it).
483 $('a.views-groups-remove-link', this.table
)
484 .once('views-rearrange-filter-handler')
485 .bind('click.views-rearrange-filter-handler', $.proxy(this, 'updateRowspans'))
486 .bind('click.views-rearrange-filter-handler', $.proxy(this, 'redrawOperatorLabels'));
490 * Insert links that allow filter groups to be added and removed.
492 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.insertAddRemoveFilterGroupLinks = function () {
495 // Insert a link for adding a new group at the top of the page, and make it
496 // match the action links styling used in a typical page.tpl.php. Note that
497 // Drupal does not provide a theme function for this markup, so this is the
499 $('<ul class="action-links"><li><a id="views-add-group-link" href="#">' + this.addGroupButton
.val() + '</a></li></ul>')
500 .prependTo(this.table
.parent())
501 // When the link is clicked, dynamically click the hidden form button for
502 // adding a new filter group.
503 .once('views-rearrange-filter-handler')
504 .bind('click.views-rearrange-filter-handler', $.proxy(this, 'clickAddGroupButton'));
506 // Find each (visually hidden) button for removing a filter group and insert
507 // a link next to it.
508 var length
= this.removeGroupButtons
.length
;
509 for (i
= 0; i
< length
; i
++) {
510 var $removeGroupButton
= $(this.removeGroupButtons
[i
]);
511 var buttonId
= $removeGroupButton
.attr('id');
512 $('<a href="#" class="views-remove-group-link">' + Drupal
.t('Remove group') + '</a>')
513 .insertBefore($removeGroupButton
)
514 // When the link is clicked, dynamically click the corresponding form
516 .once('views-rearrange-filter-handler')
517 .bind('click.views-rearrange-filter-handler', {buttonId
: buttonId
}, $.proxy(this, 'clickRemoveGroupButton'));
522 * Dynamically click the button that adds a new filter group.
524 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.clickAddGroupButton = function () {
525 // Due to conflicts between Drupal core's AJAX system and the Views AJAX
526 // system, the only way to get this to work seems to be to trigger both the
527 // .mousedown() and .submit() events.
528 this.addGroupButton
.mousedown();
529 this.addGroupButton
.submit();
534 * Dynamically click a button for removing a filter group.
537 * Event being triggered, with event.data.buttonId set to the ID of the
538 * form button that should be clicked.
540 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.clickRemoveGroupButton = function (event
) {
541 // For some reason, here we only need to trigger .submit(), unlike for
542 // Drupal.viewsUi.rearrangeFilterHandler.prototype.clickAddGroupButton()
543 // where we had to trigger .mousedown() also.
544 jQuery('input#' + event
.data
.buttonId
, this.table
).submit();
549 * Move the groups operator so that it's between the first two groups, and
550 * duplicate it between any subsequent groups.
552 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.duplicateGroupsOperator = function () {
554 var dropdowns
, newRow
;
556 var titleRows
= $('tr.views-group-title'), titleRow
;
558 // Get rid of the explanatory text around the operator; its placement is
559 // explanatory enough.
560 this.operator
.find('label').add('div.description').addClass('element-invisible');
561 this.operator
.find('select').addClass('form-select');
563 // Keep a list of the operator dropdowns, so we can sync their behavior later.
564 dropdowns
= this.operator
;
566 // Move the operator to a new row just above the second group.
567 titleRow
= $('tr#views-group-title-2');
568 newRow
= $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
569 newRow
.find('td').append(this.operator
);
570 newRow
.insertBefore(titleRow
);
571 var i
, length
= titleRows
.length
;
572 // Starting with the third group, copy the operator to a new row above the
574 for (i
= 2; i
< length
; i
++) {
575 titleRow
= $(titleRows
[i
]);
576 // Make a copy of the operator dropdown and put it in a new table row.
577 var fakeOperator
= this.operator
.clone();
578 fakeOperator
.attr('id', '');
579 newRow
= $('<tr class="filter-group-operator-row"><td colspan="5"></td></tr>');
580 newRow
.find('td').append(fakeOperator
);
581 newRow
.insertBefore(titleRow
);
582 dropdowns
= dropdowns
.add(fakeOperator
);
589 * Make the duplicated groups operators change in sync with each other.
591 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.syncGroupsOperators = function () {
592 if (this.dropdowns
.length
< 2) {
593 // We only have one dropdown (or none at all), so there's nothing to sync.
597 this.dropdowns
.change(jQuery
.proxy(this, 'operatorChangeHandler'));
601 * Click handler for the operators that appear between filter groups.
603 * Forces all operator dropdowns to have the same value.
605 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.operatorChangeHandler = function (event
) {
607 var $target
= $(event
.target
);
608 var operators
= this.dropdowns
.find('select').not($target
);
610 // Change the other operators to match this new value.
611 operators
.val($target
.val());
614 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.modifyTableDrag = function () {
615 var tableDrag
= Drupal
.tableDrag
['views-rearrange-filters'];
616 var filterHandler
= this;
619 * Override the row.onSwap method from tabledrag.js.
621 * When a row is dragged to another place in the table, several things need
623 * - The row needs to be moved so that it's within one of the filter groups.
624 * - The operator cells that span multiple rows need their rowspan attributes
625 * updated to reflect the number of rows in each group.
626 * - The operator labels that are displayed next to each filter need to be
627 * redrawn, to account for the row's new location.
629 tableDrag
.row
.prototype.onSwap = function () {
630 if (filterHandler
.hasGroupOperator
) {
631 // Make sure the row that just got moved (this.group) is inside one of
632 // the filter groups (i.e. below an empty marker row or a draggable). If
633 // it isn't, move it down one.
634 var thisRow
= jQuery(this.group
);
635 var previousRow
= thisRow
.prev('tr');
636 if (previousRow
.length
&& !previousRow
.hasClass('group-message') && !previousRow
.hasClass('draggable')) {
637 // Move the dragged row down one.
638 var next
= thisRow
.next();
640 this.swap('after', next
);
643 filterHandler
.updateRowspans();
645 // Redraw the operator labels that are displayed next to each filter, to
646 // account for the row's new location.
647 filterHandler
.redrawOperatorLabels();
651 * Override the onDrop method from tabledrag.js.
653 tableDrag
.onDrop = function () {
656 // If the tabledrag change marker (i.e., the "*") has been inserted inside
657 // a row after the operator label (i.e., "And" or "Or") rearrange the items
658 // so the operator label continues to appear last.
659 var changeMarker
= $(this.oldRowElement
).find('.tabledrag-changed');
660 if (changeMarker
.length
) {
661 // Search for occurrences of the operator label before the change marker,
663 var operatorLabel
= changeMarker
.prevAll('.views-operator-label');
664 if (operatorLabel
.length
) {
665 operatorLabel
.insertAfter(changeMarker
);
669 // Make sure the "group" dropdown is properly updated when rows are dragged
670 // into an empty filter group. This is borrowed heavily from the block.js
671 // implementation of tableDrag.onDrop().
672 var groupRow
= $(this.rowObject
.element
).prevAll('tr.group-message').get(0);
673 var groupName
= groupRow
.className
.replace(/([^ ]+[ ]+)*group-([^ ]+)-message([ ]+[^ ]+)*/, '$2');
674 var groupField
= $('select.views-group-select', this.rowObject
.element
);
675 if ($(this.rowObject
.element
).prev('tr').is('.group-message') && !groupField
.is('.views-group-select-' + groupName
)) {
676 var oldGroupName
= groupField
.attr('class').replace(/([^ ]+[ ]+)*views-group-select-([^ ]+)([ ]+[^ ]+)*/, '$2');
677 groupField
.removeClass('views-group-select-' + oldGroupName
).addClass('views-group-select-' + groupName
);
678 groupField
.val(groupName
);
685 * Redraw the operator labels that are displayed next to each filter.
687 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.redrawOperatorLabels = function () {
689 for (i
= 0; i
< this.draggableRows
.length
; i
++) {
690 // Within the row, the operator labels are displayed inside the first table
691 // cell (next to the filter name).
692 var $draggableRow
= $(this.draggableRows
[i
]);
693 var $firstCell
= $('td:first', $draggableRow
);
694 if ($firstCell
.length
) {
695 // The value of the operator label ("And" or "Or") is taken from the
696 // first operator dropdown we encounter, going backwards from the current
697 // row. This dropdown is the one associated with the current row's filter
699 var operatorValue
= $draggableRow
.prevAll('.views-group-title').find('option:selected').html();
700 var operatorLabel
= '<span class="views-operator-label">' + operatorValue
+ '</span>';
701 // If the next visible row after this one is a draggable filter row,
702 // display the operator label next to the current row. (Checking for
703 // visibility is necessary here since the "Remove" links hide the removed
704 // row but don't actually remove it from the document).
705 var $nextRow
= $draggableRow
.nextAll(':visible').eq(0);
706 var $existingOperatorLabel
= $firstCell
.find('.views-operator-label');
707 if ($nextRow
.hasClass('draggable')) {
708 // If an operator label was already there, replace it with the new one.
709 if ($existingOperatorLabel
.length
) {
710 $existingOperatorLabel
.replaceWith(operatorLabel
);
712 // Otherwise, append the operator label to the end of the table cell.
714 $firstCell
.append(operatorLabel
);
717 // If the next row doesn't contain a filter, then this is the last row
718 // in the group. We don't want to display the operator there (since
719 // operators should only display between two related filters, e.g.
720 // "filter1 AND filter2 AND filter3"). So we remove any existing label
721 // that this row has.
723 $existingOperatorLabel
.remove();
730 * Update the rowspan attribute of each cell containing an operator dropdown.
732 Drupal
.viewsUi
.rearrangeFilterHandler
.prototype.updateRowspans = function () {
734 var i
, $row
, $currentEmptyRow
, draggableCount
, $operatorCell
;
735 var rows
= $(this.table
).find('tr');
736 var length
= rows
.length
;
737 for (i
= 0; i
< length
; i
++) {
739 if ($row
.hasClass('views-group-title')) {
740 // This row is a title row.
741 // Keep a reference to the cell containing the dropdown operator.
742 $operatorCell
= $($row
.find('td.group-operator'));
743 // Assume this filter group is empty, until we find otherwise.
745 $currentEmptyRow
= $row
.next('tr');
746 $currentEmptyRow
.removeClass('group-populated').addClass('group-empty');
747 // The cell with the dropdown operator should span the title row and
748 // the "this group is empty" row.
749 $operatorCell
.attr('rowspan', 2);
751 else if (($row
).hasClass('draggable') && $row
.is(':visible')) {
752 // We've found a visible filter row, so we now know the group isn't empty.
754 $currentEmptyRow
.removeClass('group-empty').addClass('group-populated');
755 // The operator cell should span all draggable rows, plus the title.
756 $operatorCell
.attr('rowspan', draggableCount
+ 1);
761 Drupal
.behaviors
.viewsFilterConfigSelectAll
= {};
764 * Add a select all checkbox, which checks each checkbox at once.
766 Drupal
.behaviors
.viewsFilterConfigSelectAll
.attach = function(context
) {
768 // Show the select all checkbox.
769 $('#views-ui-config-item-form div.form-item-options-value-all', context
).once(function() {
772 .find('input[type=checkbox]')
774 var checked
= $(this).is(':checked');
775 // Update all checkbox beside the select all checkbox.
776 $(this).parents('.form-checkboxes').find('input[type=checkbox]').each(function() {
777 $(this).attr('checked', checked
);
780 // Uncheck the select all checkbox if any of the others are unchecked.
781 $('#views-ui-config-item-form div.form-type-checkbox').not($('.form-item-options-value-all')).find('input[type=checkbox]').each(function() {
782 $(this).click(function() {
783 if ($(this).is('checked') == 0) {
784 $('#edit-options-value-all').removeAttr('checked');
791 * Ensure the desired default button is used when a form is implicitly submitted via an ENTER press on textfields, radios, and checkboxes.
793 * @see http://www.w3.org/TR/html5/association-of-controls-and-forms.html#implicit-submission
795 Drupal
.behaviors
.viewsImplicitFormSubmission
= {};
796 Drupal
.behaviors
.viewsImplicitFormSubmission
.attach = function (context
, settings
) {
798 $(':text, :password, :radio, :checkbox', context
).once('viewsImplicitFormSubmission', function() {
799 $(this).keypress(function(event
) {
800 if (event
.which
== 13) {
801 var formId
= this.form
.id
;
802 if (formId
&& settings
.viewsImplicitFormSubmission
&& settings
.viewsImplicitFormSubmission
[formId
] && settings
.viewsImplicitFormSubmission
[formId
].defaultButton
) {
803 event
.preventDefault();
804 var buttonId
= settings
.viewsImplicitFormSubmission
[formId
].defaultButton
;
805 var $button
= $('#' + buttonId
, this.form
);
806 if ($button
.length
== 1 && $button
.is(':enabled')) {
807 if (Drupal
.ajax
&& Drupal
.ajax
[buttonId
]) {
808 $button
.trigger(Drupal
.ajax
[buttonId
].element_settings
.event
);
821 * Remove icon class from elements that are themed as buttons or dropbuttons.
823 Drupal
.behaviors
.viewsRemoveIconClass
= {};
824 Drupal
.behaviors
.viewsRemoveIconClass
.attach = function (context
, settings
) {
825 jQuery('.ctools-button', context
).once('RemoveIconClass', function () {
828 $('.icon', $this).removeClass('icon');
829 $('.horizontal', $this).removeClass('horizontal');
834 * Change "Expose filter" buttons into checkboxes.
836 Drupal
.behaviors
.viewsUiCheckboxify
= {};
837 Drupal
.behaviors
.viewsUiCheckboxify
.attach = function (context
, settings
) {
839 var $buttons
= $('#edit-options-expose-button-button, #edit-options-group-button-button').once('views-ui-checkboxify');
840 var length
= $buttons
.length
;
842 for (i
= 0; i
< length
; i
++) {
843 new Drupal
.viewsUi
.Checkboxifier($buttons
[i
]);
848 * Change the default widget to select the default group according to the
849 * selected widget for the exposed group.
851 Drupal
.behaviors
.viewsUiChangeDefaultWidget
= {};
852 Drupal
.behaviors
.viewsUiChangeDefaultWidget
.attach = function (context
, settings
) {
854 function change_default_widget(multiple
) {
856 $('input.default-radios').hide();
857 $('td.any-default-radios-row').parent().hide();
858 $('input.default-checkboxes').show();
861 $('input.default-checkboxes').hide();
862 $('td.any-default-radios-row').parent().show();
863 $('input.default-radios').show();
866 // Update on widget change.
867 $('input[name="options[group_info][multiple]"]').change(function() {
868 change_default_widget($(this).attr("checked"));
870 // Update the first time the form is rendered.
871 $('input[name="options[group_info][multiple]"]').trigger('change');
875 * Attaches an expose filter button to a checkbox that triggers its click event.
878 * The DOM object representing the button to be checkboxified.
880 Drupal
.viewsUi
.Checkboxifier = function (button
) {
882 this.$button
= $(button
);
883 this.$parent
= this.$button
.parent('div.views-expose, div.views-grouped');
884 this.$input
= this.$parent
.find('input:checkbox, input:radio');
885 // Hide the button and its description.
887 this.$parent
.find('.exposed-description, .grouped-description').hide();
889 this.$input
.click($.proxy(this, 'clickHandler'));
894 * When the checkbox is checked or unchecked, simulate a button press.
896 Drupal
.viewsUi
.Checkboxifier
.prototype.clickHandler = function (e
) {
897 this.$button
.mousedown();
898 this.$button
.submit();
902 * Change the Apply button text based upon the override select state.
904 Drupal
.behaviors
.viewsUiOverrideSelect
= {};
905 Drupal
.behaviors
.viewsUiOverrideSelect
.attach = function (context
, settings
) {
907 $('#edit-override-dropdown', context
).once('views-ui-override-button-text', function() {
909 var $submit
= $('#edit-submit', context
);
910 var old_value
= $submit
.val();
912 $submit
.once('views-ui-override-button-text')
913 .bind('mouseup', function() {
914 $(this).val(old_value
);
918 $(this).bind('change', function() {
919 if ($(this).val() == 'default') {
920 $submit
.val(Drupal
.t('Apply (all displays)'));
922 else if ($(this).val() == 'default_revert') {
923 $submit
.val(Drupal
.t('Revert to default'));
926 $submit
.val(Drupal
.t('Apply (this display)'));
934 Drupal
.viewsUi
.resizeModal = function (e
, no_shrink
) {
936 var $modal
= $('.views-ui-dialog');
937 var $scroll
= $('.scroll', $modal
);
938 if ($modal
.size() == 0 || $modal
.css('display') == 'none') {
942 var maxWidth
= parseInt($(window
).width() * .85); // 70% of window
943 var minWidth
= parseInt($(window
).width() * .6); // 70% of window
945 // Set the modal to the minwidth so that our width calculation of
947 $modal
.css('width', minWidth
);
948 var width
= minWidth
;
950 // Don't let the window get more than 80% of the display high.
951 var maxHeight
= parseInt($(window
).height() * .8);
954 minHeight
= $modal
.height();
957 if (minHeight
> maxHeight
) {
958 minHeight
= maxHeight
;
963 // Calculate the height of the 'scroll' region.
964 var scrollHeight
= 0;
966 scrollHeight
+= parseInt($scroll
.css('padding-top'));
967 scrollHeight
+= parseInt($scroll
.css('padding-bottom'));
969 $scroll
.children().each(function() {
970 var w
= $(this).innerWidth();
974 scrollHeight
+= $(this).outerHeight(true);
977 // Now, calculate what the difference between the scroll and the modal
981 difference
+= parseInt($scroll
.css('padding-top'));
982 difference
+= parseInt($scroll
.css('padding-bottom'));
983 difference
+= $('.views-override').outerHeight(true);
984 difference
+= $('.views-messages').outerHeight(true);
985 difference
+= $('#views-ajax-title').outerHeight(true);
986 difference
+= $('.views-add-form-selected').outerHeight(true);
987 difference
+= $('.form-buttons', $modal
).outerHeight(true);
989 height
= scrollHeight
+ difference
;
991 if (height
> maxHeight
) {
993 scrollHeight
= maxHeight
- difference
;
995 else if (height
< minHeight
) {
997 scrollHeight
= minHeight
- difference
;
1000 if (width
> maxWidth
) {
1004 // Get where we should move content to
1005 var top
= ($(window
).height() / 2) - (height
/ 2);
1006 var left
= ($(window
).width() / 2) - (width
/ 2);
1010 'left': left
+ 'px',
1011 'width': width
+ 'px',
1012 'height': height
+ 'px'
1015 // Ensure inner popup height matches.
1016 $(Drupal
.settings
.views
.ajax
.popup
).css('height', height
+ 'px');
1019 'height': scrollHeight
+ 'px',
1020 'max-height': scrollHeight
+ 'px'
1026 jQuery(window
).bind('resize', Drupal
.viewsUi
.resizeModal
);
1027 jQuery(window
).bind('scroll', Drupal
.viewsUi
.resizeModal
);