grid-and-widgets
v0.0.14
Published
I created this package to introduce a new way to build web pages in Vue 3. I needed a way to create custom webpages quickly with drag and drop capability for our project lectful.com Since our platform is a SAAS solution where all of our customers share t
Downloads
11
Readme
Grid And Widgets
I created this package to introduce a new way to build web pages in Vue 3. I needed a way to create custom webpages quickly with drag and drop capability for our project lectful.com Since our platform is a SAAS solution where all of our customers share the same codebase but different databases, we needed a way to store the layout of pages of each customer in their database.
So, why didn't I just use some HTML WYSIWYG editor to build webpages? Well, manipulating HTML directly is difficult, not predictable, and would require us to always add HTML templates with bunch of slots...
The solution I came up with is to store the webpage layout as JSON instead of HTML. And then we build a bunch of independant widgets that can be dragged and dropped into the layout. I was able to do this thanks to the flexible Grid system that CSS offers.
How to install
npm install grid-and-widgets
or in the case of gitlab
npm install https://www.gitlab.com/vipayers/grid-and-widgets
How to use
This code lets you use the package globally in your app.js file
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
import 'grid-and-widgets/dist/style.css'
import GridAndWidgets from 'grid-and-widgets'; //Import as a plugin
app.use(GridAndWidgets) // And then use the plugin
app.mount('#app')
Or you can use it locally in your any component file
<template>
<div>
<Builder :widgets="widgets" v-model:content="content" @saveButtonClicked="save()" />
</div>
</template>
<script>
import 'grid-and-widgets/dist/style.css'
import {Builder, Viewer} from 'grid-and-widgets'
import TextComponent from 'SOMEPATH/Widgets/Text/Component.vue';
import TextConfig from 'SOMEPATH/Widgets/Text/Config'
import TextIcon from 'SOMEPATH/Widgets/Text/Icon'
import ImageComponent from 'SOMEPATH/Widgets/Image/Component.vue';
import ImageConfig from 'SOMEPATH/Widgets/Image/Config'
import ImageIcon from 'SOMEPATH/Widgets/Image/Icon'
const widgets = {
'text' : {
name : 'Text',
component : TextComponent,
config : TextConfig,
icon : TextIcon
},
'image' :{
name : 'Image',
component : ImageComponent,
config : ImageConfig,
icon : ImageIcon
}
}
export default {
components:{
Builder,
Viewer
},
methods:{
save(){
//Save page layout, maybe by sending an Axios request to the backend
}
},
data(){
return {
//This structure must be respected
content : { layout: { lg: [], md: [], sm: [], xs: [], xxs: [] }, is_responsive: true }
}
}
}
</script>
Widgets
Widgets are the main building blocks that compose a webpage. We have seen in the previous example how to add them to the builder. Widgets have three composed of three things:
- Main Widget Component The main widget component is the actual UI of the widget that will be visible to the end user.
- A config component The config component of the widget is a component that allows you to customize your widget.
- An Icon component Each widget has an Icon, so you simply define the Icon of the widget, for example an SVG or PNG...
let's see an example widget that lets user display an image
SOMEPATH/Widgets/Image/Component.vue
<template>
<div class="w-full h-full object-cover">
<img class="w-full h-full object-cover" :src="url" />
</div>
</template>
<script>
export default {
props:{
url : String
}
}
</script>
SOMEPATH/Widgets/Image/Config
<template>
<div>
<h1>Image url</h1>
<input :value="modelValue.url" @input="$emit('update:modelValue', {url : $event.target.value})" class="bg-transparent border p-1 rounded mt-1" />
</div>
</template>
<script>
export default {
props:{
modelValue:{
type : Object,
default : {
url : ''
}
}
}
}
</script>
SOMEPATH/Widgets/Text/Icon
<template>
<div class="w-full h-full text-gray-300">
<svg viewBox="0 0 24 24">
<path fill="currentColor" d="M21,3H3C2,3 1,4 1,5V19A2,2 0 0,0 3,21H21C22,21 23,20 23,19V5C23,4 22,3 21,3M5,17L8.5,12.5L11,15.5L14.5,11L19,17H5Z"></path>
</svg>
</div>
</template>
Resize container height to fit widget height.
Sometimes, some widgets might have a non fixed height (for example elements that show a hidden content
when clicked...), however The grid has a specific height defined for each container, so whenever a widget changes size it needs to totify the grid to update its height so that it accounts for the new size of our widget.
To do that, inside our widget we can dispatch an event named fitHeight
with the widget element so that the grid recalculate the container height. Here is an example widget.
<template>
<div ref="myComponent">
<button @click="toggle">Show content</button>
<div v-if="isShown">
Some hidden content that only appears when the isShown variable is true. When it appears it changes the
height of the component.
</div>
</div>
</template>
<script>
export default {
methods:{
toggle(){
this.isShown = !this.isShown;
//We use $nextTick so that we send the height after the component is rendered with the new height.
this.$nextTick(() => {
this.$emit('fitHeight', { element : this.$refs.myComponent })
})
//Or, We can specify the new container height explicitly
let height = this.$refs.myComponent.clientHeight; //Or any value
this.$nextTick(() => {
this.$emit('fitHeight', { height })
})
}
}
}
</script>