Caleb Porzio %!s(int64=5) %!d(string=hai) anos
pai
achega
e077cd0f12
Modificáronse 9 ficheiros con 159 adicións e 143 borrados
  1. 1 1
      dist/mix-manifest.json
  2. 65 64
      dist/project-x.js
  3. 4 0
      index.html
  4. 31 24
      src/component.js
  5. 32 21
      src/index.js
  6. 5 1
      test/cloak.spec.js
  7. 1 1
      test/constructor.spec.js
  8. 20 0
      test/data.spec.js
  9. 0 31
      test/on.spec.js

+ 1 - 1
dist/mix-manifest.json

@@ -1,4 +1,4 @@
 {
-    "/project-x.js": "/project-x.js?id=ab86c5db3e64294a9256",
+    "/project-x.js": "/project-x.js?id=e45ec620878b956774b7",
     "/project-x.min.js": "/project-x.min.js?id=bed9d0d6b5b36985cfb4"
 }

+ 65 - 64
dist/project-x.js

@@ -856,14 +856,6 @@ try {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return Component; });
 /* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./src/utils.js");
-function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
-
-function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
-
-function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
-
-function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
-
 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
 
 function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
@@ -879,12 +871,30 @@ function () {
     _classCallCheck(this, Component);
 
     this.el = el;
-    this.data = Object(_utils__WEBPACK_IMPORTED_MODULE_0__["saferEval"])(this.el.getAttribute('x-data'), {});
-    this.concernedData = [];
+    var rawData = Object(_utils__WEBPACK_IMPORTED_MODULE_0__["saferEval"])(this.el.getAttribute('x-data'), {});
+    this.data = this.wrapDataInObservable(rawData);
     this.initialize();
   }
 
   _createClass(Component, [{
+    key: "wrapDataInObservable",
+    value: function wrapDataInObservable(data) {
+      this.concernedData = [];
+      var self = this;
+      return new Proxy(data, {
+        set: function set(obj, property, value) {
+          var setWasSuccessful = Reflect.set(obj, property, value);
+
+          if (self.concernedData.indexOf(property) === -1) {
+            self.concernedData.push(property);
+          }
+
+          self.refresh();
+          return setWasSuccessful;
+        }
+      });
+    }
+  }, {
     key: "initialize",
     value: function initialize() {
       var _this = this;
@@ -954,7 +964,13 @@ function () {
     key: "refresh",
     value: function refresh() {
       var self = this;
-      Object(_utils__WEBPACK_IMPORTED_MODULE_0__["debounce"])(_utils__WEBPACK_IMPORTED_MODULE_0__["walk"], 5)(this.el, function (el) {
+
+      var walkThenClearDependancyTracker = function walkThenClearDependancyTracker(rootEl, callback) {
+        Object(_utils__WEBPACK_IMPORTED_MODULE_0__["walk"])(rootEl, callback);
+        self.concernedData = [];
+      };
+
+      Object(_utils__WEBPACK_IMPORTED_MODULE_0__["debounce"])(walkThenClearDependancyTracker, 5)(this.el, function (el) {
         Object(_utils__WEBPACK_IMPORTED_MODULE_0__["getXAttrs"])(el).forEach(function (_ref2) {
           var type = _ref2.type,
               value = _ref2.value,
@@ -1064,17 +1080,9 @@ function () {
   }, {
     key: "runListenerHandler",
     value: function runListenerHandler(expression, e) {
-      var _this$concernedData;
-
-      var _this$evaluateCommand = this.evaluateCommandExpression(expression, {
+      this.evaluateCommandExpression(expression, {
         '$event': e
-      }),
-          deps = _this$evaluateCommand.deps;
-
-      (_this$concernedData = this.concernedData).push.apply(_this$concernedData, _toConsumableArray(deps));
-
-      this.concernedData = this.concernedData.filter(_utils__WEBPACK_IMPORTED_MODULE_0__["onlyUnique"]);
-      this.refresh();
+      });
     }
   }, {
     key: "evaluateReturnExpression",
@@ -1095,18 +1103,7 @@ function () {
   }, {
     key: "evaluateCommandExpression",
     value: function evaluateCommandExpression(expression, extraData) {
-      var affectedDataKeys = [];
-      var proxiedData = new Proxy(this.data, {
-        set: function set(obj, property, value) {
-          var setWasSuccessful = Reflect.set(obj, property, value);
-          affectedDataKeys.push(property);
-          return setWasSuccessful;
-        }
-      });
-      Object(_utils__WEBPACK_IMPORTED_MODULE_0__["saferEvalNoReturn"])(expression, proxiedData, extraData);
-      return {
-        deps: affectedDataKeys
-      };
+      Object(_utils__WEBPACK_IMPORTED_MODULE_0__["saferEvalNoReturn"])(expression, this.data, extraData);
     }
   }, {
     key: "updateTextValue",
@@ -1204,7 +1201,6 @@ var projectX = {
   start: function start() {
     var _this = this;
 
-    var targetNode, observerOptions, observer;
     return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default.a.async(function start$(_context) {
       while (1) {
         switch (_context.prev = _context.next) {
@@ -1218,57 +1214,62 @@ var projectX = {
             return _babel_runtime_regenerator__WEBPACK_IMPORTED_MODULE_0___default.a.awrap(Object(_utils__WEBPACK_IMPORTED_MODULE_2__["domReady"])());
 
           case 3:
-            this.discoverComponents(); // It's easier and more performant to just support Turbolinks than listen
+            this.discoverComponents(function (el) {
+              _this.initializeElement(el);
+            }); // It's easier and more performant to just support Turbolinks than listen
             // to MutationOberserver mutations at the document level.
 
             document.addEventListener("turbolinks:load", function () {
-              _this.discoverUndiscoveredComponents();
+              _this.discoverUninitializedComponents(function (el) {
+                _this.initializeElement(el);
+              });
             });
-            targetNode = document.querySelector('body');
-            observerOptions = {
-              childList: true,
-              attributes: true,
-              subtree: true
-            };
-            observer = new MutationObserver(function (mutations) {
-              for (var i = 0; i < mutations.length; i++) {
-                if (mutations[i].addedNodes.length > 0) {
-                  mutations[i].addedNodes.forEach(function (node) {
-                    if (node.nodeType !== 1) return;
-
-                    if (node.matches('[x-data]')) {
-                      _this.initializeElement(node);
-                    }
-                  });
-                }
-              }
+            this.listenForNewUninitializedComponentsAtRunTime(function (el) {
+              _this.initializeElement(el);
             });
-            observer.observe(targetNode, observerOptions);
 
-          case 9:
+          case 6:
           case "end":
             return _context.stop();
         }
       }
     }, null, this);
   },
-  discoverComponents: function discoverComponents() {
-    var _this2 = this;
-
+  discoverComponents: function discoverComponents(callback) {
     var rootEls = document.querySelectorAll('[x-data]');
     rootEls.forEach(function (rootEl) {
-      _this2.initializeElement(rootEl);
+      callback(rootEl);
     });
   },
-  discoverUndiscoveredComponents: function discoverUndiscoveredComponents() {
-    var _this3 = this;
-
+  discoverUninitializedComponents: function discoverUninitializedComponents(callback) {
     var rootEls = document.querySelectorAll('[x-data]');
     Array.from(rootEls).filter(function (el) {
       return el.__x === undefined;
     }).forEach(function (rootEl) {
-      _this3.initializeElement(rootEl);
+      callback(rootEl);
+    });
+  },
+  listenForNewUninitializedComponentsAtRunTime: function listenForNewUninitializedComponentsAtRunTime(callback) {
+    var targetNode = document.querySelector('body');
+    var observerOptions = {
+      childList: true,
+      attributes: true,
+      subtree: true
+    };
+    var observer = new MutationObserver(function (mutations) {
+      for (var i = 0; i < mutations.length; i++) {
+        if (mutations[i].addedNodes.length > 0) {
+          mutations[i].addedNodes.forEach(function (node) {
+            if (node.nodeType !== 1) return;
+
+            if (node.matches('[x-data]')) {
+              callback(node);
+            }
+          });
+        }
+      }
     });
+    observer.observe(targetNode, observerOptions);
   },
   initializeElement: function initializeElement(el) {
     el.__x = new _component__WEBPACK_IMPORTED_MODULE_1__["default"](el);

+ 4 - 0
index.html

@@ -16,6 +16,10 @@
             <span x-text="foo"></span>
         </div>
 
+        <div x-data="{ count: 0 }">
+            <span x-text=""></span>
+        </div>
+
         <div x-data="{ foo: 'bar' }">
             <input x-model="foo"></input>
 

+ 31 - 24
src/component.js

@@ -4,13 +4,32 @@ export default class Component {
     constructor(el) {
         this.el = el
 
-        this.data = saferEval(this.el.getAttribute('x-data'), {})
+        const rawData = saferEval(this.el.getAttribute('x-data'), {})
 
-        this.concernedData = []
+        this.data = this.wrapDataInObservable(rawData)
 
         this.initialize()
     }
 
+    wrapDataInObservable(data) {
+        this.concernedData = []
+
+        var self = this
+        return new Proxy(data, {
+            set(obj, property, value) {
+                const setWasSuccessful = Reflect.set(obj, property, value)
+
+                if (self.concernedData.indexOf(property) === -1) {
+                    self.concernedData.push(property)
+                }
+
+                self.refresh()
+
+                return setWasSuccessful
+            }
+        })
+    }
+
     initialize() {
         walk(this.el, el => {
             getXAttrs(el).forEach(({ type, value, modifiers, expression }) => {
@@ -61,7 +80,14 @@ export default class Component {
 
     refresh() {
         var self = this
-        debounce(walk, 5)(this.el, function (el) {
+
+        const walkThenClearDependancyTracker = (rootEl, callback) => {
+            walk(rootEl, callback)
+
+            self.concernedData = []
+        }
+
+        debounce(walkThenClearDependancyTracker, 5)(this.el, function (el) {
             getXAttrs(el).forEach(({ type, value, modifiers, expression }) => {
                 switch (type) {
                     case 'model':
@@ -149,12 +175,7 @@ export default class Component {
     }
 
     runListenerHandler(expression, e) {
-        const { deps } = this.evaluateCommandExpression(expression, { '$event': e })
-
-        this.concernedData.push(...deps)
-        this.concernedData = this.concernedData.filter(onlyUnique)
-
-        this.refresh()
+        this.evaluateCommandExpression(expression, { '$event': e })
     }
 
     evaluateReturnExpression(expression) {
@@ -177,21 +198,7 @@ export default class Component {
     }
 
     evaluateCommandExpression(expression, extraData) {
-        var affectedDataKeys = []
-
-        const proxiedData = new Proxy(this.data, {
-            set(obj, property, value) {
-                const setWasSuccessful = Reflect.set(obj, property, value)
-
-                affectedDataKeys.push(property)
-
-                return setWasSuccessful
-            }
-        })
-
-        saferEvalNoReturn(expression, proxiedData, extraData)
-
-        return { deps: affectedDataKeys }
+        saferEvalNoReturn(expression, this.data, extraData)
     }
 
     updateTextValue(el, value) {

+ 32 - 21
src/index.js

@@ -8,15 +8,44 @@ const projectX = {
             await domReady()
         }
 
-        this.discoverComponents()
+        this.discoverComponents(el => {
+            this.initializeElement(el)
+        })
 
         // It's easier and more performant to just support Turbolinks than listen
         // to MutationOberserver mutations at the document level.
         document.addEventListener("turbolinks:load", () => {
-            this.discoverUndiscoveredComponents()
+            this.discoverUninitializedComponents(el => {
+                this.initializeElement(el)
+            })
         })
 
+        this.listenForNewUninitializedComponentsAtRunTime(el => {
+            this.initializeElement(el)
+        })
+    },
+
+    discoverComponents: function (callback) {
+        const rootEls = document.querySelectorAll('[x-data]');
+
+        rootEls.forEach(rootEl => {
+            callback(rootEl)
+        })
+    },
+
+    discoverUninitializedComponents: function (callback) {
+        const rootEls = document.querySelectorAll('[x-data]');
+
+        Array.from(rootEls)
+            .filter(el => el.__x === undefined)
+            .forEach(rootEl => {
+                callback(rootEl)
+            })
+    },
+
+    listenForNewUninitializedComponentsAtRunTime: function (callback) {
         var targetNode = document.querySelector('body');
+
         var observerOptions = {
             childList: true,
             attributes: true,
@@ -30,7 +59,7 @@ const projectX = {
                         if (node.nodeType !== 1) return
 
                         if (node.matches('[x-data]')) {
-                            this.initializeElement(node)
+                            callback(node)
                         }
                     })
                 }
@@ -40,24 +69,6 @@ const projectX = {
         observer.observe(targetNode, observerOptions);
     },
 
-    discoverComponents: function () {
-        const rootEls = document.querySelectorAll('[x-data]');
-
-        rootEls.forEach(rootEl => {
-            this.initializeElement(rootEl)
-        })
-    },
-
-    discoverUndiscoveredComponents: function () {
-        const rootEls = document.querySelectorAll('[x-data]');
-
-        Array.from(rootEls)
-            .filter(el => el.__x === undefined)
-            .forEach(rootEl => {
-                this.initializeElement(rootEl)
-            })
-    },
-
     initializeElement: function (el) {
         el.__x = new Component(el)
     }

+ 5 - 1
test/cloak.spec.js

@@ -1,6 +1,10 @@
-import projectX from 'projectX'
+import projectX from 'project-x'
 import { wait } from 'dom-testing-library'
 
+global.MutationObserver = class {
+    observe() {}
+}
+
 test('x-cloak is removed', async () => {
     document.body.innerHTML = `
         <div x-data="{ hidden: true }">

+ 1 - 1
test/constructor.spec.js

@@ -5,7 +5,7 @@ global.MutationObserver = class {
     observe() {}
 }
 
-test('auto-detect new components and dont loose state of existing ones', async () => {
+test('auto-detect new components and dont lose state of existing ones', async () => {
     var runObserver
 
     global.MutationObserver = class {

+ 20 - 0
test/data.spec.js

@@ -0,0 +1,20 @@
+import projectX from 'project-x'
+import { fireEvent, wait } from 'dom-testing-library'
+
+global.MutationObserver = class {
+    observe() {}
+}
+
+test('data manipulated on component object is reactive', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ foo: 'bar' }">
+            <span x-text="foo"></span>
+        </div>
+    `
+
+    projectX.start()
+
+    document.querySelector('div').__x.data.foo = 'baz'
+
+    await wait(() => { expect(document.querySelector('span').innerText).toEqual('baz') })
+})

+ 0 - 31
test/on.spec.js

@@ -23,37 +23,6 @@ test('data modified in event listener updates effected attribute bindings', asyn
     await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
 })
 
-test('data modified in event listener doesnt update uneffected attribute bindings', async () => {
-    document.body.innerHTML = `
-        <div x-data="{ foo: 'bar', count: 0 }">
-            <button x-on:click="foo = 'baz'"></button>
-            <button x-on:click="count++"></button>
-
-            <span x-bind:output="foo"></span>
-            <span x-bind:output="count++"></span>
-        </div>
-    `
-
-    projectX.start()
-
-    expect(document.querySelectorAll('span')[0].getAttribute('output')).toEqual('bar')
-    expect(document.querySelectorAll('span')[1].getAttribute('output')).toEqual('0')
-
-    document.querySelectorAll('button')[0].click()
-
-    await wait(async () => {
-        expect(document.querySelectorAll('span')[0].getAttribute('output')).toEqual('baz')
-        expect(document.querySelectorAll('span')[1].getAttribute('output')).toEqual('0')
-
-        document.querySelectorAll('button')[1].click()
-
-        await wait(() => {
-            expect(document.querySelectorAll('span')[0].getAttribute('output')).toEqual('baz')
-            expect(document.querySelectorAll('span')[1].getAttribute('output')).toEqual('3')
-        })
-    })
-})
-
 test('.stop modifier', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar' }">