Skip to content

Commit 47fddce

Browse files
committed
wip
1 parent e8aabfc commit 47fddce

File tree

9 files changed

+109
-14
lines changed

9 files changed

+109
-14
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Add the following script to the end of your `<head>` section.
4646

4747
## Learn
4848

49-
There are 6 directives available to you:
49+
There are 7 directives available to you:
5050

5151
| Directive
5252
| --- |
@@ -55,6 +55,7 @@ There are 6 directives available to you:
5555
| `x-on` |
5656
| `x-model` |
5757
| `x-text` |
58+
| `x-ref` |
5859
| `x-cloak` |
5960

6061
Here's how they each work:
@@ -145,6 +146,17 @@ Adding `.stop` to an event listener will call `stopPropagation` on the triggered
145146

146147
---
147148

149+
### `x-ref`
150+
**Example:** `<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>`
151+
152+
**Structure:** `<div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>`
153+
154+
`x-ref` provides a convenient way to retrieve raw DOM elements out of your component. By setting an `x-ref` attribute on an element, you are making it available to all event handlers inside an object called `$refs`.
155+
156+
This is a helpful alternative to setting ids and using `document.querySelector` all over the place.
157+
158+
---
159+
148160
### `x-cloak`
149161
**Example:** `<div x-data="{}" x-cloak></div>`
150162

dist/mix-manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"/project-x.js": "/project-x.js?id=7551001734d762a47f88",
3-
"/project-x.min.js": "/project-x.min.js?id=c8c54e0f29e2563e41d2"
2+
"/project-x.js": "/project-x.js?id=bd450a04a07206020f63",
3+
"/project-x.min.js": "/project-x.min.js?id=aca2ba4b017885bdd4a5"
44
}

dist/project-x.js

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,7 +1081,8 @@ function () {
10811081
key: "runListenerHandler",
10821082
value: function runListenerHandler(expression, e) {
10831083
this.evaluateCommandExpression(expression, {
1084-
'$event': e
1084+
'$event': e,
1085+
'$refs': this.getRefsProxy()
10851086
});
10861087
}
10871088
}, {
@@ -1170,6 +1171,28 @@ function () {
11701171
option.selected = arrayWrappedValue.includes(option.value || option.text);
11711172
});
11721173
}
1174+
}, {
1175+
key: "getRefsProxy",
1176+
value: function getRefsProxy() {
1177+
var self = this; // One of the goals of this project is to not hold elements in memory, but rather re-evaluate
1178+
// the DOM when the system needs something from it. This way, the framework is flexible and
1179+
// friendly to outside DOM changes from libraries like Vue/Livewire.
1180+
// For this reason, I'm using an "on-demand" proxy to fake a "$refs" object.
1181+
1182+
return new Proxy({}, {
1183+
get: function get(object, property) {
1184+
var ref; // We can't just query the DOM because it's hard to filter out refs in
1185+
// nested components.
1186+
1187+
Object(_utils__WEBPACK_IMPORTED_MODULE_0__["walkSkippingNestedComponents"])(self.el, function (el) {
1188+
if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) {
1189+
ref = el;
1190+
}
1191+
});
1192+
return ref;
1193+
}
1194+
});
1195+
}
11731196
}]);
11741197

