ResizeHandle.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. define("dojox/layout/ResizeHandle", ["dojo/_base/kernel","dojo/_base/lang","dojo/_base/connect","dojo/_base/array","dojo/_base/event",
  2. "dojo/_base/fx","dojo/_base/window","dojo/fx","dojo/window","dojo/dom","dojo/dom-class",
  3. "dojo/dom-geometry","dojo/dom-style","dijit/_base/manager","dijit/_Widget","dijit/_TemplatedMixin",
  4. "dojo/_base/declare"], function (
  5. kernel, lang, connect, arrayUtil, eventUtil, fxBase, windowBase, fxUtil, windowUtil,
  6. domUtil, domClass, domGeometry, domStyle, manager, Widget, TemplatedMixin, declare) {
  7. kernel.experimental("dojox.layout.ResizeHandle");
  8. /*=====
  9. var Widget = dijit._Widget;
  10. var TemplatedMixin = dijit._TemplatedMixin;
  11. =====*/
  12. var ResizeHandle = declare("dojox.layout.ResizeHandle",[Widget, TemplatedMixin],
  13. {
  14. // summary: A dragable handle used to resize an attached node.
  15. //
  16. // description:
  17. // The handle on the bottom-right corner of FloatingPane or other widgets that allows
  18. // the widget to be resized.
  19. // Typically not used directly.
  20. //
  21. // targetId: String
  22. // id of the Widget OR DomNode that I will size
  23. targetId: "",
  24. // targetContainer: DomNode
  25. // over-ride targetId and attch this handle directly to a reference of a DomNode
  26. targetContainer: null,
  27. // resizeAxis: String
  28. // one of: x|y|xy limit resizing to a single axis, default to xy ...
  29. resizeAxis: "xy",
  30. // activeResize: Boolean
  31. // if true, node will size realtime with mouse movement,
  32. // if false, node will create virtual node, and only resize target on mouseUp
  33. activeResize: false,
  34. // activeResizeClass: String
  35. // css class applied to virtual resize node.
  36. activeResizeClass: "dojoxResizeHandleClone",
  37. // animateSizing: Boolean
  38. // only applicable if activeResize = false. onMouseup, animate the node to the
  39. // new size
  40. animateSizing: true,
  41. // animateMethod: String
  42. // one of "chain" or "combine" ... visual effect only. combine will "scale"
  43. // node to size, "chain" will alter width, then height
  44. animateMethod: "chain",
  45. // animateDuration: Integer
  46. // time in MS to run sizing animation. if animateMethod="chain", total animation
  47. // playtime is 2*animateDuration
  48. animateDuration: 225,
  49. // minHeight: Integer
  50. // smallest height in px resized node can be
  51. minHeight: 100,
  52. // minWidth: Integer
  53. // smallest width in px resize node can be
  54. minWidth: 100,
  55. // constrainMax: Boolean
  56. // Toggle if this widget cares about the maxHeight and maxWidth
  57. // parameters.
  58. constrainMax: false,
  59. // maxHeight: Integer
  60. // Largest height size in px the resize node can become.
  61. maxHeight:0,
  62. // maxWidth: Integer
  63. // Largest width size in px the reize node can become.
  64. maxWidth:0,
  65. // fixedAspect: Boolean
  66. // Toggle to enable this widget to maintain the aspect
  67. // ratio of the attached node.
  68. fixedAspect: false,
  69. // intermediateChanges: Boolean
  70. // Toggle to enable/disable this widget from firing onResize
  71. // events at every step of a resize. If `activeResize` is true,
  72. // and this is false, onResize only fires _after_ the drop
  73. // operation. Animated resizing is not affected by this setting.
  74. intermediateChanges: false,
  75. // startTopic: String
  76. // The name of the topic this resizehandle publishes when resize is starting
  77. startTopic: "/dojo/resize/start",
  78. // endTopic: String
  79. // The name of the topic this resizehandle publishes when resize is complete
  80. endTopic:"/dojo/resize/stop",
  81. templateString: '<div dojoAttachPoint="resizeHandle" class="dojoxResizeHandle"><div></div></div>',
  82. postCreate: function(){
  83. // summary: setup our one major listener upon creation
  84. this.connect(this.resizeHandle, "onmousedown", "_beginSizing");
  85. if(!this.activeResize){
  86. // there shall be only a single resize rubberbox that at the top
  87. // level so that we can overlay it on anything whenever the user
  88. // resizes something. Since there is only one mouse pointer he
  89. // can't at once resize multiple things interactively.
  90. this._resizeHelper = manager.byId('dojoxGlobalResizeHelper');
  91. if(!this._resizeHelper){
  92. this._resizeHelper = new _ResizeHelper({
  93. id: 'dojoxGlobalResizeHelper'
  94. }).placeAt(windowBase.body());
  95. domClass.add(this._resizeHelper.domNode, this.activeResizeClass);
  96. }
  97. }else{ this.animateSizing = false; }
  98. if(!this.minSize){
  99. this.minSize = { w: this.minWidth, h: this.minHeight };
  100. }
  101. if(this.constrainMax){
  102. this.maxSize = { w: this.maxWidth, h: this.maxHeight }
  103. }
  104. // should we modify the css for the cursor hover to n-resize nw-resize and w-resize?
  105. this._resizeX = this._resizeY = false;
  106. var addClass = lang.partial(domClass.add, this.resizeHandle);
  107. switch(this.resizeAxis.toLowerCase()){
  108. case "xy" :
  109. this._resizeX = this._resizeY = true;
  110. // FIXME: need logic to determine NW or NE class to see
  111. // based on which [todo] corner is clicked
  112. addClass("dojoxResizeNW");
  113. break;
  114. case "x" :
  115. this._resizeX = true;
  116. addClass("dojoxResizeW");
  117. break;
  118. case "y" :
  119. this._resizeY = true;
  120. addClass("dojoxResizeN");
  121. break;
  122. }
  123. },
  124. _beginSizing: function(/*Event*/ e){
  125. // summary: setup movement listeners and calculate initial size
  126. if(this._isSizing){ return; }
  127. connect.publish(this.startTopic, [ this ]);
  128. this.targetWidget = manager.byId(this.targetId);
  129. this.targetDomNode = this.targetWidget ? this.targetWidget.domNode : domUtil.byId(this.targetId);
  130. if(this.targetContainer){ this.targetDomNode = this.targetContainer; }
  131. if(!this.targetDomNode){ return; }
  132. if(!this.activeResize){
  133. var c = domGeometry.position(this.targetDomNode, true);
  134. this._resizeHelper.resize({l: c.x, t: c.y, w: c.w, h: c.h});
  135. this._resizeHelper.show();
  136. if(!this.isLeftToRight()){
  137. this._resizeHelper.startPosition = {l: c.x, t: c.y};
  138. }
  139. }
  140. this._isSizing = true;
  141. this.startPoint = { x:e.clientX, y:e.clientY };
  142. // widget.resize() or setting style.width/height expects native box model dimension
  143. // (in most cases content-box, but it may be border-box if in backcompact mode)
  144. var style = domStyle.getComputedStyle(this.targetDomNode),
  145. borderModel = domGeometry.boxModel==='border-model',
  146. padborder = borderModel?{w:0,h:0}:domGeometry.getPadBorderExtents(this.targetDomNode, style),
  147. margin = domGeometry.getMarginExtents(this.targetDomNode, style),
  148. mb;
  149. mb = this.startSize = {
  150. w: domStyle.get(this.targetDomNode, 'width', style),
  151. h: domStyle.get(this.targetDomNode, 'height', style),
  152. //ResizeHelper.resize expects a bounding box of the
  153. //border box, so let's keep track of padding/border
  154. //width/height as well
  155. pbw: padborder.w, pbh: padborder.h,
  156. mw: margin.w, mh: margin.h};
  157. if(!this.isLeftToRight() && dojo.style(this.targetDomNode, "position") == "absolute"){
  158. var p = domGeometry.position(this.targetDomNode, true);
  159. this.startPosition = {l: p.x, t: p.y};
  160. }
  161. this._pconnects = [
  162. connect.connect(windowBase.doc,"onmousemove",this,"_updateSizing"),
  163. connect.connect(windowBase.doc,"onmouseup", this, "_endSizing")
  164. ];
  165. eventUtil.stop(e);
  166. },
  167. _updateSizing: function(/*Event*/ e){
  168. // summary: called when moving the ResizeHandle ... determines
  169. // new size based on settings/position and sets styles.
  170. if(this.activeResize){
  171. this._changeSizing(e);
  172. }else{
  173. var tmp = this._getNewCoords(e, 'border', this._resizeHelper.startPosition);
  174. if(tmp === false){ return; }
  175. this._resizeHelper.resize(tmp);
  176. }
  177. e.preventDefault();
  178. },
  179. _getNewCoords: function(/* Event */ e, /* String */ box, /* Object */startPosition){
  180. // On IE, if you move the mouse above/to the left of the object being resized,
  181. // sometimes clientX/Y aren't set, apparently. Just ignore the event.
  182. try{
  183. if(!e.clientX || !e.clientY){ return false; }
  184. }catch(e){
  185. // sometimes you get an exception accessing above fields...
  186. return false;
  187. }
  188. this._activeResizeLastEvent = e;
  189. var dx = (this.isLeftToRight()?1:-1) * (this.startPoint.x - e.clientX),
  190. dy = this.startPoint.y - e.clientY,
  191. newW = this.startSize.w - (this._resizeX ? dx : 0),
  192. newH = this.startSize.h - (this._resizeY ? dy : 0),
  193. r = this._checkConstraints(newW, newH)
  194. ;
  195. startPosition = (startPosition || this.startPosition);
  196. if(startPosition && this._resizeX){
  197. // adjust x position for RtoL
  198. r.l = startPosition.l + dx;
  199. if(r.w != newW){
  200. r.l += (newW - r.w);
  201. }
  202. r.t = startPosition.t;
  203. }
  204. switch(box){
  205. case 'margin':
  206. r.w += this.startSize.mw;
  207. r.h += this.startSize.mh;
  208. //pass through
  209. case "border":
  210. r.w += this.startSize.pbw;
  211. r.h += this.startSize.pbh;
  212. break;
  213. //default: //native, do nothing
  214. }
  215. return r; // Object
  216. },
  217. _checkConstraints: function(newW, newH){
  218. // summary: filter through the various possible constaint possibilities.
  219. // minimum size check
  220. if(this.minSize){
  221. var tm = this.minSize;
  222. if(newW < tm.w){
  223. newW = tm.w;
  224. }
  225. if(newH < tm.h){
  226. newH = tm.h;
  227. }
  228. }
  229. // maximum size check:
  230. if(this.constrainMax && this.maxSize){
  231. var ms = this.maxSize;
  232. if(newW > ms.w){
  233. newW = ms.w;
  234. }
  235. if(newH > ms.h){
  236. newH = ms.h;
  237. }
  238. }
  239. if(this.fixedAspect){
  240. var w = this.startSize.w, h = this.startSize.h,
  241. delta = w * newH - h * newW;
  242. if(delta<0){
  243. newW = newH * w / h;
  244. }else if(delta>0){
  245. newH = newW * h / w;
  246. }
  247. }
  248. return { w: newW, h: newH }; // Object
  249. },
  250. _changeSizing: function(/*Event*/ e){
  251. // summary: apply sizing information based on information in (e) to attached node
  252. var isWidget = this.targetWidget && lang.isFunction(this.targetWidget.resize),
  253. tmp = this._getNewCoords(e, isWidget && 'margin');
  254. if(tmp === false){ return; }
  255. if(isWidget){
  256. this.targetWidget.resize(tmp);
  257. }else{
  258. if(this.animateSizing){
  259. var anim = fxUtil[this.animateMethod]([
  260. fxBase.animateProperty({
  261. node: this.targetDomNode,
  262. properties: {
  263. width: { start: this.startSize.w, end: tmp.w }
  264. },
  265. duration: this.animateDuration
  266. }),
  267. fxBase.animateProperty({
  268. node: this.targetDomNode,
  269. properties: {
  270. height: { start: this.startSize.h, end: tmp.h }
  271. },
  272. duration: this.animateDuration
  273. })
  274. ]);
  275. anim.play();
  276. }else{
  277. domStyle.set(this.targetDomNode,{
  278. width: tmp.w + "px",
  279. height: tmp.h + "px"
  280. });
  281. }
  282. }
  283. if(this.intermediateChanges){
  284. this.onResize(e);
  285. }
  286. },
  287. _endSizing: function(/*Event*/ e){
  288. // summary: disconnect listenrs and cleanup sizing
  289. arrayUtil.forEach(this._pconnects, connect.disconnect);
  290. var pub = lang.partial(connect.publish, this.endTopic, [ this ]);
  291. if(!this.activeResize){
  292. this._resizeHelper.hide();
  293. this._changeSizing(e);
  294. setTimeout(pub, this.animateDuration + 15);
  295. }else{
  296. pub();
  297. }
  298. this._isSizing = false;
  299. this.onResize(e);
  300. },
  301. onResize: function(e){
  302. // summary: Stub fired when sizing is done. Fired once
  303. // after resize, or often when `intermediateChanges` is
  304. // set to true.
  305. }
  306. });
  307. var _ResizeHelper = dojo.declare("dojox.layout._ResizeHelper", Widget, {
  308. // summary: A global private resize helper shared between any
  309. // `dojox.layout.ResizeHandle` with activeSizing off.
  310. show: function(){
  311. // summary: show helper to start resizing
  312. domStyle.set(this.domNode, "display", "");
  313. },
  314. hide: function(){
  315. // summary: hide helper after resizing is complete
  316. domStyle.set(this.domNode, "display", "none");
  317. },
  318. resize: function(/* Object */dim){
  319. // summary: size the widget and place accordingly
  320. domGeometry.setMarginBox(this.domNode, dim);
  321. }
  322. });
  323. return ResizeHandle;
  324. });