You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SidebarSearch.js 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /**
  2. * --------------------------------------------
  3. * AdminLTE SidebarSearch.js
  4. * License MIT
  5. * --------------------------------------------
  6. */
  7. import $, {trim} from 'jquery'
  8. /**
  9. * Constants
  10. * ====================================================
  11. */
  12. const NAME = 'SidebarSearch'
  13. const DATA_KEY = 'lte.sidebar-search'
  14. const JQUERY_NO_CONFLICT = $.fn[NAME]
  15. const CLASS_NAME_OPEN = 'sidebar-search-open'
  16. const CLASS_NAME_ICON_SEARCH = 'fa-search'
  17. const CLASS_NAME_ICON_CLOSE = 'fa-times'
  18. const CLASS_NAME_HEADER = 'nav-header'
  19. const CLASS_NAME_SEARCH_RESULTS = 'sidebar-search-results'
  20. const CLASS_NAME_LIST_GROUP = 'list-group'
  21. const SELECTOR_DATA_WIDGET = '[data-widget="sidebar-search"]'
  22. const SELECTOR_SIDEBAR = '.main-sidebar .nav-sidebar'
  23. const SELECTOR_NAV_LINK = '.nav-link'
  24. const SELECTOR_NAV_TREEVIEW = '.nav-treeview'
  25. const SELECTOR_SEARCH_INPUT = `${SELECTOR_DATA_WIDGET} .form-control`
  26. const SELECTOR_SEARCH_BUTTON = `${SELECTOR_DATA_WIDGET} .btn`
  27. const SELECTOR_SEARCH_ICON = `${SELECTOR_SEARCH_BUTTON} i`
  28. const SELECTOR_SEARCH_LIST_GROUP = `.${CLASS_NAME_LIST_GROUP}`
  29. const SELECTOR_SEARCH_RESULTS = `.${CLASS_NAME_SEARCH_RESULTS}`
  30. const SELECTOR_SEARCH_RESULTS_GROUP = `${SELECTOR_SEARCH_RESULTS} .${CLASS_NAME_LIST_GROUP}`
  31. const Default = {
  32. arrowSign: '->',
  33. minLength: 3,
  34. maxResults: 7,
  35. highlightName: true,
  36. highlightPath: false,
  37. highlightClass: 'text-light',
  38. notFoundText: 'No element found!'
  39. }
  40. const SearchItems = []
  41. /**
  42. * Class Definition
  43. * ====================================================
  44. */
  45. class SidebarSearch {
  46. constructor(_element, _options) {
  47. this.element = _element
  48. this.options = $.extend({}, Default, _options)
  49. this.items = []
  50. }
  51. // Public
  52. static _jQueryInterface(config) {
  53. let data = $(this).data(DATA_KEY)
  54. if (!data) {
  55. data = $(this).data()
  56. }
  57. const _options = $.extend({}, Default, typeof config === 'object' ? config : data)
  58. const plugin = new SidebarSearch($(this), _options)
  59. $(this).data(DATA_KEY, typeof config === 'object' ? config : data)
  60. if (typeof config === 'string' && /init|toggle|close|open|search/.test(config)) {
  61. plugin[config]()
  62. } else {
  63. plugin.init()
  64. }
  65. }
  66. init() {
  67. if ($(SELECTOR_DATA_WIDGET).length === 0) {
  68. return
  69. }
  70. if ($(SELECTOR_DATA_WIDGET).next(SELECTOR_SEARCH_RESULTS).length === 0) {
  71. $(SELECTOR_DATA_WIDGET).after(
  72. $('<div />', {class: CLASS_NAME_SEARCH_RESULTS})
  73. )
  74. }
  75. if ($(SELECTOR_SEARCH_RESULTS).children(SELECTOR_SEARCH_LIST_GROUP).length === 0) {
  76. $(SELECTOR_SEARCH_RESULTS).append(
  77. $('<div />', {class: CLASS_NAME_LIST_GROUP})
  78. )
  79. }
  80. this._addNotFound()
  81. $(SELECTOR_SIDEBAR).children().each((i, child) => {
  82. this._parseItem(child)
  83. })
  84. }
  85. search() {
  86. const searchValue = $(SELECTOR_SEARCH_INPUT).val().toLowerCase()
  87. if (searchValue.length < this.options.minLength) {
  88. $(SELECTOR_SEARCH_RESULTS_GROUP).empty()
  89. this._addNotFound()
  90. this.close()
  91. return
  92. }
  93. const searchResults = SearchItems.filter(item => (item.name).toLowerCase().includes(searchValue))
  94. const endResults = $(searchResults.slice(0, this.options.maxResults))
  95. $(SELECTOR_SEARCH_RESULTS_GROUP).empty()
  96. if (endResults.length === 0) {
  97. this._addNotFound()
  98. } else {
  99. endResults.each((i, result) => {
  100. $(SELECTOR_SEARCH_RESULTS_GROUP).append(this._renderItem(escape(result.name), escape(result.link), result.path))
  101. })
  102. }
  103. this.open()
  104. }
  105. open() {
  106. $(SELECTOR_DATA_WIDGET).parent().addClass(CLASS_NAME_OPEN)
  107. $(SELECTOR_SEARCH_ICON).removeClass(CLASS_NAME_ICON_SEARCH).addClass(CLASS_NAME_ICON_CLOSE)
  108. }
  109. close() {
  110. $(SELECTOR_DATA_WIDGET).parent().removeClass(CLASS_NAME_OPEN)
  111. $(SELECTOR_SEARCH_ICON).removeClass(CLASS_NAME_ICON_CLOSE).addClass(CLASS_NAME_ICON_SEARCH)
  112. }
  113. // Private
  114. toggle() {
  115. if ($(SELECTOR_DATA_WIDGET).parent().hasClass(CLASS_NAME_OPEN)) {
  116. this.close()
  117. } else {
  118. this.open()
  119. }
  120. }
  121. _parseItem(item, path = []) {
  122. if ($(item).hasClass(CLASS_NAME_HEADER)) {
  123. return
  124. }
  125. const itemObject = {}
  126. const navLink = $(item).clone().find(`> ${SELECTOR_NAV_LINK}`)
  127. const navTreeview = $(item).clone().find(`> ${SELECTOR_NAV_TREEVIEW}`)
  128. const link = navLink.attr('href')
  129. const name = navLink.find('p').children().remove().end().text()
  130. itemObject.name = this._trimText(name)
  131. itemObject.link = link
  132. itemObject.path = path
  133. if (navTreeview.length === 0) {
  134. SearchItems.push(itemObject)
  135. } else {
  136. const newPath = itemObject.path.concat([itemObject.name])
  137. navTreeview.children().each((i, child) => {
  138. this._parseItem(child, newPath)
  139. })
  140. }
  141. }
  142. _trimText(text) {
  143. return trim(text.replace(/(\r\n|\n|\r)/gm, ' '))
  144. }
  145. _renderItem(name, link, path) {
  146. path = path.join(` ${this.options.arrowSign} `)
  147. name = unescape(name)
  148. if (this.options.highlightName || this.options.highlightPath) {
  149. const searchValue = $(SELECTOR_SEARCH_INPUT).val().toLowerCase()
  150. const regExp = new RegExp(searchValue, 'gi')
  151. if (this.options.highlightName) {
  152. name = name.replace(
  153. regExp,
  154. str => {
  155. return `<strong class="${this.options.highlightClass}">${str}</strong>`
  156. }
  157. )
  158. }
  159. if (this.options.highlightPath) {
  160. path = path.replace(
  161. regExp,
  162. str => {
  163. return `<strong class="${this.options.highlightClass}">${str}</strong>`
  164. }
  165. )
  166. }
  167. }
  168. const groupItemElement = $('<a/>', {
  169. href: link,
  170. class: 'list-group-item'
  171. })
  172. const searchTitleElement = $('<div/>', {
  173. class: 'search-title'
  174. }).html(name)
  175. const searchPathElement = $('<div/>', {
  176. class: 'search-path'
  177. }).html(path)
  178. groupItemElement.append(searchTitleElement).append(searchPathElement)
  179. return groupItemElement
  180. }
  181. // Static
  182. _addNotFound() {
  183. $(SELECTOR_SEARCH_RESULTS_GROUP).append(this._renderItem(this.options.notFoundText, '#', []))
  184. }
  185. }
  186. /**
  187. * Data API
  188. * ====================================================
  189. */
  190. $(document).on('click', SELECTOR_SEARCH_BUTTON, event => {
  191. event.preventDefault()
  192. SidebarSearch._jQueryInterface.call($(SELECTOR_DATA_WIDGET), 'toggle')
  193. })
  194. $(document).on('keyup', SELECTOR_SEARCH_INPUT, event => {
  195. if (event.keyCode == 38) {
  196. event.preventDefault()
  197. $(SELECTOR_SEARCH_RESULTS_GROUP).children().last().focus()
  198. return
  199. }
  200. if (event.keyCode == 40) {
  201. event.preventDefault()
  202. $(SELECTOR_SEARCH_RESULTS_GROUP).children().first().focus()
  203. return
  204. }
  205. setTimeout(() => {
  206. SidebarSearch._jQueryInterface.call($(SELECTOR_DATA_WIDGET), 'search')
  207. }, 100)
  208. })
  209. $(document).on('keydown', SELECTOR_SEARCH_RESULTS_GROUP, event => {
  210. const $focused = $(':focus')
  211. if (event.keyCode == 38) {
  212. event.preventDefault()
  213. if ($focused.is(':first-child')) {
  214. $focused.siblings().last().focus()
  215. } else {
  216. $focused.prev().focus()
  217. }
  218. }
  219. if (event.keyCode == 40) {
  220. event.preventDefault()
  221. if ($focused.is(':last-child')) {
  222. $focused.siblings().first().focus()
  223. } else {
  224. $focused.next().focus()
  225. }
  226. }
  227. })
  228. $(window).on('load', () => {
  229. SidebarSearch._jQueryInterface.call($(SELECTOR_DATA_WIDGET), 'init')
  230. })
  231. /**
  232. * jQuery API
  233. * ====================================================
  234. */
  235. $.fn[NAME] = SidebarSearch._jQueryInterface
  236. $.fn[NAME].Constructor = SidebarSearch
  237. $.fn[NAME].noConflict = function () {
  238. $.fn[NAME] = JQUERY_NO_CONFLICT
  239. return SidebarSearch._jQueryInterface
  240. }
  241. export default SidebarSearch