1

This is my first project with Vue and I'm a couple of months into it. I have test question content in xml. In some cases the xml contains html. I'm grabbing the xml via ajax and using that content in templates which are built dynamically based on the needs of a particular test question instance. I would like to have reactive inputs in which the user will enter answers and then submit for evaluation. The html and number of inputs in a question varies widely in the data. But an example might look something like this

<item id="q0" type="question">
    <text>Complete the data in the table below.
        <table>
            <tr>
                <th>row 1, col 1</th>
                <th>row 1, col 2</th>
            </tr>
            <tr>
                <td>row 2, col 1</td>
                <td>
                    <input id="input0"/>
                </td>
            </tr>
        </table>
    </text>
<item>

The issue is that I don't know how to create a reactive input and render the surrounding html dynamically.

I tried this type of thing

https://jsfiddle.net/4u5tnw90/9/

but if you add v-html="item" to div#table it breaks. I assume because the html pieces are not legal html. I'm thinking that I'm going to have to parse the text element and create a VNode with createElement for each html element contained within and then render it. But I'm hoping that someone can save me from that fate. Is there another way?

2 Answers 2

0

Why are you having html code in your data. Your html code should be in your template . I assume you need to render a table for a list of questions. In that case, your data should hold the array of questions

HTML:

<div id="app">
   <div id="table" v-for="(item,idx) in items">
    <span>{{item}}</span>
        <table>
            <tr>
                <th>row 1, col 1</th>
                <th>row 1, col 2</th>
            </tr>
            <tr>
                <td>row 2, col 1</td>
                <td>
                    <custom-item1 v-if="idx < items.length-1">
                    </custom-item1>
                </td>
            </tr>
        </table>


      </div>


 </div>

Vue:

new Vue({
  el: '#app',
  data: {
    items: ['question1','question2','question3']
  },
  components: {
    CustomItem1: {
        template: '<div><input v-model="text"/><br />{{text}}</div>',
        data: function(){
          return {
            text: ''
          }
        }
    }
  }
})

Checkout my fiddle

Sign up to request clarification or add additional context in comments.

1 Comment

By 'data' I mean source content. It's xml content with html embedded in the text and it's what I inherited. I don't have any control over that.
0

Here's what I ended up doing:

I created an Input component that would store user input to the vuex backend as it was entered.

Input.vue

<template>
    <div class="um-group-input">
        <input :id="groupId" v-model="text" @input="storeAnswer"/>
    </div>
</template>

<script>
    export default {
        name: 'Input',
        props: ['groupId', 'itemId'],
        components: {RejoinderDetail},
        data: function() {
            return {
            text: ""
            };
        },
        methods:{
            storeAnswer() {
            this.$store.commit('storeUserAnswer', {
                itemId: this.itemId,
                groupId: this.groupId,
                value: this.text
            })
            }
        }
    }
</script>

<style>
    .um-group-input{
    display: inline-block
    }
</style>

I created a QuestionText component that used the xmldom package to parse the xml content and then iterate on it, creating text nodes, html elements, and inserting the Input component in place of the html input element.

notes:

  • createElement is aliased as h below
  • _v is an internal Vue method that returns a plain text VNode. Got that here

QuestionText.vue

<script>
    import Input from './inputs/Input'

    let xmldom = require('xmldom')

    let DOMParser = xmldom.DOMParser
    let xmlParser = new DOMParser()

    export default {
        props: {
            itemId: {
            type: String,
            required: true
            }
        },
        components: { Input, SelectBox },
        render: function(h) {
            let xmlDoc = this.parseQText()
            let childNodesArray = this.createChildNodes(xmlDoc, [], h)

            return h('div', { class: { 'um-question-text': true } }, childNodesArray)
        },
        computed: {
            item() {
            return this.$store.getters.getItem(this.itemId)
            },
            questionText() {
            return this.$store.getters.getContent(this.item.text[0])
            }
        },
        methods: {
            parseQText() {
            return xmlParser.parseFromString('<div>'+ this.questionText+'</div>')
            },
            nodeType(val) {
            return { 1: 'element', 3: 'text' }[val]
            },
            createChildNodes(node, childNodesArray, h) {
            for (let i = 0; i < node.childNodes.length; i++) {
                let n = node.childNodes[i]
                let nodeType = this.nodeType(n.nodeType)
                if (nodeType === 'text') {
                    /* add text  with no tags around it */
                childNodesArray.push(this._v(n.nodeValue))
                } else if (n.nodeName === 'input') {
                    /* add input component */
                let groupId = this.$store.getters.getGroupIdFromInputName(this.itemId, n.getAttribute('name'))
                let options = {
                    props: { itemId: this.itemId, groupId: groupId }
                }
                childNodesArray.push(h('Input', options))
                } else {
                    /* handle other, possible nested html */
                childNodesArray.push(h(n.nodeName, this.createChildNodes(n, [], h)))
                }
            }

            return childNodesArray
            }
        }
    }
</script>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.