LP#1832897: add Angular widget to for selecting multiple linked rows
authorGalen Charlton <gmc@equinoxinitiative.org>
Mon, 17 Jun 2019 01:53:36 +0000 (21:53 -0400)
committerJane Sandberg <sandbej@linnbenton.edu>
Wed, 4 Sep 2019 02:30:34 +0000 (19:30 -0700)
This component provides a widget to allow the user to select
multiple linked rows. In particularly, it is meant to handle
IDL fields whose underlying database columns are intarrays that
refer to records in another IDL class.

The widget's user interface consists of an eg-combobox for selecting
new values to add to the list and a list of the existing values.

The component has the following attributes:

- idlClass: IDL class of the records being linked to
- linkedLibraryLabel: if supplied, specifies that the display
  label in the comboox should include the library shortname as
  found in the specified field.
- startValue: init value to display

This component emits onChange events.

Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>
Open-ILS/src/eg2/src/app/share/multi-select/multi-select.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/multi-select/multi-select.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/common.module.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html

diff --git a/Open-ILS/src/eg2/src/app/share/multi-select/multi-select.component.html b/Open-ILS/src/eg2/src/app/share/multi-select/multi-select.component.html
new file mode 100644 (file)
index 0000000..c4aa9e5
--- /dev/null
@@ -0,0 +1,13 @@
+<div>
+  <div class="row">
+    <eg-combobox [idlClass]="idlClass" 
+      [idlIncludeLibraryInLabel]="linkedLibraryLabel" [asyncSupportsEmptyTermClick]="true"
+      (onChange)="valueSelected($event)">
+    </eg-combobox>
+    <button class="btn btn-outline-dark" (click)="addSelectedValue()" [disabled]="!this.selected" i18n>Add</button>
+  </div>
+  <div class="row" *ngFor="let entry of entrylist">
+    <div class="col-lg-4">{{entry.label}}</div>
+    <button class="btn btn-sm btn-warning" (click)="removeValue(entry)" i18n>Remove</button>
+  </div>
+</div>
diff --git a/Open-ILS/src/eg2/src/app/share/multi-select/multi-select.component.ts b/Open-ILS/src/eg2/src/app/share/multi-select/multi-select.component.ts
new file mode 100644 (file)
index 0000000..0c35beb
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * <eg-multi-select idlClass="acpl" linkedLibraryLabel="owning_lib">
+ * </eg-multi-select>
+ */
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter, ElementRef} from '@angular/core';
+import {map} from 'rxjs/operators';
+import {Observable, of, Subject} from 'rxjs';
+import {StoreService} from '@eg/core/store.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+  selector: 'eg-multi-select',
+  templateUrl: './multi-select.component.html',
+  styles: [`
+    .icons {margin-left:-18px}
+    .material-icons {font-size: 16px;font-weight:bold}
+  `]
+})
+export class MultiSelectComponent implements OnInit {
+
+    selected: ComboboxEntry;
+    entrylist: ComboboxEntry[];
+
+    @Input() idlClass: string;
+    @Input() linkedLibraryLabel: string;
+    @Input() startValue: string;
+
+    @Output() onChange: EventEmitter<string>;
+
+    constructor(
+      private store: StoreService,
+      private pcrud: PcrudService,
+    ) {
+        this.entrylist = [];
+        this.onChange = new EventEmitter<string>();
+    }
+
+    valueSelected(entry: ComboboxEntry) {
+        if (entry) {
+            this.selected = entry;
+        } else {
+            this.selected = null;
+        }
+    }
+    addSelectedValue() {
+        this.entrylist.push(this.selected);
+        this.onChange.emit(this.compileCurrentValue());
+    }
+    removeValue(entry: ComboboxEntry) {
+        this.entrylist = this.entrylist.filter(ent => ent.id !== entry.id);
+        this.onChange.emit(this.compileCurrentValue());
+    }
+
+    compileCurrentValue(): string {
+        const valstr = this.entrylist.map(entry => entry.id).join(',');
+        return '{' + valstr + '}';
+    }
+
+    ngOnInit() {
+        if (this.startValue && this.startValue !== '{}') {
+            let valstr = this.startValue;
+            valstr = valstr.replace(/^{/, '');
+            valstr = valstr.replace(/}$/, '');
+            const ids = valstr.split(',');
+            const extra_args = {};
+            if (this.linkedLibraryLabel) {
+                const flesh_fields: Object = {};
+                flesh_fields[this.idlClass] = [ this.linkedLibraryLabel ];
+                extra_args['flesh'] = 1;
+                extra_args['flesh_fields'] = flesh_fields;
+                this.pcrud.search(this.idlClass, { 'id' : ids }, extra_args).pipe(map(data => {
+                    this.entrylist.push({
+                        'id' : data.id(),
+                        'label' : data.name() + ' (' + data[this.linkedLibraryLabel]().shortname() + ')'
+                    });
+                })).toPromise();
+            } else {
+                this.pcrud.search(this.idlClass, { 'id' : ids }, extra_args).pipe(map(data => {
+                    this.entrylist.push({ 'id' : data.id(), 'label' : data.name() });
+                })).toPromise();
+            }
+        }
+    }
+
+}
+
+
index 12c0ff1..e833a34 100644 (file)
@@ -17,6 +17,8 @@ import {TranslateComponent} from '@eg/staff/share/translate/translate.component'
 import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component';
 import {EgHelpPopoverComponent} from '@eg/share/eg-help-popover/eg-help-popover.component';
 import {DatetimeValidatorDirective} from '@eg/share/validators/datetime_validator.directive';
 import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component';
 import {EgHelpPopoverComponent} from '@eg/share/eg-help-popover/eg-help-popover.component';
 import {DatetimeValidatorDirective} from '@eg/share/validators/datetime_validator.directive';
