Layer.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. /**
  2. * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
  3. * automatic maintaining of shadow/shim positions.
  4. */
  5. Ext.define('Ext.Layer', {
  6. extend: 'Ext.Element',
  7. uses: ['Ext.Shadow'],
  8. /**
  9. * @cfg {Boolean} [shim=true]
  10. * False to disable the iframe shim in browsers which need one.
  11. */
  12. /**
  13. * @cfg {String/Boolean} [shadow=false]
  14. * True to automatically create an {@link Ext.Shadow}, or a string indicating the
  15. * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow.
  16. */
  17. /**
  18. * @cfg {Object} [dh={tag: 'div', cls: 'x-layer'}]
  19. * DomHelper object config to create element with.
  20. */
  21. /**
  22. * @cfg {Boolean} [constrain=true]
  23. * False to disable constrain to viewport.
  24. */
  25. /**
  26. * @cfg {String} cls
  27. * CSS class to add to the element
  28. */
  29. /**
  30. * @cfg {Number} [zindex=11000]
  31. * Starting z-index.
  32. */
  33. /**
  34. * @cfg {Number} [shadowOffset=4]
  35. * Number of pixels to offset the shadow
  36. */
  37. /**
  38. * @cfg {Boolean} [useDisplay=false]
  39. * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
  40. * to use css style <tt>'display:none;'</tt> to hide the Layer.
  41. */
  42. /**
  43. * @cfg {String} visibilityCls
  44. * The CSS class name to add in order to hide this Layer if this layer
  45. * is configured with <code>{@link #hideMode}: 'asclass'</code>
  46. */
  47. /**
  48. * @cfg {String} hideMode
  49. * A String which specifies how this Layer will be hidden.
  50. * Values may be:
  51. *
  52. * - `'display'` : The Component will be hidden using the `display: none` style.
  53. * - `'visibility'` : The Component will be hidden using the `visibility: hidden` style.
  54. * - `'offsets'` : The Component will be hidden by absolutely positioning it out of the visible area
  55. * of the document. This is useful when a hidden Component must maintain measurable dimensions.
  56. * Hiding using `display` results in a Component having zero dimensions.
  57. */
  58. // shims are shared among layer to keep from having 100 iframes
  59. statics: {
  60. shims: []
  61. },
  62. isLayer: true,
  63. /**
  64. * Creates new Layer.
  65. * @param {Object} [config] An object with config options.
  66. * @param {String/HTMLElement} [existingEl] Uses an existing DOM element.
  67. * If the element is not found it creates it.
  68. */
  69. constructor: function(config, existingEl) {
  70. config = config || {};
  71. var me = this,
  72. dh = Ext.DomHelper,
  73. cp = config.parentEl,
  74. pel = cp ? Ext.getDom(cp) : document.body,
  75. hm = config.hideMode;
  76. if (existingEl) {
  77. me.dom = Ext.getDom(existingEl);
  78. }
  79. if (!me.dom) {
  80. me.dom = dh.append(pel, config.dh || {
  81. tag: 'div',
  82. cls: Ext.baseCSSPrefix + 'layer' // primarily to give el 'position:absolute'
  83. });
  84. } else {
  85. me.addCls(Ext.baseCSSPrefix + 'layer');
  86. if (!me.dom.parentNode) {
  87. pel.appendChild(me.dom);
  88. }
  89. }
  90. if (config.id) {
  91. me.id = me.dom.id = config.id;
  92. } else {
  93. me.id = Ext.id(me.dom);
  94. }
  95. Ext.Element.addToCache(me);
  96. if (config.cls) {
  97. me.addCls(config.cls);
  98. }
  99. me.constrain = config.constrain !== false;
  100. // Allow Components to pass their hide mode down to the Layer if they are floating.
  101. // Otherwise, allow useDisplay to override the default hiding method which is visibility.
  102. // TODO: Have ExtJS's Element implement visibilityMode by using classes as in Mobile.
  103. if (hm) {
  104. me.setVisibilityMode(Ext.Element[hm.toUpperCase()]);
  105. if (me.visibilityMode == Ext.Element.ASCLASS) {
  106. me.visibilityCls = config.visibilityCls;
  107. }
  108. } else if (config.useDisplay) {
  109. me.setVisibilityMode(Ext.Element.DISPLAY);
  110. } else {
  111. me.setVisibilityMode(Ext.Element.VISIBILITY);
  112. }
  113. if (config.shadow) {
  114. me.shadowOffset = config.shadowOffset || 4;
  115. me.shadow = new Ext.Shadow({
  116. offset: me.shadowOffset,
  117. mode: config.shadow
  118. });
  119. me.disableShadow();
  120. } else {
  121. me.shadowOffset = 0;
  122. }
  123. me.useShim = config.shim !== false && Ext.useShims;
  124. if (config.hidden === true) {
  125. me.hide();
  126. } else {
  127. me.show();
  128. }
  129. },
  130. getZIndex: function() {
  131. return parseInt((this.getShim() || this).getStyle('z-index'), 10);
  132. },
  133. getShim: function() {
  134. var me = this,
  135. shim, pn;
  136. if (!me.useShim) {
  137. return null;
  138. }
  139. if (!me.shim) {
  140. shim = me.self.shims.shift();
  141. if (!shim) {
  142. shim = me.createShim();
  143. shim.enableDisplayMode('block');
  144. shim.hide();
  145. }
  146. pn = me.dom.parentNode;
  147. if (shim.dom.parentNode != pn) {
  148. pn.insertBefore(shim.dom, me.dom);
  149. }
  150. me.shim = shim;
  151. }
  152. return me.shim;
  153. },
  154. hideShim: function() {
  155. var me = this;
  156. if (me.shim) {
  157. me.shim.setDisplayed(false);
  158. me.self.shims.push(me.shim);
  159. delete me.shim;
  160. }
  161. },
  162. disableShadow: function() {
  163. var me = this;
  164. if (me.shadow && !me.shadowDisabled) {
  165. me.shadowDisabled = true;
  166. me.shadow.hide();
  167. me.lastShadowOffset = me.shadowOffset;
  168. me.shadowOffset = 0;
  169. }
  170. },
  171. enableShadow: function(show) {
  172. var me = this;
  173. if (me.shadow && me.shadowDisabled) {
  174. me.shadowDisabled = false;
  175. me.shadowOffset = me.lastShadowOffset;
  176. delete me.lastShadowOffset;
  177. if (show) {
  178. me.sync(true);
  179. }
  180. }
  181. },
  182. /**
  183. * @private
  184. * Synchronize this Layer's associated elements, the shadow, and possibly the shim.
  185. *
  186. * This code can execute repeatedly in milliseconds,
  187. * eg: dragging a Component configured liveDrag: true, or which has no ghost method
  188. * so code size was sacrificed for efficiency (e.g. no getBox/setBox, no XY calls)
  189. *
  190. * @param {Boolean} doShow Pass true to ensure that the shadow is shown.
  191. */
  192. sync: function(doShow) {
  193. var me = this,
  194. shadow = me.shadow,
  195. shadowPos, shimStyle, shadowSize,
  196. shim, l, t, w, h, shimIndex;
  197. if (!me.updating && me.isVisible() && (shadow || me.useShim)) {
  198. shim = me.getShim();
  199. l = me.getLocalX();
  200. t = me.getLocalY();
  201. w = me.dom.offsetWidth;
  202. h = me.dom.offsetHeight;
  203. if (shadow && !me.shadowDisabled) {
  204. if (doShow && !shadow.isVisible()) {
  205. shadow.show(me);
  206. } else {
  207. shadow.realign(l, t, w, h);
  208. }
  209. if (shim) {
  210. // TODO: Determine how the shims zIndex is above the layer zIndex at this point
  211. shimIndex = shim.getStyle('z-index');
  212. if (shimIndex > me.zindex) {
  213. me.shim.setStyle('z-index', me.zindex - 2);
  214. }
  215. shim.show();
  216. // fit the shim behind the shadow, so it is shimmed too
  217. if (shadow.isVisible()) {
  218. shadowPos = shadow.el.getXY();
  219. shimStyle = shim.dom.style;
  220. shadowSize = shadow.el.getSize();
  221. if (Ext.supports.CSS3BoxShadow) {
  222. shadowSize.height += 6;
  223. shadowSize.width += 4;
  224. shadowPos[0] -= 2;
  225. shadowPos[1] -= 4;
  226. }
  227. shimStyle.left = (shadowPos[0]) + 'px';
  228. shimStyle.top = (shadowPos[1]) + 'px';
  229. shimStyle.width = (shadowSize.width) + 'px';
  230. shimStyle.height = (shadowSize.height) + 'px';
  231. } else {
  232. shim.setSize(w, h);
  233. shim.setLeftTop(l, t);
  234. }
  235. }
  236. } else if (shim) {
  237. // TODO: Determine how the shims zIndex is above the layer zIndex at this point
  238. shimIndex = shim.getStyle('z-index');
  239. if (shimIndex > me.zindex) {
  240. me.shim.setStyle('z-index', me.zindex - 2);
  241. }
  242. shim.show();
  243. shim.setSize(w, h);
  244. shim.setLeftTop(l, t);
  245. }
  246. }
  247. return me;
  248. },
  249. remove: function() {
  250. this.hideUnders();
  251. this.callParent();
  252. },
  253. // private
  254. beginUpdate: function() {
  255. this.updating = true;
  256. },
  257. // private
  258. endUpdate: function() {
  259. this.updating = false;
  260. this.sync(true);
  261. },
  262. // private
  263. hideUnders: function() {
  264. if (this.shadow) {
  265. this.shadow.hide();
  266. }
  267. this.hideShim();
  268. },
  269. // private
  270. constrainXY: function() {
  271. if (this.constrain) {
  272. var vw = Ext.Element.getViewWidth(),
  273. vh = Ext.Element.getViewHeight(),
  274. s = Ext.getDoc().getScroll(),
  275. xy = this.getXY(),
  276. x = xy[0],
  277. y = xy[1],
  278. so = this.shadowOffset,
  279. w = this.dom.offsetWidth + so,
  280. h = this.dom.offsetHeight + so,
  281. moved = false; // only move it if it needs it
  282. // first validate right/bottom
  283. if ((x + w) > vw + s.left) {
  284. x = vw - w - so;
  285. moved = true;
  286. }
  287. if ((y + h) > vh + s.top) {
  288. y = vh - h - so;
  289. moved = true;
  290. }
  291. // then make sure top/left isn't negative
  292. if (x < s.left) {
  293. x = s.left;
  294. moved = true;
  295. }
  296. if (y < s.top) {
  297. y = s.top;
  298. moved = true;
  299. }
  300. if (moved) {
  301. Ext.Layer.superclass.setXY.call(this, [x, y]);
  302. this.sync();
  303. }
  304. }
  305. return this;
  306. },
  307. getConstrainOffset: function() {
  308. return this.shadowOffset;
  309. },
  310. // overridden Element method
  311. setVisible: function(visible, animate, duration, callback, easing) {
  312. var me = this,
  313. cb;
  314. // post operation processing
  315. cb = function() {
  316. if (visible) {
  317. me.sync(true);
  318. }
  319. if (callback) {
  320. callback();
  321. }
  322. };
  323. // Hide shadow and shim if hiding
  324. if (!visible) {
  325. me.hideUnders(true);
  326. }
  327. me.callParent([visible, animate, duration, callback, easing]);
  328. if (!animate) {
  329. cb();
  330. }
  331. return me;
  332. },
  333. // private
  334. beforeFx: function() {
  335. this.beforeAction();
  336. return this.callParent(arguments);
  337. },
  338. // private
  339. afterFx: function() {
  340. this.callParent(arguments);
  341. this.sync(this.isVisible());
  342. },
  343. // private
  344. beforeAction: function() {
  345. if (!this.updating && this.shadow) {
  346. this.shadow.hide();
  347. }
  348. },
  349. // overridden Element method
  350. setLeft: function(left) {
  351. this.callParent(arguments);
  352. return this.sync();
  353. },
  354. setTop: function(top) {
  355. this.callParent(arguments);
  356. return this.sync();
  357. },
  358. setLeftTop: function(left, top) {
  359. this.callParent(arguments);
  360. return this.sync();
  361. },
  362. setXY: function(xy, animate, duration, callback, easing) {
  363. var me = this;
  364. // Callback will restore shadow state and call the passed callback
  365. callback = me.createCB(callback);
  366. me.fixDisplay();
  367. me.beforeAction();
  368. me.callParent([xy, animate, duration, callback, easing]);
  369. if (!animate) {
  370. callback();
  371. }
  372. return me;
  373. },
  374. // private
  375. createCB: function(callback) {
  376. var me = this,
  377. showShadow = me.shadow && me.shadow.isVisible();
  378. return function() {
  379. me.constrainXY();
  380. me.sync(showShadow);
  381. if (callback) {
  382. callback();
  383. }
  384. };
  385. },
  386. // overridden Element method
  387. setX: function(x, animate, duration, callback, easing) {
  388. this.setXY([x, this.getY()], animate, duration, callback, easing);
  389. return this;
  390. },
  391. // overridden Element method
  392. setY: function(y, animate, duration, callback, easing) {
  393. this.setXY([this.getX(), y], animate, duration, callback, easing);
  394. return this;
  395. },
  396. // overridden Element method
  397. setSize: function(w, h, animate, duration, callback, easing) {
  398. var me = this;
  399. // Callback will restore shadow state and call the passed callback
  400. callback = me.createCB(callback);
  401. me.beforeAction();
  402. me.callParent([w, h, animate, duration, callback, easing]);
  403. if (!animate) {
  404. callback();
  405. }
  406. return me;
  407. },
  408. // overridden Element method
  409. setWidth: function(w, animate, duration, callback, easing) {
  410. var me = this;
  411. // Callback will restore shadow state and call the passed callback
  412. callback = me.createCB(callback);
  413. me.beforeAction();
  414. me.callParent([w, animate, duration, callback, easing]);
  415. if (!animate) {
  416. callback();
  417. }
  418. return me;
  419. },
  420. // overridden Element method
  421. setHeight: function(h, animate, duration, callback, easing) {
  422. var me = this;
  423. // Callback will restore shadow state and call the passed callback
  424. callback = me.createCB(callback);
  425. me.beforeAction();
  426. me.callParent([h, animate, duration, callback, easing]);
  427. if (!animate) {
  428. callback();
  429. }
  430. return me;
  431. },
  432. // overridden Element method
  433. setBounds: function(x, y, width, height, animate, duration, callback, easing) {
  434. var me = this;
  435. // Callback will restore shadow state and call the passed callback
  436. callback = me.createCB(callback);
  437. me.beforeAction();
  438. if (!animate) {
  439. Ext.Layer.superclass.setXY.call(me, [x, y]);
  440. Ext.Layer.superclass.setSize.call(me, width, height);
  441. callback();
  442. } else {
  443. me.callParent([x, y, width, height, animate, duration, callback, easing]);
  444. }
  445. return me;
  446. },
  447. /**
  448. * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer
  449. * z-index is automatically incremented depending upon the presence of a shim or a
  450. * shadow in so that it always shows above those two associated elements.
  451. *
  452. * Any shim, will be assigned the passed z-index. A shadow will be assigned the next
  453. * highet z-index, and the Layer's element will receive the highest z-index.
  454. *
  455. * @param {Number} zindex The new z-index to set
  456. * @return {Ext.Layer} The Layer
  457. */
  458. setZIndex: function(zindex) {
  459. var me = this;
  460. me.zindex = zindex;
  461. if (me.getShim()) {
  462. me.shim.setStyle('z-index', zindex++);
  463. }
  464. if (me.shadow) {
  465. me.shadow.setZIndex(zindex++);
  466. }
  467. return me.setStyle('z-index', zindex);
  468. },
  469. onOpacitySet: function(opacity){
  470. var shadow = this.shadow;
  471. if (shadow) {
  472. shadow.setOpacity(opacity);
  473. }
  474. }
  475. });