11751198
return Component;
@@ -1331,7 +1354,7 @@ function walkSkippingNestedComponents(el, callback) {
13311354

13321355
while (node) {
13331356
if (node.hasAttribute('x-data')) return;
1334-
walk(node, callback);
1357+
walkSkippingNestedComponents(node, callback);
13351358
node = node.nextElementSibling;
13361359
}
13371360
}
@@ -1365,12 +1388,12 @@ function saferEvalNoReturn(expression, dataContext) {
13651388
return new Function(['$data'].concat(_toConsumableArray(Object.keys(additionalHelperVariables))), "with($data) { ".concat(expression, " }")).apply(void 0, [dataContext].concat(_toConsumableArray(Object.values(additionalHelperVariables))));
13661389
}
13671390
function isXAttr(attr) {
1368-
var xAttrRE = /x-(on|bind|data|text|model|cloak)/;
1391+
var xAttrRE = /x-(on|bind|data|text|model|cloak|ref)/;
13691392
return xAttrRE.test(attr.name);
13701393
}
13711394
function getXAttrs(el, type) {
13721395
return Array.from(el.attributes).filter(isXAttr).map(function (attr) {
1373-
var typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak)/);
1396+
var typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak|ref)/);
13741397
var valueMatch = attr.name.match(/:([a-zA-Z\-]+)/);
13751398
var modifiers = attr.name.match(/\.[^.\]]+(?=[^\]]*$)/g) || [];
13761399
return {

dist/project-x.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index.html

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@
55
[x-cloak] { display: none; }
66
</style>
77

8-
<script src="https://cdn.jsdelivr.net/gh/calebporzio/project-x@v0.2.0/dist/project-x.min.js" defer></script>
8+
<script src="https://cdn.jsdelivr.net/gh/calebporzio/project-x@v0.3.0/dist/project-x.min.js" defer></script>
99
</head>
1010
<body>
11+
<div x-data="{ foo: 'bar' }">
12+
<span x-text="foo"></span>
13+
14+
<div x-data="{ foo: 'bob' }">
15+
<span x-ref="lob">hey</span>
16+
<button x-on:click="console.log($refs.lob)">Something</button>
17+
</div>
18+
</div>
19+
1120
<div x-data="{ foo: 'bar' }">
1221
<div x-on:click="foo = 'baz'">
1322
<button x-on:click.stop></button>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"main": "dist/project-x.js",
33
"name": "project-x",
4-
"version": "0.2.0",
4+
"version": "0.3.0",
55
"scripts": {
66
"test": "jest",
77
"test:debug": "node --inspect node_modules/.bin/jest --runInBand",

src/component.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,10 @@ export default class Component {
175175
}
176176

177177
runListenerHandler(expression, e) {
178-
this.evaluateCommandExpression(expression, { '$event': e })
178+
this.evaluateCommandExpression(expression, {
179+
'$event': e,
180+
'$refs': this.getRefsProxy()
181+
})
179182
}
180183

181184
evaluateReturnExpression(expression) {
@@ -262,4 +265,28 @@ export default class Component {
262265
option.selected = arrayWrappedValue.includes(option.value || option.text)
263266
})
264267
}
268+
269+
getRefsProxy() {
270+
var self = this
271+
272+
// One of the goals of this project is to not hold elements in memory, but rather re-evaluate
273+
// the DOM when the system needs something from it. This way, the framework is flexible and
274+
// friendly to outside DOM changes from libraries like Vue/Livewire.
275+
// For this reason, I'm using an "on-demand" proxy to fake a "$refs" object.
276+
return new Proxy({}, {
277+
get(object, property) {
278+
var ref
279+
280+
// We can't just query the DOM because it's hard to filter out refs in
281+
// nested components.
282+
walkSkippingNestedComponents(self.el, el => {
283+
if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) {
284+
ref = el
285+
}
286+
})
287+
288+
return ref
289+
}
290+
})
291+
}
265292
}

src/utils.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function walkSkippingNestedComponents(el, callback) {
2424
while (node) {
2525
if (node.hasAttribute('x-data')) return
2626

27-
walk(node, callback)
27+
walkSkippingNestedComponents(node, callback)
2828
node = node.nextElementSibling
2929
}
3030
}
@@ -61,7 +61,7 @@ export function saferEvalNoReturn(expression, dataContext, additionalHelperVaria
6161
}
6262

6363
export function isXAttr(attr) {
64-
const xAttrRE = /x-(on|bind|data|text|model|cloak)/
64+
const xAttrRE = /x-(on|bind|data|text|model|cloak|ref)/
6565

6666
return xAttrRE.test(attr.name)
6767
}
@@ -70,7 +70,7 @@ export function getXAttrs(el, type) {
7070
return Array.from(el.attributes)
7171
.filter(isXAttr)
7272
.map(attr => {
73-
const typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak)/)
73+
const typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak|ref)/)
7474
const valueMatch = attr.name.match(/:([a-zA-Z\-]+)/)
7575
const modifiers = attr.name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
7676

test/ref.spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import projectX from 'project-x'
2+
import { wait } from 'dom-testing-library'
3+
4+
global.MutationObserver = class {
5+
observe() {}
6+
}
7+
8+
test('can reference elements from event listeners', async () => {
9+
document.body.innerHTML = `
10+
<div x-data="{}">
11+
<span x-ref="bob"></span>
12+
13+
<button x-on:click="$refs['bob'].innerText = 'lob'"></button>
14+
</div>
15+
`
16+
17+
projectX.start()
18+
19+
expect(document.querySelector('span').innerText).toEqual(undefined)
20+
21+
document.querySelector('button').click()
22+
23+
await wait(() => { expect(document.querySelector('span').innerText).toEqual('lob') })
24+
})

0 commit comments

Comments
 (0)