modx.searchbar.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. MODx.SearchBar = function(config) {
  2. config = config || {};
  3. Ext.applyIf(config, {
  4. renderTo: 'modx-manager-search'
  5. ,listClass: 'modx-manager-search-results'
  6. ,emptyText: _('search')
  7. ,id: 'modx-uberbar'
  8. ,maxHeight: this.getViewPortSize()
  9. ,typeAhead: true
  10. // ,listAlign: [ 'tl-bl?', [0, 0] ] // this is default
  11. ,listAlign: [ 'tl-bl?', [-12, 0] ] // account for padding + border width of container (added by Ext JS)
  12. ,triggerConfig: {
  13. tag: 'button'
  14. ,type: "submit"
  15. ,"aria-label": "Go"
  16. ,cls: 'x-form-trigger icon icon-large icon-search'
  17. }
  18. ,defaultAutoCreate: {
  19. tag: "input"
  20. ,type: "text"
  21. ,size: "24"
  22. ,autocomplete: "off"
  23. ,"aria-label" : _('search')
  24. }
  25. ,minChars: 1
  26. ,displayField: 'name'
  27. ,valueField: '_action'
  28. ,width: 259
  29. ,maxWidth: 437 // Increase to animate + grow when focused
  30. ,itemSelector: '.x-combo-list-item'
  31. ,tpl: new Ext.XTemplate(
  32. '<tpl for=".">',
  33. // Section wrapper
  34. '<div class="section">',
  35. // Display header only once
  36. '<tpl if="this.type != values.type">',
  37. '<tpl exec="this.type = values.type; values.label = this.getLabel(values)"></tpl>',
  38. '<h3>{label:htmlEncode}</h3>',
  39. '</tpl>',
  40. // Real result, make it use the default styles for a combobox dropdown with x-combo-list-item
  41. '<p class="x-combo-list-item"><a href="?a={_action}"><tpl exec="values.icon = this.getClass(values)"><i class="icon icon-{icon:htmlEncode}"></i></tpl>{name:htmlEncode}<tpl if="description"><em> – {description:htmlEncode}</em></tpl></a></p>',
  42. '</div >',
  43. '</tpl>'
  44. ,{
  45. /**
  46. * Get the appropriate CSS class based on the result type
  47. *
  48. * @param {Array} values
  49. * @returns {string}
  50. */
  51. getClass: function(values) {
  52. if (values.icon) {
  53. return values.icon;
  54. }
  55. if (values.class) {
  56. switch (values.class) {
  57. case 'modDocument':
  58. return 'file';
  59. case 'modSymLink':
  60. return 'files-o';
  61. case 'modWebLink':
  62. return 'link';
  63. case 'modStaticResource':
  64. return 'file-text-o';
  65. default:
  66. break;
  67. }
  68. }
  69. switch (values.type) {
  70. case 'resources':
  71. return 'file';
  72. case 'chunks':
  73. return 'th-large';
  74. case 'templates':
  75. return 'columns';
  76. case 'snippets':
  77. return 'code';
  78. case 'tvs':
  79. return 'list-alt';
  80. case 'plugins':
  81. return 'cogs';
  82. case 'users':
  83. return 'user';
  84. case 'actions':
  85. return 'mail-forward';
  86. }
  87. }
  88. /**
  89. * Get the result type lexicon
  90. *
  91. * @param {Array} values
  92. *
  93. * @returns {String}
  94. */
  95. ,getLabel: function(values) {
  96. if (values.label) {
  97. return values.label;
  98. }
  99. return _('search_resulttype_' + values.type);
  100. }
  101. }
  102. )
  103. ,store: new Ext.data.JsonStore({
  104. url: MODx.config.connector_url
  105. ,baseParams: {
  106. action: 'search/search'
  107. }
  108. ,root: 'results'
  109. ,totalProperty: 'total'
  110. ,fields: ['name', '_action', 'description', 'type', 'icon', 'label', 'class']
  111. ,listeners: {
  112. beforeload: function(store, options) {
  113. if (options.params._action) {
  114. // Prevent weird query on first combo box blur
  115. return false;
  116. }
  117. }
  118. }
  119. })
  120. ,listeners: {
  121. beforequery: {
  122. fn: function() {
  123. this.tpl.type = null;
  124. }
  125. }
  126. ,focus: this.focusBar
  127. ,blur: this.blurBar
  128. ,scope: this
  129. }
  130. });
  131. MODx.SearchBar.superclass.constructor.call(this, config);
  132. this.setKeyMap();
  133. };
  134. Ext.extend(MODx.SearchBar, Ext.form.ComboBox, {
  135. // Initialize the keyboard shortcuts to focus the bar (ctrl + alt + /) and hide it (esc)
  136. setKeyMap: function() {
  137. // This keymap is conflicting with typing certain characters, see #11974
  138. /*new Ext.KeyMap(document, {
  139. key: [191, 0]
  140. ,ctrl: true
  141. ,alt: true
  142. ,handler: function() {
  143. this.hideBar();
  144. this.toggle();
  145. }
  146. ,scope: this
  147. ,stopEvent: true
  148. });*/
  149. // Escape to hide SearchBar
  150. new Ext.KeyMap(document, {
  151. key: 27
  152. ,handler: function() {
  153. this.hideBar();
  154. }
  155. ,scope: this
  156. ,stopEvent: false
  157. });
  158. }
  159. /**
  160. * Override to support opening results in new window/tab
  161. */
  162. ,initList : function() {
  163. if(!this.list){
  164. var cls = 'x-combo-list',
  165. listParent = Ext.getDom(this.getListParent() || Ext.getBody());
  166. this.list = new Ext.Layer({
  167. parentEl: listParent,
  168. shadow: this.shadow,
  169. cls: [cls, this.listClass].join(' '),
  170. constrain:false,
  171. zindex: this.getZIndex(listParent)
  172. });
  173. var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
  174. this.list.setSize(lw, 0);
  175. this.list.swallowEvent('mousewheel');
  176. this.assetHeight = 0;
  177. if(this.syncFont !== false){
  178. this.list.setStyle('font-size', this.el.getStyle('font-size'));
  179. }
  180. if(this.title){
  181. this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
  182. this.assetHeight += this.header.getHeight();
  183. }
  184. this.innerList = this.list.createChild({cls:cls+'-inner'});
  185. this.mon(this.innerList, 'mouseover', this.onViewOver, this);
  186. this.mon(this.innerList, 'mousemove', this.onViewMove, this);
  187. this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
  188. if(this.pageSize){
  189. this.footer = this.list.createChild({cls:cls+'-ft'});
  190. this.pageTb = new Ext.PagingToolbar({
  191. store: this.store,
  192. pageSize: this.pageSize,
  193. renderTo:this.footer
  194. });
  195. this.assetHeight += this.footer.getHeight();
  196. }
  197. if(!this.tpl){
  198. this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
  199. }
  200. this.view = new Ext.DataView({
  201. applyTo: this.innerList,
  202. tpl: this.tpl,
  203. singleSelect: true,
  204. selectedClass: this.selectedClass,
  205. itemSelector: this.itemSelector || '.' + cls + '-item',
  206. emptyText: this.listEmptyText,
  207. deferEmptyText: false
  208. });
  209. // Original view listeners
  210. // this.mon(this.view, {
  211. // containerclick : this.onViewClick,
  212. // click : this.onViewClick,
  213. // scope :this
  214. // });
  215. this.view.on('click', function(view, index, node, vent) {
  216. /**
  217. * Force node selection to make sure it is available in onViewClick
  218. *
  219. * @see Ext.form.ComboBox#onViewClick
  220. */
  221. view.select(node);
  222. if (!window.event) {
  223. window.event = vent;
  224. }
  225. this.onViewClick();
  226. }, this);
  227. this.bindStore(this.store, true);
  228. if(this.resizable){
  229. this.resizer = new Ext.Resizable(this.list, {
  230. pinned:true, handles:'se'
  231. });
  232. this.mon(this.resizer, 'resize', function(r, w, h){
  233. this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
  234. this.listWidth = w;
  235. this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
  236. this.restrictHeight();
  237. }, this);
  238. this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
  239. }
  240. }
  241. }
  242. // Nullify the "parent" function
  243. ,onTypeAhead : function() {}
  244. /**
  245. * Go to the selected record "action" page
  246. *
  247. * @param {Object} record
  248. * @param {Number} index
  249. */
  250. ,onSelect: function(record, index) {
  251. var e = Ext.EventObject;
  252. e.stopPropagation();
  253. e.preventDefault();
  254. var target = '?a=' + record.data._action;
  255. if (e.ctrlKey || e.metaKey || e.shiftKey) {
  256. return window.open(target);
  257. }
  258. MODx.loadPage(target);
  259. }
  260. /**
  261. * Toggle the search drawer visibility
  262. *
  263. * @param {Boolean} hide Whether or not to force-hide MODx.SearchBar
  264. */
  265. ,toggle: function(hide) {
  266. var uberbar = Ext.get( this.container.id );
  267. if (uberbar.hasClass('visible') || hide ) {
  268. this.blurBar();
  269. uberbar.removeClass('visible');
  270. } else {
  271. uberbar.addClass('visible');
  272. this.focusBar();
  273. }
  274. }
  275. ,hideBar: function() {
  276. this.toggle(true);
  277. }
  278. ,focusBar: function() {
  279. this.selectText();
  280. this.animate();
  281. }
  282. ,blurBar: function() {
  283. this.animate(true);
  284. }
  285. /**
  286. * Animate the input "grow"
  287. *
  288. * @param {Boolean} blur Whether or not the input loses focus (to "minimize" the input width)
  289. */
  290. ,animate: function(blur) {
  291. var to = blur ? this.width : this.maxWidth;
  292. this.wrap.setWidth(to, true);
  293. this.el.setWidth(to - this.getTriggerWidth(), true);
  294. }
  295. /**
  296. * Compute the available max height so results could be scrollable if required
  297. *
  298. * @returns {number}
  299. */
  300. ,getViewPortSize: function() {
  301. var height = 300;
  302. if (window.innerHeight !== undefined) {
  303. height = window.innerHeight;
  304. }
  305. return height - 70;
  306. }
  307. });
  308. Ext.reg('modx-searchbar', MODx.SearchBar);