a18a2e1974454ec900250679bd55d8f0cef892e0
[Packages/TYPO3.CMS.git] / t3lib / js / extjs / ux / ext.resizable.js
1 /**
2 *
3 * Ext.ux.EventZone Extension Class for Ext 3.x Library
4 *
5 * @author Nigel White
6 *
7 * @license Ext.ux.EventZone is licensed under the terms of
8 * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
9 * that the code/component(s) do NOT become part of another Open Source or Commercially
10 * licensed development library or toolkit without explicit permission.
11 *
12 * License details: http://www.gnu.org/licenses/lgpl.html
13 *
14 * @class Ext.ux.EventZone
15 * <p>This class implements a "virtual element" at a relative size and position
16 * <i>within</i> an existing element. It provides mouse events from a zone of an element of
17 * defined dimensions.</p>
18 * <p>The zone is defined using <code>top</code>, <code>right</code>, <code>bottom</code>,
19 * <code>left</code>, <code>width</code> and <code>height</code> options which specify
20 * the bounds of the zone in a similar manner to the CSS style properties of those names.</p>
21 * @cfg {String|HtmlElement} el The element in which to create the zone.
22 * @cfg {Array} points An Array of points within the element defining the event zone.
23 * @cfg {Number} top The top of the zone. If negative means an offset from the bottom.
24 * @cfg {Number} right The right of the zone. If negative means an offset from the right.
25 * @cfg {Number} left The left of the zone. If negative means an offset from the right.
26 * @cfg {Number} bottom The bottom of the zone. If negative means an offset from the bottom.
27 * @cfg {Number} width The width of the zone.
28 * @cfg {Number} height The height of the zone.
29 * @constructor
30 * Create a new EventZone
31 * @param {Object} config The config object.
32 */
33 Ext.ux.EventZone = Ext.extend(Ext.util.Observable, {
34
35 constructor: function(config) {
36 this.initialConfig = config;
37 this.addEvents(
38 /**
39 * @event mouseenter
40 * This event fires when the mouse enters the zone.
41 * @param {EventObject} e the underlying mouse event.
42 * @param {EventZone} this
43 */
44 'mouseenter',
45 /**
46 * @event mousedown
47 * This event fires when the mouse button is depressed within the zone.
48 * @param {EventObject} e the underlying mouse event.
49 * @param {EventZone} this
50 */
51 'mousedown',
52 /**
53 * @event mousemove
54 * This event fires when the mouse moves within the zone.
55 * @param {EventObject} e the underlying mouse event.
56 * @param {EventZone} this
57 */
58 'mousemove',
59 /**
60 * @event mouseup
61 * This event fires when the mouse button is released within the zone.
62 * @param {EventObject} e the underlying mouse event.
63 * @param {EventZone} this
64 */
65 'mouseup',
66 /**
67 * @event mouseenter
68 * This event fires when the mouse is clicked within the zone.
69 * @param {EventObject} e the underlying mouse event.
70 * @param {EventZone} this
71 */
72 'click',
73 /**
74 * @event mouseleave
75 * This event fires when the mouse leaves the zone.
76 * @param {EventObject} e the underlying mouse event.
77 * @param {EventZone} this
78 */
79 'mouseleave'
80 );
81 Ext.apply(this, config);
82 this.el = Ext.get(this.el);
83
84 // If a polygon within the element is specified...
85 if (this.points) {
86 this.polygon = new Ext.lib.Polygon(this.points);
87 this.points = this.polygon.points;
88 }
89
90 Ext.ux.EventZone.superclass.constructor.call(this);
91 this.el.on({
92 mouseenter: this.handleMouseEvent,
93 mousedown: this.handleMouseEvent,
94 mousemove: this.handleMouseEvent,
95 mouseup: this.handleMouseEvent,
96 click: this.handleMouseEvent,
97 mouseleave: this.handleMouseEvent,
98 scope: this
99 });
100 },
101
102 handleMouseEvent: function(e) {
103 var r = this.polygon ? this.getPolygon() : this.getRegion();
104 var inBounds = r.contains(e.getPoint());
105
106 switch (e.type) {
107 // mouseenter fires this
108 case 'mouseover':
109 if (inBounds) {
110 this.mouseIn = true;
111 this.fireEvent('mouseenter', e, this);
112 }
113 break;
114 // mouseleave fires this
115 case 'mouseout':
116 this.mouseIn = false;
117 this.fireEvent('mouseleave', e, this);
118 break;
119 case 'mousemove':
120 if (inBounds) {
121 if (this.mouseIn) {
122 this.fireEvent('mousemove', e, this);
123 } else {
124 this.mouseIn = true;
125 this.fireEvent('mouseenter', e, this);
126 }
127 } else {
128 if (this.mouseIn) {
129 this.mouseIn = false;
130 this.fireEvent('mouseleave', e, this);
131 }
132 }
133 break;
134 default:
135 if (inBounds) {
136 this.fireEvent(e.type, e, this);
137 }
138 }
139 },
140
141 getPolygon: function() {
142 var xy = this.el.getXY();
143 return this.polygon.translate(xy[0], xy[1]);
144 },
145
146 getRegion: function() {
147 var r = this.el.getRegion();
148
149 // Adjust left boundary of region
150 if (Ext.isNumber(this.left)) {
151 if (this.left < 0) {
152 r.left = r.right + this.left;
153 } else {
154 r.left += this.left;
155 }
156 }
157
158 // Adjust right boundary of region
159 if (Ext.isNumber(this.width)) {
160 r.right = r.left + this.width;
161 } else if (Ext.isNumber(this.right)) {
162 r.right = (this.right < 0) ? r.right + this.right : r.left + this.right;
163 }
164
165 // Adjust top boundary of region
166 if (Ext.isNumber(this.top)) {
167 if (this.top < 0) {
168 r.top = r.bottom + this.top;
169 } else {
170 r.top += this.top;
171 }
172 }
173
174 // Adjust bottom boundary of region
175 if (Ext.isNumber(this.height)) {
176 r.bottom = r.top + this.height;
177 } else if (Ext.isNumber(this.bottom)) {
178 r.bottom = (this.bottom < 0) ? r.bottom + this.bottom : r.top + this.bottom;
179 }
180
181 return r;
182 }
183 });
184
185 /**
186 * @class Ext.lib.Polygon
187 * <p>This class encapsulates an absolute area of the document bounded by a list of points.</p>
188 * @constructor
189 * Create a new Polygon
190 * @param {Object} points An Array of <code>[n,n]</code> point specification Arrays, or
191 * an Array of Ext.lib.Points, or an HtmlElement, or an Ext.lib.Region.
192 */
193 Ext.lib.Polygon = Ext.extend(Ext.lib.Region, {
194 constructor: function(points) {
195 var i, l, el;
196 if (l = points.length) {
197 if (points[0].x) {
198 for (i = 0; i < l; i++) {
199 points[i] = [ points[i].x, points[i].y ];
200 }
201 }
202 this.points = points;
203 } else {
204 if (el = Ext.get(points)) {
205 points = Ext.lib.Region.getRegion(el.dom);
206 }
207 if (points instanceof Ext.lib.Region) {
208 this.points = [
209 [points.left, points.top],
210 [points.right, points.top],
211 [points.right, points.bottom],
212 [points.left, points.bottom]
213 ];
214 }
215 }
216 },
217
218 /**
219 * Returns a new Polygon translated by the specified <code>X</code> and <code>Y</code> increments.
220 * @param xDelta {Number} The <code>X</code> translation increment.
221 * @param xDelta {Number} The <code>Y</code> translation increment.
222 * @return {Polygon} The resulting Polygon.
223 */
224 translate: function(xDelta, yDelta) {
225 var r = [], p = this.points, l = p.length, i;
226 for (i = 0; i < l; i++) {
227 r[i] = [ p[i][0] + xDelta, p[i][1] + yDelta ];
228 }
229 return new Ext.lib.Polygon(r);
230 },
231
232 /**
233 * Returns the area of this Polygon.
234 */
235 getArea: function() {
236 var p = this.points, l = p.length, area = 0, i, j = 0;
237 for (i = 0; i < l; i++) {
238 j++;
239 if (j == l) {
240 j = 0;
241 }
242 area += (p[i][0] + p[j][0]) * (p[i][1] - p[j][1]);
243 }
244 return area * 0.5;
245 },
246
247 /**
248 * Returns <code>true</code> if this Polygon contains the specified point. Thanks
249 * to http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for the algorithm.
250 * @param pt {Point|Number} Either an Ext.lib.Point object, or the <code>X</code> coordinate to test.
251 * @param py {Number} <b>Optional.</b> If the first parameter was an <code>X</code> coordinate, this is the <code>Y</code> coordinate.
252 */
253 contains: function(pt, py) {
254 var f = (arguments.length == 1),
255 testX = f ? pt.x : pt,
256 testY = f ? pt.y : py,
257 p = this.points,
258 nvert = p.length,
259 j = nvert - 1,
260 i, j, c = false;
261 for (i = 0; i < nvert; j = i++) {
262 if ( ((p[i][1] > testY) != (p[j][1] > testY)) &&
263 (testX < (p[j][0]-p[i][0]) * (testY-p[i][1]) / (p[j][1]-p[i][1]) + p[i][0])) {
264 c = !c;
265 }
266 }
267 return c;
268 }
269 });
270
271 /**
272 * @class Ext.Resizable
273 * @extends Ext.util.Observable
274 * This is an override of Ext.Resizable to make usage of the Ex.ux.EventZone
275 * <p>Applies virtual drag handles to an element to make it resizable.</p>
276 * <p>Here is the list of valid resize handles:</p>
277 * <pre>
278 Value Description
279 ------ -------------------
280 'n' north
281 's' south
282 'e' east
283 'w' west
284 'nw' northwest
285 'sw' southwest
286 'se' southeast
287 'ne' northeast
288 'all' all
289 </pre>
290 * <p>Here's an example showing the creation of a typical Resizable:</p>
291 * <pre><code>
292 var resizer = new Ext.Resizable('element-id', {
293 handles: 'all',
294 minWidth: 200,
295 minHeight: 100,
296 maxWidth: 500,
297 maxHeight: 400,
298 pinned: true
299 });
300 resizer.on('resize', myHandler);
301 </code></pre>
302 * <p>To hide a particular handle, set its display to none in CSS, or through script:<br>
303 * resizer.east.setDisplayed(false);</p>
304 * @constructor
305 * Create a new resizable component
306 * @param {Mixed} el The id or element to resize
307 * @param {Object} config configuration options
308 */
309 Ext.Resizable = function(el, config){
310 this.el = Ext.get(el);
311
312 /**
313 * The proxy Element that is resized in place of the real Element during the resize operation.
314 * This may be queried using {@link Ext.Element#getBox} to provide the new area to resize to.
315 * Read only.
316 * @type Ext.Element.
317 * @property proxy
318 */
319 this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
320 this.proxy.unselectable();
321 this.proxy.enableDisplayMode('block');
322
323 Ext.apply(this, config);
324
325 if(this.pinned){
326 this.disableTrackOver = true;
327 this.el.addClass('x-resizable-pinned');
328 }
329 // if the element isn't positioned, make it relative
330 var position = this.el.getStyle('position');
331 if(position != 'absolute' && position != 'fixed'){
332 this.el.setStyle('position', 'relative');
333 }
334 if(!this.handles){ // no handles passed, must be legacy style
335 this.handles = 's,e,se';
336 if(this.multiDirectional){
337 this.handles += ',n,w';
338 }
339 }
340 if(this.handles == 'all'){
341 this.handles = 'n s e w ne nw se sw';
342 }
343 var hs = this.handles.split(/\s*?[,;]\s*?| /);
344 var ps = Ext.Resizable.positions;
345 for(var i = 0, len = hs.length; i < len; i++){
346 if(hs[i] && ps[hs[i]]){
347 var pos = ps[hs[i]];
348 this[pos] = new Ext.Resizable.Handle(this, pos);
349 }
350 }
351 // legacy
352 this.corner = this.southeast;
353
354 if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
355 this.updateBox = true;
356 }
357 this.activeHandle = null;
358
359 if(this.adjustments == 'auto'){
360 var hw = this.west, he = this.east, hn = this.north, hs = this.south;
361 this.adjustments = [
362 (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
363 (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
364 ];
365 }
366
367 if(this.draggable){
368 this.dd = this.dynamic ?
369 this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
370 this.dd.setHandleElId(this.el.id);
371 }
372
373 this.addEvents(
374 /**
375 * @event beforeresize
376 * Fired before resize is allowed. Set {@link #enabled} to false to cancel resize.
377 * @param {Ext.Resizable} this
378 * @param {Ext.EventObject} e The mousedown event
379 */
380 'beforeresize',
381 /**
382 * @event resize
383 * Fired after a resize.
384 * @param {Ext.Resizable} this
385 * @param {Number} width The new width
386 * @param {Number} height The new height
387 * @param {Ext.EventObject} e The mouseup event
388 */
389 'resize'
390 );
391
392 if(this.width !== null && this.height !== null){
393 this.resizeTo(this.width, this.height);
394 }
395 if(Ext.isIE){
396 this.el.dom.style.zoom = 1;
397 }
398 Ext.Resizable.superclass.constructor.call(this);
399 };
400
401 Ext.extend(Ext.Resizable, Ext.util.Observable, {
402
403 /**
404 * @cfg {Array/String} adjustments String 'auto' or an array [width, height] with values to be <b>added</b> to the
405 * resize operation's new size (defaults to <tt>[0, 0]</tt>)
406 */
407 adjustments : [0, 0],
408 /**
409 * @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
410 */
411 animate : false,
412 /**
413 * @cfg {Mixed} constrainTo Constrain the resize to a particular element
414 */
415 /**
416 * @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
417 */
418 draggable: false,
419 /**
420 * @cfg {Number} duration Animation duration if animate = true (defaults to 0.35)
421 */
422 duration : 0.35,
423 /**
424 * @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
425 */
426 dynamic : false,
427 /**
428 * @cfg {String} easing Animation easing if animate = true (defaults to <tt>'easingOutStrong'</tt>)
429 */
430 easing : 'easeOutStrong',
431 /**
432 * @cfg {Boolean} enabled False to disable resizing (defaults to true)
433 */
434 enabled : true,
435 /**
436 * @property enabled Writable. False if resizing is disabled.
437 * @type Boolean
438 */
439 /**
440 * @cfg {String} handles String consisting of the resize handles to display (defaults to undefined).
441 * Specify either <tt>'all'</tt> or any of <tt>'n s e w ne nw se sw'</tt>.
442 */
443 handles : false,
444 /**
445 * @cfg {Boolean} multiDirectional <b>Deprecated</b>. Deprecated style of adding multi-direction resize handles.
446 */
447 multiDirectional : false,
448 /**
449 * @cfg {Number} height The height of the element in pixels (defaults to null)
450 */
451 height : null,
452 /**
453 * @cfg {Number} width The width of the element in pixels (defaults to null)
454 */
455 width : null,
456 /**
457 * @cfg {Number} heightIncrement The increment to snap the height resize in pixels
458 * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
459 */
460 heightIncrement : 0,
461 /**
462 * @cfg {Number} widthIncrement The increment to snap the width resize in pixels
463 * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
464 */
465 widthIncrement : 0,
466 /**
467 * @cfg {Number} minHeight The minimum height for the element (defaults to 5)
468 */
469 minHeight : 5,
470 /**
471 * @cfg {Number} minWidth The minimum width for the element (defaults to 5)
472 */
473 minWidth : 5,
474 /**
475 * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
476 */
477 maxHeight : 10000,
478 /**
479 * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
480 */
481 maxWidth : 10000,
482 /**
483 * @cfg {Number} minX The minimum x for the element (defaults to 0)
484 */
485 minX: 0,
486 /**
487 * @cfg {Number} minY The minimum x for the element (defaults to 0)
488 */
489 minY: 0,
490 /**
491 * @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
492 * user mouses over the resizable borders. This is only applied at config time. (defaults to false)
493 */
494 pinned : false,
495 /**
496 * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
497 * and width during resize (defaults to false)
498 */
499 preserveRatio : false,
500 /**
501 * @cfg {Ext.lib.Region} resizeRegion Constrain the resize to a particular region
502 */
503
504
505 /**
506 * Perform a manual resize and fires the 'resize' event.
507 * @param {Number} width
508 * @param {Number} height
509 */
510 resizeTo : function(width, height){
511 this.el.setSize(width, height);
512 this.fireEvent('resize', this, width, height, null);
513 },
514
515 // private
516 startSizing : function(e, handle){
517 this.fireEvent('beforeresize', this, e);
518 if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
519 e.stopEvent();
520
521 Ext.getDoc().on({
522 scope: this,
523 mousemove: this.onMouseMove,
524 mouseup: {
525 fn: this.onMouseUp,
526 single: true,
527 scope: this
528 }
529 });
530 Ext.getBody().addClass('ux-resizable-handle-' + handle.position);
531
532 this.resizing = true;
533 this.startBox = this.el.getBox();
534 this.startPoint = e.getXY();
535 this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
536 (this.startBox.y + this.startBox.height) - this.startPoint[1]];
537
538 if(this.constrainTo) {
539 var ct = Ext.get(this.constrainTo);
540 this.resizeRegion = ct.getRegion().adjust(
541 ct.getFrameWidth('t'),
542 ct.getFrameWidth('l'),
543 -ct.getFrameWidth('b'),
544 -ct.getFrameWidth('r')
545 );
546 }
547
548 this.proxy.setStyle('visibility', 'hidden'); // workaround display none
549 this.proxy.show();
550 this.proxy.setBox(this.startBox);
551 if(!this.dynamic){
552 this.proxy.setStyle('visibility', 'visible');
553 }
554 }
555 },
556
557 // private
558 onMouseDown : function(handle, e){
559 if(this.enabled && !this.activeHandle){
560 e.stopEvent();
561 this.activeHandle = handle;
562 this.startSizing(e, handle);
563 }
564 },
565
566 // private
567 onMouseUp : function(e){
568 Ext.getBody().removeClass('ux-resizable-handle-' + this.activeHandle.position)
569 .un('mousemove', this.onMouseMove, this);
570 var size = this.resizeElement();
571 this.resizing = false;
572 this.handleOut(this.activeHandle);
573 this.proxy.hide();
574 this.fireEvent('resize', this, size.width, size.height, e);
575 this.activeHandle = null;
576 },
577
578 // private
579 snap : function(value, inc, min){
580 if(!inc || !value){
581 return value;
582 }
583 var newValue = value;
584 var m = value % inc;
585 if(m > 0){
586 if(m > (inc/2)){
587 newValue = value + (inc-m);
588 }else{
589 newValue = value - m;
590 }
591 }
592 return Math.max(min, newValue);
593 },
594
595 /**
596 * <p>Performs resizing of the associated Element. This method is called internally by this
597 * class, and should not be called by user code.</p>
598 * <p>If a Resizable is being used to resize an Element which encapsulates a more complex UI
599 * component such as a Panel, this method may be overridden by specifying an implementation
600 * as a config option to provide appropriate behaviour at the end of the resize operation on
601 * mouseup, for example resizing the Panel, and relaying the Panel's content.</p>
602 * <p>The new area to be resized to is available by examining the state of the {@link #proxy}
603 * Element. Example:
604 <pre><code>
605 new Ext.Panel({
606 title: 'Resize me',
607 x: 100,
608 y: 100,
609 renderTo: Ext.getBody(),
610 floating: true,
611 frame: true,
612 width: 400,
613 height: 200,
614 listeners: {
615 render: function(p) {
616 new Ext.Resizable(p.getEl(), {
617 handles: 'all',
618 pinned: true,
619 transparent: true,
620 resizeElement: function() {
621 var box = this.proxy.getBox();
622 p.updateBox(box);
623 if (p.layout) {
624 p.doLayout();
625 }
626 return box;
627 }
628 });
629 }
630 }
631 }).show();
632 </code></pre>
633 */
634 resizeElement : function(){
635 var box = this.proxy.getBox();
636 if(this.updateBox){
637 this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
638 }else{
639 this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
640 }
641 if(!this.dynamic){
642 this.proxy.hide();
643 }
644 return box;
645 },
646
647 // private
648 constrain : function(v, diff, m, mx){
649 if(v - diff < m){
650 diff = v - m;
651 }else if(v - diff > mx){
652 diff = v - mx;
653 }
654 return diff;
655 },
656
657 // private
658 onMouseMove : function(e){
659 if(this.enabled && this.activeHandle){
660 try{// try catch so if something goes wrong the user doesn't get hung
661
662 if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
663 return;
664 }
665
666 //var curXY = this.startPoint;
667 var curSize = this.curSize || this.startBox,
668 x = this.startBox.x, y = this.startBox.y,
669 ox = x,
670 oy = y,
671 w = curSize.width,
672 h = curSize.height,
673 ow = w,
674 oh = h,
675 mw = this.minWidth,
676 mh = this.minHeight,
677 mxw = this.maxWidth,
678 mxh = this.maxHeight,
679 wi = this.widthIncrement,
680 hi = this.heightIncrement,
681 eventXY = e.getXY(),
682 diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
683 diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
684 pos = this.activeHandle.position,
685 tw,
686 th;
687
688 switch(pos){
689 case 'east':
690 w += diffX;
691 w = Math.min(Math.max(mw, w), mxw);
692 break;
693 case 'south':
694 h += diffY;
695 h = Math.min(Math.max(mh, h), mxh);
696 break;
697 case 'southeast':
698 w += diffX;
699 h += diffY;
700 w = Math.min(Math.max(mw, w), mxw);
701 h = Math.min(Math.max(mh, h), mxh);
702 break;
703 case 'north':
704 diffY = this.constrain(h, diffY, mh, mxh);
705 y += diffY;
706 h -= diffY;
707 break;
708 case 'west':
709 diffX = this.constrain(w, diffX, mw, mxw);
710 x += diffX;
711 w -= diffX;
712 break;
713 case 'northeast':
714 w += diffX;
715 w = Math.min(Math.max(mw, w), mxw);
716 diffY = this.constrain(h, diffY, mh, mxh);
717 y += diffY;
718 h -= diffY;
719 break;
720 case 'northwest':
721 diffX = this.constrain(w, diffX, mw, mxw);
722 diffY = this.constrain(h, diffY, mh, mxh);
723 y += diffY;
724 h -= diffY;
725 x += diffX;
726 w -= diffX;
727 break;
728 case 'southwest':
729 diffX = this.constrain(w, diffX, mw, mxw);
730 h += diffY;
731 h = Math.min(Math.max(mh, h), mxh);
732 x += diffX;
733 w -= diffX;
734 break;
735 }
736
737 var sw = this.snap(w, wi, mw);
738 var sh = this.snap(h, hi, mh);
739 if(sw != w || sh != h){
740 switch(pos){
741 case 'northeast':
742 y -= sh - h;
743 break;
744 case 'north':
745 y -= sh - h;
746 break;
747 case 'southwest':
748 x -= sw - w;
749 break;
750 case 'west':
751 x -= sw - w;
752 break;
753 case 'northwest':
754 x -= sw - w;
755 y -= sh - h;
756 break;
757 }
758 w = sw;
759 h = sh;
760 }
761
762 if(this.preserveRatio){
763 switch(pos){
764 case 'southeast':
765 case 'east':
766 h = oh * (w/ow);
767 h = Math.min(Math.max(mh, h), mxh);
768 w = ow * (h/oh);
769 break;
770 case 'south':
771 w = ow * (h/oh);
772 w = Math.min(Math.max(mw, w), mxw);
773 h = oh * (w/ow);
774 break;
775 case 'northeast':
776 w = ow * (h/oh);
777 w = Math.min(Math.max(mw, w), mxw);
778 h = oh * (w/ow);
779 break;
780 case 'north':
781 tw = w;
782 w = ow * (h/oh);
783 w = Math.min(Math.max(mw, w), mxw);
784 h = oh * (w/ow);
785 x += (tw - w) / 2;
786 break;
787 case 'southwest':
788 h = oh * (w/ow);
789 h = Math.min(Math.max(mh, h), mxh);
790 tw = w;
791 w = ow * (h/oh);
792 x += tw - w;
793 break;
794 case 'west':
795 th = h;
796 h = oh * (w/ow);
797 h = Math.min(Math.max(mh, h), mxh);
798 y += (th - h) / 2;
799 tw = w;
800 w = ow * (h/oh);
801 x += tw - w;
802 break;
803 case 'northwest':
804 tw = w;
805 th = h;
806 h = oh * (w/ow);
807 h = Math.min(Math.max(mh, h), mxh);
808 w = ow * (h/oh);
809 y += th - h;
810 x += tw - w;
811 break;
812
813 }
814 }
815 this.proxy.setBounds(x, y, w, h);
816 if(this.dynamic){
817 this.resizeElement();
818 }
819 }catch(ex){}
820 }
821 },
822
823 // private
824 handleOver : function(handle){
825 if(this.enabled){
826 Ext.getBody().addClass('ux-resizable-handle-' + handle.position);
827 }
828 },
829
830 // private
831 handleOut : function(handle){
832 if(!this.resizing){
833 Ext.getBody().removeClass('ux-resizable-handle-' + handle.position);
834 }
835 },
836
837 /**
838 * Returns the element this component is bound to.
839 * @return {Ext.Element}
840 */
841 getEl : function(){
842 return this.el;
843 },
844
845 /**
846 * Destroys this resizable. If the element was wrapped and
847 * removeEl is not true then the element remains.
848 * @param {Boolean} removeEl (optional) true to remove the element from the DOM
849 */
850 destroy : function(removeEl){
851 Ext.destroy(this.dd, this.proxy);
852 this.proxy = null;
853
854 var ps = Ext.Resizable.positions;
855 for(var k in ps){
856 if(typeof ps[k] != 'function' && this[ps[k]]){
857 this[ps[k]].destroy();
858 }
859 }
860 if(removeEl){
861 this.el.update('');
862 Ext.destroy(this.el);
863 this.el = null;
864 }
865 this.purgeListeners();
866 },
867
868 syncHandleHeight : function(){
869 var h = this.el.getHeight(true);
870 if(this.west){
871 this.west.el.setHeight(h);
872 }
873 if(this.east){
874 this.east.el.setHeight(h);
875 }
876 }
877 });
878
879 // private
880 // hash to map config positions to true positions
881 Ext.Resizable.positions = {
882 n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
883 };
884 Ext.Resizable.cfg = {
885 north: {left: 7, right: -7, height: 7},
886 south: {left: 7, right: -7, top: -7},
887 east: {top: 7, bottom: -7, left: -7},
888 west: {top: 7, bottom: -7, width: 7},
889 southeast: {top: -7, left: -7},
890 southwest: {top: -7, width: 7},
891 northwest: {height: 7, width: 7},
892 northeast: {left: -7, height: 7}
893 };
894
895 // private
896 Ext.Resizable.Handle = function(rz, pos){
897 this.position = pos;
898 this.rz = rz;
899 var cfg = Ext.Resizable.cfg[pos] || Ext.Resizable.cfg[Ext.Resizable.positions[pos]];
900 this.ez = new Ext.ux.EventZone(Ext.apply({
901 position: pos,
902 el: rz.el
903 }, cfg));
904 this.ez.on({
905 mousedown: this.onMouseDown,
906 mouseenter: this.onMouseOver,
907 mouseleave: this.onMouseOut,
908 scope: this
909 });
910 };
911
912 // private
913 Ext.Resizable.Handle.prototype = {
914 cursor: 'move',
915
916 // private
917 afterResize : function(rz){
918 // do nothing
919 },
920 // private
921 onMouseDown : function(e){
922 this.rz.onMouseDown(this, e);
923 },
924 // private
925 onMouseOver : function(e){
926 this.rz.handleOver(this, e);
927 },
928 // private
929 onMouseOut : function(e){
930 this.rz.handleOut(this, e);
931 },
932 // private
933 destroy : function(){
934 Ext.destroy(this.el);
935 this.el = null;
936 }
937 };
938
939 /**
940 *
941 * Ext.ux.elasticTextArea Extension Class for Ext 3.x Library
942 *
943 * @author Steffen Kamper
944 *
945 * @license Ext.ux.elasticTextArea is licensed under the terms of
946 * the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
947 * that the code/component(s) do NOT become part of another Open Source or Commercially
948 * licensed development library or toolkit without explicit permission.
949 *
950 * License details: http://www.gnu.org/licenses/lgpl.html
951 *
952 */
953 Ext.ux.elasticTextArea = function(){
954
955 var defaultConfig = function(){
956 return {
957 minHeight : 0
958 ,maxHeight : 0
959 ,growBy: 12
960 }
961 }
962
963 var processOptions = function(config){
964 var o = defaultConfig();
965 var options = {};
966 Ext.apply(options, config, o);
967
968 return options ;
969 }
970
971 return {
972 div : null
973 ,applyTo: function(elementId, options){
974
975 var el = Ext.get(elementId);
976 var width = el.getWidth();
977 var height = el.getHeight();
978
979 var styles = el.getStyles('padding-top', 'padding-bottom', 'padding-left', 'padding-right', 'line-height', 'font-size', 'font-family', 'font-weight');
980
981 if(! this.div){
982 var options = processOptions(options);
983
984 this.div = Ext.DomHelper.append(Ext.getBody() || document.body, {
985 'id':elementId + '-preview-div'
986 ,'tag' : 'div'
987 ,'background': 'red'
988 ,'style' : 'position: absolute; top: -100000px; left: -100000px;'
989 }, true)
990 Ext.DomHelper.applyStyles(this.div, styles);
991
992 el.on('keyup', function() {
993 this.applyTo(elementId, options);
994 }, this);
995 }
996
997 //replace \n with <br>&nbsp; so that the enter key can trigger and height increase
998 //but first remove all previous entries, so that the height mesurement can be as accurate as possible
999 this.div.update(
1000 el.dom.value.replace(/<br \/>&nbsp;/, '<br />')
1001 .replace(/<|>/g, ' ')
1002 .replace(/&/g,"&amp;")
1003 .replace(/\n/g, '<br />&nbsp;')
1004 );
1005
1006 var growBy = parseInt(el.getStyle('line-height'));
1007 growBy = growBy ? growBy + 1 : 1;
1008 if (growBy === 1) {
1009 growBy = options.growBy;
1010 }
1011 var textHeight = this.div.getHeight();
1012 textHeight = textHeight ? textHeight + growBy : growBy;
1013
1014 if ( (textHeight > options.maxHeight ) && (options.maxHeight > 0) ){
1015 textHeight = options.maxHeight ;
1016 el.setStyle('overflow', 'auto');
1017 }
1018 if ( (textHeight < options.minHeight ) && (options.minHeight > 0) ) {
1019 textHeight = options.minHeight ;
1020 el.setStyle('overflow', 'auto');
1021 }
1022
1023 el.setHeight(textHeight , true);
1024 }
1025 }
1026 }