sn-controls-aurelia
Version:
Aurelia controls for sensenet ECM
298 lines (240 loc) • 10.1 kB
text/typescript
/**
* @module CollectionControls
*
* */ /** */
import { bindable, computedFrom, customElement, autoinject } from 'aurelia-framework';
import { Content, Query, Repository, IContent } from 'sn-client-js';
import { Subscription } from 'rxjs/Subscription';
import { BindingSignaler } from 'aurelia-templating-resources';
export enum CollectionView {
List = 'List',
Grid = 'Grid',
Details = 'Details',
}
/**
* Component that lets you display and work with a simple Content Collection
*
* Usage example:
* ```html
* <content-list scope.bind="Scope"
* get-items.call="GetSelectedChildren(scope, query)"
* on-activate.call="Select(content)"
* on-drop-files.call="FilesDropped(event, files)"
* on-drop-content.call="ContentDropped(content)"
* on-drop-content-list.call="ContentListDropped(contentList)"
* on-drop-content-on-item.call="ContentDroppedOnItem(content, item)"
* on-drop-content-list-on-item.call="ContentListDroppedOnItem(contentList, item)"
* view-type.bind="ViewType"
* query.bind='query'
* selection.two-way="SelectedContent"
* actions.bind="exploreActions"></content-list>
* ```
*/
export class ContentList {
protected readonly selectionChangedSignalKey = 'content-list-selection-changed';
constructor(
private readonly bindingSignaler: BindingSignaler
) {
}
public IsLoading: boolean = false;
public Scope: Content;
public Selection: Content[] = [];
get hasScope(): boolean {
return this.Scope && this.Scope.SavedFields && Object.keys(this.Scope.SavedFields).length > 0 || false;
}
public Query?: Query;
private Subscriptions: Subscription[] = [];
get Repository(): Repository.BaseRepository {
return this.Scope.GetRepository();
};
clearSubscriptions() {
this.Subscriptions.forEach(subscription => {
subscription.unsubscribe();
});
this.Subscriptions = [];
}
detached() {
this.clearSubscriptions();
}
public GetItems: (params: { scope: Content, query?: Query }) => Promise<Content[]>;
public OnActivate: (params: { content: Content }) => void;
public Actions: { name: string; action: (content: Content) => void }[] = [];
public OnDropFiles: (params: {event: DragEvent, files: FileList}) => void = (params) => {
};
public OnDropContent: (params: {event: DragEvent, content: Content}) => void = (params: {content: Content}) => {
if (this.Scope.Path && this.Scope.Path !== params.content.ParentPath && this.Scope.Path !== params.content.Path && !params.content.IsAncestorOf(this.Scope)) {
params.content.MoveTo(this.Scope.Path);
}
}
public OnDropContentOnItem: (params: {event: DragEvent, content: Content, item: Content}) => void = (params) => {
if (params.item.Path && params.item.Path !== params.content.ParentPath && params.item.Path !== params.content.Path && !params.content.IsAncestorOf(params.item)) {
params.content.MoveTo(params.item.Path);
}
}
public OnDropContentList: (params: {event: DragEvent, contentList: Content[]}) => void = (params) => {
if (this.Scope.Path){
params.contentList.forEach(c => this.OnDropContent({content: c, event: params.event}));
}
};
public OnDropFilesOnItem: (params: {files: FileList, item: Content, event: DragEvent, }) => void = (params) => {
console.log('Dropped files on item', params);
};
public OnDropContentListOnItem: (params: {contentList: Content[], item: Content, event: DragEvent, }) => void = (params) => {
if (this.Scope.Path){
params.contentList.forEach(c => this.OnDropContentOnItem({content: c, item: params.item, event: params.event}));
}
};
public triggerAction(event: MouseEvent, action: (item: Content) => void, item: Content) {
event.stopPropagation();
action(item);
}
ViewType: CollectionView = CollectionView.List;
ShowScope: boolean = true;
MultiSelect: boolean = true;
EnableSelection: boolean = true;
activateItem(content: Content) {
this.OnActivate && this.OnActivate({ content: content });
}
selectItem(event: MouseEvent, content: Content) {
if (!this.EnableSelection) {
return;
}
const selectionIndex = this.Selection.indexOf(content);
if (this.MultiSelect && (event.ctrlKey || event.shiftKey)) {
// CTRL+Click - Add/Remove toggle to Selection
if (event.ctrlKey) {
this.toggleSelection(event, content);
return;
}
// SHIFT+Click - Select Range
if (event.shiftKey){
const lastSelected = this.Selection[this.Selection.length - 1];
const selectedIndex1 = this.Items.indexOf(lastSelected) + 1;
const selectedIndex2 = this.Items.indexOf(content) + 1;
const minSelected = Math.min(selectedIndex1, selectedIndex2) - 1;
const maxSelected = Math.max(selectedIndex1, selectedIndex2);
this.Items.slice(minSelected, maxSelected).forEach(item => {
if (selectionIndex === -1){
this.Selection.push(item);
}
})
}
} else {
this.Selection = [content];
}
this.bindingSignaler.signal(this.selectionChangedSignalKey);
}
toggleSelection(event: MouseEvent, content: Content) {
event.preventDefault()
event.stopImmediatePropagation();
if (!this.MultiSelect) {
this.selectItem(event, content);
return;
}
const tempSelection = this.Selection.map(s => s);
const index = tempSelection.indexOf(content);
if (index === -1) {
tempSelection.push(content);
} else {
tempSelection.splice(index, 1);
}
this.Selection = tempSelection;
setTimeout(() => {
this.bindingSignaler.signal(this.selectionChangedSignalKey);
});
}
isSelected(content: Content): boolean {
return this.Selection && this.Selection.indexOf(content) !== -1;
}
public async Reinitialize() {
if (this.IsLoading) {
// already triggered
return;
}
this.Selection = [];
this.IsLoading = true;
this.clearSubscriptions();
if (this.hasScope) {
this.Subscriptions.push(this.Repository.Events.OnContentCreated.subscribe(created => this.handleContentChanges(created.Content)));
this.Subscriptions.push(this.Repository.Events.OnContentDeleted.subscribe(deleted => this.handleContentChanges(deleted.ContentData)));
this.Subscriptions.push(this.Repository.Events.OnContentModified.subscribe(mod => this.handleContentChanges(mod.Content)));
this.Subscriptions.push(this.Repository.Events.OnContentMoved.subscribe(move => this.handleContentChanges(move.Content, { Path: move.From }, { Path: move.To })));
}
try {
const newItems = await this.GetItems({ scope: this.Scope, query: this.Query });
this.Items = newItems;
} catch (error) {
}
this.IsLoading = false;
}
ScopeChanged() {
this.Reinitialize();
};
QueryChanged(){
this.Reinitialize()
};
public Items: Content[];
public ReloadOnContentChange: (change: Content | IContent) => boolean =
(content: Content | IContent) => {
return true;
// ToDo: check this based on scope and Query
// return (this.Query !== undefined) || this.hasScope && ( isContent(content) && this.Scope.IsAncestorOf(content as Content)) || (content.Path && this.Scope.IsAncestorOf(content as any)) || false;
}
handleContentChanges(...changes: (Content | IContent)[]) {
changes.forEach(change => {
if (this.ReloadOnContentChange(change)) {
this.Reinitialize();
return;
}
});
return;
}
dropContent(event: DragEvent, stringifiedContent: string, stringifiedContentList?: string[], files?: FileList) {
if (stringifiedContent){
const droppedContent = this.Repository.ParseContent(stringifiedContent);
this.OnDropContent({event, content: droppedContent});
}
if (stringifiedContentList){
const contentList = stringifiedContentList.map(c => this.Repository.ParseContent(c))
this.OnDropContentList({event, contentList});
}
if (files){
this.OnDropFiles({event, files});
}
}
dropContentOnItem(event: DragEvent, item: Content, stringifiedContent?: string, stringifiedContentList?: string[], files?: FileList){
if (stringifiedContent){
const droppedContent = this.Repository.ParseContent(stringifiedContent);
this.OnDropContentOnItem({event, content: droppedContent, item});
}
if (stringifiedContentList){
const contentList = stringifiedContentList.map(c => this.Repository.ParseContent(c))
this.OnDropContentListOnItem({event, contentList, item});
}
if (files){
this.OnDropFilesOnItem({event, files, item});
}
}
}