SidebarSearch.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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. init() {
  53. if ($(SELECTOR_DATA_WIDGET).next(SELECTOR_SEARCH_RESULTS).length == 0) {
  54. $(SELECTOR_DATA_WIDGET).after(
  55. $('<div />', { class: CLASS_NAME_SEARCH_RESULTS })
  56. )
  57. }
  58. if ($(SELECTOR_SEARCH_RESULTS).children(SELECTOR_SEARCH_LIST_GROUP).length == 0) {
  59. $(SELECTOR_SEARCH_RESULTS).append(
  60. $('<div />', { class: CLASS_NAME_LIST_GROUP })
  61. )
  62. }
  63. this._addNotFound()
  64. $(SELECTOR_SIDEBAR).children().each((i, child) => {
  65. this._parseItem(child)
  66. })
  67. }
  68. search() {
  69. const searchValue = $(SELECTOR_SEARCH_INPUT).val().toLowerCase()
  70. if (searchValue.length < this.options.minLength) {
  71. $(SELECTOR_SEARCH_RESULTS_GROUP).empty()
  72. this._addNotFound()
  73. this.close()
  74. return
  75. }
  76. const searchResults = SearchItems.filter(item => (item.name).toLowerCase().includes(searchValue))
  77. const endResults = $(searchResults.slice(0, this.options.maxResults))
  78. $(SELECTOR_SEARCH_RESULTS_GROUP).empty()
  79. if (endResults.length === 0) {
  80. this._addNotFound()
  81. } else {
  82. endResults.each((i, result) => {
  83. $(SELECTOR_SEARCH_RESULTS_GROUP).append(this._renderItem(result.name, result.link, result.path))
  84. })
  85. }
  86. this.open()
  87. }
  88. open() {
  89. $(SELECTOR_DATA_WIDGET).parent().addClass(CLASS_NAME_OPEN)
  90. $(SELECTOR_SEARCH_ICON).removeClass(CLASS_NAME_ICON_SEARCH).addClass(CLASS_NAME_ICON_CLOSE)
  91. }
  92. close() {
  93. $(SELECTOR_DATA_WIDGET).parent().removeClass(CLASS_NAME_OPEN)
  94. $(SELECTOR_SEARCH_ICON).removeClass(CLASS_NAME_ICON_CLOSE).addClass(CLASS_NAME_ICON_SEARCH)
  95. }
  96. toggle() {
  97. if ($(SELECTOR_DATA_WIDGET).parent().hasClass(CLASS_NAME_OPEN)) {
  98. this.close()
  99. } else {
  100. this.open()
  101. }
  102. }
  103. // Private
  104. _parseItem(item, path = []) {
  105. if ($(item).hasClass(CLASS_NAME_HEADER)) {
  106. return
  107. }
  108. const itemObject = {}
  109. const navLink = $(item).clone().find(`> ${SELECTOR_NAV_LINK}`)
  110. const navTreeview = $(item).clone().find(`> ${SELECTOR_NAV_TREEVIEW}`)
  111. const link = navLink.attr('href')
  112. const name = navLink.find('p').children().remove().end().text()
  113. itemObject.name = this._trimText(name)
  114. itemObject.link = link
  115. itemObject.path = path
  116. if (navTreeview.length === 0) {
  117. SearchItems.push(itemObject)
  118. } else {
  119. const newPath = itemObject.path.concat([itemObject.name])
  120. navTreeview.children().each((i, child) => {
  121. this._parseItem(child, newPath)
  122. })
  123. }
  124. }
  125. _trimText(text) {
  126. return trim(text.replace(/(\r\n|\n|\r)/gm, ' '))
  127. }
  128. _renderItem(name, link, path) {
  129. path = path.join(` ${this.options.arrowSign} `)
  130. if (this.options.highlightName || this.options.highlightPath) {
  131. const searchValue = $(SELECTOR_SEARCH_INPUT).val().toLowerCase()
  132. const regExp = new RegExp(searchValue, 'gi')
  133. if (this.options.highlightName) {
  134. name = name.replace(
  135. regExp,
  136. str => {
  137. return `<b class="${this.options.highlightClass}">${str}</b>`
  138. }
  139. )
  140. }
  141. if (this.options.highlightPath) {
  142. path = path.replace(
  143. regExp,
  144. str => {
  145. return `<b class="${this.options.highlightClass}">${str}</b>`
  146. }
  147. )
  148. }
  149. }
  150. return `<a href="${link}" class="list-group-item">
  151. <div class="search-title">
  152. ${name}
  153. </div>
  154. <div class="search-path">
  155. ${path}
  156. </div>
  157. </a>`
  158. }
  159. _addNotFound() {
  160. $(SELECTOR_SEARCH_RESULTS_GROUP).append(this._renderItem(this.options.notFoundText, '#', []))
  161. }
  162. // Static
  163. static _jQueryInterface(config) {
  164. let data = $(this).data(DATA_KEY)
  165. if (!data) {
  166. data = $(this).data()
  167. }
  168. const _options = $.extend({}, Default, typeof config === 'object' ? config : data)
  169. const plugin = new SidebarSearch($(this), _options)
  170. $(this).data(DATA_KEY, typeof config === 'object' ? config : data)
  171. if (typeof config === 'string' && config.match(/init|toggle|close|open|search/)) {
  172. plugin[config]()
  173. } else {
  174. plugin.init()
  175. }
  176. }
  177. }
  178. /**
  179. * Data API
  180. * ====================================================
  181. */
  182. $(document).on('click', SELECTOR_SEARCH_BUTTON, event => {
  183. event.preventDefault()
  184. SidebarSearch._jQueryInterface.call($(SELECTOR_DATA_WIDGET), 'toggle')
  185. })
  186. $(document).on('keyup', SELECTOR_SEARCH_INPUT, () => {
  187. let timer = 0
  188. clearTimeout(timer)
  189. timer = setTimeout(() => {
  190. SidebarSearch._jQueryInterface.call($(SELECTOR_DATA_WIDGET), 'search')
  191. }, 100)
  192. })
  193. $(window).on('load', () => {
  194. SidebarSearch._jQueryInterface.call($(SELECTOR_DATA_WIDGET), 'init')
  195. })
  196. /**
  197. * jQuery API
  198. * ====================================================
  199. */
  200. $.fn[NAME] = SidebarSearch._jQueryInterface
  201. $.fn[NAME].Constructor = SidebarSearch
  202. $.fn[NAME].noConflict = function () {
  203. $.fn[NAME] = JQUERY_NO_CONFLICT
  204. return SidebarSearch._jQueryInterface
  205. }
  206. export default SidebarSearch