-
-
Save mika76/b16befd862ca81a05399a4cfa066876e to your computer and use it in GitHub Desktop.
Array-based muuri grid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div | |
ref="muuriel" | |
class="muuri" | |
> | |
<div | |
class="muuri-item" | |
v-for="field in value" | |
:muurikey="field[muurikey]" | |
:key="field[muurikey]" | |
> | |
<slot | |
:field="field" | |
/> | |
</div> | |
</div> | |
</template> | |
<script lang="ts"> | |
import { | |
Component, | |
Vue, | |
Prop, | |
PropSync, | |
Ref, | |
Watch, | |
} from "vue-property-decorator"; | |
import Muuri from "muuri"; | |
import Options from "@/types/modules/muuri/options"; | |
@Component({ | |
}) | |
export default class MuuriGridVue extends Vue { | |
// ┌─────────────┐ | |
// │ Props │ | |
// └─────────────┘ | |
@Prop() readonly muurikey!: string; | |
@Prop({ default: () => ({}) }) readonly options!: Options; | |
// ┌────────────┐ | |
// │ Refs │ | |
// └────────────┘ | |
@Ref() readonly muuriel!: HTMLDivElement; | |
// ┌────────────────────────────┐ | |
// │ Value Sync (v-model) │ | |
// └────────────────────────────┘ | |
@Prop() readonly value!: any[]; | |
get fields() { return this.value; } | |
set fields(newVal: any[]) { this.$emit("input", newVal); } | |
// ┌─────────────┐ | |
// │ State │ | |
// └─────────────┘ | |
grid: Muuri | null = null; | |
// ┌───────────────────────┐ | |
// │ Lifecycle Hooks │ | |
// └───────────────────────┘ | |
beforeDestroy() { | |
if(this.grid) { | |
this.grid.destroy(false); | |
} | |
} | |
// ┌────────────────────┐ | |
// │ Grid Helpers │ | |
// └────────────────────┘ | |
@Watch("options", { immediate: true }) | |
async init() { | |
if(this.options !== null) { | |
while(!this.muuriel) await this.$nextTick(); | |
if(this.grid) this.grid.destroy(); | |
this.grid = new Muuri(this.muuriel, this.options); | |
this.grid.on("move", () => this.updateData()); | |
} | |
} | |
layout() { | |
if(this.grid) { | |
this.grid.refreshItems(); | |
this.grid.layout(); | |
} | |
} | |
private getOrder() { | |
if(this.grid) { | |
return this.grid | |
.getItems() | |
.map((item) => item.getElement().getAttribute("muurikey")); | |
} else return []; | |
} | |
// ┌─────────────────┐ | |
// │ Data Sync │ | |
// └─────────────────┘ | |
updateData() { | |
// If there's no grid, there's nothing to model the data after | |
if(!this.grid) return; | |
// Get data order by keys | |
const order = this.getOrder(); | |
// Use keys to sort data | |
this.fields | |
.sort((a, b) => order.indexOf(a[this.muurikey]) - order.indexOf(b[this.muurikey])); | |
} | |
@Watch("fields", { deep: true }) | |
async updateGrid(newData: any[], oldData: any[]) { | |
// If there's no grid, we don't need to update it | |
if(!this.grid) return; | |
// Map to what vue uses as keys (used to determine what to update/replace/reorder) | |
const oldKeys = oldData.map((e) => e[this.muurikey]); | |
const newKeys = newData.map((e) => e[this.muurikey]); | |
// Get which keys (individual elements) were added/removed | |
const added = newKeys.filter((key) => oldKeys.indexOf(key) === -1); | |
const removed = oldKeys.filter((key) => newKeys.indexOf(key) === -1); | |
// Deal with the removed tiles first | |
removed.forEach((po) => { | |
this.grid!.remove(this.getOrder().indexOf(po), { | |
// Vue will take care of this | |
removeElements: false, | |
// We will be adding items too, so not quite yet | |
layout: false, | |
}); | |
}); | |
await this.$nextTick(); | |
// Now add in the new ones | |
this.grid.add( | |
([ // Wrap NodeList in array for extra methods | |
...this.muuriel.childNodes, | |
] as HTMLElement[]) | |
// Filter out anything not in list of added orders | |
.filter((el) => added.indexOf(el.getAttribute("muurikey")!) !== -1), | |
{ | |
// We will be re-ordering, so wait a bit longer | |
layout: false, | |
}); | |
// Re-order if needed | |
const items = this.grid.getItems(); | |
const newOrder = this.fields.map((field) => field[this.muurikey]); | |
this.grid.sort((a, b) => { | |
return newOrder.indexOf(a.getElement().getAttribute("muurikey")!) | |
- newOrder.indexOf(b.getElement().getAttribute("muurikey")!); | |
}); | |
// Now we can start the layout | |
this.layout(); | |
} | |
} | |
</script> | |
<style lang="scss" scoped> | |
.muuri { | |
position: relative; | |
& > .muuri-item { | |
display: block; | |
position: absolute; | |
&.muuri-item-dragging { | |
z-index: 3; | |
} | |
&.muuri-item-releasing { | |
z-index: 2; | |
} | |
&.muuri-item-hidden { | |
z-index: 0; | |
} | |
} | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment