Source: components/sl-select.js

  1. import Ember from 'ember';
  2. import InputBased from '../mixins/sl-input-based';
  3. import TooltipEnabled from '../mixins/sl-tooltip-enabled';
  4. import layout from '../templates/components/sl-select';
  5. /**
  6. * @module
  7. * @augments ember/Component
  8. * @augments module:mixins/sl-input-based
  9. * @augments module:mixins/sl-tooltip-based
  10. */
  11. export default Ember.Component.extend( InputBased, TooltipEnabled, {
  12. // -------------------------------------------------------------------------
  13. // Dependencies
  14. // -------------------------------------------------------------------------
  15. // Attributes
  16. /** @type {String[]} */
  17. classNames: [
  18. 'form-group',
  19. 'sl-select'
  20. ],
  21. /** @type {Object} */
  22. layout,
  23. // -------------------------------------------------------------------------
  24. // Actions
  25. // -------------------------------------------------------------------------
  26. // Events
  27. // -------------------------------------------------------------------------
  28. // Properties
  29. /**
  30. * Whether to show the search filter input or not
  31. *
  32. * @type {Boolean}
  33. */
  34. disableSearch: false,
  35. /**
  36. * The internal input element, used for Select2's bindings
  37. *
  38. * @type {?Object}
  39. */
  40. input: null,
  41. /**
  42. * The maximum number of selections allowed when `multiple` is enabled
  43. *
  44. * @type {?Number}
  45. */
  46. maximumSelectionSize: null,
  47. /**
  48. * Whether to allow multiple selections
  49. *
  50. * @type {Boolean}
  51. */
  52. multiple: false,
  53. /**
  54. * The path key for each option object's description
  55. *
  56. * @type {String}
  57. */
  58. optionDescriptionPath: 'description',
  59. /**
  60. * The path key for each option object's label
  61. *
  62. * @type {String}
  63. */
  64. optionLabelPath: 'label',
  65. /**
  66. * The path key for each option object's value
  67. *
  68. * @type {String}
  69. */
  70. optionValuePath: 'value',
  71. /**
  72. * The current value of the select input
  73. *
  74. * @type {?Array|String}
  75. */
  76. value: null,
  77. // -------------------------------------------------------------------------
  78. // Observers
  79. /**
  80. * Teardown the select2 to prevent memory leaks
  81. *
  82. * @function
  83. * @returns {undefined}
  84. */
  85. destroySelect2: Ember.on(
  86. 'willClearRender',
  87. function() {
  88. this.input.off( 'change' ).select2( 'destroy' );
  89. }
  90. ),
  91. /**
  92. * Set up select2 initialization after the element is inserted in the DOM
  93. *
  94. * @function
  95. * @returns {undefined}
  96. */
  97. setupSelect2: Ember.on(
  98. 'didInsertElement',
  99. function() {
  100. const input = this.$( 'input' ).select2({
  101. maximumSelectionSize: this.get( 'maximumSelectionSize' ),
  102. multiple: this.get( 'multiple' ),
  103. placeholder: this.get( 'placeholder' ),
  104. formatResult: ( item ) => {
  105. if ( !item ) {
  106. return null;
  107. }
  108. if ( Ember.typeOf( item ) !== 'object' && Ember.typeOf( item ) !== 'instance' ) {
  109. return item;
  110. }
  111. const description = Ember.get(
  112. item,
  113. this.get( 'optionDescriptionPath' )
  114. );
  115. let output = Ember.get(
  116. item,
  117. this.get( 'optionLabelPath' )
  118. );
  119. if ( description ) {
  120. output += ' <span class="text-muted">' +
  121. description + '</span>';
  122. }
  123. return output;
  124. },
  125. formatSelection: ( item ) => {
  126. if ( !item ) {
  127. return null;
  128. }
  129. const typeOfItem = Ember.typeOf( item );
  130. if (
  131. 'object' === typeOfItem ||
  132. 'instance' === typeOfItem
  133. ) {
  134. return Ember.get( item, this.get( 'optionLabelPath' ) );
  135. }
  136. return item;
  137. },
  138. id: ( item ) => {
  139. let value = item;
  140. const typeOfItem = Ember.typeOf( item );
  141. if (
  142. 'object' === typeOfItem ||
  143. 'instance' === typeOfItem
  144. ) {
  145. const optionValuePath = this.get( 'optionValuePath' );
  146. value = Ember.get( item, optionValuePath );
  147. }
  148. return value;
  149. },
  150. initSelection: ( element, callback ) => {
  151. const value = element.val();
  152. if ( !value || !value.length ) {
  153. return callback( [] );
  154. }
  155. const content = this.get( 'content' );
  156. const contentLength = content.length;
  157. const filteredContent = [];
  158. const multiple = this.get( 'multiple' );
  159. const optionValuePath = this.get( 'optionValuePath' );
  160. const values = 'array' === Ember.typeOf( value ) ? value : value.split( ',' );
  161. let unmatchedValues = values.length;
  162. for ( let i = 0; i < contentLength; i++ ) {
  163. const item = content[i];
  164. const typeOfItem = Ember.typeOf( item );
  165. const text = 'object' === typeOfItem ||
  166. 'instance' === typeOfItem ?
  167. Ember.get( item, optionValuePath ) :
  168. item;
  169. const matchIndex = values.indexOf( text.toString() );
  170. if ( matchIndex !== -1 ) {
  171. filteredContent[ matchIndex ] = item;
  172. if ( 0 === --unmatchedValues ) {
  173. break;
  174. }
  175. }
  176. }
  177. if ( 0 === unmatchedValues ) {
  178. element.select2( 'readonly', false );
  179. } else {
  180. element.select2( 'readonly', true );
  181. const warning = 'sl-select:select2#initSelection was' +
  182. ' not able to map each "' + optionValuePath + '"' +
  183. ' to an object from "content". The remaining keys' +
  184. ' are: ' + values + '. The input will be disabled' +
  185. ' until a) the desired objects are added to the' +
  186. ' "content" array, or b) the "value" is changed.';
  187. Ember.warn( warning, !values.length );
  188. }
  189. return callback(
  190. multiple ?
  191. filteredContent :
  192. Ember.get( filteredContent, 'firstObject' )
  193. );
  194. },
  195. minimumResultsForSearch: this.get( 'disableSearch' ) ? -1 : 0,
  196. query: ( query ) => {
  197. const content = this.get( 'content' ) || [];
  198. const optionLabelPath = this.get( 'optionLabelPath' );
  199. const select2 = input.data( 'select2' ).opts;
  200. query.callback({
  201. results: content.reduce( ( results, item ) => {
  202. const typeOfItem = Ember.typeOf( item );
  203. const text = 'object' === typeOfItem ||
  204. 'instance' === typeOfItem ?
  205. Ember.get( item, optionLabelPath ) :
  206. item;
  207. if (
  208. text &&
  209. select2.matcher( query.term, text.toString() )
  210. ) {
  211. results.push( item );
  212. }
  213. return results;
  214. }, [] )
  215. });
  216. }
  217. });
  218. input.on( 'change', () => {
  219. this.set( 'value', input.select2( 'val' ) );
  220. });
  221. if ( !this.get( 'multiple' ) ) {
  222. this.$( 'input.select2-input' ).attr(
  223. 'placeholder',
  224. 'Search...'
  225. );
  226. }
  227. this.input = input;
  228. }
  229. )
  230. // -------------------------------------------------------------------------
  231. // Methods
  232. });