Caleb Porzio 5 سال پیش
والد
کامیت
47fddcea83
9فایلهای تغییر یافته به همراه108 افزوده شده و 13 حذف شده
  1. 13 1
      README.md
  2. 2 2
      dist/mix-manifest.json
  3. 27 4
      dist/project-x.js
  4. 0 0
      dist/project-x.min.js
  5. 10 1
      index.html
  6. 1 1
      package.json
  7. 28 1
      src/component.js
  8. 3 3
      src/utils.js
  9. 24 0
      test/ref.spec.js

+ 13 - 1
README.md

@@ -46,7 +46,7 @@ Add the following script to the end of your `<head>` section.
 
 ## Learn
 
-There are 6 directives available to you:
+There are 7 directives available to you:
 
 | Directive
 | --- |
@@ -55,6 +55,7 @@ There are 6 directives available to you:
 | `x-on` |
 | `x-model` |
 | `x-text` |
+| `x-ref` |
 | `x-cloak` |
 
 Here's how they each work:
@@ -145,6 +146,17 @@ Adding `.stop` to an event listener will call `stopPropagation` on the triggered
 
 ---
 
+### `x-ref`
+**Example:** `<div x-ref="foo"></div><button x-on:click="$refs.foo.innerText = 'bar'"></button>`
+
+**Structure:** `<div x-ref="[ref name]"></div><button x-on:click="$refs.[ref name].innerText = 'bar'"></button>`
+
+`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`.
+
+This is a helpful alternative to setting ids and using `document.querySelector` all over the place.
+
+---
+
 ### `x-cloak`
 **Example:** `<div x-data="{}" x-cloak></div>`
 

+ 2 - 2
dist/mix-manifest.json

@@ -1,4 +1,4 @@
 {
-    "/project-x.js": "/project-x.js?id=7551001734d762a47f88",
-    "/project-x.min.js": "/project-x.min.js?id=c8c54e0f29e2563e41d2"
+    "/project-x.js": "/project-x.js?id=bd450a04a07206020f63",
+    "/project-x.min.js": "/project-x.min.js?id=aca2ba4b017885bdd4a5"
 }

+ 27 - 4
dist/project-x.js

@@ -1081,7 +1081,8 @@ function () {
     key: "runListenerHandler",
     value: function runListenerHandler(expression, e) {
       this.evaluateCommandExpression(expression, {
-        '$event': e
+        '$event': e,
+        '$refs': this.getRefsProxy()
       });
     }
   }, {
@@ -1170,6 +1171,28 @@ function () {
         option.selected = arrayWrappedValue.includes(option.value || option.text);
       });
     }
+  }, {
+    key: "getRefsProxy",
+    value: function getRefsProxy() {
+      var self = this; // One of the goals of this project is to not hold elements in memory, but rather re-evaluate
+      // the DOM when the system needs something from it. This way, the framework is flexible and
+      // friendly to outside DOM changes from libraries like Vue/Livewire.
+      // For this reason, I'm using an "on-demand" proxy to fake a "$refs" object.
+
+      return new Proxy({}, {
+        get: function get(object, property) {
+          var ref; // We can't just query the DOM because it's hard to filter out refs in
+          // nested components.
+
+          Object(_utils__WEBPACK_IMPORTED_MODULE_0__["walkSkippingNestedComponents"])(self.el, function (el) {
+            if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) {
+              ref = el;
+            }
+          });
+          return ref;
+        }
+      });
+    }
   }]);
 
   return Component;
@@ -1331,7 +1354,7 @@ function walkSkippingNestedComponents(el, callback) {
 
   while (node) {
     if (node.hasAttribute('x-data')) return;
-    walk(node, callback);
+    walkSkippingNestedComponents(node, callback);
     node = node.nextElementSibling;
   }
 }
