Scott Casey
Scott Casey

Reputation: 85

vue.js reactive class instance

I'm working on a worldbuilding app as my first Vue.js project. As part of this, I have several ES6 classes that describe what certain data types can look like (e.g. An entire Project, a Planet, a Star, etc). I'm trying to build a sidebar that will be used to see the nested tree structure of a project like so:

Project
|- System
   |- Star
   |- Planet
      |- Planet

The tree structure works great when I load in an existing data file or edit the name of an existing entry. The problem comes when I try to add additional items to the tree. The data and source file are updated with the new entity, but the render does not update to reflect the additional data.

My most recent attempt looks like this (addItem() is where the item gets added to the project):

//ProjectEntry.vue
<script setup>
import { nextTick, ref, computed } from 'vue';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

import { Button, ToolButton } from '@/components';
import { Project,Planet, System, Star, Galaxy } from '@/stellarClasses';
import { useDataStore } from '@/stores';

const SOs = {Planet, System, Star, Galaxy};

const name = ref(null);
const loading = ref(false);
const edit = ref(false);
const {
    data,
    type,
    level,
    content,
    nameProp,
    project
  } = defineProps({
    data:Object,
    level:[Number,String],
    content:String,
    type:String,
    nameProp:String,
    project:Object
  });
  console.log('data',data);
const children = ref(data.children || data.value?.children);

const contained = computed(() => Object.entries(children.value));
const dataStore = useDataStore();
console.log('type',type);
const possibleAdds = {
  Project: ['System','Star','Planet'],
  System:['Star','Planet'],
  Galaxyy:['System','Star','Planet'],
  Star:[],
  Planet:['Planet']
  
};
const icons = {
  System:'solar-system',
  Star:'sun',
  Galaxy:'galaxy',
  Planet:'earth-oceania'
};
const nextLevel = +level > 5 ?
  undefined :
  +level;
const toggleEdit = () => {
  edit.value = !edit.value
  nextTick(()=>{
    name.value?.focus();
  });
};
const submitValue = async (event) => {
  if(!name.value) return;
  if(name.value.value !== data[nameProp]){
    data[nameProp] = name.value.value;
    project.save();
    // debugger;
    // await dataStore.updateWorld(
    //   data.id,
    //   data
    // );
  }
  toggleEdit();
};

/**
 * Adds a new item of the indicated type to the children of the parent.
 */
const addItem = async (type) => {
  const mySO = new SOs[type]({name:`New ${type}`,type});
  // debugger;
  data.value.__v_raw.addChild(mySO);
  await data.value.__v_raw.save();
};

</script>

<template>
<li :class="`project-entry ${type}${dataStore.loading ? ' loading' : ''}`">
  <div>
    <span v-if="icons[type]" class="fa-li">
      <FontAwesomeIcon :icon="`fa-solid fa-${icons[type]}`" />
    </span>
    <component :is="level ? `h${level}` : 'span'" v-if="!edit" @dblclick="toggleEdit">
      {{content}}
    </component>
    <input ref="name" v-else @focus="$event.target.select()" type="text" @keyup.enter="submitValue" @blur="submitValue" :value="content">
  </div>
  <div class="insert-container">
    <Button v-for="typ in possibleAdds[type]" :key="`${data.id}-${typ}`" @click="addItem(typ)" :title="`Add ${typ}`">
      <FontAwesomeIcon :icon="`fa-solid fa-${icons[typ]}`" />
    </Button>
  </div>
  <ul v-if="contained.length" class="fa-ul">
    <ProjectEntry v-for="[id,obj] in contained" :data="obj" :key="id" :level="nextLevel" :project="project" :type="obj.type" name-prop="name" :content="obj.name || `new ${obj.type}`" />
  </ul>
</li>
</template>

<style lang="scss">
// Styling omitted for brevity
</style>

The above is called from this line of the editor's view:

<ProjectEntry v-for="obj in dataStore.worlds.__v_raw" :key="obj.id || obj.name" level="4" type="Project" :data="obj" :project="obj" name-prop="name" :content="obj.name || 'new project'"></ProjectEntry>

The children of a given entity are stored in an object on the class called children indexed by the child's id. Here's one of my test datafiles to demonstrate. Note that some info has been reduced down to just it's ID instead of the full object as part of the serialization of the class stack:

{
  "type": "Project",
  "name": "Torrigoyd",
  "id": "9b33f1c0-6499-11ed-b4f9-c95a1a859b28",
  "creation": 1668484641500,
  "children": {
    "fced8ea0-6541-11ed-b960-3dcc95839585": {
      "parent": "9b33f1c0-6499-11ed-b4f9-c95a1a859b28",
      "type": "Star",
      "name": "Tor",
      "id": "fced8ea0-6541-11ed-b960-3dcc95839585",
      "creation": 1668556960906,
      "children": {},
      "mass": 1,
      "age": 4.5
    },
    "e33ef3b0-65d0-11ed-84cd-07d9a27628fb": {
      "parent": "9b33f1c0-6499-11ed-b4f9-c95a1a859b28",
      "type": "Planet",
      "name": "Torrigoyd",
      "id": "e33ef3b0-65d0-11ed-84cd-07d9a27628fb",
      "creation": 1668618335851,
      "children": {}
    },
    "252eb930-65d2-11ed-9a40-2330c26a73ef": {
      "parent": "9b33f1c0-6499-11ed-b4f9-c95a1a859b28",
      "type": "Star",
      "name": "New Star",
      "id": "252eb930-65d2-11ed-9a40-2330c26a73ef",
      "creation": 1668618875971,
      "children": {},
      "mass": 1,
      "age": 4.5
    }
  }
}

So, how can I get the file tree to update when an item is added to the children object of the project, or one of it's descendants? Do I need to rethink my data structure (I'm sure it could be better), or do I just need to reference it a different way in the vue component?


EDIT: Realized it might be helpful to see the class methods involved:

// The addChild method
addChild(...children){
  children.forEach(child => {
    child.parent = this;
    this.children[child.id] = child;
  });
}

// The save method
save() {
  const dataStore = useDataStore();
  const newWorlds = dataStore.worlds.__v_raw;
  dataStore.$patch({
    worlds:newWorlds
  });
  this.#db.data = this.toJSON();
  return this.#db.write();
}

Upvotes: 2

Views: 1072

Answers (1)

Scott Casey
Scott Casey

Reputation: 85

Found what I was doing wrong. For some reason (my past self only knows why), I was storing the class instances as refs in the array of worlds. This of course made them NOT instances directly of the class. Removed that assignment to ref, and switched to the array children storage as suggested. All is good now.

Upvotes: 1

Related Questions