+import {ReactiveFormsModule} from '@angular/forms';
+import {MultiSelectComponent} from '@eg/share/multi-select/multi-select.component';
 
 /**
  * Imports the EG common modules and adds modules common to all staff UI's.
 
 /**
  * Imports the EG common modules and adds modules common to all staff UI's.
@@ -37,6 +39,7 @@ import {DatetimeValidatorDirective} from '@eg/share/validators/datetime_validato
     AdminPageComponent,
     EgHelpPopoverComponent,
     DatetimeValidatorDirective,
     AdminPageComponent,
     EgHelpPopoverComponent,
     DatetimeValidatorDirective,
+    MultiSelectComponent
   ],
   imports: [
     EgCommonModule,
   ],
   imports: [
     EgCommonModule,
@@ -60,6 +63,7 @@ import {DatetimeValidatorDirective} from '@eg/share/validators/datetime_validato
     AdminPageComponent,
     EgHelpPopoverComponent,
     DatetimeValidatorDirective,
     AdminPageComponent,
     EgHelpPopoverComponent,
     DatetimeValidatorDirective,
+    MultiSelectComponent
   ]
 })
 
   ]
 })
 
index 3e29856..88716f3 100644 (file)
     </eg-combobox>
   </div>
   <div class="col-lg-3">
     </eg-combobox>
   </div>
   <div class="col-lg-3">
-    <eg-combobox placeholder="Combobox with @idlClass = 'acpl'" [idlClass]="'acpl'" idlIncludeLibraryInLabel="owning_lib" [asyncSupportsEmptyTermClick]="true">
-    </eg-combobox>
+    <eg-multi-select idlClass="acpl" linkedLibraryLabel="owning_lib" [startValue]="'{129,130,131}'">
+    </eg-multi-select>
   </div>
 </div>
 <!-- /Progress Dialog Experiments ----------------------------- -->
   </div>
 </div>
 <!-- /Progress Dialog Experiments ----------------------------- -->