@@ -1365,12 +1388,12 @@ function saferEvalNoReturn(expression, dataContext) {
   return new Function(['$data'].concat(_toConsumableArray(Object.keys(additionalHelperVariables))), "with($data) { ".concat(expression, " }")).apply(void 0, [dataContext].concat(_toConsumableArray(Object.values(additionalHelperVariables))));
 }
 function isXAttr(attr) {
-  var xAttrRE = /x-(on|bind|data|text|model|cloak)/;
+  var xAttrRE = /x-(on|bind|data|text|model|cloak|ref)/;
   return xAttrRE.test(attr.name);
 }
 function getXAttrs(el, type) {
   return Array.from(el.attributes).filter(isXAttr).map(function (attr) {
-    var typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak)/);
+    var typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak|ref)/);
     var valueMatch = attr.name.match(/:([a-zA-Z\-]+)/);
     var modifiers = attr.name.match(/\.[^.\]]+(?=[^\]]*$)/g) || [];
     return {

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
dist/project-x.min.js


+ 10 - 1
index.html

@@ -5,9 +5,18 @@
             [x-cloak] { display: none; }
         </style>
 
-        <script src="https://cdn.jsdelivr.net/gh/calebporzio/project-x@v0.2.0/dist/project-x.min.js" defer></script>
+        <script src="https://cdn.jsdelivr.net/gh/calebporzio/project-x@v0.3.0/dist/project-x.min.js" defer></script>
     </head>
     <body>
+        <div x-data="{ foo: 'bar' }">
+            <span x-text="foo"></span>
+
+            <div x-data="{ foo: 'bob' }">
+                <span x-ref="lob">hey</span>
+                <button x-on:click="console.log($refs.lob)">Something</button>
+            </div>
+        </div>
+
         <div x-data="{ foo: 'bar' }">
             <div x-on:click="foo = 'baz'">
                 <button x-on:click.stop></button>

+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "main": "dist/project-x.js",
   "name": "project-x",
-  "version": "0.2.0",
+  "version": "0.3.0",
   "scripts": {
     "test": "jest",
     "test:debug": "node --inspect node_modules/.bin/jest --runInBand",

+ 28 - 1
src/component.js

@@ -175,7 +175,10 @@ export default class Component {
     }
 
     runListenerHandler(expression, e) {
-        this.evaluateCommandExpression(expression, { '$event': e })
+        this.evaluateCommandExpression(expression, {
+            '$event': e,
+            '$refs': this.getRefsProxy()
+        })
     }
 
     evaluateReturnExpression(expression) {
@@ -262,4 +265,28 @@ export default class Component {
             option.selected = arrayWrappedValue.includes(option.value || option.text)
         })
     }
+
+    getRefsProxy() {
+        var self = this
+
+        // One of the goals of this project is to not hold elements in memory, but rather re-evaluate
+        // the DOM when the system needs something from it. This way, the framework is flexible and
+        // friendly to outside DOM changes from libraries like Vue/Livewire.
+        // For this reason, I'm using an "on-demand" proxy to fake a "$refs" object.
+        return new Proxy({}, {
+            get(object, property) {
+                var ref
+
+                // We can't just query the DOM because it's hard to filter out refs in
+                // nested components.
+                walkSkippingNestedComponents(self.el, el => {
+                    if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) {
+                        ref = el
+                    }
+                })
+
+                return ref
+            }
+        })
+    }
 }

+ 3 - 3
src/utils.js

@@ -24,7 +24,7 @@ export function walkSkippingNestedComponents(el, callback) {
     while (node) {
         if (node.hasAttribute('x-data')) return
 
-        walk(node, callback)
+        walkSkippingNestedComponents(node, callback)
         node = node.nextElementSibling
     }
 }
@@ -61,7 +61,7 @@ export function saferEvalNoReturn(expression, dataContext, additionalHelperVaria
 }
 
 export function isXAttr(attr) {
-    const xAttrRE = /x-(on|bind|data|text|model|cloak)/
+    const xAttrRE = /x-(on|bind|data|text|model|cloak|ref)/
 
     return xAttrRE.test(attr.name)
 }
@@ -70,7 +70,7 @@ export function getXAttrs(el, type) {
     return Array.from(el.attributes)
         .filter(isXAttr)
         .map(attr => {
-            const typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak)/)
+            const typeMatch = attr.name.match(/x-(on|bind|data|text|model|cloak|ref)/)
             const valueMatch = attr.name.match(/:([a-zA-Z\-]+)/)
             const modifiers = attr.name.match(/\.[^.\]]+(?=[^\]]*$)/g) || []
 

+ 24 - 0
test/ref.spec.js

@@ -0,0 +1,24 @@
+import projectX from 'project-x'
+import { wait } from 'dom-testing-library'
+
+global.MutationObserver = class {
+    observe() {}
+}
+
+test('can reference elements from event listeners', async () => {
+    document.body.innerHTML = `
+        <div x-data="{}">
+            <span x-ref="bob"></span>
+
+            <button x-on:click="$refs['bob'].innerText = 'lob'"></button>
+        </div>
+    `
+
+    projectX.start()
+
+    expect(document.querySelector('span').innerText).toEqual(undefined)
+
+    document.querySelector('button').click()
+
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('lob') })
+})

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است