Skip to content

Vue

All parts of the application can be written as vue applications. However, this topic is very large. We will only briefly outline here how to load vue applications as a component.

The property this.element has another method named vue(). With vue() you can load a vue application into the component element.

What are we learning here?

  • Loading a vue application with this.element.vue()

You can pass props to the vue application with the second argument vue(Vue,Props). One prop will always be passed with the name component. This is a component instance. Inside of vue you can with this.component always access all component features.

The basic vue file should always look like this:

<template>
  <!-- Your template here -->
</template>
<script>
  export default {
    props: {
      component: { type: Object, default: null },
    },
  };
</script>
<style lang="postcss" scoped>
  /* Your styles here */
</style>

We will now rebuild our "Basic Widget with Stage" from the begining as a vue application. Here is the code first. We explain the individual parts below.

The vue application in one file:

src/demo/apps/widget-basics-vue/content.vue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<template class="widget">
  <h2>{{ myHeader }}</h2>
  <div class="mt-2 card">
    <div class="card-header">Loading Components</div>
    <div class="card-body"><div ref="tools" class="mt-2"></div></div>
  </div>
  <div class="mt-2 card">
    <div class="card-header">Pass Options</div>
    <div class="card-body">
      Argument 1: {{ myWidgetArg1 }}<br />
      Argument 2: {{ myWidgetArg2 }}
    </div>
  </div>
  <div class="mt-2 card">
    <div class="card-header">
      Adding Content
      <button class="btn btn-xs btn-success" @click="myParagraphs++">
        <i class="fas fa-plus"></i>
      </button>
      <button class="btn btn-xs btn-danger" @click="myParagraphs--">
        <i class="fas fa-minus"></i>
      </button>
    </div>
    <div class="card-body">
        <p v-for="n in myParagraphs" :key="n">{{ myParagraphText }}</p>
    </div>
  </div>
  <div class="arrow arrow-left">
    <i class="fas fa-chevron-circle-left"></i>
  </div>
  <div class="arrow arrow-right">
    <i class="fas fa-chevron-circle-right"></i>
  </div>
  <div class="arrow arrow-up"><i class="fas fa-chevron-circle-up"></i></div>
  <div class="arrow arrow-down">
    <i class="fas fa-chevron-circle-down"></i>
  </div>
</template>
<script>
import $ from "jquery";
import LoadingComponents from "../../widgets/loading-components";
export default {
  props: {
    component: { type: Object, default: null }
  },
  data: function () {
    return {
      myHeader: "Vue Widget Basics",
      myWidgetArg1: "Default 1",
      myWidgetArg2: "Default 2",
      myParagraphs: 0,
      myParagraphText: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr"
    };
  },
  watch: {
    myParagraphs(newValue) {
      if (newValue < 0) this.myParagraphs = 0;
    }
  },
  mounted: function () {
    $(this.$el.parentElement).css({
      padding: "20px",
      position: "relative",
    });
    this.component.registerStage(this.$refs.tools).load(LoadingComponents);
  }
};
</script>
<style lang="postcss" scoped>
.arrow {
  opacity: 0.5;
  position: absolute;
  color: red;
}
.arrow-left {
  top: 50%;
  transform: translate(0, -100%);
  left: 0;
}
.arrow-right {
  top: 50%;
  transform: translate(0, -100%);
  right: 0;
}
.arrow-up {
  left: 50%;
  transform: translate(-50%, 0);
  top: 0;
}
.arrow-down {
  left: 50%;
  transform: translate(-50%, 0);
  bottom: 0;
}
</style>

Now we would need a wigdet or canvas in which we load the vue component and use this in an application context. The widget would consist of only one method with only one line to load the vue application. This would look like:

class extends Widget {
  async start() {
    this.element.vue(WidgetBasicsVue, {}, { myWidgetArg1: "Value 1" });
  }
}

Or you can use an inline implementation of a widget to load the widget without a separate widget class file:

await this.stage.load(
  class extends Widget {
    async start() {
      this.element.vue(WidgetBasicsVue, {}, { myWidgetArg1: "Value 1" });
    }
  }
);

But for a widget that loads only a vue application and have no other logic there are two helper components: VueWidget and VueCanvas. In addition, these components expose some useful properties to the outside (vue and vueApp - see below) and forward certain functions to the vue instance (all data hadling methods like hasChanges, commitChanges, saveData - will be discussed in a separate chapter).

In summary, one can say: use these components whenever possible.

You can pass the following options to load the vue application:

  • vue: The vue application class.
  • props: Props to use when creating the vue application.

