diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index afaddeb..53ade47 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -712,7 +712,12 @@ function user_admin_permissions($form, $form_state, $rid = NULL) { // Have to build checkboxes here after checkbox arrays are built foreach ($role_names as $rid => $name) { - $form['checkboxes'][$rid] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => isset($status[$rid]) ? $status[$rid] : array()); + $form['checkboxes'][$rid] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => isset($status[$rid]) ? $status[$rid] : array(), + '#attributes' => array('class' => array('rid-' . $rid)), + ); $form['role_names'][$rid] = array('#markup' => check_plain($name), '#tree' => TRUE); } diff --git a/modules/user/user.permissions.js b/modules/user/user.permissions.js index 4ef576c..988820e 100644 --- a/modules/user/user.permissions.js +++ b/modules/user/user.permissions.js @@ -5,34 +5,64 @@ */ Drupal.behaviors.permissions = { attach: function (context) { - $('table#permissions:not(.permissions-processed)').each(function () { + var self = this; + $('table#permissions').once('permissions', function () { + // On a site with many roles and permissions, this behavior initially has + // to perform thousands of DOM manipulations to inject checkboxes and hide + // them. By detaching the table from the DOM, all operations can be + // performed without triggering internal layout and re-rendering processes + // in the browser. + var $table = $(this); + if ($table.prev().length) { + var $ancestor = $table.prev(), method = 'after'; + } + else { + var $ancestor = $table.parent(), method = 'append'; + } + $table.detach(); + // Create dummy checkboxes. We use dummy checkboxes instead of reusing // the existing checkboxes here because new checkboxes don't alter the // submitted form. If we'd automatically check existing checkboxes, the // permission table would be polluted with redundant entries. This // is deliberate, but desirable when we automatically check them. - $(':checkbox', this).not('[name^="2["]').not('[name^="1["]').each(function () { - $(this).addClass('real-checkbox'); - $('') - .attr('title', Drupal.t("This permission is inherited from the authenticated user role.")) - .insertAfter(this) - .hide(); - }); + var $dummy = $('') + .attr('title', Drupal.t("This permission is inherited from the authenticated user role.")) + .hide(); - // Helper function toggles all dummy checkboxes based on the checkboxes' - // state. If the "authenticated user" checkbox is checked, the checked - // and disabled checkboxes are shown, the real checkboxes otherwise. - var toggle = function () { - $(this).closest('tr') - .find('.real-checkbox')[this.checked ? 'hide' : 'show']().end() - .find('.dummy-checkbox')[this.checked ? 'show' : 'hide'](); - }; + $('input[type=checkbox]', this).not('.rid-2, .rid-1').addClass('real-checkbox').each(function () { + $dummy.clone().insertAfter(this); + }); // Initialize the authenticated user checkbox. - $(':checkbox[name^="2["]', this) - .click(toggle) - .each(function () { toggle.call(this); }); - }).addClass('permissions-processed'); + $('input[type=checkbox].rid-2', this) + .bind('click.permissions', self.toggle) + // .triggerHandler() cannot be used here, as it only affects the first + // element. + .each(self.toggle); + + // Re-insert the table into the DOM. + $ancestor[method]($table); + }); + }, + + /** + * Toggles all dummy checkboxes based on the checkboxes' state. + * + * If the "authenticated user" checkbox is checked, the checked and disabled + * checkboxes are shown, the real checkboxes otherwise. + */ + toggle: function () { + var authCheckbox = this, $row = $(this).closest('tr'); + // jQuery performs too many layout calculations for .hide() and .show(), + // leading to a major page rendering lag on sites with many roles and + // permissions. Therefore, we toggle visibility directly. + $row.find('.real-checkbox').each(function () { + this.style.display = (authCheckbox.checked ? 'none' : ''); + }); + $row.find('.dummy-checkbox').each(function () { + this.style.display = (authCheckbox.checked ? '' : 'none'); + }); } };