ngx-masonry-gallery
Version:
Masonry gallery for Angular
293 lines (286 loc) • 13 kB
JavaScript
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, Component, ChangeDetectionStrategy, Input, Output, NgModule } from '@angular/core';
import * as imagesLoaded from 'imagesloaded';
import * as Masonry from 'masonry-layout';
class Utilities {
newGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
// tslint:disable-next-line:no-bitwise triple-equals
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
let utilities = new Utilities();
class MasonryGalleryComponent {
constructor(renderer) {
this.renderer = renderer;
this.images = [];
this.width = 330;
this.gutter = 5;
this.verticalGutter = 5;
this.imageClasses = [];
this.clickImage = new EventEmitter();
this.removeComplete = new EventEmitter();
this.layoutComplete = new EventEmitter();
this.galleryGuid = utilities.newGuid();
this.mansonryItemSelectorClass = `grid-item-${this.galleryGuid}`;
this.activeImages = [];
this.viewReady = false;
}
ngOnChanges(changes) {
if (changes.images && changes.images.currentValue) {
if (!this.viewReady) {
// process images once we can
this.changesToProcess = changes;
}
else {
this.processImages(changes);
}
}
}
ngOnDestroy() {
var _a, _b;
(_b = (_a = this.msnry) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
}
handleClick(image) {
this.clickImage.next(image);
}
ngAfterViewInit() {
this.viewReady = true;
this.initMasonry();
// process images now
if (this.changesToProcess) {
this.processImages(this.changesToProcess);
this.changesToProcess = undefined;
}
}
addImages(images) {
if (images && images.length > 0) {
this.addImagesToGallery(images);
}
}
removeImages(images) {
if (images && images.length > 0) {
images.forEach((image) => {
this.removeImageFromGallery(image);
});
}
}
processImages(changes) {
const imagesToProcess = this.getAddedAndRemovesImages(changes);
// add images to mansonry layout
this.addImages(imagesToProcess.addedImages);
// removes images from layout
this.removeImages(imagesToProcess.removedImages);
}
getAddedAndRemovesImages(changes) {
let addedImages = [];
const removedImages = [];
const newImagesValue = changes.images
.currentValue;
const oldImagesValue = changes.images
.previousValue;
if (!oldImagesValue) {
// all images are new ones
addedImages = changes.images.currentValue;
}
else {
// process added images
newImagesValue.forEach((newImage) => {
const existingImage = oldImagesValue.find((m) => m.imageUrl.toLowerCase() === newImage.imageUrl.toLowerCase());
if (existingImage) {
// image was in previous value && is in new, do nothing
}
else {
// image is new
addedImages.push(newImage);
}
});
// process removed images
oldImagesValue.forEach((oldImage) => {
const existingImage = newImagesValue.find((m) => m.imageUrl.toLowerCase() === oldImage.imageUrl.toLowerCase());
if (existingImage) {
// image was in previous value && is in new, do nothing
}
else {
// image is removed
removedImages.push(oldImage);
}
});
}
return {
addedImages: addedImages,
removedImages: removedImages,
};
}
initMasonry() {
var _a, _b;
const grid = document.getElementById(this.galleryGuid);
if (!grid) {
console.warn(`Invalid grid element with id '${this.galleryGuid}'`);
return;
}
// remove all existing data from grid
grid.innerHTML = "";
const msnry = new Masonry(grid, {
// options...
itemSelector: "." + this.mansonryItemSelectorClass,
columnWidth: this.width,
gutter: this.gutter,
});
const that = this;
(_a = msnry.on) === null || _a === void 0 ? void 0 : _a.call(msnry, "layoutComplete", (items) => {
that.layoutComplete.next(items);
});
(_b = msnry.on) === null || _b === void 0 ? void 0 : _b.call(msnry, "removeComplete", (items) => {
that.removeComplete.next(items);
});
this.grid = grid;
this.msnry = msnry;
}
removeImageFromGallery(image) {
var _a, _b, _c, _d;
// get image guid
const imageIdResult = this.activeImages.find((m) => m.image.imageUrl.toLowerCase() === image.imageUrl.toLowerCase());
if (!imageIdResult) {
// image was not found, this is probably an error
console.warn(`Image with url '${image.imageUrl}' was not found. If you are adding images, make sure to 'replace' the images array with a new one
so that detection change can be executed instead of just adding an image to array
(which doesn't fire change detection on array property)`);
return;
}
// find image based on its id
const imageElem = document.getElementById(imageIdResult.id);
if (!imageElem) {
// image was not found in DOM
console.warn(`Image with id '{${imageIdResult.id}}' was not found in DOM. Have you manipulated the DOM in some way?`);
return;
}
// remove image from gallery
(_b = (_a = this.msnry) === null || _a === void 0 ? void 0 : _a.remove) === null || _b === void 0 ? void 0 : _b.call(_a, [imageElem]);
// refresh layout
(_d = (_c = this.msnry) === null || _c === void 0 ? void 0 : _c.layout) === null || _d === void 0 ? void 0 : _d.call(_c);
// remove image from array
for (let i = 0; i < this.activeImages.length; i++) {
const idWithImage = this.activeImages[i];
if (idWithImage.image.imageUrl.toLowerCase() ===
imageIdResult.image.imageUrl.toLowerCase()) {
this.activeImages.splice(i, 1);
}
}
}
addImagesToGallery(images) {
if (!this.grid) {
throw Error("Grid element is not yet ready, are you trying to add image too soon?");
}
const imagesWrapper = this.renderer.createElement("span");
images.forEach((image) => {
// generate unique image id
const imageId = this.getImageId();
// create element
const imageElem = this.renderer.createElement("img");
imageElem.setAttribute("id", imageId);
imageElem.setAttribute("alt", image.alt ? image.alt : "no description");
imageElem.setAttribute("src", image.imageUrl);
// note - images are hidden by default and should be shown only after they are loaded
imageElem.setAttribute("style", `display: block; width: ${this.width}px; margin-bottom: ${this.verticalGutter}px`);
imageElem.className = this.getImageClass();
imageElem.addEventListener("click", () => {
this.handleClick(image);
});
// store guid with this image
this.activeImages.push({
id: imageId,
image: image,
});
// add to dom and mansory & refresh layout
this.renderer.appendChild(imagesWrapper, imageElem);
});
// add html to dom
this.renderer.appendChild(this.grid, imagesWrapper);
// add images once they are loaded
const imgLoad = imagesLoaded(imagesWrapper);
imgLoad.on("progress", (instance, image) => {
var _a, _b, _c, _d;
if (image &&
image.isLoaded &&
this.msnry &&
this.msnry.appended &&
this.msnry.reloadItems) {
// this.renderer.appendChild(this.grid, image.img);
// unhide image
// this.renderer.setStyle(image.img, "display", "block");
// remove image from gallery
(_b = (_a = this.msnry) === null || _a === void 0 ? void 0 : _a.addItems) === null || _b === void 0 ? void 0 : _b.call(_a, [image.img]);
// refresh layout
(_d = (_c = this.msnry) === null || _c === void 0 ? void 0 : _c.layout) === null || _d === void 0 ? void 0 : _d.call(_c);
}
});
}
getImageClass() {
let className = this.mansonryItemSelectorClass;
if (this.imageClasses && this.imageClasses.length > 0) {
const customClass = this.imageClasses.join(" ");
className += " " + customClass;
}
return className;
}
getImageId() {
return this.galleryGuid + "_" + utilities.newGuid();
}
}
/** @nocollapse */ MasonryGalleryComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryComponent, deps: [{ token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ MasonryGalleryComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.0", type: MasonryGalleryComponent, selector: "ngx-masonry-gallery", inputs: { images: "images", width: "width", gutter: "gutter", verticalGutter: "verticalGutter", imageClasses: "imageClasses" }, outputs: { clickImage: "clickImage", removeComplete: "removeComplete", layoutComplete: "layoutComplete" }, usesOnChanges: true, ngImport: i0, template: '<div [id]="galleryGuid"></div>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryComponent, decorators: [{
type: Component,
args: [{
changeDetection: ChangeDetectionStrategy.OnPush,
selector: "ngx-masonry-gallery",
template: '<div [id]="galleryGuid"></div>',
}]
}], ctorParameters: function () { return [{ type: i0.Renderer2 }]; }, propDecorators: { images: [{
type: Input
}], width: [{
type: Input
}], gutter: [{
type: Input
}], verticalGutter: [{
type: Input
}], imageClasses: [{
type: Input
}], clickImage: [{
type: Output
}], removeComplete: [{
type: Output
}], layoutComplete: [{
type: Output
}] } });
class MasonryGalleryModule {
}
/** @nocollapse */ MasonryGalleryModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
/** @nocollapse */ MasonryGalleryModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, declarations: [MasonryGalleryComponent], imports: [CommonModule], exports: [MasonryGalleryComponent] });
/** @nocollapse */ MasonryGalleryModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, imports: [CommonModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.0", ngImport: i0, type: MasonryGalleryModule, decorators: [{
type: NgModule,
args: [{
imports: [
CommonModule
],
declarations: [
MasonryGalleryComponent,
],
exports: [
MasonryGalleryComponent,
],
}]
}] });
/*
* Public API
*/
/**
* Generated bundle index. Do not edit.
*/
export { MasonryGalleryComponent, MasonryGalleryModule };
//# sourceMappingURL=ngx-masonry-gallery.mjs.map