Child text node in Vue component not rendering as expected with render function

I'm currently working on creating an editable component using Vue 2. I am trying to utilize the contenteditable attribute within any tag, as a substitute for a regular input field. My goal is to implement a placeholder feature that displays a default value when the user does not provide any content. However, I am encountering difficulty getting this functionality to work.

I have set up the component to monitor its current value and assign true to data.isEmpty when there is no user-generated content present. The intention is for the component to display the placeholder value in such cases, but it is currently appearing blank.

Upon inspecting the result of the render method with a console.log, it indicates that the placeholder child node has been instantiated correctly. Nevertheless, the placeholder content fails to appear in the final HTML output for some unknown reason.

For reference, here is a link to the JSFiddle containing the code: https://jsfiddle.net/dy27fa8t/

Additionally, here is an embedded code snippet:

Vue.component('editable-content', {
  props: {
    initial: {
      type: String
    },
    placeholder: {
      type: String,
      required: false
    }
  },
  data() {
    return {
      value: this.initial,
      isEmpty: this.initial === ''
    }
  },
  render: function(createElement) {
    const self = this
    return createElement(
      'div', {
        attrs: {
          contenteditable: true
        },
        on: {
          input: function(event) {
            self.value = event.target.innerHTML
            self.$emit('edited', event.target.value)
          }
        }
      },
      this.isEmpty ? this.placeholder : this.value
    )
  },
  watch: {
    value(to, from) {
      this.isEmpty = to === ''
    }
  }
})

