Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Import the ```NgsgModule``` in your ```AppModule```.
```

## Apply the directive
Loop over your elements with *ngFor. 🛎️ the items needs to be an array.
Loop over your elements with *ngFor. 🛎️ the items needs to be an array. Alternate you can also use the async pipe to pass in your items.

![Grid demo](https://raw.githubusercontent.com/kreuzerk/ng-sortgrid/master/projects/ng-sortgrid-demo/src/assets/gs1.png)

Expand Down
7 changes: 6 additions & 1 deletion projects/ng-sortgrid-demo/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<div class="container">
<h1>1. Getting started</h1>
<ngsg-demo-step title="Loop over your elements with *ngFor. 🛎️ the items needs to be an array." image="gs1.png"></ngsg-demo-step>
<ngsg-demo-step title="Loop over your elements with *ngFor. 🛎️ the items needs to be an array. Alternate you can also use the async pipe - see below" image="gs1.png"></ngsg-demo-step>
<ngsg-demo-step title="Apply the ngSortgridItem directive" image="gs2.png"></ngsg-demo-step>
<ngsg-demo-memory></ngsg-demo-memory>

Expand All @@ -22,6 +22,11 @@ <h1>3. Group sortgrids</h1>
one group in another group.</p>
<ngsg-demo-step title="Pass in a unique name to the ngSortGridGroup input" image="gs5.png"></ngsg-demo-step>
<ngsg-demo-groups-memory></ngsg-demo-groups-memory>

<hr class="chaptor-separator"/>
<h1>4. Use the async pipe</h1>
<ngsg-demo-step title="Use the async pipe to loop over the items and to pass in the ngSortGridItems" image="gs6.png"></ngsg-demo-step>
<ngsg-demo-async></ngsg-demo-async>
</div>

<footer class="py-5 bg-dark">
Expand Down
4 changes: 3 additions & 1 deletion projects/ng-sortgrid-demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {StepComponent} from './examples/step/step.component';
import {ReactOnChangesMemoryComponent} from './examples/react-on-changes/react-on-changes-memory.component';
import {GroupsMemoryComponent} from './examples/groups/groups-memory.component';
import {CardComponent} from './examples/card/card.component';
import {AsyncPipeMemoryComponent} from './examples/async-pipe/async-pipe-memory.component';

@NgModule({
declarations: [
Expand All @@ -20,7 +21,8 @@ import {CardComponent} from './examples/card/card.component';
StepComponent,
ReactOnChangesMemoryComponent,
GroupsMemoryComponent,
CardComponent
CardComponent,
AsyncPipeMemoryComponent
],
imports: [BrowserModule, NgsgModule],
providers: [],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.spinner {
width: 40px;
height: 40px;

position: relative;
margin: 100px auto;
}

.double-bounce1, .double-bounce2 {
width: 100%;
height: 100%;
border-radius: 50%;
background-color: #333;
opacity: 0.6;
position: absolute;
top: 0;
left: 0;

-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
animation: sk-bounce 2.0s infinite ease-in-out;
}

.double-bounce2 {
-webkit-animation-delay: -1.0s;
animation-delay: -1.0s;
}

@-webkit-keyframes sk-bounce {
0%, 100% { -webkit-transform: scale(0.0) }
50% { -webkit-transform: scale(1.0) }
}

@keyframes sk-bounce {
0%, 100% {
transform: scale(0.0);
-webkit-transform: scale(0.0);
} 50% {
transform: scale(1.0);
-webkit-transform: scale(1.0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h5 style="margin-bottom: 20px">4. Load items and use them with the async pipe</h5>
<button style="margin-bottom: 20px" class="btn btn-primary" (click)="loadItems()">Load items</button>
<div class="card border-primary mb-3">
<div class="card-body">
<h1 class="card-title">Sort order</h1>
<h2 class="card-text">{{ sortOrder }}</h2>
</div>
</div>
<div class="example-container">
<div *ngIf="loading" class="spinner">
<div class="double-bounce1"></div>
<div class="double-bounce2"></div>
</div>
<ngsg-card *ngFor="let item of item$ | async"
ngSortgridItem
ngSortGridGroup="async-items"
[ngSortGridItems]="item$ | async"
[item]="item"
(sorted)="applyOrder($event)"
class="example-box">
</ngsg-card>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Component} from '@angular/core';
import {Observable, of} from 'rxjs';
import {delay, tap} from 'rxjs/operators';

@Component({
selector: 'ngsg-demo-async',
templateUrl: './async-pipe-memory.component.html',
styleUrls: ['./async-pipe-memory.component.css', '../memory-demo.css']
})
export class AsyncPipeMemoryComponent {

item$: Observable<number[]>;
loading = false;
public sortOrder: number[];

public loadItems(): void {
this.loading = true;
this.item$ = of([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).pipe(
delay(1500),
tap(() => this.loading = false)
);
}

public applyOrder(newOrder: number[]): void {
this.sortOrder = newOrder;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ <h5 style="margin-bottom: 20px">3. Drag the items around - hold CMD or Control a
<div class="example-container">
<ngsg-card *ngFor="let item of items"
ngSortgridItem
ngSortGridGroup="getting-started"
[ngSortGridItems]="items"
[item]="item"
class="example-box">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ <h2 class="card-text">{{ sortOrder }}</h2>
<div class="example-container">
<ngsg-card *ngFor="let item of items"
ngSortgridItem
ngSortGridGroup="react-on-changes"
[item]="item"
[ngSortGridItems]="items"
(sorted)="applyOrder($event)"
Expand Down
Binary file added projects/ng-sortgrid-demo/src/assets/gs6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 76 additions & 25 deletions projects/ng-sortgrid/src/lib/ngsg-item.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,34 @@ import createSpy = jasmine.createSpy;
import {NgsgElementsHelper} from './ngsg-elements.helper';

describe('NgsgItemDirective', () => {

let sut: NgsgItemDirective;

const elementRef = {nativeElement: {}};
const ngsgSortService = createSpyObj<NgsgSortService>('ngsgSortService', ['initSort', 'sort', 'endSort']);
const ngsgSelectionService = createSpyObj<NgsgSelectionService>('ngsgSelectionService',
['selectElementIfNoSelection', 'updateSelectedDragItem']);
const ngsgSelectionService = createSpyObj<NgsgSelectionService>('ngsgSelectionService', [
'selectElementIfNoSelection',
'updateSelectedDragItem'
]);
const ngsgReflectService = createSpyObj<NgsgReflectService>('ngsgReflectService', ['reflectChanges']);
const ngsgStore = createSpyObj<NgsgStoreService>('ngsgStore',
['initState', 'hasSelectedItems', 'resetSelectedItems']);
const ngsgStore = createSpyObj<NgsgStoreService>('ngsgStore', [
'initState',
'hasSelectedItems',
'resetSelectedItems',
'hasGroup',
'hasItems',
'setItems'
]);
const ngsgEventService = new NgsgEventsService();

beforeEach(() => {
sut = new NgsgItemDirective(elementRef, ngsgSortService, ngsgSelectionService,
ngsgReflectService, ngsgStore, ngsgEventService);
});

it('should log a warning if we do not pass in sort grid items', () => {
const consoleWarnSpy = spyOn(global.console, 'warn');
sut.ngOnInit();
expect(consoleWarnSpy).toHaveBeenCalledWith(
`Ng-sortgrid: No items provided - please use [sortGridItems] to pass in an array of items -
otherwhise the ordered items will not be emitted in the (sorted) event`);
});

it('should init the store with the sortGridGroup, the ngSortGridItems and the classes', () => {
const sortGridGroup = 'sortgridgroup';
const sortGridItems = ['item one', 'item two', 'item three'] as any;
sut.ngSortGridItems = sortGridItems;
sut.ngSortGridGroup = sortGridGroup;

sut.ngOnInit();
expect(ngsgStore.initState).toHaveBeenCalledWith(sortGridGroup, sortGridItems, {});
sut = new NgsgItemDirective(
elementRef,
ngsgSortService,
ngsgSelectionService,
ngsgReflectService,
ngsgStore,
ngsgEventService
);
});

it('should set the draggable attribute on the elment', () => {
Expand Down Expand Up @@ -133,6 +128,7 @@ describe('NgsgItemDirective', () => {

it('should sort if the group contains selectedItems', () => {
ngsgStore.hasSelectedItems.and.returnValue(true);
ngsgStore.hasItems.and.returnValue(true);
sut.drop({target: {matches: () => true}});
expect(ngsgSortService.endSort).toHaveBeenCalled();
});
Expand Down Expand Up @@ -169,6 +165,7 @@ describe('NgsgItemDirective', () => {
const reflectedChanges = ['item two', 'item one', 'item three'];

ngsgStore.hasSelectedItems.and.returnValue(true);
ngsgStore.hasItems.and.returnValue(true);
ngsgReflectService.reflectChanges.and.returnValue(reflectedChanges);
sut.ngSortGridGroup = group;

Expand Down Expand Up @@ -212,4 +209,58 @@ describe('NgsgItemDirective', () => {
expect(ngsgSelectionService.updateSelectedDragItem).toHaveBeenCalledWith(group, host, true);
});

it(`should init the state with empty items if group has yet not been
initialized and the currentValue is null`, () => {
const group = 'test-group';
const changes = {
ngSortGridItems: {
currentValue: null
}
} as any;
sut.ngSortGridGroup = group;
ngsgStore.hasGroup.and.returnValue(false);

sut.ngOnChanges(changes);
expect(ngsgStore.initState).toHaveBeenCalledWith(group, []);
});

it('should init the state with items from the currentValue if group has yet not been initialized', () => {
const group = 'test-group';
const changes = {
ngSortGridItems: {
currentValue: null
}
} as any;
sut.ngSortGridGroup = group;
ngsgStore.hasGroup.and.returnValue(false);

sut.ngOnChanges(changes);
expect(ngsgStore.initState).toHaveBeenCalledWith(group, []);
});

it('should set the items if the group has allready been initialized', () => {
const group = 'test-group';
const items = ['Item one', 'item two'];
const changes = {
ngSortGridItems: {
currentValue: items
}
} as any;
sut.ngSortGridGroup = group;
ngsgStore.hasGroup.and.returnValue(true);

sut.ngOnChanges(changes);
expect(ngsgStore.setItems).toHaveBeenCalledWith(group, items);
});

it('should log a warning message if you drop and you did not provide any items', () => {
const expectedWarniningMessage =
`Ng-sortgrid: No items provided - please use [sortGridItems] to pass in an array of items -
otherwhise the ordered items can not be emitted in the (sorted) event`;
const consoleWarnSpy = spyOn(console, 'warn');
ngsgStore.hasItems.and.returnValue(false);

sut.drop(event);
expect(consoleWarnSpy).toHaveBeenCalledWith(expectedWarniningMessage);
});
});
30 changes: 22 additions & 8 deletions projects/ng-sortgrid/src/lib/ngsg-item.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
EventEmitter,
HostListener,
Input,
NgZone, OnDestroy,
NgZone, OnChanges, OnDestroy,
OnInit,
Output
Output, SimpleChanges
} from '@angular/core';

import {NgsgReflectService} from './ngsg-reflect.service';
Expand All @@ -19,11 +19,12 @@ import {NgsgElementsHelper} from './ngsg-elements.helper';
import {NgsgEventsService} from './ngsg-events.service';
import {Subject} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {group} from '@angular/animations';

const selector = '[ngSortgridItem]';

@Directive({selector})
export class NgsgItemDirective implements OnInit, AfterViewInit, OnDestroy {
export class NgsgItemDirective implements OnInit, OnChanges, AfterViewInit, OnDestroy {
@Input() ngSortGridGroup = 'defaultGroup';
@Input() ngSortGridItems;

Expand All @@ -43,16 +44,22 @@ export class NgsgItemDirective implements OnInit, AfterViewInit, OnDestroy {
}

ngOnInit(): void {
if (!this.ngSortGridItems) {
console.warn(`Ng-sortgrid: No items provided - please use [sortGridItems] to pass in an array of items -
otherwhise the ordered items will not be emitted in the (sorted) event`);
}
this.ngsgStore.initState(this.ngSortGridGroup, this.ngSortGridItems, {});
this.ngsgEventService.dropped$.pipe(
takeUntil(this.destroy$)
).subscribe(() => this.selected = false);
}

ngOnChanges(changes: SimpleChanges): void {
const sortGridItemChanges = changes.ngSortGridItems;
const sortGridItems = sortGridItemChanges.currentValue ? sortGridItemChanges.currentValue : [];

if (!this.ngsgStore.hasGroup(this.ngSortGridGroup)) {
this.ngsgStore.initState(this.ngSortGridGroup, sortGridItems);
return;
}
this.ngsgStore.setItems(this.ngSortGridGroup, sortGridItems);
}

ngAfterViewInit(): void {
this.el.nativeElement.draggable = true;
}
Expand Down Expand Up @@ -94,6 +101,13 @@ export class NgsgItemDirective implements OnInit, AfterViewInit, OnDestroy {
if (!this.ngsgStore.hasSelectedItems(this.ngSortGridGroup)) {
return;
}

if (!this.ngsgStore.hasItems(this.ngSortGridGroup)) {
console.warn(`Ng-sortgrid: No items provided - please use [sortGridItems] to pass in an array of items -
otherwhise the ordered items can not be emitted in the (sorted) event`);
return;
}

this.sortService.endSort();
const element = !this.occuredOnHost(event) ? NgsgElementsHelper.findHost(event.target, selector) : event.target;
const reflectedChanges = this.reflectService.reflectChanges(this.ngSortGridGroup, element);
Expand Down
31 changes: 31 additions & 0 deletions projects/ng-sortgrid/src/lib/ngsg-store.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,37 @@ describe('NgsgStoreService', () => {
expect(sut.getItems(group)).toEqual([]);
});

it('should return false if the group does not contain items', () => {
const group = 'testgroup';
sut.initState(group);

const hasItems = sut.hasItems(group);
expect(hasItems).toBeFalsy();
});

it('should return true if the group contains items', () => {
const group = 'testgroup';
sut.initState(group, ['item one', 'item two']);

const hasItems = sut.hasItems(group);
expect(hasItems).toBeTruthy();
});

it('should return false if the current group has yet not been initialized', () => {
const group = 'testgroup';

const hasGroup = sut.hasGroup(group);
expect(hasGroup).toBeFalsy();
});

it('should return true if the current group has been initialized', () => {
const group = 'testgroup';
sut.initState(group);

const hasGroup = sut.hasGroup(group);
expect(hasGroup).toBeTruthy();
});

it('should add the classes to the group', () => {
const group = 'testgroup';
const items = ['Item1', 'Item2'];
Expand Down
Loading