root / drupal7 / sites / all / modules / admin_menu / admin_menu.js @ 5a4b9049
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 |
// Delayed mouseout.
|
223 |
$('li.expandable', $adminMenu).hover( |
224 |
function () {
|
225 |
// Stop the timer.
|
226 |
clearTimeout(this.sfTimer);
|
227 |
// Display child lists.
|
228 |
$('> ul', this) |
229 |
.css({left: 'auto', display: 'block'}) |
230 |
// Immediately hide nephew lists.
|
231 |
.parent().siblings('li').children('ul').css({left: '-999em', display: 'none'}); |
232 |
}, |
233 |
function () {
|
234 |
// Start the timer.
|
235 |
var uls = $('> ul', this); |
236 |
this.sfTimer = setTimeout(function () { |
237 |
uls.css({left: '-999em', display: 'none'}); |
238 |
}, 400);
|
239 |
} |
240 |
); |
241 |
}; |
242 |
|
243 |
/**
|
244 |
* Apply the search bar functionality.
|
245 |
*/
|
246 |
Drupal.admin.behaviors.search = function (context, settings, $adminMenu) { |
247 |
// @todo Add a HTML ID.
|
248 |
var $input = $('input.admin-menu-search', $adminMenu); |
249 |
// Initialize the current search needle.
|
250 |
var needle = $input.val(); |
251 |
// Cache of all links that can be matched in the menu.
|
252 |
var links;
|
253 |
// Minimum search needle length.
|
254 |
var needleMinLength = 2; |
255 |
// Append the results container.
|
256 |
var $results = $('<div />').insertAfter($input); |
257 |
|
258 |
/**
|
259 |
* Executes the search upon user input.
|
260 |
*/
|
261 |
function keyupHandler() { |
262 |
var matches, $html, value = $(this).val(); |
263 |
// Only proceed if the search needle has changed.
|
264 |
if (value !== needle) {
|
265 |
needle = value; |
266 |
// Initialize the cache of menu links upon first search.
|
267 |
if (!links && needle.length >= needleMinLength) {
|
268 |
// @todo Limit to links in dropdown menus; i.e., skip menu additions.
|
269 |
links = buildSearchIndex($adminMenu.find('li:not(.admin-menu-action, .admin-menu-action li) > a')); |
270 |
} |
271 |
// Empty results container when deleting search text.
|
272 |
if (needle.length < needleMinLength) {
|
273 |
$results.empty();
|
274 |
} |
275 |
// Only search if the needle is long enough.
|
276 |
if (needle.length >= needleMinLength && links) {
|
277 |
matches = findMatches(needle, links); |
278 |
// Build the list in a detached DOM node.
|
279 |
$html = buildResultsList(matches);
|
280 |
// Display results.
|
281 |
$results.empty().append($html); |
282 |
} |
283 |
} |
284 |
} |
285 |
|
286 |
/**
|
287 |
* Builds the search index.
|
288 |
*/
|
289 |
function buildSearchIndex($links) { |
290 |
return $links |
291 |
.map(function () {
|
292 |
var text = (this.textContent || this.innerText); |
293 |
// Skip menu entries that do not contain any text (e.g., the icon).
|
294 |
if (typeof text === 'undefined') { |
295 |
return;
|
296 |
} |
297 |
return {
|
298 |
text: text,
|
299 |
textMatch: text.toLowerCase(),
|
300 |
element: this |
301 |
}; |
302 |
}); |
303 |
} |
304 |
|
305 |
/**
|
306 |
* Searches the index for a given needle and returns matching entries.
|
307 |
*/
|
308 |
function findMatches(needle, links) { |
309 |
var needleMatch = needle.toLowerCase();
|
310 |
// Select matching links from the cache.
|
311 |
return $.grep(links, function (link) { |
312 |
return link.textMatch.indexOf(needleMatch) !== -1; |
313 |
}); |
314 |
} |
315 |
|
316 |
/**
|
317 |
* Builds the search result list in a detached DOM node.
|
318 |
*/
|
319 |
function buildResultsList(matches) { |
320 |
var $html = $('<ul class="dropdown admin-menu-search-results" />'); |
321 |
$.each(matches, function () { |
322 |
var result = this.text; |
323 |
var $element = $(this.element); |
324 |
|
325 |
// Check whether there is a top-level category that can be prepended.
|
326 |
var $category = $element.closest('#admin-menu-wrapper > ul > li'); |
327 |
var categoryText = $category.find('> a').text() |
328 |
if ($category.length && categoryText) { |
329 |
result = categoryText + ': ' + result;
|
330 |
} |
331 |
|
332 |
var $result = $('<li><a href="' + $element.attr('href') + '">' + result + '</a></li>'); |
333 |
$result.data('original-link', $(this.element).parent()); |
334 |
$html.append($result); |
335 |
}); |
336 |
return $html; |
337 |
} |
338 |
|
339 |
/**
|
340 |
* Highlights selected result.
|
341 |
*/
|
342 |
function resultsHandler(e) { |
343 |
var $this = $(this); |
344 |
var show = e.type === 'mouseenter' || e.type === 'focusin'; |
345 |
$this.trigger(show ? 'showPath' : 'hidePath', [this]); |
346 |
} |
347 |
|
348 |
/**
|
349 |
* Closes the search results and clears the search input.
|
350 |
*/
|
351 |
function resultsClickHandler(e, link) { |
352 |
var $original = $(this).data('original-link'); |
353 |
$original.trigger('mouseleave'); |
354 |
$input.val('').trigger('keyup'); |
355 |
} |
356 |
|
357 |
/**
|
358 |
* Shows the link in the menu that corresponds to a search result.
|
359 |
*/
|
360 |
function highlightPathHandler(e, link) { |
361 |
if (link) {
|
362 |
var $original = $(link).data('original-link'); |
363 |
var show = e.type === 'showPath'; |
364 |
// Toggle an additional CSS class to visually highlight the matching link.
|
365 |
// @todo Consider using same visual appearance as regular hover.
|
366 |
$original.toggleClass('highlight', show); |
367 |
$original.trigger(show ? 'mouseenter' : 'mouseleave'); |
368 |
} |
369 |
} |
370 |
|
371 |
// Attach showPath/hidePath handler to search result entries.
|
372 |
$results.delegate('li', 'mouseenter mouseleave focus blur', resultsHandler); |
373 |
// Hide the result list after a link has been clicked, useful for overlay.
|
374 |
$results.delegate('li', 'click', resultsClickHandler); |
375 |
// Attach hover/active highlight behavior to search result entries.
|
376 |
$adminMenu.delegate('.admin-menu-search-results li', 'showPath hidePath', highlightPathHandler); |
377 |
// Attach the search input event handler.
|
378 |
$input.bind('keyup search', keyupHandler); |
379 |
}; |
380 |
|
381 |
/**
|
382 |
* @} End of "defgroup admin_behaviors".
|
383 |
*/
|
384 |
|
385 |
})(jQuery); |