uibuilder.onChange msgs

Not sure if this is the right place for posting it, but creating an issue for uibuilder wouldn't be valid as it isn't an issue.

@TotallyInformation could you help me on the road a bit, let's say I have the following:

html

<div id="app">
	{{ input.payload.temperature }}
	{{ input.payload.lux }}
</div>

vue

var app = new Vue({
	el: '#app',
	data: {
		input: {},
	},
	computed: {},
	methods: {},
	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(v) {
			vueApp.input = v;
		});
	},
});

Although when rendering the values show up as expected, but the console complaints:
TypeError: undefined is not an object (evaluating 'input.payload.temperature')

The idea is: I want to inject everything in the onchange msg and access the properties in the html, is this possible or do you need to specify each element in data ?

Working properly with this:

data: {
	input: { payload: '' },
},
1 Like

Glad you got something that works. Personally, I would have something like:

var app = new Vue({
	el: '#app',
	data: {
		temperature: -99,
		lux: -99,
	},
	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(msg) {
			vueApp.temperature = msg.payload.temperature;
			vueApp.lux= msg.payload.lux;
		});
	},
});

Simply because that makes your HTML and Vue variables easier to understand. Only if I had a large number of properties or I didn't know in advance what they might be would I use an object in Vue.

1 Like

Looking a bit further my idea doesn't work as properties get overwritten when new data comes for 1 property.

If I have 100's of elements, I would prefer not to address/define them all individually 3 times, as that is what you are essentially doing; first in the onchange, in the data and in the html, there must be a more efficient way to do this.

Regarding your first question you can use v-if, e.g.:

<div v-if="input">payload: {{input.payload}}</div>

Have a look at the following as it gives an example of a very complex incoming message format. The data is sent once a minute and contains all the data from my heating system augmented with a variety of additional sensors.

So it absolutely is feasible to manage a large, complex object the way you are doing it. In my case, the variable homeData contains the details. It is an array but that is because it is easier to use an array directly with bootstrap-vue's table component. Each array element is itself a complex object.

There is no absolutely right and wrong way to do this, my suggestion was merely based on you only having two properties to deal with. If you look at this code, you will see that I'm using a mix of approaches depending on the data. I have a single, large incoming message which is then split into a number of Vue data variables but some of those are still complex.

Ok I am getting closer,still get some error about undefined objects.

I have this for the vue part:

'use strict';

var app = new Vue({
	el: '#app',
	data: function() {
		return {
			fan_kleine_kamer: { onoff: 'off', value: 'Off' },
		};
	},
	computed: {},
	methods: {},
	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(v) {
			vueApp.fan_kleine_kamer = v.fan_kleine_kamer;
			console.log(v.fan_kleine_kamer);
		});
	},
});

in the html, the onoff is used for a class

Reload the page, the class is loaded and the value is 'off', warning:

When I insert my data, no errors and the changes are applied.

