mirror of
https://github.com/archtechx/livewire-petite-vue.git
synced 2025-12-12 00:24:03 +00:00
initial
This commit is contained in:
commit
ff59f31cb4
4 changed files with 235 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
84
README.md
Normal file
84
README.md
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
# vue-petite driver for Livewire
|
||||||
|
|
||||||
|
This package provides a layer for
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Currently it's only possible to use this library using a `<script type="module">`. Later we'd also like to support a self-initializing non-module `<script>` as well as an npm package.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { createApp } from 'todo'
|
||||||
|
|
||||||
|
window.addEventListener('livewire:load', () => {
|
||||||
|
createApp().mount()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
The imported `createApp` automatically includes a bit of global state and a `v-livewire` directive. If you'd like to do this manually, you can use:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import { state, directive } from 'todo'
|
||||||
|
import { createApp } from 'https://unpkg.com/petite-vue?module'
|
||||||
|
|
||||||
|
window.addEventListener('livewire:load', () => {
|
||||||
|
createApp(state).directive('livewire', directive).mount()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The package provides a `v-livewire` directive that lets you configure bi-directional links between Vue state and Livewire state.
|
||||||
|
|
||||||
|
For example, if you had a `messages` property in Vue and an `items` property in Livewire, you could do:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div v-livewire="{ messages: 'items' }" v-scope="{ messages: {} }">
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that you **always need to have the property in Vue as well**. You need some initial state, and your template must work with the empty state. In our case, an empty state for messages is just `{}`.
|
||||||
|
|
||||||
|
If the properties are named the same, you can simply pass an array:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div v-livewire="['messages']" v-scope="{ messages: {} }">
|
||||||
|
```
|
||||||
|
|
||||||
|
If you'd like to defer value changes, i.e. have reactive state in Vue but only update Livewire backend state when a Livewire action is executed, you can use the `.defer` modifier:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div v-livewire.defer="['messages']" v-scope="{ messages: {} }">
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div v-livewire="['messages']" v-scope="{ messages: {}, foo: 'bar' }">
|
||||||
|
You can use Vue-only state in the component: {{ foo }}
|
||||||
|
<input v-model="foo">
|
||||||
|
|
||||||
|
<template v-for="(message, id) in messages">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<label :for="`message-${id}`">Message</label>
|
||||||
|
<input :id="`message-${id}`" v-model.lazy="messages[id].message">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" @click="wire.send(id)">
|
||||||
|
Send
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" @click="wire.remove(id)">
|
||||||
|
Remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Things to note
|
||||||
|
|
||||||
|
Vue uses templates which contain `{{ these }}` things, and that doesn't pair with Livewire as well as Alpine.
|
||||||
|
|
||||||
|
For that reason, the library automatically adds `wire:ignore` to the root element of each petite-vue component.
|
||||||
29
package.json
Normal file
29
package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"name": "livewire-petite-vue",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "petite-vue driver for Livewire",
|
||||||
|
"files": [
|
||||||
|
"src/index.js"
|
||||||
|
],
|
||||||
|
"main": "src/index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/archtechx/livewire-petite-vue.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"livewire",
|
||||||
|
"vue",
|
||||||
|
"vuejs"
|
||||||
|
],
|
||||||
|
"author": "Samuel Štancl <samuel@archte.ch>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/archtechx/livewire-petite-vue/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/archtechx/livewire-petite-vue#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"laravel-mix": "^6.0.25",
|
||||||
|
"ts-loader": "^9.2.3",
|
||||||
|
"typescript": "^4.3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/index.js
Normal file
120
src/index.js
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
import { createApp, nextTick, reactive } from 'https://unpkg.com/petite-vue?module'
|
||||||
|
|
||||||
|
let wireProxies = new WeakMap
|
||||||
|
|
||||||
|
// Vue-tracked object that changes after every LW request
|
||||||
|
const __livewireMemo = reactive({
|
||||||
|
previous: 'none',
|
||||||
|
checksum: 'start',
|
||||||
|
})
|
||||||
|
|
||||||
|
Livewire.hook('message.received', (data) => __livewireMemo.checksum = data.response.serverMemo.checksum)
|
||||||
|
|
||||||
|
// Vue changes -> LW
|
||||||
|
let wireProperty = (livewire, prop, defer = false) => new Proxy(livewire.get(prop), {
|
||||||
|
set(target, property, value) {
|
||||||
|
livewire.set(prop + '.' + property, value, defer)
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
get(target, property, val, receiver) {
|
||||||
|
if (property === Symbol.toStringTag) {
|
||||||
|
return 'WireProperty'
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = livewire.get(prop + '.' + property);
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return wireProperty(livewire, prop + '.' + property, defer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Vue changes -> LW
|
||||||
|
let wireProxy = (livewire, defer = false) => {
|
||||||
|
if (wireProxies.has(livewire) && ! defer) {
|
||||||
|
return wireProxies.get(livewire)
|
||||||
|
}
|
||||||
|
|
||||||
|
let proxy = new Proxy(__livewireMemo, {
|
||||||
|
set(target, property, value) {
|
||||||
|
livewire.set(property, value, defer)
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
|
||||||
|
get(target, property) {
|
||||||
|
if (property === 'deferred') {
|
||||||
|
return wireProxy(livewire, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property === Symbol.toStringTag) {
|
||||||
|
return 'WireProxy'
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = livewire.get(property);
|
||||||
|
|
||||||
|
if (value === undefined && ! property.startsWith('__v')) {
|
||||||
|
return (...args) => livewire.call(property, ...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
return wireProperty(livewire, property, defer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (! defer) {
|
||||||
|
wireProxies.set(livewire, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
const directive = ctx => {
|
||||||
|
ctx.el.setAttribute('wire:ignore', true)
|
||||||
|
|
||||||
|
const wire = wireProxy(ctx.el.closest('[wire\\:id]').__livewire);
|
||||||
|
|
||||||
|
const state = ctx.ctx.scope;
|
||||||
|
|
||||||
|
state.wire = wire
|
||||||
|
|
||||||
|
ctx.effect(() => {
|
||||||
|
if (state.__livewireMemo.checksum !== state.__livewireMemo.previous) {
|
||||||
|
let data = ctx.get()
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
data = data.reduce((map, property) => {
|
||||||
|
map[property] = property
|
||||||
|
|
||||||
|
return map
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [property, livewire] of Object.entries(data)) {
|
||||||
|
// LW changes -> Vue
|
||||||
|
if (ctx.modifiers && ctx.modifiers.defer) {
|
||||||
|
state[property] = wire.deferred[livewire]
|
||||||
|
} else {
|
||||||
|
state[property] = wire[livewire]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
__livewireMemo,
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = (data) => createApp({ ...state, ...data }).directive('livewire', directive)
|
||||||
|
|
||||||
|
export { state, directive, create as createApp, nextTick, reactive };
|
||||||
Loading…
Add table
Add a link
Reference in a new issue