1 | /** |
---|
2 | * @file display_editor.js |
---|
3 | * |
---|
4 | * Contains the javascript for the Panels display editor. |
---|
5 | */ |
---|
6 | |
---|
7 | (function ($) { |
---|
8 | /** Delete pane button **/ |
---|
9 | Drupal.Panels.bindClickDelete = function(context) { |
---|
10 | $('a.pane-delete:not(.pane-delete-processed)', context) |
---|
11 | .addClass('pane-delete-processed') |
---|
12 | .click(function() { |
---|
13 | if (confirm('Remove this pane?')) { |
---|
14 | var id = '#' + $(this).attr('id').replace('pane-delete-', ''); |
---|
15 | $(id).remove(); |
---|
16 | Drupal.Panels.Draggable.savePositions(); |
---|
17 | } |
---|
18 | return false; |
---|
19 | }); |
---|
20 | }; |
---|
21 | |
---|
22 | Drupal.Panels.bindPortlet = function() { |
---|
23 | var handle = $(this).find('.panel-pane-collapsible > div.pane-title'); |
---|
24 | var content = $(this).find('.panel-pane-collapsible > div.pane-content'); |
---|
25 | if (content.length) { |
---|
26 | var toggle = $('<span class="toggle toggle-collapsed"></span>'); |
---|
27 | handle.before(toggle); |
---|
28 | toggle.click(function() { |
---|
29 | content.slideToggle(20); |
---|
30 | toggle.toggleClass('toggle-collapsed'); |
---|
31 | }); |
---|
32 | handle.click(function() { |
---|
33 | content.slideToggle(20); |
---|
34 | toggle.toggleClass('toggle-collapsed'); |
---|
35 | }); |
---|
36 | content.hide(); |
---|
37 | } |
---|
38 | }; |
---|
39 | |
---|
40 | Drupal.Panels.Draggable = { |
---|
41 | // The draggable object |
---|
42 | object: null, |
---|
43 | |
---|
44 | // Where objects can be dropped |
---|
45 | dropzones: [], |
---|
46 | current_dropzone: null, |
---|
47 | |
---|
48 | // positions within dropzones where an object can be plazed |
---|
49 | landing_pads: [], |
---|
50 | current_pad: null, |
---|
51 | |
---|
52 | // Where the object is |
---|
53 | mouseOffset: { x: 0, y: 0 }, |
---|
54 | windowOffset: { x: 0, y: 0 }, |
---|
55 | offsetDivHeight: 0, |
---|
56 | |
---|
57 | // original settings to be restored |
---|
58 | original: {}, |
---|
59 | // a placeholder so that if the object is let go but not over a drop zone, |
---|
60 | // it can be put back where it belongs |
---|
61 | placeholder: {}, |
---|
62 | |
---|
63 | hoverclass: 'hoverclass', |
---|
64 | helperclass: 'helperclass', |
---|
65 | accept: 'div.panels-display', |
---|
66 | handle: 'div.grabber', |
---|
67 | draggable: 'div.panel-portlet', |
---|
68 | main: 'div#panels-dnd-main', |
---|
69 | |
---|
70 | // part of the id to remove to get just the number |
---|
71 | draggableId: 'panel-pane-', |
---|
72 | // What to add to the front of a the id to get the form id for a panel |
---|
73 | formId: 'input#edit-', |
---|
74 | |
---|
75 | maxWidth: 250, |
---|
76 | |
---|
77 | unsetDropZone: function() { |
---|
78 | $(this.current_dropzone.obj).removeClass(this.hoverclass); |
---|
79 | this.current_dropzone = null; |
---|
80 | for (var i in this.landing_pads) { |
---|
81 | $(this.landing_pads[i].obj).remove(); |
---|
82 | } |
---|
83 | this.landing_pads = []; |
---|
84 | this.current_pad = null; |
---|
85 | }, |
---|
86 | |
---|
87 | createLandingPad: function(where, append) { |
---|
88 | var obj = $('<div class="' + this.helperclass +'" id="' + |
---|
89 | $(where).attr('id') + '-dropzone"> </div>'); |
---|
90 | if (append) { |
---|
91 | $(where).append(obj); |
---|
92 | } |
---|
93 | else { |
---|
94 | $(where).before(obj); |
---|
95 | } |
---|
96 | var offset = $(obj).offset(); |
---|
97 | |
---|
98 | $(obj).css({ |
---|
99 | display: 'none' |
---|
100 | }); |
---|
101 | this.landing_pads.push({ |
---|
102 | centerX: offset.left + ($(obj).innerWidth() / 2), |
---|
103 | centerY: offset.top + ($(obj).innerHeight() / 2), |
---|
104 | obj: obj |
---|
105 | }); |
---|
106 | return obj; |
---|
107 | }, |
---|
108 | |
---|
109 | calculateDropZones: function(event, dropzone) { |
---|
110 | var dropzones = []; |
---|
111 | $(this.accept).each(function() { |
---|
112 | var offset = $(this).offset(); |
---|
113 | offset.obj = this; |
---|
114 | offset.width = $(this).outerWidth(); |
---|
115 | offset.height = $(this).outerHeight(); |
---|
116 | dropzones.push(offset); |
---|
117 | }); |
---|
118 | this.dropzones = dropzones; |
---|
119 | }, |
---|
120 | |
---|
121 | reCalculateDropZones: function() { |
---|
122 | for (var i in this.dropzones) { |
---|
123 | offset = $(this.dropzones[i].obj).offset(); |
---|
124 | offset.width = $(this.dropzones[i].obj).outerWidth(); |
---|
125 | offset.height = $(this.dropzones[i].obj).outerHeight(); |
---|
126 | $.extend(this.dropzones[i], offset); |
---|
127 | } |
---|
128 | }, |
---|
129 | |
---|
130 | changeDropZone: function(new_dropzone) { |
---|
131 | // Unset our old dropzone. |
---|
132 | if (this.current_dropzone) { |
---|
133 | this.unsetDropZone(); |
---|
134 | } |
---|
135 | |
---|
136 | // Set up our new dropzone. |
---|
137 | this.current_dropzone = new_dropzone; |
---|
138 | $(this.current_dropzone.obj).addClass(this.hoverclass); |
---|
139 | // add a landing pad |
---|
140 | this.createLandingPad(this.current_dropzone.obj, true); |
---|
141 | |
---|
142 | var that = this; |
---|
143 | // Create a landing pad before each existing portlet. |
---|
144 | $(this.current_dropzone.obj).find(this.draggable).each(function() { |
---|
145 | if (that.object.id != this.id) { |
---|
146 | that.createLandingPad(this, false); |
---|
147 | } |
---|
148 | }); |
---|
149 | }, |
---|
150 | |
---|
151 | findLandingPad: function(x, y) { |
---|
152 | var shortest_distance = null; |
---|
153 | var nearest_pad = null; |
---|
154 | // find the nearest pad. |
---|
155 | for (var i in this.landing_pads) { |
---|
156 | // This isn't the real distance, this is the square of the |
---|
157 | // distance -- no point in spending processing time on |
---|
158 | // sqrt. |
---|
159 | var dstx = Math.abs(x - this.landing_pads[i].centerX); |
---|
160 | var dsty = Math.abs(y - this.landing_pads[i].centerY); |
---|
161 | var distance = (dstx * dstx) + (dsty * dsty); |
---|
162 | if (shortest_distance == null || distance < shortest_distance) { |
---|
163 | shortest_distance = distance; |
---|
164 | nearest_pad = this.landing_pads[i]; |
---|
165 | } |
---|
166 | } |
---|
167 | if (nearest_pad != this.current_pad) { |
---|
168 | if (this.current_pad) { |
---|
169 | $(this.current_pad.obj).hide(); |
---|
170 | } |
---|
171 | this.current_pad = nearest_pad; |
---|
172 | $(nearest_pad.obj).show(); |
---|
173 | } |
---|
174 | }, |
---|
175 | |
---|
176 | findDropZone: function(x, y) { |
---|
177 | // Go through our dropzones and see if we're over one. |
---|
178 | var new_dropzone = null; |
---|
179 | for (var i in this.dropzones) { |
---|
180 | // console.log('x:' + x + ' left:' + this.dropzones[i].left + ' right: ' + this.dropzones[i].left + this.dropzones[i].width); |
---|
181 | if (this.dropzones[i].left < x && |
---|
182 | x < this.dropzones[i].left + this.dropzones[i].width && |
---|
183 | this.dropzones[i].top < y && |
---|
184 | y < this.dropzones[i].top + this.dropzones[i].height) { |
---|
185 | new_dropzone = this.dropzones[i]; |
---|
186 | break; |
---|
187 | } |
---|
188 | } |
---|
189 | // If we're over one, see if it's different. |
---|
190 | if (new_dropzone) { |
---|
191 | var changed = false; |
---|
192 | if (!this.current_dropzone || new_dropzone.obj.id != this.current_dropzone.obj.id) { |
---|
193 | this.changeDropZone(new_dropzone); |
---|
194 | changed = true; |
---|
195 | } |
---|
196 | this.findLandingPad(x, y); |
---|
197 | if (changed) { |
---|
198 | // recalculate the size of our drop zones due to the fact that we're drawing landing pads. |
---|
199 | this.reCalculateDropZones(); |
---|
200 | } |
---|
201 | } |
---|
202 | // If we're not over one, be sure to unhilite one if we were just |
---|
203 | // over it. |
---|
204 | else if (this.current_dropzone) { |
---|
205 | this.unsetDropZone(); |
---|
206 | } |
---|
207 | }, |
---|
208 | |
---|
209 | /** save button clicked, or pane deleted **/ |
---|
210 | savePositions: function() { |
---|
211 | var draggable = Drupal.Panels.Draggable; |
---|
212 | $(draggable.accept).each(function() { |
---|
213 | var val = ''; |
---|
214 | $(this).find(draggable.draggable).each(function() { |
---|
215 | if (val) { |
---|
216 | val += ','; |
---|
217 | } |
---|
218 | |
---|
219 | val += this.id.replace(draggable.draggableId, ''); |
---|
220 | }); |
---|
221 | // Note: _ is replaced with - because Drupal automatically does this |
---|
222 | // with form ids. |
---|
223 | $(draggable.formId + this.id.replace(/_/g, '-')).val(val); |
---|
224 | }); |
---|
225 | return false; |
---|
226 | } |
---|
227 | }; |
---|
228 | |
---|
229 | Drupal.Panels.DraggableHandler = function() { |
---|
230 | $(this).addClass('panel-draggable'); |
---|
231 | var draggable = Drupal.Panels.Draggable; |
---|
232 | var scrollBuffer = 10; |
---|
233 | var scrollDistance = 10; |
---|
234 | var scrollTimer = 30; |
---|
235 | |
---|
236 | getMouseOffset = function(docPos, mousePos, windowPos) { |
---|
237 | return { x: mousePos.x - docPos.x + windowPos.x, y: mousePos.y - docPos.y + windowPos.y}; |
---|
238 | }; |
---|
239 | |
---|
240 | getMousePos = function(ev) { |
---|
241 | ev = ev || window.event; |
---|
242 | |
---|
243 | if (ev.pageX || ev.pageY) { |
---|
244 | return { x:ev.pageX, y:ev.pageY }; |
---|
245 | } |
---|
246 | return { |
---|
247 | x:ev.clientX + document.body.scrollLeft - document.body.clientLeft, |
---|
248 | y:ev.clientY + document.body.scrollTop - document.body.clientTop |
---|
249 | }; |
---|
250 | }; |
---|
251 | |
---|
252 | getPosition = function(e) { |
---|
253 | /* |
---|
254 | if (document.defaultView && document.defaultView.getComputedStyle) { |
---|
255 | var css = document.defaultView.getComputedStyle(e, null); |
---|
256 | return { |
---|
257 | x: parseInt(css.getPropertyValue('left')), |
---|
258 | y: parseInt(css.getPropertyValue('top')) |
---|
259 | }; |
---|
260 | } |
---|
261 | */ |
---|
262 | var left = 0; |
---|
263 | var top = 0; |
---|
264 | |
---|
265 | while (e.offsetParent) { |
---|
266 | left += e.offsetLeft; |
---|
267 | top += e.offsetTop; |
---|
268 | e = e.offsetParent; |
---|
269 | } |
---|
270 | |
---|
271 | left += e.offsetLeft; |
---|
272 | top += e.offsetTop; |
---|
273 | |
---|
274 | return { x:left, y:top }; |
---|
275 | }; |
---|
276 | |
---|
277 | mouseUp = function(e) { |
---|
278 | clearTimeout(draggable.timeoutId); |
---|
279 | draggable.dropzones = []; |
---|
280 | |
---|
281 | if (draggable.current_pad) { |
---|
282 | // Drop the object where we're hovering |
---|
283 | $(draggable.object).insertAfter($(draggable.current_pad.obj)); |
---|
284 | Drupal.Panels.changed($(draggable.object)); |
---|
285 | } |
---|
286 | else { |
---|
287 | // or put it back where it came from |
---|
288 | $(draggable.object).insertAfter(draggable.placeholder); |
---|
289 | } |
---|
290 | // remove the placeholder |
---|
291 | draggable.placeholder.remove(); |
---|
292 | |
---|
293 | // restore original settings. |
---|
294 | $(draggable.object).css(draggable.original); |
---|
295 | if (draggable.current_dropzone) { |
---|
296 | draggable.unsetDropZone(); |
---|
297 | } |
---|
298 | |
---|
299 | $(document).unbind('mouseup').unbind('mousemove'); |
---|
300 | draggable.savePositions(); |
---|
301 | }; |
---|
302 | |
---|
303 | mouseMove = function(e) { |
---|
304 | draggable.mousePos = getMousePos(e); |
---|
305 | |
---|
306 | draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y); |
---|
307 | |
---|
308 | var windowMoved = parseInt(draggable.offsetDivHeight - $(draggable.main).innerHeight()); |
---|
309 | |
---|
310 | draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + windowMoved + 'px'; |
---|
311 | draggable.object.style.left = draggable.mousePos.x - draggable.mouseOffset.x + 'px'; |
---|
312 | $(draggable.object).toggleClass('moving'); |
---|
313 | }; |
---|
314 | |
---|
315 | mouseDown = function(e) { |
---|
316 | // If we mouse-downed over something clickable, don't drag! |
---|
317 | if (e.target.nodeName == 'A' || e.target.nodeName == 'INPUT' || e.target.parentNode.nodeName == 'A' || e.target.nodeName.nodeName == 'INPUT') { |
---|
318 | return; |
---|
319 | } |
---|
320 | |
---|
321 | draggable.object = $(this).parent(draggable.draggable).get(0); |
---|
322 | |
---|
323 | // create a placeholder so we can put this object back if dropped in an invalid location. |
---|
324 | draggable.placeholder = $('<div class="draggable-placeholder-object" style="display:none"></div>"'); |
---|
325 | $(draggable.object).after(draggable.placeholder); |
---|
326 | |
---|
327 | // Store original CSS so we can put it back. |
---|
328 | draggable.original = { |
---|
329 | position: $(draggable.object).css('position'), |
---|
330 | width: 'auto', |
---|
331 | left: $(draggable.object).css('left'), |
---|
332 | top: $(draggable.object).css('top'), |
---|
333 | 'z-index': $(draggable.object).css('z-index'), |
---|
334 | 'margin-bottom': $(draggable.object).css('margin-bottom'), |
---|
335 | 'margin-top': $(draggable.object).css('margin-top'), |
---|
336 | 'margin-left': $(draggable.object).css('margin-left'), |
---|
337 | 'margin-right': $(draggable.object).css('margin-right'), |
---|
338 | 'padding-bottom': $(draggable.object).css('padding-bottom'), |
---|
339 | 'padding-top': $(draggable.object).css('padding-top'), |
---|
340 | 'padding-left': $(draggable.object).css('padding-left'), |
---|
341 | 'padding-right': $(draggable.object).css('padding-right') |
---|
342 | }; |
---|
343 | |
---|
344 | draggable.mousePos = getMousePos(e); |
---|
345 | var originalPos = $(draggable.object).offset(); |
---|
346 | var width = Math.min($(draggable.object).innerWidth(), draggable.maxWidth); |
---|
347 | |
---|
348 | draggable.offsetDivHeight = $(draggable.main).innerHeight(); |
---|
349 | draggable.findDropZone(draggable.mousePos.x, draggable.mousePos.y); |
---|
350 | |
---|
351 | // Make copies of these because in FF3, they actually change when we |
---|
352 | // move the item, whereas they did not in FF2. |
---|
353 | |
---|
354 | if (e.layerX || e.layerY) { |
---|
355 | var layerX = e.layerX; |
---|
356 | var layerY = e.layerY; |
---|
357 | } |
---|
358 | else if (e.originalEvent && e.originalEvent.layerX) { |
---|
359 | var layerX = e.originalEvent.layerX; |
---|
360 | var layerY = e.originalEvent.layerY; |
---|
361 | } |
---|
362 | |
---|
363 | // Make the draggable relative, get it out of the way and make it |
---|
364 | // invisible. |
---|
365 | $(draggable.object).css({ |
---|
366 | position: 'relative', |
---|
367 | 'z-index': 100, |
---|
368 | width: width + 'px', |
---|
369 | 'margin-bottom': (-1 * parseInt($(draggable.object).outerHeight())) + 'px', |
---|
370 | 'margin-top': 0, |
---|
371 | 'margin-left': 0, |
---|
372 | 'margin-right': (-1 * parseInt($(draggable.object).outerWidth())) + 'px', |
---|
373 | 'padding-bottom': 0, |
---|
374 | 'padding-top': 0, |
---|
375 | 'padding-left': 0, |
---|
376 | 'padding-right': 0, |
---|
377 | 'left': 0, |
---|
378 | 'top': 0 |
---|
379 | }) |
---|
380 | .insertAfter($(draggable.main)); |
---|
381 | var newPos = $(draggable.object).offset(); |
---|
382 | |
---|
383 | var windowOffset = { left: originalPos.left - newPos.left, top: originalPos.top - newPos.top } |
---|
384 | |
---|
385 | // if they grabbed outside the area where we make the draggable smaller, move it |
---|
386 | // closer to the cursor. |
---|
387 | if (layerX != 'undefined' && layerX > width) { |
---|
388 | windowOffset.left += layerX - 10; |
---|
389 | } |
---|
390 | else if (layerX != 'undefined' && e.offsetX > width) { |
---|
391 | windowOffset.left += e.offsetX - 10; |
---|
392 | } |
---|
393 | |
---|
394 | // This is stored so we can move with it. |
---|
395 | draggable.mouseOffset = { x: draggable.mousePos.x - windowOffset.left, y: draggable.mousePos.y - windowOffset.top}; |
---|
396 | draggable.offsetDivHeight = $(draggable.main).innerHeight(); |
---|
397 | |
---|
398 | draggable.object.style.top = windowOffset.top + 'px'; |
---|
399 | draggable.object.style.left = windowOffset.left + 'px'; |
---|
400 | $(document).unbind('mouseup').unbind('mousemove').mouseup(mouseUp).mousemove(mouseMove); |
---|
401 | |
---|
402 | draggable.calculateDropZones(draggable.mousePos, e); |
---|
403 | draggable.timeoutId = setTimeout('timer()', scrollTimer); |
---|
404 | return false; |
---|
405 | }; |
---|
406 | |
---|
407 | timer = function() { |
---|
408 | if (!draggable.timeCount) { |
---|
409 | draggable.timeCount = 0; |
---|
410 | } |
---|
411 | draggable.timeCount = draggable.timeCount + 1; |
---|
412 | var left = $(window).scrollLeft(); |
---|
413 | var right = left + $(window).width(); |
---|
414 | var top = $(window).scrollTop(); |
---|
415 | var bottom = top + $(window).height(); |
---|
416 | |
---|
417 | if (draggable.mousePos.x < left + scrollBuffer && left > 0) { |
---|
418 | window.scrollTo(left - scrollDistance, top); |
---|
419 | draggable.mousePos.x -= scrollDistance; |
---|
420 | draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; |
---|
421 | } |
---|
422 | else if (draggable.mousePos.x > right - scrollBuffer) { |
---|
423 | window.scrollTo(left + scrollDistance, top); |
---|
424 | draggable.mousePos.x += scrollDistance; |
---|
425 | draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; |
---|
426 | } |
---|
427 | else if (draggable.mousePos.y < top + scrollBuffer && top > 0) { |
---|
428 | window.scrollTo(left, top - scrollDistance); |
---|
429 | draggable.mousePos.y -= scrollDistance; |
---|
430 | draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; |
---|
431 | } |
---|
432 | else if (draggable.mousePos.y > bottom - scrollBuffer) { |
---|
433 | window.scrollTo(left, top + scrollDistance); |
---|
434 | draggable.mousePos.y += scrollDistance; |
---|
435 | draggable.object.style.top = draggable.mousePos.y - draggable.mouseOffset.y + 'px'; |
---|
436 | } |
---|
437 | |
---|
438 | draggable.timeoutId = setTimeout('timer()', scrollTimer); |
---|
439 | } |
---|
440 | |
---|
441 | $(this).mousedown(mouseDown); |
---|
442 | }; |
---|
443 | |
---|
444 | $.fn.extend({ |
---|
445 | panelsDraggable: Drupal.Panels.DraggableHandler |
---|
446 | }); |
---|
447 | |
---|
448 | /** |
---|
449 | * Implement Drupal behavior for autoattach |
---|
450 | */ |
---|
451 | Drupal.behaviors.PanelsDisplayEditor = function(context) { |
---|
452 | // Show javascript only items. |
---|
453 | $('span#panels-js-only').css('display', 'inline'); |
---|
454 | |
---|
455 | $('#panels-dnd-main div.panel-pane:not(.panel-portlet)') |
---|
456 | .addClass('panel-portlet') |
---|
457 | .each(Drupal.Panels.bindPortlet); |
---|
458 | |
---|
459 | // The above doesn't work if context IS the pane, so do this to catch that. |
---|
460 | if ($(context).hasClass('panel-pane') && !$(context).hasClass('panel-portlet')) { |
---|
461 | $(context) |
---|
462 | .addClass('panel-portlet') |
---|
463 | .each(Drupal.Panels.bindPortlet); |
---|
464 | } |
---|
465 | |
---|
466 | // Make draggables and make sure their positions are saved. |
---|
467 | $(context).find('div.grabber:not(.panel-draggable)').panelsDraggable(); |
---|
468 | Drupal.Panels.Draggable.savePositions(); |
---|
469 | |
---|
470 | // Bind buttons. |
---|
471 | $('input#panels-hide-all', context).click(Drupal.Panels.clickHideAll); |
---|
472 | $('input#panels-show-all', context).click(Drupal.Panels.clickShowAll); |
---|
473 | |
---|
474 | Drupal.Panels.bindClickDelete(context); |
---|
475 | |
---|
476 | $('#panels-live-preview-button:not(.panels-preview-processed)') |
---|
477 | .addClass('panels-preview-processed') |
---|
478 | .click(function () { |
---|
479 | if (!$('#panels-preview').size()) { |
---|
480 | $('#panels-dnd-main').parents('form').after('<div id="panels-preview"></div>'); |
---|
481 | } |
---|
482 | |
---|
483 | $('#panels-preview').html(Drupal.theme('CToolsModalThrobber')); |
---|
484 | }); |
---|
485 | |
---|
486 | var setTitleClass = function () { |
---|
487 | if ($('#edit-display-title-hide-title').val() == 2) { |
---|
488 | $('#panels-dnd-main').removeClass('panels-set-title-hide'); |
---|
489 | } |
---|
490 | else { |
---|
491 | $('#panels-dnd-main').addClass('panels-set-title-hide'); |
---|
492 | } |
---|
493 | } |
---|
494 | |
---|
495 | // The panes have an option to set the display title, but only if |
---|
496 | // a select is set to the proper value. This sets a class on the |
---|
497 | // main edit div so that the option to set the display title |
---|
498 | // is hidden if that is not selected, and visible if it is. |
---|
499 | $('#edit-display-title-hide-title:not(.panels-title-processed)') |
---|
500 | .addClass('panels-title-processed') |
---|
501 | .change(setTitleClass); |
---|
502 | |
---|
503 | setTitleClass(); |
---|
504 | }; |
---|
505 | |
---|
506 | /** |
---|
507 | * AJAX responder command to render the preview. |
---|
508 | */ |
---|
509 | Drupal.CTools.AJAX.commands.panel_preview = function(command) { |
---|
510 | $('#panels-preview').html(command.output); |
---|
511 | } |
---|
512 | |
---|
513 | })(jQuery); |
---|