Basic Examples
Dynamic HTML Tag Switching
vue
<template>
<div class="demo">
<div class="controls">
<button @click="switchToDiv">Switch to Div</button>
<button @click="switchToSpan">Switch to Span</button>
<button @click="switchToButton">Switch to Button</button>
</div>
<EwVueComponent
:is="currentTag"
:class="tagClass"
@click="handleClick"
>
Current tag: {{ currentTag }}
</EwVueComponent>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { EwVueComponent } from 'ew-vue-component'
const currentTag = ref('div')
const tagClass = ref('demo-element')
const switchToDiv = () => {
currentTag.value = 'div'
tagClass.value = 'demo-element div-style'
}
const switchToSpan = () => {
currentTag.value = 'span'
tagClass.value = 'demo-element span-style'
}
const switchToButton = () => {
currentTag.value = 'button'
tagClass.value = 'demo-element button-style'
}
const handleClick = () => {
console.log('Clicked:', currentTag.value)
}
</script>
<style scoped>
.demo {
padding: 2rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.controls {
margin-bottom: 1rem;
display: flex;
gap: 0.5rem;
}
.controls button {
padding: 0.5rem 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.25rem;
background: white;
cursor: pointer;
}
.controls button:hover {
background: #f8fafc;
}
.demo-element {
padding: 1rem;
border: 2px solid #e2e8f0;
border-radius: 0.5rem;
margin-top: 1rem;
}
.div-style {
background: #f0f9ff;
border-color: #0ea5e9;
}
.span-style {
background: #fef3c7;
border-color: #f59e0b;
display: inline-block;
}
.button-style {
background: #dcfce7;
border-color: #22c55e;
cursor: pointer;
}
.button-style:hover {
background: #bbf7d0;
}
</style>
Component Object Rendering
vue
<template>
<div class="demo">
<button @click="toggleComponent">Toggle Component</button>
<EwVueComponent :is="currentComponent" />
</div>
</template>
<script setup>
import { ref, h } from 'vue'
import { EwVueComponent } from 'ew-vue-component'
const isFirstComponent = ref(true)
const firstComponent = {
render() {
return h('div', { class: 'component-a' }, [
h('h3', 'Component A'),
h('p', 'This is a component created through render function'),
h('button', { onClick: () => alert('From Component A') }, 'Click me')
])
}
}
const secondComponent = {
render() {
return h('div', { class: 'component-b' }, [
h('h3', 'Component B'),
h('p', 'This is another component created through render function'),
h('input', {
placeholder: 'Enter some content',
onInput: (e) => console.log('Input:', e.target.value)
})
])
}
}
const currentComponent = ref(firstComponent)
const toggleComponent = () => {
isFirstComponent.value = !isFirstComponent.value
currentComponent.value = isFirstComponent.value ? firstComponent : secondComponent
}
</script>
<style scoped>
.demo {
padding: 2rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.demo button {
margin-bottom: 1rem;
padding: 0.5rem 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.25rem;
background: white;
cursor: pointer;
}
.component-a {
background: #f0f9ff;
padding: 1rem;
border-radius: 0.5rem;
border: 2px solid #0ea5e9;
}
.component-a h3 {
color: #0c4a6e;
margin: 0 0 0.5rem 0;
}
.component-a button {
background: #0ea5e9;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
cursor: pointer;
}
.component-b {
background: #fef3c7;
padding: 1rem;
border-radius: 0.5rem;
border: 2px solid #f59e0b;
}
.component-b h3 {
color: #92400e;
margin: 0 0 0.5rem 0;
}
.component-b input {
width: 100%;
padding: 0.5rem;
border: 1px solid #f59e0b;
border-radius: 0.25rem;
margin-top: 0.5rem;
}
</style>
Async Component Loading
vue
<template>
<div class="demo">
<div class="controls">
<button @click="loadUserProfile">Load User Profile</button>
<button @click="loadSettings">Load Settings</button>
<button @click="loadDashboard">Load Dashboard</button>
</div>
<div v-if="loading" class="loading">
Loading...
</div>
<EwVueComponent
v-else
:is="currentAsyncComponent"
@error="handleError"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { EwVueComponent } from 'ew-vue-component'
const currentAsyncComponent = ref(null)
const loading = ref(false)
const loadUserProfile = async () => {
loading.value = true
try {
// Simulate async loading
await new Promise(resolve => setTimeout(resolve, 1000))
currentAsyncComponent.value = () => import('./UserProfile.vue')
} catch (error) {
handleError(error)
} finally {
loading.value = false
}
}
const loadSettings = async () => {
loading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 800))
currentAsyncComponent.value = () => import('./Settings.vue')
} catch (error) {
handleError(error)
} finally {
loading.value = false
}
}
const loadDashboard = async () => {
loading.value = true
try {
await new Promise(resolve => setTimeout(resolve, 1200))
currentAsyncComponent.value = () => import('./Dashboard.vue')
} catch (error) {
handleError(error)
} finally {
loading.value = false
}
}
const handleError = (error) => {
console.error('Async component loading failed:', error)
loading.value = false
}
</script>
<style scoped>
.demo {
padding: 2rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.controls {
margin-bottom: 1rem;
display: flex;
gap: 0.5rem;
}
.controls button {
padding: 0.5rem 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.25rem;
background: white;
cursor: pointer;
}
.controls button:hover {
background: #f8fafc;
}
.loading {
padding: 2rem;
text-align: center;
color: #6b7280;
}
</style>
Props and Event Forwarding
vue
<template>
<div class="demo">
<div class="controls">
<label>
Component Type:
<select v-model="componentType">
<option value="button">Button</option>
<option value="input">Input</option>
<option value="textarea">Textarea</option>
</select>
</label>
</div>
<EwVueComponent
:is="componentType"
:value="inputValue"
:disabled="isDisabled"
:placeholder="placeholder"
:class="componentClass"
@click="handleClick"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
>
{{ buttonText }}
</EwVueComponent>
<div class="output">
<h4>Current Value:</h4>
<p>{{ inputValue || 'No value' }}</p>
<h4>Events Log:</h4>
<ul>
<li v-for="(event, index) in eventLog" :key="index">
{{ event }}
</li>
</ul>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { EwVueComponent } from 'ew-vue-component'
const componentType = ref('button')
const inputValue = ref('')
const isDisabled = ref(false)
const eventLog = ref([])
const placeholder = computed(() => {
switch (componentType.value) {
case 'input': return 'Enter text here...'
case 'textarea': return 'Enter multiline text...'
default: return ''
}
})
const buttonText = computed(() =>
componentType.value === 'button' ? 'Click me!' : ''
)
const componentClass = computed(() => `demo-${componentType.value}`)
const addEvent = (eventName, detail = '') => {
const timestamp = new Date().toLocaleTimeString()
eventLog.value.unshift(`${timestamp}: ${eventName} ${detail}`)
if (eventLog.value.length > 5) {
eventLog.value.pop()
}
}
const handleClick = (event) => {
addEvent('clicked')
}
const handleInput = (event) => {
inputValue.value = event.target.value
addEvent('input', `"${event.target.value}"`)
}
const handleFocus = (event) => {
addEvent('focused')
}
const handleBlur = (event) => {
addEvent('blurred')
}
</script>
<style scoped>
.demo {
padding: 2rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.controls {
margin-bottom: 1rem;
}
.controls label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.controls select {
padding: 0.25rem 0.5rem;
border: 1px solid #cbd5e1;
border-radius: 0.25rem;
}
.demo-button {
padding: 0.75rem 1.5rem;
background: #3b82f6;
color: white;
border: none;
border-radius: 0.5rem;
cursor: pointer;
font-size: 1rem;
}
.demo-button:hover {
background: #2563eb;
}
.demo-input,
.demo-textarea {
width: 100%;
padding: 0.75rem;
border: 2px solid #d1d5db;
border-radius: 0.5rem;
font-size: 1rem;
}
.demo-input:focus,
.demo-textarea:focus {
outline: none;
border-color: #3b82f6;
}
.demo-textarea {
min-height: 100px;
resize: vertical;
}
.output {
margin-top: 2rem;
padding: 1rem;
background: #f9fafb;
border-radius: 0.5rem;
}
.output h4 {
margin: 0 0 0.5rem 0;
color: #374151;
}
.output p {
margin: 0 0 1rem 0;
padding: 0.5rem;
background: white;
border-radius: 0.25rem;
font-family: monospace;
}
.output ul {
margin: 0;
padding: 0;
list-style: none;
}
.output li {
padding: 0.25rem 0.5rem;
background: white;
border-radius: 0.25rem;
margin-bottom: 0.25rem;
font-family: monospace;
font-size: 0.875rem;
}
</style>
Slot Forwarding
vue
<template>
<div class="demo">
<div class="controls">
<label>
Component:
<select v-model="currentComponent">
<option value="CardComponent">Card Component</option>
<option value="ModalComponent">Modal Component</option>
<option value="TabsComponent">Tabs Component</option>
</select>
</label>
</div>
<EwVueComponent :is="componentMap[currentComponent]">
<!-- Default slot -->
<template #default>
<p>This is the main content that goes in the default slot.</p>
<p>It can contain multiple elements and will be passed through to the target component.</p>
</template>
<!-- Named slots -->
<template #header>
<h3>{{ currentComponent }} Header</h3>
<p>This content appears in the header slot.</p>
</template>
<template #footer>
<div class="footer-content">
<button @click="handleAction">Action Button</button>
<span>Footer content for {{ currentComponent }}</span>
</div>
</template>
<!-- Scoped slot (for components that support it) -->
<template #item="{ item, index }" v-if="currentComponent === 'TabsComponent'">
<div class="tab-item">
<span>{{ index + 1 }}.</span>
<strong>{{ item.title }}</strong>
<p>{{ item.description }}</p>
</div>
</template>
</EwVueComponent>
</div>
</template>
<script setup>
import { ref, h } from 'vue'
import { EwVueComponent } from 'ew-vue-component'
const currentComponent = ref('CardComponent')
// Mock components that demonstrate different slot usage
const CardComponent = {
name: 'CardComponent',
render() {
return h('div', { class: 'card' }, [
h('div', { class: 'card-header' }, this.$slots.header?.()),
h('div', { class: 'card-body' }, this.$slots.default?.()),
h('div', { class: 'card-footer' }, this.$slots.footer?.())
])
}
}
const ModalComponent = {
name: 'ModalComponent',
render() {
return h('div', { class: 'modal' }, [
h('div', { class: 'modal-header' }, this.$slots.header?.()),
h('div', { class: 'modal-content' }, this.$slots.default?.()),
h('div', { class: 'modal-footer' }, this.$slots.footer?.())
])
}
}
const TabsComponent = {
name: 'TabsComponent',
data() {
return {
items: [
{ title: 'Tab 1', description: 'Content for first tab' },
{ title: 'Tab 2', description: 'Content for second tab' },
{ title: 'Tab 3', description: 'Content for third tab' }
]
}
},
render() {
return h('div', { class: 'tabs' }, [
h('div', { class: 'tabs-header' }, this.$slots.header?.()),
h('div', { class: 'tabs-content' }, [
this.$slots.default?.(),
h('div', { class: 'tab-items' },
this.items.map((item, index) =>
this.$slots.item?.({ item, index }) || h('div', `${item.title}: ${item.description}`)
)
)
]),
h('div', { class: 'tabs-footer' }, this.$slots.footer?.())
])
}
}
const componentMap = {
CardComponent,
ModalComponent,
TabsComponent
}
const handleAction = () => {
alert(`Action triggered for ${currentComponent.value}`)
}
</script>
<style scoped>
.demo {
padding: 2rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.controls {
margin-bottom: 2rem;
}
.controls label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.controls select {
padding: 0.25rem 0.5rem;
border: 1px solid #cbd5e1;
border-radius: 0.25rem;
}
/* Card Component Styles */
.card {
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
overflow: hidden;
}
.card-header {
padding: 1rem;
background: #f8fafc;
border-bottom: 1px solid #e2e8f0;
}
.card-body {
padding: 1rem;
}
.card-footer {
padding: 1rem;
background: #f8fafc;
border-top: 1px solid #e2e8f0;
}
/* Modal Component Styles */
.modal {
background: white;
border-radius: 0.5rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
max-width: 500px;
margin: 0 auto;
}
.modal-header {
padding: 1rem;
border-bottom: 1px solid #e2e8f0;
background: #3b82f6;
color: white;
}
.modal-content {
padding: 1.5rem;
}
.modal-footer {
padding: 1rem;
border-top: 1px solid #e2e8f0;
background: #f8fafc;
}
/* Tabs Component Styles */
.tabs {
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.tabs-header {
padding: 1rem;
background: linear-gradient(45deg, #667eea, #764ba2);
color: white;
}
.tabs-content {
padding: 1rem;
}
.tab-items {
margin-top: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.tab-item {
padding: 0.75rem;
border: 1px solid #e2e8f0;
border-radius: 0.25rem;
background: #f9fafb;
}
.tab-item span {
color: #6b7280;
margin-right: 0.5rem;
}
.tab-item strong {
color: #374151;
}
.tab-item p {
margin: 0.25rem 0 0 0;
color: #6b7280;
font-size: 0.875rem;
}
.tabs-footer {
padding: 1rem;
border-top: 1px solid #e2e8f0;
background: #f8fafc;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-content button {
padding: 0.5rem 1rem;
background: #3b82f6;
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.footer-content button:hover {
background: #2563eb;
}
</style>
Performance with Caching
vue
<template>
<div class="demo">
<div class="controls">
<button @click="loadHeavyComponent">Load Heavy Component</button>
<button @click="loadComplexComponent">Load Complex Component</button>
<button @click="clearCache">Clear Cache</button>
<label>
<input v-model="enableCache" type="checkbox" />
Enable Caching
</label>
</div>
<div class="info">
<p>Load time: {{ loadTime }}ms</p>
<p>Cache status: {{ cacheStatus }}</p>
<p>Component loads: {{ loadCount }}</p>
</div>
<EwVueComponent
:is="currentComponent"
:cache="enableCache"
:cache-key="cacheKey"
:cache-ttl="cacheTtl"
:plugins="[performancePlugin]"
@error="handleError"
/>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { EwVueComponent } from 'ew-vue-component'
const currentComponent = ref(null)
const enableCache = ref(true)
const loadTime = ref(0)
const cacheStatus = ref('No component loaded')
const loadCount = ref(0)
const componentType = ref('')
const cacheKey = computed(() => `demo-${componentType.value}`)
const cacheTtl = computed(() => 300000) // 5 minutes
// Mock heavy component that takes time to "load"
const createHeavyComponent = () => ({
name: 'HeavyComponent',
render() {
return h('div', { class: 'heavy-component' }, [
h('h3', 'Heavy Component'),
h('p', 'This component simulates a heavy, expensive-to-create component.'),
h('div', { class: 'content' }, [
h('p', 'Load count: ' + loadCount.value),
h('p', 'This component would typically contain complex logic, large datasets, or expensive computations.'),
...Array.from({ length: 10 }, (_, i) =>
h('div', { class: 'item', key: i }, `Heavy item ${i + 1}`)
)
])
])
}
})
const createComplexComponent = () => ({
name: 'ComplexComponent',
render() {
return h('div', { class: 'complex-component' }, [
h('h3', 'Complex Component'),
h('p', 'This component simulates complex business logic and rendering.'),
h('div', { class: 'grid' },
Array.from({ length: 20 }, (_, i) =>
h('div', { class: 'grid-item', key: i }, [
h('h4', `Item ${i + 1}`),
h('p', `Complex calculation result: ${Math.random().toFixed(4)}`),
h('button', { onClick: () => console.log(`Clicked item ${i + 1}`) }, 'Action')
])
)
)
])
}
})
const performancePlugin = {
name: 'performance',
beforeRender(component, props, context) {
context.data.startTime = performance.now()
},
afterRender(component, props, context) {
const endTime = performance.now()
loadTime.value = Math.round(endTime - context.data.startTime)
}
}
const loadHeavyComponent = async () => {
const startLoad = performance.now()
componentType.value = 'heavy'
try {
// Simulate loading delay
await new Promise(resolve => setTimeout(resolve, 500))
loadCount.value++
currentComponent.value = createHeavyComponent()
cacheStatus.value = enableCache.value ? 'Cached' : 'Not cached'
} catch (error) {
handleError(error)
}
}
const loadComplexComponent = async () => {
componentType.value = 'complex'
try {
// Simulate loading delay
await new Promise(resolve => setTimeout(resolve, 300))
loadCount.value++
currentComponent.value = createComplexComponent()
cacheStatus.value = enableCache.value ? 'Cached' : 'Not cached'
} catch (error) {
handleError(error)
}
}
const clearCache = () => {
// This would trigger cache clearing in a real implementation
cacheStatus.value = 'Cache cleared'
loadCount.value = 0
}
const handleError = (error) => {
console.error('Component loading error:', error)
cacheStatus.value = 'Error occurred'
}
</script>
<style scoped>
.demo {
padding: 2rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.controls {
margin-bottom: 1rem;
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.controls button {
padding: 0.5rem 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.25rem;
background: white;
cursor: pointer;
}
.controls button:hover {
background: #f8fafc;
}
.controls label {
display: flex;
align-items: center;
gap: 0.5rem;
}
.info {
padding: 1rem;
background: #f8fafc;
border-radius: 0.5rem;
margin-bottom: 1rem;
}
.info p {
margin: 0 0 0.5rem 0;
font-family: monospace;
}
.heavy-component {
padding: 1rem;
background: #fef3c7;
border: 2px solid #f59e0b;
border-radius: 0.5rem;
}
.heavy-component .content {
margin-top: 1rem;
}
.heavy-component .item {
padding: 0.5rem;
margin: 0.25rem 0;
background: white;
border-radius: 0.25rem;
}
.complex-component {
padding: 1rem;
background: #f0f9ff;
border: 2px solid #0ea5e9;
border-radius: 0.5rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.grid-item {
padding: 1rem;
background: white;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
}
.grid-item h4 {
margin: 0 0 0.5rem 0;
color: #374151;
}
.grid-item p {
margin: 0 0 0.5rem 0;
font-size: 0.875rem;
color: #6b7280;
}
.grid-item button {
padding: 0.25rem 0.5rem;
background: #0ea5e9;
color: white;
border: none;
border-radius: 0.25rem;
cursor: pointer;
font-size: 0.875rem;
}
.grid-item button:hover {
background: #0284c7;
}
</style>