Newer
Older
<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>
<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"
>
<template #item="item">
<CircularCard>
{{ item.key.startsWith("a") ? "X" : "O" }}
</CircularCard>
<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>
v-model="ticTacToe2"
class="tic-tac-toe"
context="ticTacToe"
>
<template #item="item">
<CircularCard>
<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>
<div ref="highlight" class="custom-highlight">
Das hier ist das Highlight
</div>
</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>
<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>
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<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"
@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"
>
<template #item="item">
<div class="container">{{ item }}</div>
</template>
</drag-grid>
<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>
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<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>
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
<div class="ttt-container">
<div>
<h2>Example 6: Disabled grid</h2>
<p>
This uses the same data as the tic tac toe but is completely disabled.
Notice how the items still move if the tic tac toe grid above is
changed. Uncheck the checkbox to enable:
</p>
<label>
<input type="checkbox" v-model="gridDisabled" />
Grid disabled?
</label>
<drag-grid
v-model="ticTacToe1"
:cols="3"
:rows="3"
:disabled="gridDisabled"
class="tic-tac-toe"
context="ticTacToe"
:validate-element="randomKey"
>
<template #item="item">
<CircularCard>
{{ item.key.startsWith("a") ? "X" : "O" }}
</CircularCard>
</template>
</drag-grid>
</div>
<div>
<h2>Example 7: Disabled fields and items with props</h2>
<p>
This is a grid with disabled fields and items. Red items are disabled
and cannot be moved.
</p>
<drag-grid
v-model="someDisabledItems"
:cols="4"
:rows="4"
class="tic-tac-toe"
:disabled-fields="disabledFields"
>
<template #item="{ rawItem }">
<div
class="container"
:style="{ background: rawItem.disabled ? 'red' : 'green' }"
></div>
</template>
<template #disabledField
><div class="container">This field is disabled!</div></template
>
</drag-grid>
<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) {
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;
},
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
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: {} },
],
{ 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" } },
{ 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",
},
},
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
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,
},
someDisabledItems: [
{ key: "key1", x: 1, y: 3, w: 1, h: 1, data: {} },
{ key: "key2", x: 2, y: 2, w: 1, h: 1, data: {} },
{ key: "key3", x: 3, y: 1, w: 1, h: 1, data: {}, disabled: true },
{ key: "key4", x: 1, y: 2, w: 1, h: 1, data: {}, disabled: true },
],
disabledFields: [
{ x: 1, y: 1 },
{ x: 2, y: 3 },
],
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,
},
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,
};
},
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
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%;
}
.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;
}