SidebarSearch.js 5.8 KB

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