| 
 | 1 | +/**  | 
 | 2 | + * -------------------------------------------------------------------------------------------  | 
 | 3 | + * Copyright (c) Microsoft Corporation.  All Rights Reserved.  Licensed under the MIT License.  | 
 | 4 | + * See License in the project root for license information.  | 
 | 5 | + * -------------------------------------------------------------------------------------------  | 
 | 6 | + */  | 
 | 7 | + | 
 | 8 | +import { html, TemplateResult } from 'lit';  | 
 | 9 | +import { property, state } from 'lit/decorators.js';  | 
 | 10 | +import { MgtTemplatedComponent, mgtHtml, customElement } from '@microsoft/mgt-element';  | 
 | 11 | +import { strings } from './strings';  | 
 | 12 | +import { fluentCombobox, fluentOption } from '@fluentui/web-components';  | 
 | 13 | +import { registerFluentComponents } from '../../utils/FluentComponents';  | 
 | 14 | +import '../../styles/style-helper';  | 
 | 15 | + | 
 | 16 | +registerFluentComponents(fluentCombobox, fluentOption);  | 
 | 17 | + | 
 | 18 | +/**  | 
 | 19 | + * Web component that allows a single entity pick from a generic endpoint from Graph. Uses mgt-get.  | 
 | 20 | + *  | 
 | 21 | + * @fires {CustomEvent<any>} selectionChanged - Fired when an option is clicked/selected  | 
 | 22 | + * @export  | 
 | 23 | + * @class MgtPicker  | 
 | 24 | + * @extends {MgtTemplatedComponent}  | 
 | 25 | + */  | 
 | 26 | +// @customElement('mgt-picker')  | 
 | 27 | +@customElement('picker')  | 
 | 28 | +export class MgtPicker extends MgtTemplatedComponent {  | 
 | 29 | +  protected get strings() {  | 
 | 30 | +    return strings;  | 
 | 31 | +  }  | 
 | 32 | + | 
 | 33 | +  /**  | 
 | 34 | +   * The resource to get  | 
 | 35 | +   *  | 
 | 36 | +   * @type {string}  | 
 | 37 | +   * @memberof MgtPicker  | 
 | 38 | +   */  | 
 | 39 | +  @property({  | 
 | 40 | +    attribute: 'resource',  | 
 | 41 | +    type: String  | 
 | 42 | +  })  | 
 | 43 | +  public resource: string;  | 
 | 44 | + | 
 | 45 | +  /**  | 
 | 46 | +   * Api version to use for request  | 
 | 47 | +   *  | 
 | 48 | +   * @type {string}  | 
 | 49 | +   * @memberof MgtPicker  | 
 | 50 | +   */  | 
 | 51 | +  @property({  | 
 | 52 | +    attribute: 'version',  | 
 | 53 | +    type: String  | 
 | 54 | +  })  | 
 | 55 | +  public version: string = 'v1.0';  | 
 | 56 | + | 
 | 57 | +  /**  | 
 | 58 | +   * Maximum number of pages to get for the resource  | 
 | 59 | +   * default = 3  | 
 | 60 | +   * if <= 0, all pages will be fetched  | 
 | 61 | +   *  | 
 | 62 | +   * @type {number}  | 
 | 63 | +   * @memberof MgtPicker  | 
 | 64 | +   */  | 
 | 65 | +  @property({  | 
 | 66 | +    attribute: 'max-pages',  | 
 | 67 | +    type: Number  | 
 | 68 | +  })  | 
 | 69 | +  public maxPages: number = 3;  | 
 | 70 | + | 
 | 71 | +  /**  | 
 | 72 | +   * A placeholder for the picker  | 
 | 73 | +   *  | 
 | 74 | +   * @type {string}  | 
 | 75 | +   * @memberof MgtPicker  | 
 | 76 | +   */  | 
 | 77 | +  @property({  | 
 | 78 | +    attribute: 'placeholder',  | 
 | 79 | +    type: String  | 
 | 80 | +  })  | 
 | 81 | +  public placeholder: string;  | 
 | 82 | + | 
 | 83 | +  /**  | 
 | 84 | +   * Key to be rendered in the picker  | 
 | 85 | +   *  | 
 | 86 | +   * @type {string}  | 
 | 87 | +   * @memberof MgtPicker  | 
 | 88 | +   */  | 
 | 89 | +  @property({  | 
 | 90 | +    attribute: 'key-name',  | 
 | 91 | +    type: String  | 
 | 92 | +  })  | 
 | 93 | +  public keyName: string;  | 
 | 94 | + | 
 | 95 | +  /**  | 
 | 96 | +   * Entity to be rendered in the picker  | 
 | 97 | +   *  | 
 | 98 | +   * @type {string}  | 
 | 99 | +   * @memberof MgtPicker  | 
 | 100 | +   */  | 
 | 101 | +  @property({  | 
 | 102 | +    attribute: 'entity-type',  | 
 | 103 | +    type: String  | 
 | 104 | +  })  | 
 | 105 | +  public entityType: string;  | 
 | 106 | + | 
 | 107 | +  /**  | 
 | 108 | +   * The scopes to request  | 
 | 109 | +   *  | 
 | 110 | +   * @type {string[]}  | 
 | 111 | +   * @memberof MgtPicker  | 
 | 112 | +   */  | 
 | 113 | +  @property({  | 
 | 114 | +    attribute: 'scopes',  | 
 | 115 | +    converter: value => {  | 
 | 116 | +      return value ? value.toLowerCase().split(',') : null;  | 
 | 117 | +    }  | 
 | 118 | +  })  | 
 | 119 | +  public scopes: string[] = [];  | 
 | 120 | + | 
 | 121 | +  /**  | 
 | 122 | +   * Enables cache on the response from the specified resource  | 
 | 123 | +   * default = false  | 
 | 124 | +   *  | 
 | 125 | +   * @type {boolean}  | 
 | 126 | +   * @memberof MgtPicker  | 
 | 127 | +   */  | 
 | 128 | +  @property({  | 
 | 129 | +    attribute: 'cache-enabled',  | 
 | 130 | +    type: Boolean  | 
 | 131 | +  })  | 
 | 132 | +  public cacheEnabled: boolean = false;  | 
 | 133 | + | 
 | 134 | +  /**  | 
 | 135 | +   * Invalidation period of the cache for the responses in milliseconds  | 
 | 136 | +   *  | 
 | 137 | +   * @type {number}  | 
 | 138 | +   * @memberof MgtPicker  | 
 | 139 | +   */  | 
 | 140 | +  @property({  | 
 | 141 | +    attribute: 'cache-invalidation-period',  | 
 | 142 | +    type: Number  | 
 | 143 | +  })  | 
 | 144 | +  public cacheInvalidationPeriod: number = 0;  | 
 | 145 | + | 
 | 146 | +  private isRefreshing: boolean;  | 
 | 147 | + | 
 | 148 | +  @state() private response: any[];  | 
 | 149 | +  @state() private error: any[];  | 
 | 150 | + | 
 | 151 | +  constructor() {  | 
 | 152 | +    super();  | 
 | 153 | +    this.placeholder = this.strings.comboboxPlaceholder;  | 
 | 154 | +    this.entityType = null;  | 
 | 155 | +    this.keyName = null;  | 
 | 156 | +    this.isRefreshing = false;  | 
 | 157 | +  }  | 
 | 158 | + | 
 | 159 | +  /**  | 
 | 160 | +   * Refresh the data  | 
 | 161 | +   *  | 
 | 162 | +   * @param {boolean} [hardRefresh=false]  | 
 | 163 | +   * if false (default), the component will only update if the data changed  | 
 | 164 | +   * if true, the data will be first cleared and reloaded completely  | 
 | 165 | +   * @memberof MgtPicker  | 
 | 166 | +   */  | 
 | 167 | +  public refresh(hardRefresh = false) {  | 
 | 168 | +    this.isRefreshing = true;  | 
 | 169 | +    if (hardRefresh) {  | 
 | 170 | +      this.clearState();  | 
 | 171 | +    }  | 
 | 172 | +    this.requestStateUpdate(hardRefresh);  | 
 | 173 | +  }  | 
 | 174 | + | 
 | 175 | +  /**  | 
 | 176 | +   * Clears the state of the component  | 
 | 177 | +   *  | 
 | 178 | +   * @protected  | 
 | 179 | +   * @memberof MgtPicker  | 
 | 180 | +   */  | 
 | 181 | +  protected clearState(): void {  | 
 | 182 | +    this.response = null;  | 
 | 183 | +    this.error = null;  | 
 | 184 | +  }  | 
 | 185 | + | 
 | 186 | +  /**  | 
 | 187 | +   * Invoked on each update to perform rendering the picker. This method must return  | 
 | 188 | +   * a lit-html TemplateResult. Setting properties inside this method will *not*  | 
 | 189 | +   * trigger the element to update.  | 
 | 190 | +   */  | 
 | 191 | +  public render() {  | 
 | 192 | +    if (this.isLoadingState && !this.response) {  | 
 | 193 | +      return this.renderTemplate('loading', null);  | 
 | 194 | +    } else if (this.hasTemplate('error')) {  | 
 | 195 | +      return this.renderTemplate('error', this.error ? this.error : null, 'error');  | 
 | 196 | +    } else if (this.hasTemplate('no-data')) {  | 
 | 197 | +      return this.renderTemplate('no-data', null);  | 
 | 198 | +    }  | 
 | 199 | + | 
 | 200 | +    return this.response?.length > 0 ? this.renderPicker() : this.renderGet();  | 
 | 201 | +  }  | 
 | 202 | + | 
 | 203 | +  /**  | 
 | 204 | +   * Render picker.  | 
 | 205 | +   *  | 
 | 206 | +   * @protected  | 
 | 207 | +   * @returns {TemplateResult}  | 
 | 208 | +   * @memberof MgtPicker  | 
 | 209 | +   */  | 
 | 210 | +  protected renderPicker(): TemplateResult {  | 
 | 211 | +    return mgtHtml`  | 
 | 212 | +      <fluent-combobox id="combobox" autocomplete="list" placeholder=${this.placeholder}>  | 
 | 213 | +        ${this.response.map(  | 
 | 214 | +          item => html`  | 
 | 215 | +          <fluent-option value=${item.id} @click=${e => this.handleClick(e, item)}> ${  | 
 | 216 | +            item[this.keyName]  | 
 | 217 | +          } </fluent-option>`  | 
 | 218 | +        )}  | 
 | 219 | +      </fluent-combobox>  | 
 | 220 | +     `;  | 
 | 221 | +  }  | 
 | 222 | + | 
 | 223 | +  /**  | 
 | 224 | +   * Render picker.  | 
 | 225 | +   *  | 
 | 226 | +   * @protected  | 
 | 227 | +   * @returns {TemplateResult}  | 
 | 228 | +   * @memberof MgtPicker  | 
 | 229 | +   */  | 
 | 230 | +  protected renderGet(): TemplateResult {  | 
 | 231 | +    return mgtHtml`  | 
 | 232 | +      <mgt-get   | 
 | 233 | +        resource=${this.resource}  | 
 | 234 | +        version=${this.version}   | 
 | 235 | +        scopes=${this.scopes}   | 
 | 236 | +        max-pages=${this.maxPages}   | 
 | 237 | +        ?cache-enabled=${this.cacheEnabled}  | 
 | 238 | +        ?cache-invalidation-period=${this.cacheInvalidationPeriod}>  | 
 | 239 | +      </mgt-get>`;  | 
 | 240 | +  }  | 
 | 241 | + | 
 | 242 | +  /**  | 
 | 243 | +   * load state into the component.  | 
 | 244 | +   *  | 
 | 245 | +   * @protected  | 
 | 246 | +   * @returns  | 
 | 247 | +   * @memberof MgtPicker  | 
 | 248 | +   */  | 
 | 249 | +  protected async loadState() {  | 
 | 250 | +    if (!this.response) {  | 
 | 251 | +      let parent = this.renderRoot.querySelector('mgt-get');  | 
 | 252 | +      parent.addEventListener('dataChange', (e): void => this.handleDataChange(e));  | 
 | 253 | +    }  | 
 | 254 | +    this.isRefreshing = false;  | 
 | 255 | +  }  | 
 | 256 | + | 
 | 257 | +  private handleDataChange(e: any): void {  | 
 | 258 | +    let response = e.detail.response.value;  | 
 | 259 | +    let error = e.detail.error ? e.detail.error : null;  | 
 | 260 | +    this.response = response;  | 
 | 261 | +    this.error = error;  | 
 | 262 | +  }  | 
 | 263 | + | 
 | 264 | +  private handleClick(e: MouseEvent, item: any) {  | 
 | 265 | +    this.fireCustomEvent('selectionChanged', item);  | 
 | 266 | +  }  | 
 | 267 | +}  | 
0 commit comments