ParseJS and VueJS

Even though I don’t know VueJS, this sounds similar to SwiftUI. Binding the view directly to the model, in your case Monster seems like it can potentially run into the issues I mentioned since model is a reference type. In SwiftUI and MVVM, there is typically a intermediary between the Model and View. It’s suggested to have the Model as value type, the View as a value type, and the ViewModel (intermediary) as reference type. It seems like you may be using the Monster model as a ViewModel when comparing to MVVM, but if you are know how to handle the problems I mentioned then you should be fine.

The main concern I was thinking is whether this could potentially override any core properties, with unexpected impacts on other core functions.

I see, I don’t think I provided anything related to these concerns

Thanks @kulanu for looking over this.

You’re correct - my code sends data even when it’s not required. I added a line on the save function that if data isn’t changed, don’t set.

And secondly, the reason I set it directly on this was because I wanted to access monster.name. I might be incorrect, but I think VueJS’ reactivity doesn’t work as expected on “deeper” properties.

Your comments are very much appreciated. I will keep an eye out for any problems related.

I’m not overly familiar with SwiftUI yet, but I would expect that you are correct in saying it functions similarly to VueJS.

Thanks again @cbaker6 :+1:

Actually Vue’s reactivity WILL recognize deeply nested properties added to root object when that root object (eg monster.data) is set as reactive on a vue instance, FYI. In any case, it’s a good solution, I borrowed your _finishFetch and added this to my wrapper class. Shalom.

1 Like

Also FYI, this equality check will work fine for primitives but not for objects or arrays stored on those kyes as they will never be equal

1 Like

I should note on the master version of the JS SDK, you can register subclasses without needing to definitively state “Class Name”. This can allow you to easily make one core helper for all your classes, such as:

class ParseVueObject extends Parse.Object {
  constructor(className) {
    // constructor className is only available on master
    super(className);
    this.loadData();
  }
  loadData() {
    const internal = ['id', 'className', 'createdAt', 'updatedAt', 'ACL'];
    const data = this.attributes;
    for (const key in data) {
      if (internal.includes(key)) {
        continue;
      }
      this[key] = data[key];
    }
  }
  async save() {
    const internal = ['id', 'className', 'createdAt', 'updatedAt', 'ACL'];
    for (const key in this) {
      if (internal.includes(key)) {
        continue;
      }
      if (JSON.stringify(this[key]) !== JSON.stringify(this.get(key))) {
        this.set(key, this[key]);
      }
    }
    await super.save();
  }
  _finishFetch(serverData) {
    super._finishFetch(serverData);
    this.loadData();
  }
}
const classNames = ['ClassOne', 'ClassTwo', 'ClassThree'];
for (const className of classNames) {
  Parse.Object.registerSubclass(className, ParseVueObject);
}

Thanks to this PR.

@kulanu for nested keys:

const internal = ["objectId", "id", "className", "createdAt", "updatedAt", "_localId", "_objCount"];
class ClassName extends Parse.Object {
  constructor() {
    super("ClassName");
    this.loadData(this);
  }
  loadData(object) {
    if (!object.toJSON) {
      return;
    }
    const data = object.toJSON();
    for (const key in data) {
      if (internal.includes(key)) {
        continue;
      }
      const value = data[key];
      object[key] = value;
      this.loadData(value);
    }
  }
  async save() {
    const saveNested = object => {
      for (const key in object) {
        if (internal.includes(key)) {
          continue;
        }
        let value = this[key];
        if (Array.isArray(value)) {
          const newArray = [];
          for (let i = 0; i < value.length; i++) {
            let nestedValue = value[i];
            if (nestedValue && nestedValue.__type) {
              nestedValue = Parse._decode(null, nestedValue);
            }
            newArray.push(nestedValue);
          }
          value = newArray;
        }
        if (value && value.__type) {
          const obj = Parse._decode(null, this[key]);
          saveNested(obj);
        } else if (JSON.stringify(object.get(key)) !== JSON.stringify(value)) {
          object.set(key, value);
        }
      }
    };
    saveNested(this);
    await super.save();
  }
  _finishFetch(serverData) {
    super._finishFetch(serverData);
    this.loadData(this);
  }
}

I haven’t tested this yet so use at your own risk. You’ll need to use .include in your queries for this to work.

This might help as to how I use the subclass with VueJS to achieve data binding without needing to override any VueJS config.

