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' ;
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 .
* @ param inputValue { String } - A comma - separated string or an array of strings .
* @ 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
*
* /
2018-12-10 16:44:37 +00:00
export default Component . extend ( {
'data-test-component' : 'search-select' ,
classNames : [ 'field' , 'search-select' ] ,
store : service ( ) ,
/ *
* @ public
* @ param Function
*
* Function called when any of the inputs change
* accepts a single param ` value `
*
* /
onChange : ( ) => { } ,
/ *
* @ public
* @ param String | Array
* A comma - separated string or an array of strings .
* Defaults to an empty array .
*
* /
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-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 ) ;
return { id : option , name : matchingOption . name , searchText : matchingOption . searchText } ;
} ) ;
this . set ( 'selectedOptions' , formattedOptions ) ;
if ( this . options ) {
options = this . options . concat ( options ) ;
}
this . set ( 'options' , options ) ;
} ,
2018-12-10 16:44:37 +00:00
fetchOptions : task ( function * ( ) {
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 } ` ;
} ,
hideCreateOptionOnSameID ( id ) {
let existingOption = this . options . findBy ( 'id' , id ) || this . options . findBy ( 'name' , id ) ;
return ! existingOption ;
} ,
2018-12-10 16:44:37 +00:00
} ,
} ) ;