Projet

Général

Profil

Paste
Télécharger (12,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / admin_menu / admin_menu.js @ 74f6bef0

1
(function($) {
2

    
3
Drupal.admin = Drupal.admin || {};
4
Drupal.admin.behaviors = Drupal.admin.behaviors || {};
5
Drupal.admin.hashes = Drupal.admin.hashes || {};
6

    
7
/**
8
 * Core behavior for Administration menu.
9
 *
10
 * Test whether there is an administration menu is in the output and execute all
11
 * registered behaviors.
12
 */
13
Drupal.behaviors.adminMenu = {
14
  attach: function (context, settings) {
15
    // Initialize settings.
16
    settings.admin_menu = $.extend({
17
      suppress: false,
18
      margin_top: false,
19
      position_fixed: false,
20
      tweak_modules: false,
21
      tweak_permissions: false,
22
      tweak_tabs: false,
23
      destination: '',
24
      basePath: settings.basePath,
25
      hash: 0,
26
      replacements: {}
27
    }, settings.admin_menu || {});
28
    // Check whether administration menu should be suppressed.
29
    if (settings.admin_menu.suppress) {
30
      return;
31
    }
32
    var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context);
33
    // Client-side caching; if administration menu is not in the output, it is
34
    // fetched from the server and cached in the browser.
35
    if (!$adminMenu.length && settings.admin_menu.hash) {
36
      Drupal.admin.getCache(settings.admin_menu.hash, function (response) {
37
          if (typeof response == 'string' && response.length > 0) {
38
            $('body', context).append(response);
39
          }
40
          var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context);
41
          // Apply our behaviors.
42
          Drupal.admin.attachBehaviors(context, settings, $adminMenu);
43
          // Allow resize event handlers to recalculate sizes/positions.
44
          $(window).triggerHandler('resize');
45
      });
46
    }
47
    // If the menu is in the output already, this means there is a new version.
48
    else {
49
      // Apply our behaviors.
50
      Drupal.admin.attachBehaviors(context, settings, $adminMenu);
51
    }
52
  }
53
};
54

    
55
/**
56
 * Collapse fieldsets on Modules page.
57
 */
58
Drupal.behaviors.adminMenuCollapseModules = {
59
  attach: function (context, settings) {
60
    if (settings.admin_menu.tweak_modules) {
61
      $('#system-modules fieldset:not(.collapsed)', context).addClass('collapsed');
62
    }
63
  }
64
};
65

    
66
/**
67
 * Collapse modules on Permissions page.
68
 */
69
Drupal.behaviors.adminMenuCollapsePermissions = {
70
  attach: function (context, settings) {
71
    if (settings.admin_menu.tweak_permissions) {
72
      // Freeze width of first column to prevent jumping.
73
      $('#permissions th:first', context).css({ width: $('#permissions th:first', context).width() });
74
      // Attach click handler.
75
      $modules = $('#permissions tr:has(td.module)', context).once('admin-menu-tweak-permissions', function () {
76
        var $module = $(this);
77
        $module.bind('click.admin-menu', function () {
78
          // @todo Replace with .nextUntil() in jQuery 1.4.
79
          $module.nextAll().each(function () {
80
            var $row = $(this);
81
            if ($row.is(':has(td.module)')) {
82
              return false;
83
            }
84
            $row.toggleClass('element-hidden');
85
          });
86
        });
87
      });
88
      // Collapse all but the targeted permission rows set.
89
      if (window.location.hash.length) {
90
        $modules = $modules.not(':has(' + window.location.hash + ')');
91
      }
92
      $modules.trigger('click.admin-menu');
93
    }
94
  }
95
};
96

    
97
/**
98
 * Apply margin to page.
99
 *
100
 * Note that directly applying marginTop does not work in IE. To prevent
101
 * flickering/jumping page content with client-side caching, this is a regular
102
 * Drupal behavior.
103
 */
104
Drupal.behaviors.adminMenuMarginTop = {
105
  attach: function (context, settings) {
106
    if (!settings.admin_menu.suppress && settings.admin_menu.margin_top) {
107
      $('body:not(.admin-menu)', context).addClass('admin-menu');
108
    }
109
  }
110
};
111

    
112
/**
113
 * Retrieve content from client-side cache.
114
 *
115
 * @param hash
116
 *   The md5 hash of the content to retrieve.
117
 * @param onSuccess
118
 *   A callback function invoked when the cache request was successful.
119
 */
120
Drupal.admin.getCache = function (hash, onSuccess) {
121
  if (Drupal.admin.hashes.hash !== undefined) {
122
    return Drupal.admin.hashes.hash;
123
  }
124
  $.ajax({
125
    cache: true,
126
    type: 'GET',
127
    dataType: 'text', // Prevent auto-evaluation of response.
128
    global: false, // Do not trigger global AJAX events.
129
    url: Drupal.settings.admin_menu.basePath.replace(/admin_menu/, 'js/admin_menu/cache/' + hash),
130
    success: onSuccess,
131
    complete: function (XMLHttpRequest, status) {
132
      Drupal.admin.hashes.hash = status;
133
    }
134
  });
135
};
136

    
137
/**
138
 * TableHeader callback to determine top viewport offset.
139
 *
140
 * @see toolbar.js
141
 */
142
Drupal.admin.height = function() {
143
  var $adminMenu = $('#admin-menu');
144
  var height = $adminMenu.outerHeight();
145
  // In IE, Shadow filter adds some extra height, so we need to remove it from
146
  // the returned height.
147
  if ($adminMenu.css('filter') && $adminMenu.css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) {
148
    height -= $adminMenu.get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength;
149
  }
150
  return height;
151
};
152

    
153
/**
154
 * @defgroup admin_behaviors Administration behaviors.
155
 * @{
156
 */
157

    
158
/**
159
 * Attach administrative behaviors.
160
 */
161
Drupal.admin.attachBehaviors = function (context, settings, $adminMenu) {
162
  if ($adminMenu.length) {
163
    $adminMenu.addClass('admin-menu-processed');
164
    $.each(Drupal.admin.behaviors, function() {
165
      this(context, settings, $adminMenu);
166
    });
167
  }
168
};
169

    
170
/**
171
 * Apply 'position: fixed'.
172
 */
173
Drupal.admin.behaviors.positionFixed = function (context, settings, $adminMenu) {
174
  if (settings.admin_menu.position_fixed) {
175
    $adminMenu.addClass('admin-menu-position-fixed');
176
    $adminMenu.css('position', 'fixed');
177
  }
178
};
179

    
180
/**
181
 * Move page tabs into administration menu.
182
 */
183
Drupal.admin.behaviors.pageTabs = function (context, settings, $adminMenu) {
184
  if (settings.admin_menu.tweak_tabs) {
185
    var $tabs = $(context).find('ul.tabs.primary');
186
    $adminMenu.find('#admin-menu-wrapper > ul').eq(1)
187
      .append($tabs.find('li').addClass('admin-menu-tab'));
188
    $(context).find('ul.tabs.secondary')
189
      .appendTo('#admin-menu-wrapper > ul > li.admin-menu-tab.active')
190
      .removeClass('secondary');
191
    $tabs.remove();
192
  }
193
};
194

    
195
/**
196
 * Perform dynamic replacements in cached menu.
197
 */
198
Drupal.admin.behaviors.replacements = function (context, settings, $adminMenu) {
199
  for (var item in settings.admin_menu.replacements) {
200
    $(item, $adminMenu).html(settings.admin_menu.replacements[item]);
201
  }
202
};
203

    
204
/**
205
 * Inject destination query strings for current page.
206
 */
207
Drupal.admin.behaviors.destination = function (context, settings, $adminMenu) {
208
  if (settings.admin_menu.destination) {
209
    $('a.admin-menu-destination', $adminMenu).each(function() {
210
      this.search += (!this.search.length ? '?' : '&') + Drupal.settings.admin_menu.destination;
211
    });
212
  }
213
};
214

    
215
/**
216
 * Apply JavaScript-based hovering behaviors.
217
 *
218
 * @todo This has to run last.  If another script registers additional behaviors
219
 *   it will not run last.
220
 */
221
Drupal.admin.behaviors.hover = function (context, settings, $adminMenu) {
222
  // Hover emulation for IE 6.
223
  if ($.browser.msie && parseInt(jQuery.browser.version) == 6) {
224
    $('li', $adminMenu).hover(
225
      function () {
226
        $(this).addClass('iehover');
227
      },
228
      function () {
229
        $(this).removeClass('iehover');
230
      }
231
    );
232
  }
233

    
234
  // Delayed mouseout.
235
  $('li.expandable', $adminMenu).hover(
236
    function () {
237
      // Stop the timer.
238
      clearTimeout(this.sfTimer);
239
      // Display child lists.
240
      $('> ul', this)
241
        .css({left: 'auto', display: 'block'})
242
        // Immediately hide nephew lists.
243
        .parent().siblings('li').children('ul').css({left: '-999em', display: 'none'});
244
    },
245
    function () {
246
      // Start the timer.
247
      var uls = $('> ul', this);
248
      this.sfTimer = setTimeout(function () {
249
        uls.css({left: '-999em', display: 'none'});
250
      }, 400);
251
    }
252
  );
253
};
254

    
255
/**
256
 * Apply the search bar functionality.
257
 */
258
Drupal.admin.behaviors.search = function (context, settings, $adminMenu) {
259
  // @todo Add a HTML ID.
260
  var $input = $('input.admin-menu-search', $adminMenu);
261
  // Initialize the current search needle.
262
  var needle = $input.val();
263
  // Cache of all links that can be matched in the menu.
264
  var links;
265
  // Minimum search needle length.
266
  var needleMinLength = 2;
267
  // Append the results container.
268
  var $results = $('<div />').insertAfter($input);
269

    
270
  /**
271
   * Executes the search upon user input.
272
   */
273
  function keyupHandler() {
274
    var matches, $html, value = $(this).val();
275
    // Only proceed if the search needle has changed.
276
    if (value !== needle) {
277
      needle = value;
278
      // Initialize the cache of menu links upon first search.
279
      if (!links && needle.length >= needleMinLength) {
280
        // @todo Limit to links in dropdown menus; i.e., skip menu additions.
281
        links = buildSearchIndex($adminMenu.find('li:not(.admin-menu-action, .admin-menu-action li) > a'));
282
      }
283
      // Empty results container when deleting search text.
284
      if (needle.length < needleMinLength) {
285
        $results.empty();
286
      }
287
      // Only search if the needle is long enough.
288
      if (needle.length >= needleMinLength && links) {
289
        matches = findMatches(needle, links);
290
        // Build the list in a detached DOM node.
291
        $html = buildResultsList(matches);
292
        // Display results.
293
        $results.empty().append($html);
294
      }
295
    }
296
  }
297

    
298
  /**
299
   * Builds the search index.
300
   */
301
  function buildSearchIndex($links) {
302
    return $links
303
      .map(function () {
304
        var text = (this.textContent || this.innerText);
305
        // Skip menu entries that do not contain any text (e.g., the icon).
306
        if (typeof text === 'undefined') {
307
          return;
308
        }
309
        return {
310
          text: text,
311
          textMatch: text.toLowerCase(),
312
          element: this
313
        };
314
      });
315
  }
316

    
317
  /**
318
   * Searches the index for a given needle and returns matching entries.
319
   */
320
  function findMatches(needle, links) {
321
    var needleMatch = needle.toLowerCase();
322
    // Select matching links from the cache.
323
    return $.grep(links, function (link) {
324
      return link.textMatch.indexOf(needleMatch) !== -1;
325
    });
326
  }
327

    
328
  /**
329
   * Builds the search result list in a detached DOM node.
330
   */
331
  function buildResultsList(matches) {
332
    var $html = $('<ul class="dropdown admin-menu-search-results" />');
333
    $.each(matches, function () {
334
      var result = this.text;
335
      var $element = $(this.element);
336

    
337
      // Check whether there is a top-level category that can be prepended.
338
      var $category = $element.closest('#admin-menu-wrapper > ul > li');
339
      var categoryText = $category.find('> a').text()
340
      if ($category.length && categoryText) {
341
        result = categoryText + ': ' + result;
342
      }
343

    
344
      var $result = $('<li><a href="' + $element.attr('href') + '">' + result + '</a></li>');
345
      $result.data('original-link', $(this.element).parent());
346
      $html.append($result);
347
    });
348
    return $html;
349
  }
350

    
351
  /**
352
   * Highlights selected result.
353
   */
354
  function resultsHandler(e) {
355
    var $this = $(this);
356
    var show = e.type === 'mouseenter' || e.type === 'focusin';
357
    $this.trigger(show ? 'showPath' : 'hidePath', [this]);
358
  }
359

    
360
  /**
361
   * Closes the search results and clears the search input.
362
   */
363
  function resultsClickHandler(e, link) {
364
    var $original = $(this).data('original-link');
365
    $original.trigger('mouseleave');
366
    $input.val('').trigger('keyup');
367
  }
368

    
369
  /**
370
   * Shows the link in the menu that corresponds to a search result.
371
   */
372
  function highlightPathHandler(e, link) {
373
    if (link) {
374
      var $original = $(link).data('original-link');
375
      var show = e.type === 'showPath';
376
      // Toggle an additional CSS class to visually highlight the matching link.
377
      // @todo Consider using same visual appearance as regular hover.
378
      $original.toggleClass('highlight', show);
379
      $original.trigger(show ? 'mouseenter' : 'mouseleave');
380
    }
381
  }
382

    
383
  // Attach showPath/hidePath handler to search result entries.
384
  $results.delegate('li', 'mouseenter mouseleave focus blur', resultsHandler);
385
  // Hide the result list after a link has been clicked, useful for overlay.
386
  $results.delegate('li', 'click', resultsClickHandler);
387
  // Attach hover/active highlight behavior to search result entries.
388
  $adminMenu.delegate('.admin-menu-search-results li', 'showPath hidePath', highlightPathHandler);
389
  // Attach the search input event handler.
390
  $input.bind('keyup search', keyupHandler);
391
};
392

    
393
/**
394
 * @} End of "defgroup admin_behaviors".
395
 */
396

    
397
})(jQuery);