0

So I have a vue app I wrote on a single page .html being served by django made up of a a couple components. I am now trying to transfer this work into a real vue.js project using the Vue CLI and I figured it would be pretty trivial to move my components from the django .html page into single file vue components. Unfortunately pretty much every line within my single file component is throwing errors (despite my entire app and all its components working in the .html) and I'm having a real rough time figuring this out. It seems as though transitioning from a vue component to a single file component requires some work.

The current error I'm getting is this:

[Vue warn]: Error in render: "TypeError: undefined is not an object (evaluating 'this.milliseconds = parseInt(duration % 1000 / 100)')"

For reasons that aren't entirely clear to me, as soon as I moved to single file component using vue CLI - every line errored out until I added 'this' before every variable. I have very little understanding of why I would need to use 'this' in a filter method, but when I remove it I get:

[Vue warn]: Error in render: "ReferenceError: Can't find variable: milliseconds"

single file component:

<template>
  <div emptyDiv>
    <h3> Stages </h3>
    <table :style="tableStyle">
      <tbody>
        <template v-for="item in jobs">
          <tr>
            <td v-for="stage_execution in item.stage_execution" :title="stage_execution.exec_node.name" :key="stage_execution.stage.name">
              <b><a :href="item.mongo_link + stage_execution.stage.name + '.txt'" style="color:black">{{ stage_execution.stage.name }}</a></b>
              <br>
              {{ stage_execution.duration_millis | durationReadable  }}
              <br>
              {{ stage_execution.status.name }}
            </td>
          </tr>
        </template>
      </tbody>
    </table>
  </div>
</template>

<script>
import moment from 'moment';

export default {
  data() {
    return {
      jobs: []
    }
  },
  computed: {
    tableStyle() {
      return {
        'background-color': '#f9f9f9',
        'border-color': '#C0C0C0',
        'padding': '8px',
        'width': '100%',
      };
    },
    emptyDiv() {
      return {
        'display': 'contents',
      };
    },
  },
  methods: {
    calculateDuration: function(time_start, time_end) {
      this.theDuration = moment.duration(time_end.diff(time_start))
      if (this.theDuration.seconds() == 0) {
        this.cleanDuration = "N/A"
      }
      else {
        this.cleanDuration = this.theDuration.hours() + " hrs " + this.theDuration.minutes() + " min " + this.theDuration.seconds() + " sec"
      }
      return this.cleanDuration
    }
  },
  filters: {
    durationReadable: function(duration) {
      console.log(parseInt(duration%1000)/100)   //this successfully logs the correct duration
      this.milliseconds = parseInt((duration%1000)/100)
      this.seconds = parseInt((duration/1000)%60)
      this.minutes = parseInt((duration/(1000*60))%60)
      if (this.minutes < 10) {
        this.minutes = '0' + this.minutes
      }
      if (this.seconds < 10){
        this.seconds = '0' + this.seconds
      }
      return this.minutes + " m " + this.seconds + " s " + this.milliseconds + ' ms'
    }
  },
  created() {
    this.binariesEndpoint = 'test.com'
    fetch(this.binariesEndpoint)
    .then(response => response.json())
    .then(body => {
      this.cleanStartTime = moment(body[0].time_start)
      console.log(body[0].time_start)
      this.cleanEndTime = moment(body[0].time_end)
      this.cleanDuration = this.calculateDuration(this.cleanStartTime, this.cleanEndTime)
      this.job_execs.push({
        'name': body[0].job.name,
        'build_id': body[0].build_id,
        'env': body[0].job.env,
        'time_start': this.cleanStartTime.format('LLL'),
        'time_end': this.cleanEndTime.format('LLL'),
        'duration': this.cleanDuration,
      })
    console.log(body[0].job.name)
    })
    .catch( err => {
      console.log('Error Fetching:', this.binariesEndpoint, err);
      return { 'failure': this.binariesEndpoint, 'reason': err };
    })
  },
};
</script>

note: the log statement in durationReadable filter correctly logs the duration.

5
  • Honestly, you are better off running vue create (projectname), and copy-pasting this code into a component file that the CLI creates. You can't add a .html file as a Vue component, because vue component files are of type .vue. Vue renders it's own virtual dom, similar to react, so .html files are not compatible. Commented Jun 13, 2019 at 17:28
  • to be clear there is no .html in my vue project - i did exactly what you are saying Commented Jun 13, 2019 at 17:30
  • I copy and pasted one of my vue components (lifted from the .html) into a .vue file within the components directory in my vue project Commented Jun 13, 2019 at 17:31
  • I see the problem, will post answer Commented Jun 13, 2019 at 17:31
  • All of those variables need to be defined in the object data returns. Otherwise, Vue doesn't know what you're doing. Commented Jun 13, 2019 at 17:31

3 Answers 3

2

You cannot reference this in filters.

Filters should be pure functions and not be dependent on this.

Instead, move your durationReadable function to the methods section. There you can reference this.

Then amend your template to use the method instead of the filter:

{{ durationReadable(stage_execution.duration_millis) }}

I hope this helps.

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

3 Comments

I did not know that about filters. Do they not have the same access to the instance?
No, they need to be pure functions. There is an issue here: github.com/vuejs/vue/issues/5998
Ahh, so it's to differentiate from computed props. That makes more sense.
0

You shouldn't be using this within durationReadable. You just need to use let and const to declare your variables.

durationReadable (duration) {
    const milliseconds = parseInt((duration%1000)/100)
    const seconds = parseInt((duration/1000)%60)
    let minutes = parseInt((duration/(1000*60))%60)
    // ... etc.
}

3 Comments

if he's referencing a Vue data property it has to be referenced with this.
@LenJoseph That would be true if he were. But he isn't. As explained in the question the only reason he's included the this. is to make an error go away. That original error is caused because the let/const was left out. They are just local variables, not data.
I ended up using let for minutes, seconds, milliseconds and this solved the immediate problem.
0

Any time you reference this.someVariable, Vue expects to read this from predefined data property. Example,

data: () => {
   return {
    milliseconds: undefined
  }
}

Now this.milliseconds will be accessible.

8 Comments

Can I do milliseconds: None ? I know I can do that in python not sure if that works in js
@david, use undefined instead of None
You can set it to anything, as long as you reference it as the type you set it by originally.Would use undefined or null if you are setting it dynamically.
defining milliseconds in data as you did above is still giving me the same error
try initializing it with 0 instead of undefined
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.