misanek-angular-dual-listbox
Version:
Angular 4+ component for a dual listbox control.
3 lines (2 loc) • 17.8 kB
JavaScript
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("@angular/core"),require("@angular/common"),require("@angular/forms")):"function"==typeof define&&define.amd?define(["exports","@angular/core","@angular/common","@angular/forms"],e):e(t["misanek-angular-dual-listbox"]={},t.ng.core,t.ng.common,t.ng.forms)}(this,function(t,e,i,n){"use strict";var r=function(){function t(t){this._name=t,this.last=null,this.picker="",this.dragStart=!1,this.dragOver=!1,this.pick=[],this.list=[],this.sift=[]}return Object.defineProperty(t.prototype,"name",{get:function(){return this._name},enumerable:!0,configurable:!0}),t}(),o=0,a=function(){function t(i){this.differs=i,this.id="dual-list-"+o++,this.key="_id",this.display="_name",this.height="100px",this.filter=!1,this.format=t.DEFAULT_FORMAT,this.sort=!1,this.disabled=!1,this.destinationChange=new e.EventEmitter,this.sorter=function(t,e){return t._name<e._name?-1:t._name>e._name?1:0},this.available=new r(t.AVAILABLE_LIST_NAME),this.confirmed=new r(t.CONFIRMED_LIST_NAME)}return t.prototype.ngOnChanges=function(e){e.filter&&!1===e.filter.currentValue&&(this.clearFilter(this.available),this.clearFilter(this.confirmed)),e.sort&&(!0===e.sort.currentValue&&this.compare===undefined?this.compare=this.sorter:!1===e.sort.currentValue&&(this.compare=undefined)),e.format&&(this.format=e.format.currentValue,"undefined"==typeof this.format.direction&&(this.format.direction=t.LTR),"undefined"==typeof this.format.add&&(this.format.add=t.DEFAULT_FORMAT.add),"undefined"==typeof this.format.remove&&(this.format.remove=t.DEFAULT_FORMAT.remove),"undefined"==typeof this.format.all&&(this.format.all=t.DEFAULT_FORMAT.all),"undefined"==typeof this.format.none&&(this.format.none=t.DEFAULT_FORMAT.none),"undefined"==typeof this.format.draggable&&(this.format.draggable=t.DEFAULT_FORMAT.draggable)),e.source&&(this.available=new r(t.AVAILABLE_LIST_NAME),this.updatedSource(),this.updatedDestination()),e.destination&&(this.confirmed=new r(t.CONFIRMED_LIST_NAME),this.updatedDestination(),this.updatedSource())},t.prototype.ngDoCheck=function(){this.source&&this.buildAvailable(this.source)&&this.onFilter(this.available),this.destination&&this.buildConfirmed(this.destination)&&this.onFilter(this.confirmed)},t.prototype.buildAvailable=function(t){var e=this,i=this.sourceDiffer.diff(t);return!!i&&(i.forEachRemovedItem(function(t){var i=e.findItemIndex(e.available.list,t.item,e.key);-1!==i&&e.available.list.splice(i,1)}),i.forEachAddedItem(function(t){-1===e.findItemIndex(e.available.list,t.item,e.key)&&e.available.list.push({_id:e.makeId(t.item),_name:e.makeName(t.item)})}),this.compare!==undefined&&this.available.list.sort(this.compare),this.available.sift=this.available.list,!0)},t.prototype.buildConfirmed=function(t){var e=this,i=!1,n=this.destinationDiffer.diff(t);return!!n&&(n.forEachRemovedItem(function(t){var n=e.findItemIndex(e.confirmed.list,t.item,e.key);-1!==n&&(e.isItemSelected(e.confirmed.pick,e.confirmed.list[n])||e.selectItem(e.confirmed.pick,e.confirmed.list[n]),e.moveItem(e.confirmed,e.available,e.confirmed.list[n],!1),i=!0)}),n.forEachAddedItem(function(t){var n=e.findItemIndex(e.available.list,t.item,e.key);-1!==n&&(e.isItemSelected(e.available.pick,e.available.list[n])||e.selectItem(e.available.pick,e.available.list[n]),e.moveItem(e.available,e.confirmed,e.available.list[n],!1),i=!0)}),this.compare!==undefined&&this.confirmed.list.sort(this.compare),this.confirmed.sift=this.confirmed.list,i&&this.trueUp(),!0)},t.prototype.updatedSource=function(){this.available.list.length=0,this.available.pick.length=0,this.source!==undefined&&(this.sourceDiffer=this.differs.find(this.source).create(null))},t.prototype.updatedDestination=function(){this.destination!==undefined&&(this.destinationDiffer=this.differs.find(this.destination).create(null))},t.prototype.direction=function(){return this.format.direction===t.LTR},t.prototype.dragEnd=function(t){return void 0===t&&(t=null),t?t.dragStart=!1:(this.available.dragStart=!1,this.confirmed.dragStart=!1),!1},t.prototype.drag=function(t,e,i){this.isItemSelected(i.pick,e)||this.selectItem(i.pick,e),i.dragStart=!0,t.dataTransfer.setData(this.id,e._id)},t.prototype.allowDrop=function(t,e){return t.dataTransfer.types.length&&t.dataTransfer.types[0]===this.id&&(t.preventDefault(),e.dragStart||(e.dragOver=!0)),!1},t.prototype.dragLeave=function(){this.available.dragOver=!1,this.confirmed.dragOver=!1},t.prototype.drop=function(t,e){t.dataTransfer.types.length&&t.dataTransfer.types[0]===this.id&&(t.preventDefault(),this.dragLeave(),this.dragEnd(),e===this.available?this.moveItem(this.available,this.confirmed):this.moveItem(this.confirmed,this.available))},t.prototype.trueUp=function(){for(var t=this,e=!1,i=this.destination.length;(i-=1)>=0;){0===this.confirmed.list.filter(function(e){return"object"==typeof t.destination[i]?e._id===t.destination[i][t.key]:e._id===t.destination[i]}).length&&(this.destination.splice(i,1),e=!0)}for(var n=function(i,n){var o=r.destination.filter(function(e){return"object"==typeof e?e[t.key]===t.confirmed.list[i]._id:e===t.confirmed.list[i]._id});0===o.length&&(o=r.source.filter(function(e){return"object"==typeof e?e[t.key]===t.confirmed.list[i]._id:e===t.confirmed.list[i]._id})).length>0&&(r.destination.push(o[0]),e=!0)},r=this,o=0,a=this.confirmed.list.length;o<a;o+=1)n(o);e&&this.destinationChange.emit(this.destination)},t.prototype.findItemIndex=function(t,e,i){void 0===i&&(i="_id");var n=-1;return"object"==typeof e?t.filter(function(r){return r._id===e[i]&&(n=t.indexOf(r),!0)}):t.filter(function(i){return i._id===e&&(n=t.indexOf(i),!0)}),n},t.prototype.makeUnavailable=function(t,e){var i=t.list.indexOf(e);-1!==i&&t.list.splice(i,1)},t.prototype.moveItem=function(t,e,i,n){var r=this;void 0===i&&(i=null),void 0===n&&(n=!0);var o=0,a=t.pick.length;i&&(a=(o=t.list.indexOf(i))+1);for(var l=function(){var n=[];if(i){var r=s.findItemIndex(t.pick,i);-1!==r&&(n[0]=t.pick[r])}else n=t.list.filter(function(e){return e._id===t.pick[o]._id});1===n.length&&(0===e.list.filter(function(t){return t._id===n[0]._id}).length&&e.list.push(n[0]),s.makeUnavailable(t,n[0]))},s=this;o<a;o+=1)l();this.compare!==undefined&&e.list.sort(this.compare),t.pick.length=0,n&&this.trueUp(),setTimeout(function(){r.onFilter(t),r.onFilter(e)},10)},t.prototype.isItemSelected=function(t,e){return t.filter(function(t){return Object.is(t,e)}).length>0},t.prototype.shiftClick=function(t,e,i,n){if(t.shiftKey&&i.last&&!Object.is(n,i.last)){var r=i.sift.indexOf(i.last);if(e>r)for(var o=r+1;o<e;o+=1)this.selectItem(i.pick,i.sift[o]);else if(-1!==r)for(o=e+1;o<r;o+=1)this.selectItem(i.pick,i.sift[o])}i.last=n},t.prototype.selectItemClick=function(t,e,i){!this.format.ctrl_click||0===e.length||t.ctrlKey||t.shiftKey?this.selectItem(e,i):(e.splice(0,e.length),this.selectItem(e,i))},t.prototype.selectItem=function(t,e){var i=t.filter(function(t){return Object.is(t,e)});if(i.length>0)for(var n=0,r=i.length;n<r;n+=1){var o=t.indexOf(i[n]);-1!==o&&t.splice(o,1)}else t.push(e)},t.prototype.selectAll=function(t){t.pick.length=0,t.pick=t.sift.slice(0)},t.prototype.selectNone=function(t){t.pick.length=0},t.prototype.isAllSelected=function(t){return 0===t.list.length||t.list.length===t.pick.length},t.prototype.isAnySelected=function(t){return t.pick.length>0},t.prototype.unpick=function(t){for(var e=t.pick.length-1;e>=0;e-=1)-1===t.sift.indexOf(t.pick[e])&&t.pick.splice(e,1)},t.prototype.clearFilter=function(t){t&&(t.picker="",this.onFilter(t))},t.prototype.onFilter=function(t){var e=this;if(t.picker.length>0)try{var i=t.list.filter(function(i){return"[object Object]"===Object.prototype.toString.call(i)?i._name!==undefined?-1!==i._name.toLocaleLowerCase(e.format.locale).indexOf(t.picker.toLocaleLowerCase(e.format.locale)):-1!==JSON.stringify(i).toLocaleLowerCase(e.format.locale).indexOf(t.picker.toLocaleLowerCase(e.format.locale)):-1!==i.toLocaleLowerCase(e.format.locale).indexOf(t.picker.toLocaleLowerCase(e.format.locale))});t.sift=i,this.unpick(t)}catch(n){n instanceof RangeError&&(this.format.locale=undefined),t.sift=t.list}else t.sift=t.list},t.prototype.makeId=function(t){return"object"==typeof t?t[this.key]:t},t.prototype.makeName=function(t,e){void 0===e&&(e="_");var i=this.display;function n(t){switch(Object.prototype.toString.call(t)){case"[object Number]":case"[object String]":return t;default:return t!==undefined?t[i]:"undefined"}}var r="";if(this.display!==undefined)switch(Object.prototype.toString.call(this.display)){case"[object Function]":r=this.display(t);break;case"[object Array]":for(var o=0,a=this.display.length;o<a;o+=1)if(r.length>0&&(r+=e),-1===this.display[o].indexOf("."))r+=t[this.display[o]];else{var l=this.display[o].split("."),s=t[l[0]];if(s)if(-1!==l[1].indexOf("substring")){var d=l[1].substring(l[1].indexOf("(")+1,l[1].indexOf(")")).split(",");switch(d.length){case 1:r+=s.substring(parseInt(d[0],10));break;case 2:r+=s.substring(parseInt(d[0],10),parseInt(d[1],10));break;default:r+=s}}else r+=s}break;default:r=n(t)}else r=n(t);return r},t}();a.AVAILABLE_LIST_NAME="available",a.CONFIRMED_LIST_NAME="confirmed",a.LTR="left-to-right",a.RTL="right-to-left",a.DEFAULT_FORMAT={add:"Add",remove:"Remove",all:"All",none:"None",direction:a.LTR,draggable:!0,locale:undefined,ctrl_click:!1},a.decorators=[{type:e.Component,args:[{selector:"dual-list",template:'\n <div class="dual-list">\n \t<div class="listbox" [ngStyle]="{ \'order\' : direction() ? 1 : 2, \'margin-left\' : direction() ? 0 : \'10px\' }">\n \t\t<button type="button" name="addBtn" class="btn btn-primary btn-block"\n \t\t\t(click)="moveItem(available, confirmed)" [ngClass]="direction() ? \'point-right\' : \'point-left\'"\n \t\t\t[disabled]="available.pick.length === 0">{{format.add}}</button>\n\n \t\t<form *ngIf="filter" class="filter">\n \t\t\t<input class="form-control" name="filterSource" [(ngModel)]="available.picker" (ngModelChange)="onFilter(available)">\n \t\t</form>\n\n \t\t<div class="record-picker">\n \t\t\t<ul [ngStyle]="{\'max-height\': height, \'min-height\': height}" [ngClass]="{over:available.dragOver}"\n \t\t\t\t(drop)="drop($event, confirmed)" (dragover)="allowDrop($event, available)" (dragleave)="dragLeave()">\n \t\t\t\t<li *ngFor="let item of available.sift; let idx=index;"\n \t\t\t\t\t(click)="disabled ? null : selectItemClick($event, available.pick, item); shiftClick($event, idx, available, item)"\n \t\t\t\t\t[ngClass]="{selected: isItemSelected(available.pick, item), disabled: disabled}"\n \t\t\t\t\t[draggable]="!disabled && format.draggable" (dragstart)="drag($event, item, available)" (dragend)="dragEnd(available)"\n \t\t\t\t><label>{{item._name}}</label></li>\n \t\t\t</ul>\n \t\t</div>\n\n \t\t<div class="button-bar">\n \t\t\t<button type="button" class="btn btn-primary pull-left" (click)="selectAll(available)"\n \t\t\t\t[disabled]="disabled || isAllSelected(available)">{{format.all}}</button>\n \t\t\t<button type="button" class="btn btn-default pull-right" (click)="selectNone(available)"\n \t\t\t\t[disabled]="!isAnySelected(available)">{{format.none}}</button>\n \t\t</div>\n \t</div>\n\n \t<div class="listbox" [ngStyle]="{ \'order\' : direction() ? 2 : 1, \'margin-left\' : direction() ? \'10px\' : 0 }">\n \t\t<button type="button" name="removeBtn" class="btn btn-primary btn-block"\n \t\t\t(click)="moveItem(confirmed, available)" [ngClass]="direction() ? \'point-left\' : \'point-right\'"\n \t\t\t[disabled]="confirmed.pick.length === 0">{{format.remove}}</button>\n\n \t\t<form *ngIf="filter" class="filter">\n \t\t\t<input class="form-control" name="filterDestination" [(ngModel)]="confirmed.picker" (ngModelChange)="onFilter(confirmed)">\n \t\t</form>\n\n \t\t<div class="record-picker">\n \t\t\t<ul [ngStyle]="{\'max-height\': height, \'min-height\': height}" [ngClass]="{over:confirmed.dragOver}"\n \t\t\t\t(drop)="drop($event, available)" (dragover)="allowDrop($event, confirmed)" (dragleave)="dragLeave()">\n \t\t\t\t<li #itmConf *ngFor="let item of confirmed.sift; let idx=index;"\n \t\t\t\t\t(click)="disabled ? null : selectItemClick($event, confirmed.pick, item); shiftClick($event, idx, confirmed, item)"\n \t\t\t\t\t[ngClass]="{selected: isItemSelected(confirmed.pick, item), disabled: disabled}"\n \t\t\t\t\t[draggable]="!disabled && format.draggable" (dragstart)="drag($event, item, confirmed)" (dragend)="dragEnd(confirmed)"\n \t\t\t\t><label>{{item._name}}</label></li>\n \t\t\t</ul>\n \t\t</div>\n\n \t\t<div class="button-bar">\n \t\t\t<button type="button" class="btn btn-primary pull-left" (click)="selectAll(confirmed)"\n \t\t\t\t[disabled]="disabled || isAllSelected(confirmed)">{{format.all}}</button>\n \t\t\t<button type="button" class="btn btn-default pull-right" (click)="selectNone(confirmed)"\n \t\t\t\t[disabled]="!isAnySelected(confirmed)">{{format.none}}</button>\n \t\t</div>\n \t</div>\n </div>\n\t',styles:['\n div.record-picker {\n \toverflow-x: hidden;\n \toverflow-y: auto;\n \tborder: 1px solid #ddd;\n \tborder-radius:8px;\n \tposition: relative;\n \tcursor: pointer;\n }\n\n /* http://www.ourtuts.com/how-to-customize-browser-scrollbars-using-css3/ */\n div.record-picker::-webkit-scrollbar {\n \twidth: 12px;\n }\n\n div.record-picker::-webkit-scrollbar-button {\n \twidth: 0px;\n \theight: 0px;\n }\n\n div.record-picker {\n \tscrollbar-base-color: #337ab7;\n \tscrollbar-3dlight-color: #337ab7;\n \tscrollbar-highlight-color: #337ab7;\n \tscrollbar-track-color: #eee;\n \tscrollbar-arrow-color: gray;\n \tscrollbar-shadow-color: gray;\n \tscrollbar-dark-shadow-color: gray;\n }\n\n div.record-picker::-webkit-scrollbar-track {\n \tbackground:#eee;\n \t-webkit-box-shadow: 0px 0px 3px #dfdfdf inset;\n \t box-shadow: 0px 0px 3px #dfdfdf inset;\n \tborder-top-right-radius: 8px;\n \tborder-bottom-right-radius: 8px;\n }\n\n div.record-picker::-webkit-scrollbar-thumb {\n \tbackground: #337ab7;\n \tborder: thin solid gray;\n \tborder-top-right-radius: 8px;\n \tborder-bottom-right-radius: 8px;\n }\n\n div.record-picker::-webkit-scrollbar-thumb:hover {\n \tbackground: #286090;\n }\n\n .record-picker ul {\n \tmargin: 0;\n \tpadding: 0 0 1px 0;\n }\n\n .record-picker li {\n \tborder-top: thin solid #ddd;\n \tborder-bottom: 1px solid #ddd;\n \tdisplay: block;\n \tpadding: 2px 2px 2px 10px;\n \tmargin-bottom: -1px;\n \tfont-size: 0.85em;\n \tcursor: pointer;\n \twhite-space: nowrap;\n \tmin-height:16px;\n }\n\n .record-picker li:hover {\n \tbackground-color: #f5f5f5;\n }\n\n .record-picker li.selected {\n \tbackground-color: #d9edf7;\n }\n\n .record-picker li.selected:hover {\n \tbackground-color: #c4e3f3;\n }\n\n .record-picker li.disabled {\n \topacity: 0.5;\n \tcursor: default;\n \tbackground-color: inherit;\n }\n\n .record-picker li:first-child {\n \tborder-top-left-radius: 8px;\n \tborder-top-right-radius: 8px;\n \tborder-top: none;\n }\n\n .record-picker li:last-child {\n \tborder-bottom-left-radius: 8px;\n \tborder-bottom-right-radius: 8px;\n \tborder-bottom: none;\n }\n\n .record-picker label {\n \tcursor: pointer;\n \tfont-weight: inherit;\n \tfont-size: 14px;\n \tpadding: 4px;\n \tmargin-bottom: -1px;\n \t-webkit-touch-callout: none;\n \t-webkit-user-select: none;\n \t-moz-user-select: none;\n \t-ms-user-select: none;\n \tuser-select: none;\n }\n\n .record-picker ul.over {\n \tbackground-color:lightgray;\n }\n\n .dual-list {\n \tdisplay: -webkit-box;\n \tdisplay: -ms-flexbox;\n \tdisplay: flex;\n \t-webkit-box-orient: horizontal;\n \t-webkit-box-direction: normal;\n \t -ms-flex-direction: row;\n \t flex-direction: row;\n \t-ms-flex-line-pack: start;\n \t align-content: flex-start;\n }\n\n .dual-list .listbox {\n \twidth: 50%;\n \tmargin: 0px;\n }\n\n .dual-list .button-bar {\n \tmargin-top: 8px;\n }\n\n /* ▶ */\n .point-right::after {\n \tcontent: "\\25B6";\n \tpadding-left: 1em;\n }\n\n /* ◀ */\n .point-left::before {\n \tcontent: "\\25C0";\n \tpadding-right: 1em;\n }\n\n .dual-list .button-bar button {\n \twidth: 47%;\n }\n\n button.btn-block {\n \tdisplay: block;\n \twidth: 100%;\n \tmargin-bottom: 8px;\n }\n\n .filter {\n \tmargin-bottom: -2.2em;\n }\n\n .filter::after {\n \tcontent:"o";\n \twidth:40px;\n \tcolor:transparent;\n \tfont-size:2em;\n \tbackground-image:url(\'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 64l192 192v192l128-32V256L512 64H0z"/></svg>\');\n \tbackground-repeat:no-repeat;\n \tbackground-position:center center;\n \topacity:.2;\n \ttop: -36px;\n \tleft: calc(100% - 21px);\n \tposition:relative;\n }\n\t']}]}],a.ctorParameters=function(){return[{type:e.IterableDiffers}]},a.propDecorators={id:[{type:e.Input}],key:[{type:e.Input}],display:[{type:e.Input}],height:[{type:e.Input}],filter:[{type:e.Input}],format:[{type:e.Input}],sort:[{type:e.Input}],compare:[{type:e.Input}],disabled:[{type:e.Input}],source:[{type:e.Input}],destination:[{type:e.Input}],destinationChange:[{type:e.Output}]};var l=function(){return function(){}}();l.decorators=[{type:e.NgModule,args:[{imports:[i.CommonModule,n.FormsModule],declarations:[a],exports:[a]}]}],l.ctorParameters=function(){return[]},t.BasicList=r,t.DualListComponent=a,t.AngularDualListBoxModule=l,Object.defineProperty(t,"__esModule",{value:!0})});
//# sourceMappingURL=misanek-angular-dual-listbox.umd.min.js.map