It does help. I have extracted a few elements from your ideas.

Just one thought - when it comes to extending the user object, you might want to make sessionToken an internal key as well - otherwise it gets set and subsequently becomes a dirty property on the user object which gets saved if you save your user.

@dblythy Following up on this thread from the spring.

If one wanted to tap into the _finishFetch method globally (across all classes) and without having to make a new Parse.Object.extend for every class, what is the right way to do this? Calling super is not allowed in non-class definition methods so I can’t a way to do it.

Eg: the following cannot work:
Parse.Object.prototype._finishFetch = function (serverData) { ... }

Object.defineProperty(Parse.Object.prototype, '_finishFetch', { ... })

I’m stuck on this one … I simply don’t want to have to create a new class for all 100+ classes I have to work with just to get access to this hook.

Of course the long term and better solution would be if Parse.Object (and Parse.Query) offered hooks that you could tap into, as it down with #initialize - eg beforeRead, afterRead, beforeSave, afterSave

Thoughts?

Hi @kulanu,

I wasn’t quite happy with my previous solution to this and felt like there could be a cleaner way to solve this issue.

I have created a new approach and a PR which should in theory be able to automatically traverse multiple levels of pointers, allow dot notation for every class, etc.

Let me know if you have any feedback.

Hi! I’m new here, so please forgive me if my answer is off in time or context or both.

I had the same issue, and ended up doing what I believe you addressed initially this way:

  const props = defineProps<{
      task: any
    }>()

  const done = computed({
    get: () => props.task.get('done'),
    set: val => { props.task.set('done',val) }
  })

and in html:
<input type="checkbox" v-model="done" />

This seems to work fine, but obviously, there will be a lot of code when you have to do this for many fields in many places

Please do let me know if you think this is an ok way of solving this, or if I would be better off look into the suggestion above instead.

This would work fine, but you would have to write it for every single column.

I tend to use:

import { Parse } from 'parse';
Parse.initialize(...);
Parse.serverURL = ...
const _internalFields = Object.freeze([
  'objectId',
  'id',
  'className',
  'attributes',
  'createdAt',
  'updatedAt',
  'then',
]);
const proxyHandler = {
  get(target, key, receiver) {
    const value = target[key];
    const reflector = Reflect.get(target, key, receiver);
    if (
      typeof value === 'function' ||
      key.toString().charAt(0) === '_' ||
      _internalFields.includes(key.toString())
    ) {
      return reflector
    }
    return receiver.get(key) ?? reflector;
  },

  set(target, key, value, receiver) {
    const current = target[key];
    if (
      typeof current !== 'function' &&
      !_internalFields.includes(key.toString()) &&
      key.toString().charAt(0) !== '_'
    ) {
      receiver.set(key, value);
    }
    return Reflect.set(target, key, value, receiver);
  },
};
class ParseVueObject extends Parse.Object {
  constructor(...args) {
    super(...args);
    return new Proxy(this, proxyHandler);
  }
}
const subclasses = ["TestObject"]
for (const sub of subclasses) {
  Parse.Object.registerSubclass(sub, ParseVueObject);
}
app.config.globalProperties.$Parse = Parse

This way you can get and set TestObject properties via dot notation without needing computed.

Not sure what the equivalent typescript would be.

Thank you! Looks great. Will try it out.

I use Vuejs too (mostly with Nuxtjs) so this PR looks very interesting (not sure how I missed it :slight_smile: ). I’ve taken to not using the SDK because of all that’s mentioned here, but this could seriously change that, if it gets merged. I suspect this will help most modern frameworks!

1 Like

@woutercouvaras Would you want to test out the PR and give some feedback? The reason it hasn’t been merged yet becase we want to know about potential side effects before merging.

Yes, sure. I’m willing to give it a test. Are there any expectations around:

  • A turnaround time to give feedback?
  • The type of feedback you’re looking for?
  • The format of the feedback?
  • Specific things you’d like tested?

As much feedback as you can provide is helpful. Maybe @dblythy as the author has any specific things in mind that would be good to test.

1 Like

I have created a sample project for examples for how to use this feature:

I’m pretty confident with this feature as I’ve been using it in production for a while now, but any additional feedback / testing is welcome as the feature can change how every Parse Object method, property etc behaves.

Thanks @dblythy, I’ll play around with this and let you know how it goes.