| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 | 
/** * @file * JS functionality that creates dynamic CSS which hides permission rows and role columns. */// Wrapper normalizes 'jQuery' to '$'.;(function fpa_scope($, Drupal, window, document) {  "use strict";    var Fpa = function (context, settings) {    this.init(context, settings);        return this;  };    Fpa.prototype.selector = {    form: '#user-admin-permissions',    table : '#permissions'  };    Fpa.prototype.init = function (context, settings) {        this.drupal_html_class_cache = {};        this.dom = {};        this.attr = settings.attr;        this.filter_timeout= null;    this.filter_timeout_time = 0;        this.module_match = '*=';        this.filter_selector_cache = {      '*=': {},      '~=': {}    };        this.selector.table_base_selector = '.fpa-table-wrapper tr[' + this.attr.module + ']';    this.selector.list_counter_selector = '.fpa-perm-counter';    this.selector.list_base_selector = '.fpa-left-section li[' + this.attr.module + ']';        if (this.select(context)) {            this.prepare();            this.authenticated_role_behavior();    }  };    Fpa.prototype.styles = {    module_active_style: '{margin-right:-1px; background-color: white; border-right: solid 1px transparent;}'  };    /**   * Select all elements that are used by FPA ahead of time and cache on 'Fpa' instance.   */  Fpa.prototype.select = function (context) {        this.dom.context = $(context);        this.dom.form = this.dom.context.find(this.selector.form);        // Prevent anything else from running if the form is not found.    if (this.dom.form.length === 0) {      return false;    }        this.dom.container = this.dom.form.find('.fpa-container');        // Raw element since $().html(); does not work for <style /> elements.    this.dom.perm_style = this.dom.container.find('.fpa-perm-styles style').get(0);    this.dom.role_style = this.dom.container.find('.fpa-role-styles style').get(0);        this.dom.section_left = this.dom.container.find('.fpa-left-section');    this.dom.section_right = this.dom.container.find('.fpa-right-section');        this.dom.table_wrapper = this.dom.section_right.find('.fpa-table-wrapper');    this.dom.table = this.dom.table_wrapper.find(this.selector.table);        this.dom.module_list = this.dom.section_left.find('ul');        this.dom.filter_form = this.dom.container.find('.fpa-filter-form');        this.dom.filter = this.dom.filter_form.find('input[type="text"]');    this.dom.role_select = this.dom.filter_form.find('select');    this.dom.checked_status = this.dom.filter_form.find('input[type="checkbox"]');        return true;  };    /**   * Prepares a string for use as a CSS identifier (element, class, or ID name).   *    * @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_clean_css_identifier/7   */  Fpa.prototype.drupal_clean_css_identifier = function (str) {        return str    // replace ' ', '_', '/', '[' with '-'    .replace(/[ _\/\[]/g, '-')    // replace ']' with ''    .replace(/\]/g, '')    // Valid characters in a CSS identifier are:    // - the hyphen (U+002D)    // - a-z (U+0030 - U+0039)    // - A-Z (U+0041 - U+005A)    // - the underscore (U+005F)    // - 0-9 (U+0061 - U+007A)    // - ISO 10646 characters U+00A1 and higher    // We strip out any character not in the above list.    .replace(/[^\u002D\u0030-\u0039\u0041-\u005A\u005F\u0061-\u007A\u00A1-\uFFFF]/, '');  };    /**   * Prepares a string for use as a valid class name.   *    * @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_html_class/7   */  Fpa.prototype.drupal_html_class = function (str) {        if (this.drupal_html_class_cache[str] === undefined) {      this.drupal_html_class_cache[str] = this.drupal_clean_css_identifier(str.toLowerCase());    }        return this.drupal_html_class_cache[str];  };    /**   * Handles applying styles to <style /> tags.   */  Fpa.prototype.set_style = (function () {        // Feature detection. Mainly for IE8.    if ($('<style type="text/css" />').get(0).styleSheet) {      return function (element, styles) {                element.styleSheet.cssText = styles;      };    }        // Default that works in modern browsers.    return function (element, styles) {      element.innerHTML = styles;    };  })();    /**   * Callback for click events on module list.   */  Fpa.prototype.filter_module = function (e) {        e.preventDefault();    e.stopPropagation();        var $this = $(e.currentTarget);        this.dom.filter.val([            // remove current module filter string.      this.dom.filter.val().replace(/(@.*)/, ''),            // remove trailing @ as that means no module; clean 'All' filter value      $this.attr(this.attr.module) !== '' ? '@' + $this.find('a[href]').text() : ''          ].join(''));         /**     * ~= matches exactly one whitespace separated word in attribute.     *      * @see http://www.w3.org/TR/CSS2/selector.html#matching-attrs     */    this.module_match = '~=';        this.filter();  };    Fpa.prototype.build_filter_selectors = function (filter_string) {        // Extracts 'permissions@module', trimming leading and trailing whitespace.    var matches = filter_string.match(/^\s*([^@]*)@?(.*?)\s*$/i);        matches.shift(); // Remove whole match item.        var safe_matches = $.map(matches, $.proxy(this.drupal_html_class, this));        this.filter_selector_cache[this.module_match][filter_string] = [      safe_matches[0].length > 0 ? '[' + this.attr.permission          + '*="' + safe_matches[0] + '"]' : '',      safe_matches[1].length > 0 ? '[' + this.attr.module + this.module_match + '"' + safe_matches[1] + '"]' : ''    ];        return this.filter_selector_cache[this.module_match][filter_string];  };    Fpa.prototype.get_filter_selectors = function (filter_string) {        filter_string = filter_string || this.dom.filter.val();        return this.filter_selector_cache[this.module_match][filter_string] || this.build_filter_selectors(filter_string);  };    Fpa.prototype.permission_grid_styles = function (filters) {        filters = filters || this.get_filter_selectors();        var checked_filters = this.build_checked_selectors();        var styles = [      this.selector.table_base_selector,      '{display: none;}'    ];        for (var i = 0; i < checked_filters.length; i++) {            styles = styles.concat([        this.selector.table_base_selector,                checked_filters[i],                filters[0],        filters[1],        '{display: table-row;}'      ]);    }        return styles.join('');      };    Fpa.prototype.counter_styles = function (filters) {        filters = filters || this.get_filter_selectors();        return [      this.selector.list_counter_selector,      '{display: none;}',            this.selector.list_counter_selector,      filters[0],      '{display: inline;}'    ].join('');      };    Fpa.prototype.module_list_styles = function (filters) {        filters = filters || this.get_filter_selectors();        return [       this.selector.list_base_selector,      (filters[1].length > 0 ? filters[1] : '[' + this.attr.module + '=""]'),      this.styles.module_active_style    ].join('');      };    Fpa.prototype.filter = function () {        var perm = this.dom.filter.val();        $.cookie('fpa_filter', perm, {path: '/'});    $.cookie('fpa_module_match', this.module_match, {path: '/'});              this.save_filters();        var filter_selector = this.get_filter_selectors(perm);        this.set_style(this.dom.perm_style, [            this.permission_grid_styles(filter_selector),            this.counter_styles(filter_selector),            this.module_list_styles(filter_selector)          ].join(''));  };    Fpa.prototype.build_role_selectors = function (roles) {        roles = roles || this.dom.role_select.val();        var selectors = ['*'];        if ($.inArray('*', roles) === -1) {            selectors = $.map(roles, $.proxy(function (value, index) {                return '[' + this.attr.role + '="' + value + '"]';              }, this));    }        return selectors;  };    Fpa.prototype.build_checked_selectors = function (roles) {        roles = roles || this.dom.role_select.val();        var checked_boxes = $.map(this.dom.checked_status, function (element, index) {      return element.checked ? $(element).val() : null;    });        var selectors = [''];        if ($.inArray('*', roles) !== -1) {      roles = $.map(this.dom.role_select.find('option').not('[value="*"]'), $.proxy(function (element, index) {                return $(element).attr('value');              }, this));    }        if (checked_boxes.length != this.dom.checked_status.length) {            selectors = $.map(roles, $.proxy(function (value, index) {                return $.map(checked_boxes, $.proxy(function (checked_attr, index) {                    return '[' + checked_attr + '~="' + value + '"]';                  }, this));              }, this));    }        return selectors;  };    // Even handler for role selection.  Fpa.prototype.filter_roles = function () {        this.save_filters();        var values = this.dom.role_select.val() || [];    var role_style_code = [];        $.cookie('fpa_roles', JSON.stringify(values), {path: '/'});        // Only filter if "All Roles" is not selected.    if ($.inArray('*', values) === -1) {            role_style_code.push('.fpa-table-wrapper [' + this.attr.role + '] {display: none;}');            if (values.length > 0) {                var role_selectors = this.build_role_selectors(values);                role_style_code = role_style_code.concat($.map(role_selectors, $.proxy(function (value, index) {                    return '.fpa-table-wrapper ' + value + ' {display: table-cell;}';                  }, this)));                // Ensure right border on last visible role.        role_style_code.push('.fpa-table-wrapper ' + role_selectors.pop() + ' {border-right: 1px solid #bebfb9;}');      }      else {        role_style_code.push('td[class="permission"] {border-right: 1px solid #bebfb9;}');      }          }        this.set_style(this.dom.role_style, role_style_code.join(''));        this.filter();  };    /**   * Prevent the current filter from being cleared on form reset.   */  Fpa.prototype.save_filters = function () {        /**     * element.defaultValue is what 'text' elements reset to.     *      * @link http://www.w3.org/TR/REC-DOM-Level-1/level-one-html.html#ID-26091157     */    this.dom.filter.get(0).defaultValue = this.dom.filter.val();        /**     * element.defaultSelected is what 'option' elements reset to.     *      * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-html.html#ID-37770574     */    this.dom.role_select.find('option').each(function (index, element) {      element.defaultSelected = element.selected;    });        /**     * element.defaultChecked is what 'checkbox' elements reset to.     *      * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-html.html#ID-20509171     */    this.dom.checked_status.each(function (index, element) {      element.defaultChecked = element.checked;    });  };    Fpa.prototype.prepare = function () {        this.filter_timeout_time = Math.min(this.dom.table.find('tr').length, 200);        this.dom.form      .delegate('.fpa-toggle-container a', 'click', $.proxy(function fpa_toggle(e) {        e.preventDefault();                var toggle_class = $(e.currentTarget).attr('fpa-toggle-class');                this.dom.container.toggleClass(toggle_class).hasClass(toggle_class);              }, this))    ;        this.dom.section_left      .delegate('li', 'click', $.proxy(this.filter_module, this))    ;        this.dom.filter      // Prevent Enter/Return from submitting form.      .keypress(function fpa_prevent_form_submission(e) {        if (e.which === 13) {          e.preventDefault();          e.stopPropagation();        }      })      // Prevent non-character keys from triggering filter.      .keyup($.proxy(function fpa_filter_keyup(e) {                // Prevent ['Enter', 'Shift', 'Ctrl', 'Alt'] from triggering filter.        if ($.inArray(e.which, [13, 16, 17, 18]) === -1) {                    window.clearTimeout(this.filter_timeout);                    this.filter_timeout = window.setTimeout($.proxy(function () {                        this.dom.table_wrapper              .detach()              .each($.proxy(function (index, element) {                                this.module_match = '*=';                                this.filter();              }, this))              .appendTo(this.dom.section_right)            ;                      }, this), this.filter_timeout_time);        }      }, this))    ;        // Handle links to sections on permission admin page.    this.dom.form      .delegate('a[href*="admin/people/permissions#"]', 'click', $.proxy(function fpa_inter_page_links_click(e) {        e.preventDefault();        e.stopPropagation();                this.dom.module_list          .find('li[' + this.attr.module + '~="' + this.drupal_html_class(e.currentTarget.hash.substring(8)) + '"]')          .click()        ;                $('body').scrollTop(this.dom.container.position().top);      }, this))    ;        // Handler for links that use #hash and can't be capture server side.    if(window.location.hash.indexOf('module-') === 1) {            this.dom.module_list        .find('li[' + this.attr.module + '~="' + this.drupal_html_class(window.location.hash.substring(8)) + '"]')        .click()      ;    }        /**     * Reset authenticated role behavior when form resets.     *      * @todo should this be synchronous? Would have to trigger reset on elements while detached.     */    this.dom.form.bind('reset', $.proxy(function fpa_form_reset(e) {            // Wait till after the form elements have been reset.      window.setTimeout($.proxy(function fpa_fix_authenticated_behavior() {                this.dom.table_wrapper          .detach() // Don't make numerous changes while elements are in the rendered DOM.          .each($.proxy(function (index, element) {                        $(element)              .find('input[type="checkbox"].rid-2')              .each(this.dummy_checkbox_behavior)            ;          }, this))          .appendTo(this.dom.section_right)        ;              }, this), 0);    }, this));            // Role checkboxes toggle all visible permissions for this column.    this.dom.section_right      .delegate('th[' + this.attr.role + '] input[type="checkbox"].fpa-checkboxes-toggle', 'change', $.proxy(function fpa_role_permissions_toggle(e) {                var $this = $(e.currentTarget);                // Get visible rows selectors.        var filters = this.get_filter_selectors(this.dom.filter.val());                this.dom.table_wrapper          .detach()          .each($.proxy(function (index, element) {                        var rid = $this.closest('[' + this.attr.role + ']').attr(this.attr.role);                        $(element)              .find([                'tr' + filters.join(''),                'td.checkbox[' + this.attr.role + '="' + rid + '"]',                'input[type="checkbox"][name]'              ].join(' ')) // Array is easier to read, separated for descendant selectors.                            .attr('checked', $this.attr('checked'))                            .filter('.rid-2') // Following only applies to "Authenticated User" role.              .each(this.dummy_checkbox_behavior)            ;          }, this))          .appendTo(this.dom.section_right)        ;              }, this))    ;        // Permission checkboxes toggle all visible permissions for this row.    this.dom.section_right      .delegate('td.permission input[type="checkbox"].fpa-checkboxes-toggle', 'change', $.proxy(function fpa_role_permissions_toggle(e) {                // Get visible rows selectors.                var $row = $(e.currentTarget).closest('tr');                $row.prev('tr').after(                    $row          .detach()          .each($.proxy(function (index, element) {                        $(element)              .find('td.checkbox')              .filter(this.build_role_selectors().join(','))              .find('input[type="checkbox"][name]')                            .attr('checked', e.currentTarget.checked)                            .filter('.rid-2') // Following only applies to "Authenticated User" role.              .each(this.dummy_checkbox_behavior)            ;                      }, this))        );              }, this))    ;        // Clear contents of search field and reset visible permissions.    this.dom.section_right      .delegate('.fpa-clear-search', 'click', $.proxy(function (e) {                this.dom.filter          .val('')        ;                this.filter();      }, this))    ;        // Change visible roles.    this.dom.role_select      .bind('change blur', $.proxy(this.filter_roles, this))    ;        this.dom.checked_status      .bind('change', $.proxy(function (e) {                this.save_filters();                this.filter();              }, this))    ;    /**     * System name is not normally selectable because its a pseudo-element.     *     * This detects clicks directly on the TR, which happens when a click is on     * the pseudo-element, and displays a prompt() with the system name as the     * pre-populated value.     */    this.dom.table      .delegate('tr[' + this.attr.system_name + ']', 'click', $.proxy(function (e) {        var $target = $(e.target);        if ($target.is('tr[' + this.attr.system_name + ']')) {          window.prompt('You can grab the system name here', $target.attr(this.attr.system_name));        }      }, this))    ;        // Focus on element takes long time, bump after normal execution.    window.setTimeout($.proxy(function fpa_filter_focus() {      this.dom.filter.focus();    }, this), 0);      };    /**   * Event handler/iterator.   *    * Should not be $.proxy()'d.   */  Fpa.prototype.dummy_checkbox_behavior = function () {    // 'this' refers to the element, not the 'Fpa' instance.    $(this).closest('tr').toggleClass('fpa-authenticated-role-behavior', this.checked);  };    Fpa.prototype.authenticated_role_behavior = function () {        this.dom.table_wrapper      .delegate('input[type=checkbox].rid-2', 'mousedown', function (e) {                $(e.currentTarget).unbind('click.permissions');      })      .delegate('input[type=checkbox].rid-2', 'change.fpa_authenticated_role', this.dummy_checkbox_behavior)    ;  };    Drupal.behaviors.fpa = {    attach: function (context, settings) {            // Add touch-screen styling for checkboxes to make easier to use.      if (document.documentElement.ontouchstart !== undefined) {        $(document.body).addClass('fpa-mobile');      }            // Fix table sticky table headers width due to changes in visible roles.      $(window)        .bind('scroll', function fpa_fix_tableheader(e) {          $(e.currentTarget).triggerHandler('resize.drupal-tableheader');        })      ;          new Fpa(context, settings.fpa);    }  };    // Override Drupal core's Authenticated role checkbox behavior.  Drupal.behaviors.permissions.attach = $.noop;    // Drupal.behaviors.formUpdated.attach = $.noop;})(jQuery, Drupal, window, document);
 |