new Vue({
  el: '#app',
  components: [
    'editable-content'
  ]
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script>

<div id="app">
  <editable-content initial="Initial value" placeholder="Placeholder" />
</div>

Answer №1

When working with a contenteditable element, the typical approach may not yield the desired results. The key is to directly manipulate the innerHTML property with a placeholder when the content is empty. You'll need to handle certain events like keydown and keyup to ensure the placeholder behaves as expected under different scenarios.

This solution has been modified to be v-model compatible and includes some styling enhancements for the placeholder.

Vue.component('editable-content', {
  props: {
    value: {
      type: String
    },
    placeholder: {
      type: String,
      required: false
    }
  },
  data() {
    return {
      isEmpty: this.value === ''
    };
  },
  methods: {
    setEmpty() {
      this.$el.innerHTML = `<div contenteditable="false" class="placeholder">${this.placeholder}</div>`;
      this.isEmpty = true;
    },
    clearEmpty() {
      this.$el.innerHTML = '';
      this.isEmpty = false;
    }
  },
  mounted() {
    if (this.$el.innerHTML === '') {
      this.setEmpty();
    }
  },
  watch: {
    value(newValue) {
      if (newValue === '') {
        this.setEmpty();
      }
    }
  },
  render: function(createElement) {
    return createElement(
      'div', {
        attrs: {
          contenteditable: true
        },
        on: {
          keydown: () => {
            if (this.isEmpty) {
              this.clearEmpty();
            }
          },
          input: (event) => {
            this.$emit('input', event.target.textContent);
          },
          keyup: () => {
            if (this.$el.innerHTML === '') {
              this.setEmpty();
            }
          }
        }
      },
      this.value
    )
  }
});

new Vue({
  el: '#app',
  data: {
    startingBlank: '',
    editedValue: 'initial value'
  },
  components: [
    'editable-content'
  ]
})
.placeholder {
  color: rgba(0,0,0, 0.5);
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script>

<div id="app">
  <editable-content v-model="startingBlank" placeholder="Placeholder"></editable-content>
  <editable-content v-model="editedValue" placeholder="Placeholder"></editable-content>
</div>

Answer №2

After careful consideration, I decided to opt for a combination of JavaScript and CSS solution which involved the use of the :empty pseudo-class. Trying to come up with a Vue-only workaround seemed too complex, so this compromise felt like the right choice. I no longer see the need to keep track of the value at all.

It's worth mentioning that with single-file components, I can take advantage of scoped CSS, making the CSS an integral part of the component's core functionality.

Vue.component('editable-content', {
  props: {
    initial: {
      type: String
    },
    placeholder: {
      type: String,
      required: false
    }
  },
  data() {
    return {
      value: this.initial
    }
  },
  render: function(createElement) {
    const self = this
    return createElement(
      'div', {
        attrs: {
          contenteditable: true,
          'data-placeholder': this.placeholder
        },
        on: {
          input: function(event) {
            self.$emit('edited', event.target.value)
          }
        }
      },
      this.value
    )
  }
})

new Vue({
  el: '#app',
  components: [
    'editable-content'
  ]
})
[data-placeholder]:empty::after {
  content: attr(data-placeholder);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.min.js"></script>

<div id="app">
  <editable-content initial="Initial value" placeholder="Placeholder" />
</div>

Answer №3

When the initial prop is not passed into the component, it will default to undefined. To handle this scenario, you can check if it is undefined before accessing its value:

data() {
    return {
      value: this.initial,
      isEmpty: typeof this.initial === 'undefined'
    }
  },

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Transferring an Applescript list to ExtendScript in Javascript as an array for use in InDesign

Situation Background I have a large number of Applescripts (AS) that designers rely on in InDesign to streamline production workflows. These AS scripts handle a lot of OS interactions that JavaScript cannot replicate, so transitioning away from AS is not ...

Next.js encountered a Runtime Error: Trying to access properties of undefined (specifically 'status') and failing

For those interested in viewing the code, it can be found here During the creation of a portfolio website using Next.js, I encountered an error. I am utilizing the latest version of Next.js (next 13.4.16) and incorporating the app router into my project. ...

Guide on Adding a Map to a List in JavaScript

Currently, I am trying to extract data from a form and add it as a map to my list. However, an error message is displayed: Cannot read property 'url' of undefined express = require("express"); app = express(); var bodyParser = require("body- ...

Deselect all the checkboxes in the treeview widget

I am using a v-treeview component with Vuetify 2.6.7. <v-treeview class="location-tree" v-model="location_tree" ref="location_tree" :search="location_search" ...

The modal will appear only if it is not initially hidden

Recently, I was tasked with incorporating a modal into a WooCommerce product page that appears when the 'add to cart' button is clicked. To initiate this process, I decided to adapt and use an example modal from w3schools. After tweaking the code ...

Tips on enhancing Ajax requests with jQuery and deferred objects

Currently, I have implemented four or more ajax rest calls within the $(document).ready(function(){} function. To handle this situation, I resorted to adding .done() to the largest call as a temporary solution. While it is "working," I am looking to enha ...

The ajax request is not rendering the data correctly

Whenever I attempt to invoke a method using ajax, the data does not display correctly. However, if I directly call the method on the page, it shows up correctly. Index.php <input class="common_selector brand custom-control-input" type="checkbox" ...

When attempting to test an ExpressJS API, an error is encountered stating "Cannot read property 'address' of undefined."

After successfully starting the app, I am able to call my API with the following commands: "dev": "env-cmd ./.env nodemon ./src/index.js", "test": "env-cmd ./test.env jest --watch" In server.js: // Loading dependencies const app = express(); ... // Load ...

An issue arose while pre-rendering the page within Next.js

Recently, I developed an API using next JS in the pages/api directory and utilized it on a page located in the pages folder. During the localhost testing phase, the API functions smoothly without any issues. However, when deploying to Vercel, I encountere ...

Cookies with HttpOnly are not included in the server request

Having developed an API in node express running on port :8000, I am consuming these APIs via a simple CRA on port :3000. The registration and login process includes setting an httpOnly cookie. Additionally, I have implemented middleware to verify each endp ...

When utilizing jQuery, I implemented this code but it does not display anything. I am unsure of the error within the code

When using jQuery, I implemented the following code snippet but it doesn't display anything. What could be causing this issue? // $.ajax({ // URL:"https://dog.ceo/api/breeds/image/random", // method:"GET", // ...

Utilizing VueJS to incorporate data retrieved from query parameters

I am currently developing a compact VueJS application that operates solely on the client-side. The application utilizes VueRouter to update the URL when users input data into the app. This allows users to refresh the page and reload their data by using the ...

Tips for positioning a picklist field dropdown on top of a lightning card in Lightning Web Components

Attempting to resolve an issue with CSS, I have tried adding the following code: .table-responsive, .dataTables_scrollBody { overflow: visible !important; } However, the solution did not work as expected. Interestingly, when applying a dropdown pickli ...

Issue with VueJS component prop displaying only the last JSON entry when it is reused

My aim is to reuse a vue component with a prop that takes data from a json object. However, the issue I'm facing is that all instances of the component are displaying the same data instead of data based on the index of the json object. This is how my ...

What are the steps to implement background synchronization in place of making fetch requests upon UI changes?

Consider this scenario: A straightforward web application with a comments feature. In my view, when a user submits a comment, the following steps would typically unfold: Show UI loader; Update the front-end state; Send a fetch request to the API to post ...

Validating object keys

I am dealing with an array of objects and I need to find a way to pass multiple keys in the function checkArray to validate these keys within each object. var test = [ { // Object details here... }, { // Another object details here... } ...

Generate a unique class for each img element randomly

Is there a way to assign each image a unique random class instead of giving all the images the same random class? Any help would be appreciated. $(document.body).ready(function () { bgImageTotal = 5; randomNumber = Math.round(Math.random() * (b ...

Resolve feature for UI routes fails to function upon refreshing the page

My app utilizes UI Route for view routing. When accessing /berlinerliste/, a function is triggered to display an array of objects. If one of these objects is clicked, the view changes to /berlinerliste/{id}/ and shows the details of that specific object. ...

Utilize Autocomplete component from Material UI to sift through distinct option values

Looking to implement unique options in the dropdown menu of an Autocomplete component from Material UI based on a specific property within a list of objects. The current issue is that duplicate values are appearing in the dropdown, like ['Color1&apos ...

Go back in time with History Back and then effortlessly glide to the top

In my Web app, I have implemented the following code to navigate back a page: window.history.go(-1); While this function works effectively, it redirects users to the same vertical position they were on the previous page. Is there a way to automatically sc ...