Let's use this helper component for our case. Here is the application that will load the vue application without needing an additional widget class file:

src/demo/apps/widget-basics-vue/index.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { Application, VueWidget } from "@codecoupler/cc-ui";
import WidgetBasicsVue from "../../widgets/demo/widget-basics.vue";
export default class extends Application {
  static defaults = {
    "@codecoupler": {
      panel: {
      panelSize: "640 400",
      headerTitle: "Test Vue Features"
      }
    }
  };
  async start() {
    await this.stage.load(VueWidget, {
      vue: WidgetBasicsVue,
      options: { myWidgetArg1: "Value 1" }
    });
  }
}

We load the new application in our system.js file (just by replacing the previous one):

src/system.js

1
2
3
4
5
6
7
import { Component } from "@codecoupler/cc-ui";
import TestApp from "./demo/apps/widget-basics-vue";
export default class extends Component {
  async boot() {
    await this.stage.load(TestApp);
  }
}

If you have loaded a vue application by yourself via this.element.vue(), you can access the vue instance with this.element.$vue and the vue application with this.element.$vueApp.

If you have used the VueWidget or VueCanvas component to load a vue application you can access them via componentInstance.vue and componentInstance.vueApp.

We will now compare the start() method of our origin widget with the mounted() method of the vue application.

Inject Template with Variables:

Widget Vue
let tpl = template({
  myHeader: this.myHeader,
  Argument1: this.options.myWidgetArg1,
  Argument2: this.options.myWidgetArg2
});
this.element.html(tpl);

This is of course not necessary for vue applications as the template is already defined in this file.

Passed options will overwrite data properties if they exist. As you can see here we pass in our application a new value for myWidgetArg1:
this.element.vue(
  WidgetBasicsVue,
  {},
  { myWidgetArg1: "Value 1" }
);
The default values are defined of course in the vue data property:
data: function () {
  return {
    myWidgetArg1: "Default 1",
    myWidgetArg2: "Default 2"
  };
},
Passed options that are not define in vue data you can access with this.component.options.

Set a class name to the widget element:

Widget Vue
this.element.addClass(styles.widget);
Used CSS:
:local(.widget) {
  padding: 20px;
  position: relative;

  & .arrow {
    ...
  }
  & .arrow-left {
    ...
  }
  & .arrow-right {
    ...
  }
  ...
}
In the vue file all classnames are scoped already because of <style scoped>. Therefore we do not need a local parent widget class and many child classes. We use the child class names in the vue style section directly:
.arrow {
  ...
}
.arrow-left {
  ...
}
.arrow-right {
  ...
}
...

For styles that have to be attached to the parent element we have two options:

Write a CSS file with only the styles for the parent element:
:local(.widget) {
  padding: 20px;
  position: relative;
}
And set the class with:
import styles from "./some-filename.module.css";
$(this.$el.parentElement).addClass(styles.widget);
Or we set it directly with:
$(this.$el.parentElement).css({
  padding: "20px",
  position: "relative"
});

Add handler for adding/removing text:

Widget Vue
let contentElement =
  this.element.find("[data-role=content]");
let addText =
  "Lorem ipsum dolor sit amet, (...)";
this.element
  .find("[data-role=text-plus]")
  .on("click", () => {
    contentElement
    .append(`<p>${addText}</p>`);
  }
);
this
  .element
  .find("[data-role=text-minus]")
  .on("click", () => {
    contentElement.find("p")
    .last()
    .remove();
  }
);
This will be done of cource with reactive components of vue.
<div class="card-header">
  Adding Content
  <button
    class="btn btn-xs btn-success"
    @click="myParagraphs++">
    <i class="fas fa-plus"></i>
  </button>
  <button
    class="btn btn-xs btn-danger"
    @click="myParagraphs--">
    <i class="fas fa-minus"></i>
  </button>
</div>
<div class="card-body">
    <p
      v-for="n in myParagraphs"
      :key="n">
      {{ myParagraphText }}
    </p>
</div>
data: function () {
  return {
    myParagraphs: 0,
    myParagraphText: "Lorem ipsum (...))"
  };
},
watch: {
  myParagraphs(newValue) {
    if (newValue < 0) this.myParagraphs = 0;
  }
}

Create a stage and load a widget:

Widget Vue
this
  .registerStage(this
    .element
    .find("[data-role=tools]")
  )
  .load(LoadingComponents);
Just replace this with this.component and the find() method with vue references:
this
  .component
  .registerStage(this.$refs.tools)
  .load(LoadingComponents);