stefanhoelzl / vue.py

Pythonic Vue.js
https://stefanhoelzl.github.io/vue.py/
MIT License
311 stars 20 forks source link

Containers in VueX are Javascript objects #23

Closed adamlwgriffiths closed 2 years ago

adamlwgriffiths commented 3 years ago

When working with VueStore, complex objects (lists, dicts) are converted to Javascript types and are not workable as python types.

eg

from vue import VueComponent, VueStore, computed

class Store(VueStore):
    data = {
        'a': 123,
        'b': 456,
    }

class DataComponent(VueComponent):
    id: str
    template = '''
        <div
            :id="id">
            {{ data }}
        </div>
    '''
    @computed
    def data(self):
        return self.store.data[self.id]

class App(VueComponent):
    template = '''
        <div>
            <data-component
                v-for="id in ids">
            </data-component>
        </div>
    '''

    @computed
    def ids(self):
        print(f'self.store.data = {self.store.data}')
        return self.store.data.keys()

App("#app", store=Store())

html

<body>
    <div id="app"></div>
</body>

I get the following output:

self.store.data = <Javascript Object object: [object Object]> 

Traceback (most recent call last):
  File http://127.0.0.1:5000/#__main__ line 1, in <module>
    import test
  File /static/test/__init__.py line 1, in <module>
    from . import app
  File /static/test/app.py line 42, in <module>
    App("#app", store=Store())
  File /static/vue/vue.py line 70, in __new__
    return Object.from_js(window.Vue.new(Object.to_js(init_dict)))
  File /static/vue/decorators/base.py line 32, in ids
    return Object.to_js(fn(*args, **kwargs))
  File /static/test/app.py line 30, in ids
    return self.store.data.keys()
brython.min.js:7 AttributeError: keys

I get similar issues with @getter and @mutate methods defined inside the store.

adamlwgriffiths commented 3 years ago

I suspect this may be related to the details in Py/JS Bridge

The wrapper around lists and dictionaries provide the same interface than native python types but due to restrictions in Brython, they are no subclasses of list/dict. This can lead to problems when passing this methods to other native python methods. Therefore a helper is provided to convert a wrapped Javascript object into a native python type.

However, when calling Object.to_py I still end up with a javascript object

Making the following changes:

    @computed
    def ids(self):
        data = Object.to_py(self.store.data)
        print(f'Object.to_py(self.store.data) = {data}')
        return data.keys()

I still end up with the same error, the print function outputs:

Object.to_py(self.store.data) = <Javascript Object object: [object Object]>

There seems to also be some conversion tied to the @getter decorator. If I add a @getter function to the Store that is just return {} it still comes back as a JSObj.

adamlwgriffiths commented 3 years ago

Ok, so it looks like the way to "fix" this is to use the bridge.List and bridge.Dict.

class Store(VueStore):
    d = {
        'a': 123,
        'b': 456,
    };
    l = [1,2,3,4]

    @getter
    def ddict(self):
        return dict(Dict(self.d))

    @getter
    def llist(self):
        return list(List(self.l))

This is pretty cumbersome.

Doing something like slicing an array becomes

def slice(self, start, stop):
    self.store.l = list(List(self.store.l))[start:stop]

I tried using @property decorator to make this a bit more streamlined, but the components get messed up when using them for component values.

The best solution I've found so far is to make a proxy object that wraps the store and have a tonne of conversion functions.

stefanhoelzl commented 2 years ago

Thanks for reporting your Issue.

Unfortunately I cannot reproduce the issue. Here is the app.py I am running

from vue import VueComponent, VueStore, computed

class Store(VueStore):
    data = {
        'a': 123,
        'b': 456,
    }

class DataComponent(VueComponent):
    id: str
    template = '''
        <div
            :id="id">
            {{ data }}
        </div>
    '''
    @computed
    def data(self):
        return self.store.data[self.id]

DataComponent.register()  # in your example the DataComponent was not registered

class App(VueComponent):
    template = '''
        <div>
            <data-component
                v-for="id in ids"
                :key="id"
                :id="id"  # in your example the id prop was not set
            />
        </div>
    '''

    @computed
    def ids(self):
        print(f'self.store.data = {self.store.data}')
        return self.store.data.keys()

App("#app", store=Store())

with the vuepy.yml to enable vuex

scripts:
  vuex: true
adamlwgriffiths commented 2 years ago

I had moved onto other things for a while and have come back with a clean slate. Got the latest vue 2.x, etc. And now this issue isn't occuring. Most curious.