2018-12-10 16:44:37 +00:00
import Component from '@ember/component' ;
import { inject as service } from '@ember/service' ;
import { task } from 'ember-concurrency' ;
import { computed } from '@ember/object' ;
2019-06-03 20:25:59 +00:00
import { singularize } from 'ember-inflector' ;
2019-10-25 18:16:45 +00:00
import layout from '../templates/components/search-select' ;
2018-12-10 16:44:37 +00:00
2019-06-03 20:25:59 +00:00
/ * *
* @ module SearchSelect
* The ` SearchSelect ` is an implementation of the [ ember - power - select - with - create ] ( https : //github.com/poteto/ember-cli-flash) used for form elements where options come dynamically from the API.
* @ example
* < SearchSelect @ id = "group-policies" @ models = { { [ "policies/acl" ] } } @ onChange = { { onChange } } @ inputValue = { { get model valuePath } } @ helpText = "Policies associated with this group" @ label = "Policies" @ fallbackComponent = "string-list" / >
*
* @ param id { String } - The name of the form field
* @ param models { String } - An array of model types to fetch from the API .
* @ param onChange { Func } - The onchange action for this form field .
2019-10-25 18:16:45 +00:00
* @ param inputValue { String | Array } - A comma - separated string or an array of strings .
2019-06-03 20:25:59 +00:00
* @ param [ helpText ] { String } - Text to be displayed in the info tooltip for this form field
* @ param label { String } - Label for this form field
* @ param fallbackComponent { String } - name of component to be rendered if the API call 403 s
*
2019-10-25 18:16:45 +00:00
* @ param options { Array } - * Advanced usage * - ` options ` can be passed directly from the outside to the
* power - select component . If doing this , ` models ` should not also be passed as that will overwrite the
* passed value .
* @ param search { Func } - * Advanced usage * - Customizes how the power - select component searches for matches -
* see the power - select docs for more information .
*
2019-06-03 20:25:59 +00:00
* /
2018-12-10 16:44:37 +00:00
export default Component . extend ( {
2019-10-25 18:16:45 +00:00
layout ,
2018-12-10 16:44:37 +00:00
'data-test-component' : 'search-select' ,
classNames : [ 'field' , 'search-select' ] ,
store : service ( ) ,
onChange : ( ) => { } ,
inputValue : computed ( function ( ) {
return [ ] ;
} ) ,
selectedOptions : null , //list of selected options
options : null , //all possible options
shouldUseFallback : false ,
shouldRenderName : false ,
init ( ) {
this . _super ( ... arguments ) ;
this . set ( 'selectedOptions' , this . inputValue || [ ] ) ;
} ,
2019-10-25 18:16:45 +00:00
didRender ( ) {
this . _super ( ... arguments ) ;
let { oldOptions , options , selectedOptions } = this ;
let hasFormattedInput = typeof selectedOptions . firstObject !== 'string' ;
if ( options && ! oldOptions && ! hasFormattedInput ) {
// this is the first time they've been set, so we need to format them
this . formatOptions ( options ) ;
}
this . set ( 'oldOptions' , options ) ;
} ,
2019-06-03 20:25:59 +00:00
formatOptions : function ( options ) {
options = options . toArray ( ) . map ( option => {
option . searchText = ` ${ option . name } ${ option . id } ` ;
return option ;
} ) ;
let formattedOptions = this . selectedOptions . map ( option => {
let matchingOption = options . findBy ( 'id' , option ) ;
options . removeObject ( matchingOption ) ;
2019-06-04 17:44:58 +00:00
return {
id : option ,
name : matchingOption ? matchingOption . name : option ,
searchText : matchingOption ? matchingOption . searchText : option ,
} ;
2019-06-03 20:25:59 +00:00
} ) ;
this . set ( 'selectedOptions' , formattedOptions ) ;
if ( this . options ) {
2019-10-25 18:16:45 +00:00
options = this . options . concat ( options ) . uniq ( ) ;
2019-06-03 20:25:59 +00:00
}
this . set ( 'options' , options ) ;
} ,
2018-12-10 16:44:37 +00:00
fetchOptions : task ( function * ( ) {
2019-10-25 18:16:45 +00:00
if ( ! this . models ) {
if ( this . options ) {
this . formatOptions ( this . options ) ;
}
return ;
}
2018-12-10 16:44:37 +00:00
for ( let modelType of this . models ) {
if ( modelType . includes ( 'identity' ) ) {
this . set ( 'shouldRenderName' , true ) ;
}
try {
let options = yield this . store . query ( modelType , { } ) ;
2019-06-03 20:25:59 +00:00
this . formatOptions ( options ) ;
2018-12-10 16:44:37 +00:00
} catch ( err ) {
if ( err . httpStatus === 404 ) {
//leave options alone, it's okay
return ;
}
if ( err . httpStatus === 403 ) {
this . set ( 'shouldUseFallback' , true ) ;
return ;
}
2019-06-03 20:25:59 +00:00
//special case for storybook
if ( this . staticOptions ) {
let options = this . staticOptions ;
this . formatOptions ( options ) ;
return ;
}
2018-12-10 16:44:37 +00:00
throw err ;
}
}
} ) . on ( 'didInsertElement' ) ,
handleChange ( ) {
if ( this . selectedOptions . length && typeof this . selectedOptions . firstObject === 'object' ) {
this . onChange ( Array . from ( this . selectedOptions , option => option . id ) ) ;
} else {
this . onChange ( this . selectedOptions ) ;
}
} ,
actions : {
onChange ( val ) {
this . onChange ( val ) ;
} ,
2019-06-03 20:25:59 +00:00
createOption ( optionId ) {
let newOption = { name : optionId , id : optionId } ;
this . selectedOptions . pushObject ( newOption ) ;
this . handleChange ( ) ;
} ,
2018-12-10 16:44:37 +00:00
selectOption ( option ) {
this . selectedOptions . pushObject ( option ) ;
this . options . removeObject ( option ) ;
this . handleChange ( ) ;
} ,
discardSelection ( selected ) {
this . selectedOptions . removeObject ( selected ) ;
this . options . pushObject ( selected ) ;
this . handleChange ( ) ;
} ,
2019-06-03 20:25:59 +00:00
constructSuggestion ( id ) {
return ` Add new ${ singularize ( this . label ) } : ${ id } ` ;
} ,
2019-10-25 18:16:45 +00:00
hideCreateOptionOnSameID ( id , options ) {
if ( options && options . length && options . firstObject . groupName ) {
return ! options . some ( group => group . options . findBy ( 'id' , id ) ) ;
}
2019-08-22 18:57:02 +00:00
let existingOption = this . options && ( this . options . findBy ( 'id' , id ) || this . options . findBy ( 'name' , id ) ) ;
2019-06-03 20:25:59 +00:00
return ! existingOption ;
} ,
2018-12-10 16:44:37 +00:00
} ,
} ) ;