Header.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. /**
  2. * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}.
  3. */
  4. Ext.define('Ext.panel.Header', {
  5. extend: 'Ext.container.Container',
  6. uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS', 'Ext.layout.component.Body', 'Ext.Img'],
  7. alias: 'widget.header',
  8. /**
  9. * @property {Boolean} isHeader
  10. * `true` in this class to identify an objact as an instantiated Header, or subclass thereof.
  11. */
  12. isHeader : true,
  13. defaultType : 'tool',
  14. indicateDrag : false,
  15. weight : -1,
  16. componentLayout: 'body',
  17. /**
  18. * @cfg {String} [titleAlign='left']
  19. * May be `"left"`, `"right"` or `"center"`.
  20. *
  21. * The alignment of the title text within the available space between the icon and the tools.
  22. */
  23. titleAlign: 'left',
  24. childEls: [
  25. 'body'
  26. ],
  27. renderTpl: [
  28. '<div id="{id}-body" class="{baseCls}-body {bodyCls}',
  29. '<tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl>"',
  30. '<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>>',
  31. '{%this.renderContainer(out,values)%}',
  32. '</div>'
  33. ],
  34. headingTpl: '<span id="{id}-textEl" class="{cls}-text {cls}-text-{ui}">{title}</span>',
  35. shrinkWrap: 3,
  36. /**
  37. * @cfg {String} title
  38. * The title text to display.
  39. */
  40. /**
  41. * @cfg {String} iconCls
  42. * CSS class for an icon in the header. Used for displaying an icon to the left of a title.
  43. */
  44. /**
  45. * @cfg {String} icon
  46. * Path to image for an icon in the header. Used for displaying an icon to the left of a title.
  47. */
  48. initComponent: function() {
  49. var me = this,
  50. ruleStyle,
  51. rule,
  52. style,
  53. ui,
  54. tempEl;
  55. me.addEvents(
  56. /**
  57. * @event click
  58. * Fires when the header is clicked. This event will not be fired
  59. * if the click was on a {@link Ext.panel.Tool}
  60. * @param {Ext.panel.Header} this
  61. * @param {Ext.EventObject} e
  62. */
  63. 'click',
  64. /**
  65. * @event dblclick
  66. * Fires when the header is double clicked. This event will not
  67. * be fired if the click was on a {@link Ext.panel.Tool}
  68. * @param {Ext.panel.Header} this
  69. * @param {Ext.EventObject} e
  70. */
  71. 'dblclick'
  72. );
  73. me.indicateDragCls = me.baseCls + '-draggable';
  74. me.title = me.title || '&#160;';
  75. me.tools = me.tools || [];
  76. me.items = me.items || [];
  77. me.orientation = me.orientation || 'horizontal';
  78. me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
  79. //add the dock as a ui
  80. //this is so we support top/right/left/bottom headers
  81. me.addClsWithUI([me.orientation, me.dock]);
  82. if (me.indicateDrag) {
  83. me.addCls(me.indicateDragCls);
  84. }
  85. // Add Icon
  86. if (!Ext.isEmpty(me.iconCls) || !Ext.isEmpty(me.icon)) {
  87. me.initIconCmp();
  88. me.items.push(me.iconCmp);
  89. }
  90. // Add Title
  91. if (me.orientation == 'vertical') {
  92. me.layout = {
  93. type : 'vbox',
  94. align: 'center'
  95. };
  96. me.textConfig = {
  97. width: 16,
  98. cls: me.baseCls + '-text',
  99. type: 'text',
  100. text: me.title,
  101. rotate: {
  102. degrees: 90
  103. }
  104. };
  105. ui = me.ui;
  106. if (Ext.isArray(ui)) {
  107. ui = ui[0];
  108. }
  109. ruleStyle = '.' + me.baseCls + '-text-' + ui;
  110. if (Ext.scopeResetCSS) {
  111. ruleStyle = '.' + Ext.baseCSSPrefix + 'reset ' + ruleStyle;
  112. }
  113. rule = Ext.util.CSS.getRule(ruleStyle);
  114. // We might have been disallowed access to the stylesheet: https://sencha.jira.com/browse/EXTJSIV-5084
  115. if (rule) {
  116. style = rule.style;
  117. } else {
  118. style = (tempEl = Ext.resetElement.createChild({style: 'position:absolute', cls: me.baseCls + '-text-' + ui})).getStyles('fontFamily', 'fontWeight', 'fontSize', 'color');
  119. tempEl.remove();
  120. }
  121. if (style) {
  122. Ext.apply(me.textConfig, {
  123. 'font-family': style.fontFamily,
  124. 'font-weight': style.fontWeight,
  125. 'font-size': style.fontSize,
  126. fill: style.color
  127. });
  128. }
  129. me.titleCmp = new Ext.draw.Component({
  130. width : 16,
  131. ariaRole : 'heading',
  132. focusable : false,
  133. viewBox : false,
  134. flex : 1,
  135. id : me.id + '_hd',
  136. autoSize : true,
  137. items : me.textConfig,
  138. xhooks: {
  139. setSize: function (width) {
  140. // don't pass 2nd arg (height) on to setSize or we break 'flex:1'
  141. this.callParent([width]);
  142. }
  143. },
  144. // this is a bit of a cheat: we are not selecting an element of titleCmp
  145. // but rather of titleCmp.items[0]
  146. childEls : [
  147. { name: 'textEl', select: '.' + me.baseCls + '-text' }
  148. ]
  149. });
  150. } else {
  151. me.layout = {
  152. type : 'hbox',
  153. align: 'middle'
  154. };
  155. me.titleCmp = new Ext.Component({
  156. ariaRole : 'heading',
  157. focusable : false,
  158. noWrap : true,
  159. flex : 1,
  160. id : me.id + '_hd',
  161. style : 'text-align:' + me.titleAlign,
  162. cls : me.baseCls + '-text-container',
  163. renderTpl : me.getTpl('headingTpl'),
  164. renderData: {
  165. title: me.title,
  166. cls : me.baseCls,
  167. ui : me.ui
  168. },
  169. childEls : ['textEl']
  170. });
  171. }
  172. me.items.push(me.titleCmp);
  173. // Add Tools
  174. me.items = me.items.concat(me.tools);
  175. me.callParent();
  176. me.on({
  177. dblclick: me.onDblClick,
  178. click: me.onClick,
  179. element: 'el',
  180. scope: me
  181. });
  182. },
  183. initIconCmp: function() {
  184. var me = this,
  185. cfg = {
  186. focusable: false,
  187. src: Ext.BLANK_IMAGE_URL,
  188. cls: [me.baseCls + '-icon', me.iconCls],
  189. id: me.id + '-iconEl',
  190. iconCls: me.iconCls
  191. };
  192. if (!Ext.isEmpty(me.icon)) {
  193. delete cfg.iconCls;
  194. cfg.src = me.icon;
  195. }
  196. me.iconCmp = new Ext.Img(cfg);
  197. },
  198. afterRender: function() {
  199. this.el.unselectable();
  200. this.callParent();
  201. },
  202. // inherit docs
  203. addUIClsToElement: function(cls) {
  204. var me = this,
  205. result = me.callParent(arguments),
  206. classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
  207. array, i;
  208. if (me.bodyCls) {
  209. array = me.bodyCls.split(' ');
  210. for (i = 0; i < classes.length; i++) {
  211. if (!Ext.Array.contains(array, classes[i])) {
  212. array.push(classes[i]);
  213. }
  214. }
  215. me.bodyCls = array.join(' ');
  216. } else {
  217. me.bodyCls = classes.join(' ');
  218. }
  219. return result;
  220. },
  221. // inherit docs
  222. removeUIClsFromElement: function(cls) {
  223. var me = this,
  224. result = me.callParent(arguments),
  225. classes = [me.baseCls + '-body-' + cls, me.baseCls + '-body-' + me.ui + '-' + cls],
  226. array, i;
  227. if (me.bodyCls) {
  228. array = me.bodyCls.split(' ');
  229. for (i = 0; i < classes.length; i++) {
  230. Ext.Array.remove(array, classes[i]);
  231. }
  232. me.bodyCls = array.join(' ');
  233. }
  234. return result;
  235. },
  236. // inherit docs
  237. addUIToElement: function() {
  238. var me = this,
  239. array, cls;
  240. me.callParent(arguments);
  241. cls = me.baseCls + '-body-' + me.ui;
  242. if (me.rendered) {
  243. if (me.bodyCls) {
  244. me.body.addCls(me.bodyCls);
  245. } else {
  246. me.body.addCls(cls);
  247. }
  248. } else {
  249. if (me.bodyCls) {
  250. array = me.bodyCls.split(' ');
  251. if (!Ext.Array.contains(array, cls)) {
  252. array.push(cls);
  253. }
  254. me.bodyCls = array.join(' ');
  255. } else {
  256. me.bodyCls = cls;
  257. }
  258. }
  259. if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
  260. me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
  261. }
  262. },
  263. // inherit docs
  264. removeUIFromElement: function() {
  265. var me = this,
  266. array, cls;
  267. me.callParent(arguments);
  268. cls = me.baseCls + '-body-' + me.ui;
  269. if (me.rendered) {
  270. if (me.bodyCls) {
  271. me.body.removeCls(me.bodyCls);
  272. } else {
  273. me.body.removeCls(cls);
  274. }
  275. } else {
  276. if (me.bodyCls) {
  277. array = me.bodyCls.split(' ');
  278. Ext.Array.remove(array, cls);
  279. me.bodyCls = array.join(' ');
  280. } else {
  281. me.bodyCls = cls;
  282. }
  283. }
  284. if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
  285. me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
  286. }
  287. },
  288. onClick: function(e) {
  289. this.fireClickEvent('click', e);
  290. },
  291. onDblClick: function(e){
  292. this.fireClickEvent('dblclick', e);
  293. },
  294. fireClickEvent: function(type, e){
  295. var toolCls = '.' + Ext.panel.Tool.prototype.baseCls;
  296. if (!e.getTarget(toolCls)) {
  297. this.fireEvent(type, this, e);
  298. }
  299. },
  300. getFocusEl: function() {
  301. return this.el;
  302. },
  303. getTargetEl: function() {
  304. return this.body || this.frameBody || this.el;
  305. },
  306. /**
  307. * Sets the title of the header.
  308. * @param {String} title The title to be set
  309. */
  310. setTitle: function(title) {
  311. var me = this,
  312. sprite,
  313. surface;
  314. if (me.rendered) {
  315. if (me.titleCmp.rendered) {
  316. if (me.titleCmp.surface) {
  317. me.title = title || '';
  318. sprite = me.titleCmp.surface.items.items[0];
  319. surface = me.titleCmp.surface;
  320. surface.remove(sprite);
  321. me.textConfig.type = 'text';
  322. me.textConfig.text = title;
  323. sprite = surface.add(me.textConfig);
  324. sprite.setAttributes({
  325. rotate: {
  326. degrees: 90
  327. }
  328. }, true);
  329. me.titleCmp.autoSizeSurface();
  330. } else {
  331. me.title = title;
  332. me.titleCmp.textEl.update(me.title || '&#160;');
  333. }
  334. me.titleCmp.updateLayout();
  335. } else {
  336. me.titleCmp.on({
  337. render: function() {
  338. me.setTitle(title);
  339. },
  340. single: true
  341. });
  342. }
  343. } else {
  344. me.title = title;
  345. }
  346. },
  347. /**
  348. * @private
  349. * Used when shrink wrapping a Panel to either content width or header width.
  350. * This returns the minimum width required to display the header, icon and tools.
  351. * **This is only intended for use with horizontal headers.**
  352. */
  353. getMinWidth: function() {
  354. var me = this,
  355. textEl = me.titleCmp.textEl.dom,
  356. result,
  357. tools = me.tools,
  358. l, i;
  359. // Measure text width as inline element so it doesn't stretch
  360. textEl.style.display = 'inline';
  361. result = textEl.offsetWidth;
  362. textEl.style.display = '';
  363. // Add tools width
  364. if (tools && (l = tools.length)) {
  365. for (i = 0; i < l; i++) {
  366. if (tools[i].el) {
  367. result += tools[i].el.dom.offsetWidth;
  368. }
  369. }
  370. }
  371. // Add iconWidth
  372. if (me.iconCmp) {
  373. result += me.iconCmp.el.dom.offsetWidth;
  374. }
  375. // Return with some space between title and tools/end of header.
  376. return result + 10;
  377. },
  378. /**
  379. * Sets the CSS class that provides the icon image for this header. This method will replace any existing
  380. * icon class if one has already been set.
  381. * @param {String} cls The new CSS class name
  382. */
  383. setIconCls: function(cls) {
  384. var me = this,
  385. isEmpty = !cls || !cls.length,
  386. iconCmp = me.iconCmp;
  387. me.iconCls = cls;
  388. if (!me.iconCmp && !isEmpty) {
  389. me.initIconCmp();
  390. me.insert(0, me.iconCmp);
  391. } else if (iconCmp) {
  392. if (isEmpty) {
  393. me.iconCmp.destroy();
  394. delete me.iconCmp;
  395. } else {
  396. iconCmp.removeCls(iconCmp.iconCls);
  397. iconCmp.addCls(cls);
  398. iconCmp.iconCls = cls;
  399. }
  400. }
  401. },
  402. /**
  403. * Sets the image path that provides the icon image for this header. This method will replace any existing
  404. * icon if one has already been set.
  405. * @param {String} icon The new icon path
  406. */
  407. setIcon: function(icon) {
  408. var me = this,
  409. isEmpty = !icon || !icon.length,
  410. iconCmp = me.iconCmp;
  411. me.icon = icon;
  412. if (!me.iconCmp && !isEmpty) {
  413. me.initIconCmp();
  414. me.insert(0, me.iconCmp);
  415. } else if (iconCmp) {
  416. if (isEmpty) {
  417. me.iconCmp.destroy();
  418. delete me.iconCmp;
  419. } else {
  420. iconCmp.setSrc(me.icon);
  421. }
  422. }
  423. },
  424. /**
  425. * Add a tool to the header
  426. * @param {Object} tool
  427. */
  428. addTool: function(tool) {
  429. this.tools.push(this.add(tool));
  430. },
  431. /**
  432. * @protected
  433. * Set up the `tools.<tool type>` link in the owning Panel.
  434. * Bind the tool to its owning Panel.
  435. * @param component
  436. * @param index
  437. */
  438. onAdd: function(component, index) {
  439. this.callParent(arguments);
  440. if (component instanceof Ext.panel.Tool) {
  441. component.bindTo(this.ownerCt);
  442. this.tools[component.type] = component;
  443. }
  444. },
  445. /**
  446. * Add bodyCls to the renderData object
  447. * @return {Object} Object with keys and values that are going to be applied to the renderTpl
  448. * @private
  449. */
  450. initRenderData: function() {
  451. return Ext.applyIf(this.callParent(), {
  452. bodyCls: this.bodyCls
  453. });
  454. }
  455. });