<script setup> import DragGrid from "../../src/DragGrid.vue"; import CircularCard from "./components/CircularCard.vue"; import NumberCounter from "./components/NumberCounter.vue"; </script> <template> <div id="app"> <h1><code>DragGrid</code> Examples</h1> <h2>Example 1</h2> <p> Grid with two programmatically blocked cells and one programmatically blocked item </p> <DragGrid :rows="8" :cols="5" :pos-validation="blockField" v-model="items" class="bordered" > <div id="blocker"> This field is blocked because it's filled, the next one programmatically </div> <template #item="item"> <div class="container">{{ item }}</div> </template> </DragGrid> <h2>Example 2: "Tic-Tac-Toe"</h2> <p> A grid functioning as a playing field and another functioning as a container for playing pieces. You can drag as many pieces as you want from the container to the field. </p> <div class="ttt-container"> <DragGrid :rows="3" :cols="3" v-model="ticTacToe1" class="tic-tac-toe" context="ticTacToe" :validate-element="randomKey" > <template #item="item"> <CircularCard> {{ item.key.startsWith("a") ? "X" : "O" }} </CircularCard> </template> </DragGrid> <div> <p> These two are two different grids but we can drag from right to left! </p> <p>Drag items from the container on the right to play on the left.</p> </div> <DragGrid :rows="1" :cols="2" v-model="ticTacToe2" class="tic-tac-toe" context="ticTacToe" :pos-validation="blockAllMoving" > <template #item="item"> <CircularCard> {{ item.data.text }} </CircularCard> </template> </DragGrid> </div> <h2>Example 3: Counters</h2> <p> Showcasing local and global state: The local state of the counter components doesn't change when moved inside one grid, but is cleared when moved to the other. The global state is in the <code>data</code> attribute of each item and gets transferred as well. Both grids have also custom highlights. </p> <div class="ttt-container"> <drag-grid v-model="counters" :cols="3" :rows="2" id="c-grid" @input="logInput" context="counter" > <template #item="item"> <number-counter v-model="item.data.num"></number-counter> </template> <template #highlight> <div ref="highlight" class="custom-highlight"> Das hier ist das Highlight </div> </template> </drag-grid> <span>← Drag here please →</span> <drag-grid v-model="counters2" :cols="3" :rows="2" context="counter"> <template #item="item"> <number-counter v-model="item.data.num"></number-counter> </template> <template #highlight> <div ref="highlight" class="custom-highlight"> Das hier ist das Highlight </div> </template> </drag-grid> </div> <h2>Example 4: Dynamic lessons</h2> <p> These lessons are loaded from <code>computed</code> to simulate a non-editable source like an API. they are changed using the method <code>handleLessonMoved</code>. Try changing the sizes of Lesson1, the grid and the texts of the items! The lesson container on the side doesn't have a highlight and doesn't keep track of the item position. Try moving <code>item3</code> back and forth! </p> <div class="ttt-container"> <div> <label> Lesson 1 has length: {{ lesson1Length }} <input type="range" v-model="lesson1Length" min="1" max="5" step="1" /> </label> <input type="text" v-model="lessonData.lesson1" /> </div> <div> <label> Lesson grid has width: {{ lesson1Cols }} <input type="range" v-model="lesson1Cols" min="2" max="5" step="1" /> </label> <input type="text" v-model="lessonData.lesson2" /> </div> <div> <label> Lesson grid has height: {{ lesson1Rows }} <input type="range" v-model="lesson1Rows" min="6" max="10" step="1" /> <input type="text" v-model="lessonData.lesson3.inside" /> <input type="text" v-model="lessonData.lesson3.outside" /> </label> </div> <drag-grid :value="lessons1" :cols="parseInt(lesson1Cols)" :rows="parseInt(lesson1Rows)" class="bordered" context="lesson" grid-id="lesson-plan" @itemChanged="handleLessonMoved" > <template #item="item"> <div class="container">{{ item }}</div> </template> </drag-grid> <drag-grid :value="lessons1" :cols="1" :rows="10" context="lesson" grid-id="lesson-storage" @itemChanged="handleLessonDropInContainer" no-highlight > <template #item="item"> <div class="container">{{ item }}</div> </template> </drag-grid> </div> <h2>Example 5: Dynamic colors</h2> <p> This example showcases the <code>rawItem</code> with the custom method <code>getColor</code>. Both grids on the outside call the method with <code>"red"</code> while the one in the middle uses <code>"green"</code>. We use arrays called <code>placedA</code>, <code>placedB</code> and <code>placedC</code> to save, which item is contained in which grid. Notice: the third grid doesn't save the item position, they are always positioned automatically. </p> <div class="ttt-container"> <drag-grid :value="colorGrid" :cols="4" :rows="4" grid-id="gridA" context="colors" class="bordered color-grid" @itemChanged="handleColorItemChange" > <template #item="item"> <div :style="{ background: item.rawItem.getColor('red'), color: 'white', }" > {{ item.data.name }} </div> </template> </drag-grid> <drag-grid :value="colorGrid" :cols="4" :rows="4" grid-id="gridB" context="colors" class="bordered color-grid" @itemChanged="handleColorItemChange" > <template #item="item"> <div :style="{ background: item.rawItem.getColor('green'), color: 'black', }" > {{ item.data.name }} </div> </template> </drag-grid> <drag-grid :value="colorGrid" :cols="4" :rows="4" grid-id="gridC" context="colors" class="bordered color-grid" @itemChanged="handleColorItemChange" > <template #item="item"> <div :style="{ background: item.rawItem.getColor('red'), color: 'white', }" > {{ item.data.name }} </div> </template> </drag-grid> </div> </div> </template> <script> export default { name: "App", methods: { blockField(x, y, key) { // We won't move fields with ID 'obj8' and nothing into (3, 3) and (4, 3) if (x === 3 && y === 3) return false; if (x === 4 && y === 3) return false; return key !== "obj8"; }, blockAllMoving() { return false; }, randomKey(element) { if (element.key.length !== 1) return; element.key += Math.random().toString(36).replace("0.", ""); }, logInput(input) { console.log("New movement detected:", input); }, handleLessonMoved(lesson) { this.logInput(lesson); switch (lesson.key) { case "lesson1": this.lesson1X = lesson.x; this.lesson1Y = lesson.y; break; case "lesson2": this.lesson2X = lesson.x; this.lesson2Y = lesson.y; break; case "lesson3": this.lesson3X = lesson.x; this.lesson3Y = lesson.y; this.lesson3inPlan = true; break; default: console.error("Something is wrong here!"); } }, handleLessonDropInContainer(lesson) { if (lesson.key === "lesson3") this.lesson3inPlan = false; }, contains(array, search) { return array.filter((obj) => obj === search).length > 0; }, findAndRemove(array, element) { let index = array.findIndex((i) => { return i === element; }); if (index >= 0) array.splice(index, 1); }, handleColorItemChange(item) { this[item.key].x = item.x; this[item.key].y = item.y; if (item.gridId === item.originGridId) return; switch (item.gridId) { case "gridA": // Moved to gridA this.findAndRemove( item.originGridId === "gridB" ? this.placedB : this.placedC, item.key ); this.placedA.push(item.key); break; case "gridB": // Moved to gridB this.findAndRemove( item.originGridId === "gridA" ? this.placedA : this.placedC, item.key ); this.placedB.push(item.key); break; case "gridC": // Moved to gridC this.findAndRemove( item.originGridId === "gridB" ? this.placedB : this.placedA, item.key ); this.placedC.push(item.key); break; } }, }, data() { return { items: [ { x: 1, y: 3, w: 1, h: 1, key: "obj1", data: {} }, { x: 2, y: 1, w: 1, h: 1, key: "obj2", data: {} }, { x: 3, y: 1, w: 2, h: 2, key: "obj3", data: {} }, { x: 5, y: 2, w: 1, h: 1, key: "obj4", data: {} }, { x: 1, y: 1, w: 1, h: 2, key: "obj5", data: {} }, { x: 5, y: 1, w: 1, h: 1, key: "obj6", data: {} }, { x: 2, y: 2, w: 1, h: 3, key: "obj7", data: {} }, { x: 1, y: 4, w: 1, h: 1, key: "obj8", data: { title: "I'm blocked from moving!" }, }, { x: 5, y: 3, w: 1, h: 1, key: "obj9", data: {} }, ], ticTacToe1: [ { x: 1, y: 1, w: 1, h: 1, key: "a1", data: { text: "X" } }, { x: 3, y: 3, w: 1, h: 1, key: "b1", data: { text: "O" } }, ], ticTacToe2: [ { x: 1, y: 1, w: 1, h: 1, key: "a", data: { text: "X" } }, { x: 2, y: 1, w: 1, h: 1, key: "b", data: { text: "O" } }, ], counters: [ { x: 1, y: 1, w: 1, h: 1, key: "a", data: { num: 1 } }, { x: 2, y: 1, w: 1, h: 1, key: "b", data: { num: 4 } }, { x: 3, y: 2, w: 1, h: 1, key: "c", data: { num: -1 } }, ], counters2: [ { x: 1, y: 2, w: 1, h: 1, key: "d", data: { num: 30 } }, { x: 3, y: 1, w: 1, h: 1, key: "e", data: { num: 3 } }, ], lesson1Length: 1, lesson1Cols: 4, lesson1Rows: 6, lesson1X: 1, lesson1Y: 1, lesson2X: 2, lesson2Y: 1, lesson3X: -1, lesson3Y: -1, lesson3inPlan: false, lessonData: { lesson1: "Nothing", lesson2: "Hallo", lesson3: { inside: "This is lesson 3 inside the plan", outside: "This is lesson3 outside of the plan", }, }, placedA: ["item1", "item2"], placedB: ["item3", "item4"], placedC: ["item5", "item6"], item1: { x: 1, y: 1, }, item2: { x: 2, y: 2, }, item3: { x: 1, y: 1, }, item4: { x: 3, y: 3, }, item5: { x: 2, y: 4, }, item6: { x: 4, y: 2, }, }; }, computed: { lessons1() { return [ { x: (grid) => { return grid.gridId === "lesson-plan" ? this.lesson1X : 0; }, y: (grid) => { return grid.gridId === "lesson-plan" ? this.lesson1Y : 0; }, w: 1, h: this.lesson1Length, key: "lesson1", data: { text: this.lessonData.lesson1, }, }, { x: (grid) => { return grid.gridId === "lesson-plan" ? this.lesson2X : 0; }, y: (grid) => { return grid.gridId === "lesson-plan" ? this.lesson2Y : 0; }, w: 1, h: parseInt(this.lesson1Length) + 1, key: "lesson2", data: { text: this.lessonData.lesson2, }, }, { x: (grid) => { if (this.lesson3inPlan) { return grid.gridId === "lesson-plan" ? this.lesson3X : -1; } return grid.gridId === "lesson-plan" ? -1 : 0; }, y: (grid) => { if (this.lesson3inPlan) { return grid.gridId === "lesson-plan" ? this.lesson3Y : -1; } return grid.gridId === "lesson-plan" ? -1 : 0; }, w: 1, h: parseInt(this.lesson1Length) + 1, key: "lesson3", data: (grid) => { return { text: grid.gridId === "lesson-storage" ? this.lessonData.lesson3.outside : this.lessonData.lesson3.inside, }; }, }, ]; }, colorGrid() { let items = ["item1", "item2", "item3", "item4", "item5", "item6"]; let greens = [ "green", "seagreen", "greenyellow", "darkolivegreen", "limegreen", "palegreen", ]; let reds = [ "darkred", "indianred", "orangered", "red", "maroon", "firebrick", ]; return items.map((itemName) => { return { key: itemName, x: (grid) => { if (grid.gridId === "gridA") { return this.contains(this.placedA, itemName) ? this[itemName].x : -1; } else if (grid.gridId === "gridB") { return this.contains(this.placedB, itemName) ? this[itemName].x : -1; } else { // gridC return this.contains(this.placedC, itemName) ? 0 : -1; } }, y: (grid) => { if (grid.gridId === "gridA") { return this.contains(this.placedA, itemName) ? this[itemName].y : -1; } else if (grid.gridId === "gridB") { return this.contains(this.placedB, itemName) ? this[itemName].y : -1; } else { // gridC return this.contains(this.placedC, itemName) ? 0 : -1; } }, w: 1, h: 1, data: { name: itemName, }, getColor: (a) => a === "red" ? reds[parseInt(itemName[4]) - 1] : greens[parseInt(itemName[4]) - 1], }; }); }, }, }; </script> <style scoped> #blocker { grid-row: 3 / span 1; grid-column: 3 / span 1; background-image: linear-gradient( 45deg, #edd85f 25%, #0f2b3d 25%, #0f2b3d 50%, #edd85f 50%, #edd85f 75%, #0f2b3d 75%, #0f2b3d 100% ); background-size: 56.57px 56.57px; color: white; font-size: large; font-weight: bold; text-shadow: 4px 4px 4px #2c3e50; } .container { background: lightcoral; width: 100%; height: 100%; } .tic-tac-toe { max-width: 400px; } .ttt-container { display: flex; justify-content: space-between; } .custom-highlight { display: flex; align-items: center; justify-content: center; background: aquamarine; width: 100%; height: 100%; } .bordered { border: 2px solid grey; } .color-grid { min-height: 11em; line-height: 2em; box-sizing: content-box; } h2 { padding-top: 1em; } h2 + p { padding-bottom: 0.5em; } code { background: lightgray; padding: 0.2em; border-radius: 3px; } </style>