Where does the warning come from ?
As I used it on a class I was not able to use the v-if (or at least, I don't know how to do it.)

(note that I read somewhere that it was better to use the return in data, won't make a difference for the error)

For components it does indeed need to return, for the outer instance I believe it does not matter. Or rather, for components it has to be a function returning an object but I am not sure about the outer instance.

Have you tried removing it as function and instead just setting it as an object as you appear to be dealing with the root instance?

Yes same behaviour.

data: { fan_kleine_kamer: { onoff: 'off', value: 'Off' } },

This would mean that I set these values as default upon the init (?)
I am missing something as it does set the values.

Perhaps I should also use an array of objects instead like your vue example, although it would become complex already :')

Under normal circumstances, this should just work. Can you post the rest of the code, it feels like there’s something missing from the eye that caused this.
Do you have the vue debugger browser extension installed? I use it a lot to inspect data and props values of both the root instance and child components.

A question beyond the scope of your problem, is there a reason why you set both onoff and value with more or less the same value except for the first letter capitalisation? If one is for comparison/settings, the other for displaying, take a look at using computed properties. That allows you to have a function set that can be used as more or less dynamic variable output, and you can base it on present variables. In other apps I use this for example to have variables presented together in Title Case or mashed together in other ways.

onoff and value are different unless onoff is off, then value becomes an 'Off' string.

I found the culprit, used an svg, which makes the source nice and readable /s had a class hiding in there.

I can see for sure that there are many way to Rome with Vue, but it still a little overwhelming.

In short what I want to create is a dashboard with (for now simple) buttons and these buttons reflect my home devices, turn on/off, have values,names and rooms. They get updated individually.

Now I would like to update them without (re)defining every single element (name, state, statevalue,room etc) in the onChange part, just receive an object and assign it directly to the data part.

Without being able to hand you a pracitcal example, you could do this from a perspective where you define the input in a known structure, for example an object with a property rooms, which holds key-value pairs for each room where the key is an identifier for name, and the value itself can be an object too that holds the values, including a name property with the human readable name. Then in the javascript code you would loop through these objects (take a look at the for ... of syntax for object iteration) and parses them when they arrive in the onChange to a structure in data, where you have say a variable for rooms.

This is where components start getting really powerful, as you define a base component for rooms, for example a bootstrap-vue tab component with contents inside. This room would then hold sub components for devices and outputs, where you could have components for device types, or even go as far as the <component :is="..."> syntax that vue has (see the guide on dynamic components) to set this up that each device type gets its own component.

As you would pass data to these components from the root instance through props, where the child components would receive the variables through the props set down the tree. This does indeed take some getting used to at the start as you have to remember that you aren't supposed to just change the values further down the tree and expect the changes to come back at a higher level... so instead you have to trigger an event that is listened to by a higher component. The docs describe this better than I can in this small post, but that point took a few weeks of debugging to truly hit home for me. What you need to remember most of all is that it is a one-way data flow down the tree, and to go the other way you need events and listeners.

If you set it up in such a dynamic way, the page should more or less be able to handle any kind of content that comes in, while taking in mind that it should follow the structure you defined this way. Let me see if I have some examples of past Vue apps lying around that show this... though they're highly specific and I'm not sure if showing the actual usage is such a good idea :wink:

Okay, I have a sample of this concept. I wrote this app, frontend and backend within 2 weeks last year. It's a bit rough around the edges, and it is not based on NR where the changes come in through a websocket. Instead there is a python webserver hanging behind it, with an API that handles the incoming requests. These API calls are defined in services files, which are not included, but that's what the imports are referring to.

Also please note that the code is ES6 based, which means it is not immediate 1-on-1 compatible with browsers, unless the browser does in fact support all those items. I haven't checked recently, and this is used with a webpack based build step.

The first file is the index.vue file. This is an outer item, yet still a component, that shows more or less the entire home page. There is a CMS hanging behind it, which can be accessed through the menu bar if you are logged in. That menu bar is another component and not included in the scope of this post. This index page is a bootstrap grid based on rows and columns. I won't describe what everything does, but on this grid I have a set up with rows, where each row contains at most 3 cards. These cards are a specific component that is included below as well.

<template>
  <div class="row flex-xl-nowrap">
    <div class="col-12 col-md-3 col-xl-2 bd-sidebar">
      <h4>Tags: </h4>
      <b-list-group>
        <template v-for="tag in tags">
          <b-list-group-item :key="tag.id" @click="applyTagFilter(tag.id)" v-if="tag.id === filter_.activeTag" active>{{ tag.tag}}</b-list-group-item>
          <b-list-group-item :key="tag.id" @click="applyTagFilter(tag.id)" v-else>{{ tag.tag }}</b-list-group-item>
        </template>
      </b-list-group>
    </div>
    <div>
      <h1>Fic Recs</h1>
      <p v-if="statusMsg !== ''">{{ statusMsg }}</p>

      <div class="fics">
        <div v-for="row in chunks" class="row"> <!-- Don't be me, define a :key here -->
          <fic-work :fic="fic" v-for="fic in row" :key="fic.id" />
        </div>
      </div>
    </div>
  </div>
</template>

This <fic-work> component defines a single card, and I show below how it looks like. The template is populated with the following javascript code:

<script>
  import WorkService from '@/services/WorkService';
  import TagService from '@/services/TagService';

  import FicWork from '@/components/FicWork';

  export default {
    name: 'index',
    data() {
      return {
        fics: [],
        tags: [],
        statusMsg: '',
        filter_: {
          activeTag: '',
        },
      };
    },
    mounted() {
      this.statusMsg = 'Loading... Please wait';
      this.initFics();
      this.initTags();
    },
    computed: {
      chunks() {
        const chunkSize = 3;

        return this.fics.reduce((accumulator, currentValue, currentIndex) => {
          // Based on https://stackoverflow.com/a/37826698
          const outerIndex = Math.floor(currentIndex / chunkSize);

          if (!accumulator[outerIndex]) {
            // eslint-disable-next-line no-param-reassign
            accumulator[outerIndex] = [];
          }

          accumulator[outerIndex].push(currentValue);

          return accumulator;
        }, []);
      },
    },
    methods: {
      async initFics() {
        const resp = await WorkService.fetchWorks();
        this.fics = resp.data.works;
        this.statusMsg = '';
      },
      async initTags() {
        const resp = await TagService.fetchTags();
        this.tags = resp.data.tags;
      },
      applyTagFilter(id) {
        this.filter_.activeTag = id;
      },
    },
    components: {
      FicWork,
    },
  };
</script>

The <fic-work> component looks as follows:

<template>
  <b-card class="col-sm-12 col-md-3 px-0 mx-4">
    <template slot="header" v-if="fic.internals.sticky">Featured</template>
    <span slot="footer">{{ fic.fandoms.map((fandom) => fandom.name).join(', ') }}</span>
    <h4 class="card-title"><a :href="fic.url">{{ fic.title }}</a></h4>
    <h6>By: <span v-html="authors.join(', ')"></span></h6>
    <span v-if="fic.ships.length > 0">Ships: <span v-html="ships.join(', ')"></span></span><br />
    <hr />
    <div v-html="fic.summary"></div>
    <p>
      <span>Rating: {{ fic.rating }}</span> |
      <span>Chapters: {{ fic.n_chapters }}</span> |
      <span>Word count: {{ fic.words_readable }}</span> |
      <template v-if="fic.setting !== ''">
        <span>Setting: {{ fic.setting }}</span> |
      </template>
      <template v-if="fic.complete">
        <span>Complete</span>
      </template>
      <template v-else>
        <span>Incomplete<template v-if="fic.status !== ''"> ({{ fic.status }})</template></span>
      </template>
    </p>
    <p>Tags: <span v-html="tags.join(', ')"></span></p>
  </b-card>
</template>

As you can see from the code above, the <fic-work> component has only a single prop, fic, which is an object. This object has a property authors which is an array of objects, where in turn each object has at least 2 properties: url and name. fic also has other properties set up like this, such as ships, tags and a bunch of numeric or string based properties. These are more or less directly coming in from the API, I'll see if I can grab an example from production to include at the bottom, but sanitised... this code is already more than enough to not sanitise :stuck_out_tongue:

Finally, the javascript for this component is defined as follows:

<script>
  export default {
    name: 'fic-work',
    props: {
      fic: {
        type: Object,
      },
    },
    computed: {
      authors() {
        return this.fic.authors.map(author => `<a href=${author.url}>${author.name}</a>`);
      },
      ships() {
        return this.fic.ships.map((ship) => {
          if (ship.work_display_name.other_display_names.length > 0) {
            return `<b>${ship.work_display_name.primary_display_name}</b> (${ship.work_display_name.other_display_names.join(', ')})`;
          }
          return `<b>${ship.work_display_name.primary_display_name}</b>`;
        });
      },
      tags() {
        return this.fic.tags.map(tag => `<span>${tag.tag}</span>`);
      },
    },
  };
</script>

I'm using computed properties here to display the values with some HTML. If I had to redo this today I would probably write this slightly different so that the HTML ends up in the template with a v-for setup, rather than in the property. Although the ships() property would still more or less exist like that this way. As for the design, I worked this structure out on paper first. I have 3 discarded setups for this still lying around.

That's more or less what I would give you from this: work it out on paper first, when you think you're happy with it, start typing it out. If you're still happy with it at that point, work it out towards an interface. If in the interface it feels off, go back one step and see if you can improve it there. If not, go back to the drawing board to your structure and see how you can improve it.

As for what a single fic object looks like, here's a redacted version of one that I just pulled from the API.

{
    "authors": [{
        "id": "5a8317ffcd19c712212327f6",
        "name": "<author's name>",
        "url": "<url to a page relevant to the author>"
    }],
    "characters": [],
    "complete": true,
    "fandoms": [{
        "id": "5a744d5dcd19c73541d7e9e1",
        "name": "The 100"
    }],
    "id": "5a831859cd19c712212327f7",
    "internals": {
        "pos": 1,
        "sticky": false
    },
    "n_chapters": 1,
    "n_words": 9706,
    "rating": "Not rated",
    "setting": "Modern AU (College)",
    "ships": [{
        "id": "5a7f395ecd19c7fb122edaf5",
        "work_display_name": {
            "other_display_names": [],
            "primary_display_name": "Clarke Griffin/Lexa"
        }
    }],
    "spoilers": "",
    "status": "",
    "summary": "<p>A summary with HTML tags, line <br /> breaks and more.</p>",
    "tags": [{
        "id": "5a81ac37cd19c713d940677f",
        "tag": "Fluff"
    }],
    "title": "<title>",
    "url": "<url>",
    "words_readable": "9,706"
}

And as mentioned before, that one data value fics at the very start in index.vue is an array of these objects.

As Lena says, it would help if you could share a more complete example including a sample msg. We can then play along and help you work out what is going wrong.

I think that, in your case, you need to use a computed function rather than just a data value. So continue to update your data object but in your html, reference a computed rather than the data itself.

Then in the computed function, you can add the necessary error checks for when the data doesn't yet exist.

Computed functions are also reactive and will only recompute when necessary so they are still efficient.

You can have a single computed function that takes a parameter of the data property name. The function will check if it exists and return it or return a default value (which you could have as a second parameter if you wanted).

That will save you having to pre-define everything.

Thanks for the elaborate detail. The component/template seems like the way to go.
Now I see the v-if usage, I am wondering, in ships() you use an if, could you have done this via the v-if as well ?

import FicWork from '@/components/FicWork'; - is the template, which is a separate file ? I will read the documentation how this works.

In the html it does look like angular with different syntax but I can cope with that, the vuejs needs a different mindset.

@TotallyInformation I sure want to show something, but as this is a steep learning curve where I (need to) change a lot in the code and really need to understand the concepts first, there is nothing to show really.

All this great information lets me reconsider the approach and I will first create a more firm dataset that can be used in the template/component form.

Thanks a lot all.

I’m on a hellish taxi drive right now, so it’s a but short. Will describe in more detail later. Ignore this line, this is how the import of the ficwork.vue file works with a build step. This would be handled through http-vue-loader without a build step.

The first two blocks I placed, template and script are from index.vue. The second two blocks are ficwork.vue. I removed the style for both as it is not relevant for the logic. The last block is the json of how a single fic item looks like.

I watched a comprehensive tutorial about vue and templates and components, looks nice and clean to separate everthing into a 'component'

Looking a bit further in @TotallyInformation's wiki, I don't see the use of template components. When I try to use import Header from './header.vue' it fails and complaints import call expects exactly one argument.

Can I import template components in uibuilder ?

You can. Take a look at the Vue user guide for single file components, and the wiki entry for using single file components without a build step using http-vue-loader.

I am working on a wiki entry to explain how to use preprocessors with this, such as writing in SCSS or LESS for styling, or TypeScript, CoffeeScript for scripting. Or even Pug for html templates. Spoilers: that is possible too, but requires more configuration and the wiki entry isn’t explaining for all of those yet.

However, because of the lack of a build step (webpack for example) this is not how you would import components now. Start with reading Julian’s wiki entry. The importing is done directly with the httpVueLoader('path/to/component.vue') command. You can compare this to what the advanced usage section of the documentation describes as “dynamic async components”. They are loaded from the call in the components section, and the import itself is handled by the HttpVueLoader internally.

1 Like

Ah I read the "simple Vue app" part, while there is "totallyinformation" :wink:

1 Like

It takes a while to get your head around any of the modern front-end libraries like Vue, REACT, etc. I found. But once you've got the basics you can do a lot even without the more advanced features which is one of the reasons why I swapped the default template from jQuery to VueJS.

Vue is the fastest growing framework and rapidly outpacing REACT. Before settling on Vue, I tried lots of different frameworks trying to find one that was reasonably simple to use for simple things but that would also scale to really complex ones.

In particular, I needed a framework that didn't force you to use a build step. Initially Vue did pretty much do that which was why I ignored it early on and tried things like MoonJS which was more flexible in that regard. However, I then realised that Vue had moved on and now provided really good support for working without a build step.

Between that and its very rapidly growing support, it was clear that it was going to be the best default. But don't forget - you can use any framework with uibuilder! If you don't like Vue, you can use anything else or even hand-crank your own, uibuilder doesn't care. :smile:

Anyway, just wanted to encourage you to carry on exploring.

1 Like