Categories
Vue Tips

Vue Tips — DOM Elements, Vue Router, and Dynamic Components

Vue.js is a popular framework for creating front end web apps.

In this article, we’ll look at some tips for writing better Vue.js apps.

Open a Link in a New Tab with Vue Router

We can open a link in a new tab with Vue Router.

For instance, we can write:

const routeData = this.$router.resolve({ name: 'foo', query: { data: "bar" }});
window.open(routeData.href, '_blank');

We call the this.$router.resolve method to get the URL to open.

Then we call window.open to open the given URL.

The first argument is the URL.

'_blank' indicates that we open the URL in a new tab.

Run Vue.js Dev Server with HTTPS

We can change the Vue dev server’s config to serve the project over HTTPS rather than HTTP.

To do that, we read the private key and certificate.

And we set the URL to serve the project on.

For instance, we can write:

const fs = require('fs')

module.exports = {
  devServer: {
    https: {
      key: fs.readFileSync('./certs/key.pem'),
      cert: fs.readFileSync('./certs/cert.pem'),
    },
    public: 'https://localhost:8888/'
  }
}

We read in the files and set them as the properties of the https property.

To make a certificate, we can use the mkcert program to do it.

We can install it on Windows, Mac OS, or Linux by following the instructions on https://github.com/FiloSottile/mkcert.

Then we can create a new key and certificate by running:

mkcert -install

and:

mkcert example.com "*.example.com" example.test localhost 127.0.0.1 ::1

Then we created a certificate valid for localhost.

We just copy the files to the cert folder and rename them to match what we have in the config.

Then we can run npm run dev and serve the project.

Rerun Vue Component mounted() Method

To rerun the code that we wrote in the mounted method, we can move the code into its own method.

Then we can call that in mounted or anywhere else.

For instance, we can write:

new Vue({
  methods: {
    init(){
      //...
    }
  },
  mounted(){
    this.init();
  }
})

Then we can write:

<button @click="init">reset</button>

We set the init method as the click handler and also run it in the mounted hook.

Now we can reuse it as we wish.

Trigger Events Using Vue

We can set a ref on an element and then trigger the event on the element.

For instance, we can write:

<template>
  <button type="button" @click="onClick" ref="aButton">
    click me
  </button>
</template>

<script>
export default {
  methods: {
    onClick($event) {
      const elem = this.$refs.aButton.$el;
      elem.click();
    }
  }
}
</script>

We have a button that we assigned a ref to.

Then we get the button’s ref’s element in the onClick method.

It returns the DOM node for the button.

Then we call click to trigger a click on the button DOM node.

Share a Method Between Components in Vue

To share a method between components in Vue, we can create a module that exports the method.

For instance, we can write:

src/shared.js

export default {
  bar() {
    alert("bar")
  }
}

Then we can use it in our component by writing:

src/App.js

<template>...</template>

<script>
import shared from './shared'

export default {
  created() {
    shared.bar();
  }
}
</script>

We imported the shared module and called the bar method inside it.

Alternatively, we can create a mixin, which is a piece of code that can be merged into our component.

For instance, we can write:

const cartMixin = {
  methods: {
    addToCart(product) {
      this.cart.push(product);
    }
  }
};

Then we can use it in our component by writing:

const Store = Vue.extend({
  template: '#app',
  mixins: [cartMixin],
  data(){
    return {
      cart: []
    }
  }
})

We call Vue.extend to create a subclass of the Vue constructor.

We can then make a new instance of Store and render that in our app.

Pass a Component as Props and Use it in a Child Component in Vue

We can pass in the component’s tag name into the is prop of the component component.

For instance, we can write:

<template>
  <div>
    <component :is="childComponent">foo bar</component>
  </div>
</template>

childComponent is the string with the tag name of the component.

The stuff between the tags is the content that’s filled into the default slot.

Conclusion

We can create a shared module or a mixin to create shared code in Vue.

Also, we can use this.$router.resolve to get the path that we can use elsewhere.

Vue projects can be served with HTTPS.

We can get the HTML element’s DOM object with refs.

Categories
Vue Tips

Vue Tips — Login, GET Requests, and Serve Vue Project via HTTPS

Vue.js is a popular framework for creating front end web apps.

In this article, we’ll look at some tips for writing better Vue.js apps.

Force Download with GET Request using Axios

We can make the response always download by passing our response data into the Blob constructor.

For instance, we can write:

axios
  .get(`download-pdf`, {
    responseType: 'arraybuffer'
  })
  .then(response => {
    const blob = new Blob(
      [response.data],
      { type: 'application/pdf' }
    ),
    const url = window.URL.createObjectURL(blob);
    window.open(url) ;
  })

We make a GET request to the download-pdf endpoint to download our PDF.

We make sure that we specify that the responseType is 'arraybuffer' to indicate that it’s a binary file.

Then in the then callback, we get the response parameter, which has the data we want in the data property.

We pass that into the Blob constructor in an array.

And we specify the MIME type of the response data.

Then we create the URL that lets us download the file with createObjectURL .

Finally, we call window.open with the url to download the file.

We can also specify the file name of the downloaded file by making a small change in the then callback.

To do that, we write:

axios
  .get(`download-pdf`, {
    responseType: 'arraybuffer'
  })
  .then(response => {
    const blob = new Blob(
      [response.data],
      { type: 'application/pdf' }
    ),
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(blob);
    link.download = "file.pdf";
    link.click();
  })

Instead of creating an object URL directly, we create an invisible link first and set the file name as the download property of it.

Then we call click to download it.

Fire an Event When v-model Changes

We can add a change event listener to our input to listen for input value changes.

For instance, we can write:

<input
  type="radio"
  name="option"
  value=""
  v-model="status"
  v-on:change="onChange"
>

onChange is the name of the change event handler.

We can also replace v-on: with @ for short:

<input
  type="radio"
  name="option"
  value=""
  v-model="status"
  @change="onChange"
>

Also, we can add a watcher for our model property instead of attach a change event listener.

For instance, we can write:

new Vue({
  el: "#app",
  data: {
    status: ''
  },
  watch: {
    status(val, oldVal) {
      console.log(val, oldVal)
    }
  }
});

With the watch property, we added a watcher for the status variable as indicated by the name.

val has the current value and oldVal has the old value.

Redirect to Requested Page After Login Using Vue Router

We can add a redirect path to the query parameter when redirecting to the login page.

For instance, we can write:

onClick() {
  if (!isAuthenticated) {
    this.$router.push({ name: 'login', query: { redirect: '/profile' } });
  }
}

We check for authentication credentials.

If they aren’t present, then we call this.$router.push to redirect to the route for the login page.

The query property has the path to redirect to when login is successful.

We have a query string with the redirect key and the /profile value.

Then in our login form component, we can write:

submitForm() {
  login(this.credentials)
    .then(() => this.$router.push(this.$route.query.redirect || '/'))
    .catch(error => {
       //...
    })
}

in the methods property.

We call login which returns a promise.

So we can call then with a callback to redirect to the path in the query string.

We get that path with the this.$route.query.redirect property.

Then we call this.$router.push to do the redirect.

In case it’s not defined, we redirect to the / route.

Run Vue.js Dev Server with HTTPS

We can run Vue’s dev server with HTTPS by changing the code in build/dev-server.js to read the certificate and the private key files.

Then we can use them to call https.createServer to create the HTTPS server.

For instance, we can write:

const https = require('https');
const fs = require('fs');
const options = {
  key: fs.readFileSync('key.pem'),
  cert: fs.readFileSync('cert.pem'))
};
const server = https.createServer(options, app).listen(port);

We read the files and put them into the options object.

Then we pass the object into the https.createServer method.

app is the Express server’s app object.

Conclusion

We can serve a Vue project with a dev HTTPS server.

Also, we can download files with Axios.

And we can add watchers to watch for variable changes.

Categories
Vue Tips

Vue Tips — Slots, Vue Router, and Mutations

Vue.js is a popular framework for creating front end web apps.

In this article, we’ll look at some tips for writing better Vue.js apps.

How to Pass Down Slots Inside Wrapper Component

To pass down slots in a wrapper component, we can loop through all the slots and pass them down to the child.

For instance, we can write:

<wrapper>
  <parent-table v-bind="$attrs" v-on="$listeners">

    <slot v-for="slot in Object.keys($slots)" :name="slot" :slot="slot"/>

    <template v-for="slot in Object.keys($scopedSlots)" :slot="slot" slot-scope="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>

  </parent-table>
</wrapper>

We get the slots with the $slots variable.

Then we use Object.keys to get names of the slots so that we can loop through all of them and pass the name down.

Likewise, we can loop through the scoped slots with the $scopedSlots variables.

We get the keys the same way and loop through them with v-for the same way.

With Vue 2.6, the v-slot= directive is introduced to let is pass the slots down.

For instance, we can write:

<wrapper>
  <parent-table v-bind="$attrs" v-on="$listeners">
    <template v-for="(_, slot) of $scopedSlots" v-slot:[slot]="scope">
      <slot :name="slot" v-bind="scope"/>
    </template>
  </parent-table>
</wrapper>

We loop through the slot with v-for .

We get the scoped slots with the $scopedSlots variable.

slot is the slot name again.

This time, we pass it to the v-slot directive as a modifier to pass down the named slot.

scope has the scope from the scoped slot.

We use v-bind to get the scope.

And we

Alternatively, we can use render function to pass down the slots.

For instance, we can write:

render(h) {
  const children = Object.keys(this.$slots)
    .map(slot => h('template', { slot }, this.$slots[slot]))

  return h('wrapper', [
    h('parent-table', {
      attrs: this.$attrs,
      on: this.$listeners,
      scopedSlots: this.$scopedSlots,
    }, children)
  ])
}

We get the slots with the this.$slots property.

We call Object.keys to get the slot names.

And we call map on it to map the slot names to the template components.

And we pass in the slots name and the scope down,.

Then we return a wrapper component with the parent-table component with the listeners, attributes, and scoped slots and children as the children.

Get Query Parameters from a URL in Vue.js

We can get the query parameters from a URL in Vue with the this.$route.query property.

To get the query parameter foo=bar , we write:

this.$route.query.foo

and we get 'bar' as the value.

This is available assuming we’re using Vue Router in our Vue app.

If we haven’t added it, we can write:

index.html

<script src="https://unpkg.com/vue-router"></script>

index.js

const router = new VueRouter({
  mode: 'history',
  routes: [
    {
      path: '/page',
      name: 'page',
      component: PageComponent
    }
  ]
});

const vm = new Vue({
  router,
  el: '#app',
  mounted() {
    const q = this.$route.query.q;
    console.log(q)
  },
});

to get it.

We create the VueRouter instance and pass it into the object we passed into the Vue constructor.

routes has the routes.

Passing Multiple Parameters to a Mutation with Vuex

To pass multiple parameters to action with Vuex, we can pass in an object as the payload.

For instance, we can create our mutation by writing:

mutations: {
  setToken(state, { token, expiration }) {
    localStorage.setItem('token', token);
    localStorage.setItem('expiration', expiration);
  }
}

We have an object as the second parameter.

It has the token and expiration properties.

Then we can invoke the mutation by writing:

store.commit('setToken', {
  token,
  expiration,
});

We invoke the setToken mutation with the token and expiration properties in an object as the 2nd argument.

Reload Route with Vue Router

To reload a route with Vue Route, we can call the this.$router.go() method.

If it has no arguments, then it’ll reload the current route.

We can also add an unique value for the key prop to the router view:

<router-view :key="$route.fullPath"></router-view>

This way, it’ll notice when the path changes and it’ll trigger a reload of the component with new data.

Conclusion

We can reload our route with this.$router.go() .

There are many ways to pass a scope down to a child.

We can get query parameters in our component if we use Vue Router.

To pass in multiple pieces of data into a mutation, we can pass in an object with all the data we want into the mutation.

Categories
Vue Tips

Vue Tips — Making Components Play Nice with Each Other

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at how Vue components can play nice with each other.

Assign Attributes to the Right Elements

We can pass attributes from a grandparent component to a grandchild component by setting the inheritAttrs option to false , and then add v-bind='$attrs' to the child of the grandparent to pass the attributes of the grandparent to the grandchild.

For instance, we can write the following code to do that:

index.js :

Vue.component("foo", {
  inheritAttrs: false,
  template: `
    <p v-bind='$attrs'>foo</p>
  `
});

new Vue({
  el: "#app"
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <foo class="foo"></foo>
    </div>
    <script src="index.js"></script>
  </body>
</html>

Then the foo class will be applied to the p element in foo since we set inheritAttrs to false in foo .

Use Browser Norms for Keyboard Navigation

We should follow the conventions for keyboard shortcuts for common operations if we’re going to handle keyboard shortcuts.

For instance, the tab key lets us move to different form fields. Enter is for submitting data, etc.

Use Events Over Callbacks

Emitting events from child to parent is better than callback functions that are passed in from parent to child.

For instance, we have to pass callbacks as props the following way:

index.js :

Vue.component("foo", {
  props: ["showAlert"],
  methods: {
    displayAlert() {
      if (typeof this.showAlert === "function") {
        this.showAlert("foo");
      }
    }
  },
  template: `
    <button @click='displayAlert'>Click Me</button>
  `
});

new Vue({
  el: "#app",
  methods: {
    showAlert(text) {
      alert(text);
    }
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <foo :show-alert="showAlert"></foo>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the show-alert prop, which we pass the showAlert method from the root instance to the child. This is more complex and error-prone because we have to make sure that we passed in a function as the value of the prop.

We have to check that by using typeof of Vue’s built-in prop type validation.

Then we have to call the function that’s passed in as the value of the prop as we did in the displayAlert function. The name is different so it won’t clash with the showAlert prop.

On the other hand, if we emit events, then we won’t have to pass functions from parent to child to run something in the parent when something happens in the child.

For example, we can rewrite the example above by writing:

index.js :

Vue.component("foo", {
  template: `
    <button @click='$emit("button-clicked", "foo")'>Click Me</button>
  `
});

new Vue({
  el: "#app",
  methods: {
    showAlert(text) {
      alert(text);
    }
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <foo @button-clicked="showAlert($event)"></foo>
    </div>
    <script src="index.js"></script>
  </body>
</html>

The code above is a lot simpler. We just call $emit with the event name as the first argument and the payload that we want to send to the parent as the second.

Then we can listen to the button-clicked event from the parent as we did in index.html by writing:

@button-clicked="showAlert($event)"

Then $event object has the payload, which is 'foo’, so we can pass it straight into the showAlert method. Finally, we’ll see the alert display when we click the button.

Limit In-Component Styles

Scoped in-component styles in single-file components are only applied to the component itself. If we’re reusing it in many locations or apps, then we should let the styles be in that app instead so that we won’t have conflicts of styles between our component and the styles that’s already in the app.

This makes styling reusable components hard since we’ve to deal with conflicts.

Therefore, we should either leave the styles out of the single-file component or have the ability to toggle them on and off.

For instance, we can toggle the styles with an is-styled prop as follows:

HelloWorld.vue :

<template>
  <div>
    <p :class="{'is-styled': isStyled}">{{ msg }}</p>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
    isStyled: Boolean
  }
};
</script>

<style scoped>
.is-styled {
  color: #42b983;
}
</style>

App.vue :

<template>
  <div id="app">
    <button @click="isStyled = !isStyled">Toggle Style</button>
    <HelloWorld msg="Hello Vue" :is-styled="isStyled"/>
  </div>
</template>

<script>
import HelloWorld from "./components/HelloWorld";

export default {
  name: "App",
  components: {
    HelloWorld
  },
  data() {
    return {
      isStyled: true
    };
  }
};
</script>

In the code above, we have :class to bind the class dynamically according to the value of the isStyled prop that we passed in in HelloWorld.vue

Then in App.vue , we can set the isStyled value and pass it into the is-styled prop as the value so that we can have the ability to turn our HelloWorld ‘s component’s styles on and off as we wish.

Conclusion

We can use v-bind='$attrs' to inherit attributes from parent to child. If we’re going to add keyboard shortcuts handling to our app, we should following the conventions of other apps to make using our app convenient.

Also, we should emit events instead of calling callbacks passed from parent to child.

Finally, if we have shared components, we should either leave out the styles or have the ability to toggle them on and off.

Categories
Vue Tips

Vue Tips — CSS Frameworks and Watching Nested Data

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at using CSS frameworks to save us time from styling everything manually and watching nested data.

Use an Off the Shelf CSS Framework

Off the shelf CSS framework saves us time from styling things that we’ve to style manually otherwise. There’re many frameworks to choose from, including Tailwind CSS, Foundation, Bulma, and Bootstrap.

They come with classes and JavaScript code to style static components like inputs and tables and also create dynamic components like modals and popups.

Some of them have their own Vue components built for it. For instance, Buefy is a library that’s based on Bulma. BootstrapVue is a component library that’s based on Bootstrap.

If there’s a Vue component library made for it then, then we should use it since they integrate much better with other Vue components in our app. They take props and emits events like any other Vue components.

For instance, we can use Buefy to take advantage of the Bulma framework without making our own styled-components based on Bulma as follows:

index.js :

new Vue({
  el: "#app",
  data: {
    name: ""
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/buefy/dist/buefy.min.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/buefy/dist/buefy.min.css" />
  </head>
  <body>
    <div id="app">
      <b-field label="Name" type="is-danger" message="Name">
        <b-input type="name" v-model="name" maxlength="30"> </b-input>
      </b-field>
      <p>{{name}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the b-field component from Buefy, which is an input form field component. Then we have our b-input component, which is the actual input component.

The label is rendered by the b-field component so that we can see the label. In addition, there’s styling with the type prop and the message prop provides a message that’s displayed.

The input has input validation and binds to a variable of our choice with v-model .

We did all that by just using the library and referencing the provided component, so it’s the best choice for quickly using styled-components.

Watching Nested Data in Vue

We can watch data with by adding a function to the watch object in our component options.

It’s useful for watching for updates in nested objects and arrays. In these cases, Vue has no idea what’s changed unless we explicitly add watcher and set it to watch for the structural changes of the object. This is because Vue sees that the reference to the object hasn’t changed.

For instance, we can use watchers to watch for changes to an array as follows:

index.js :

new Vue({
  el: "#app",
  data: {
    nums: [1, 2, 3]
  },
  methods: {
    append() {
      this.nums.push(Math.max(...this.nums) + 1);
    }
  },
  watch: {
    nums: {
      deep: true,
      handler() {
        console.log("nums changed");
      }
    }
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <button @click="append">Append</button>
      <p v-for="n in nums">{{n}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have an append method to push numbers into the nums array as it’s called. Then we have the watch object with the nums object inside to watch for changes in nums . We set deep to true so that it watches for all structural changes. The handler is then run when it’s changed.

Then when we click the Append button, the handler function will run.

There’s also an immediate option so that the watcher is run when the field is initialized. For instance, we can use it as follows:

new Vue({
  el: "#app",
  data: {
    nums: [1, 2, 3]
  },
  methods: {
    append() {
      this.nums.push(Math.max(...this.nums) + 1);
    }
  },
  watch: {
    nums: {
      deep: true,
      immediate: true,
      handler() {
        console.log("nums changed");
      }
    }
  }
});

Then we’ll see nums changed logged when we first load the app.

If we want to use the same handler function to watch more than one item, we can set it to the function in the methods object.

For instance, we can write:

index.js :

new Vue({
  el: "#app",
  data: {
    nums: [1, 2, 3],
    nums2: [4, 5, 6]
  },
  methods: {
    append() {
      this.nums.push(Math.max(...this.nums) + 1);
    },
    appendTo2() {
      this.nums2.push(Math.max(...this.nums2) + 1);
    },
    watch() {
      console.log("nums changed");
    }
  },
  watch: {
    nums: {
      deep: true,
      immediate: true,
      handler: "watch"
    },
    nums2: {
      deep: true,
      immediate: true,
      handler: "watch"
    }
  }
});

index.html :

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  </head>
  <body>
    <div id="app">
      <button @click="append">Append</button>
      <button @click="appendTo2">Append 2</button>
      <p v-for="n in nums">{{n}}</p>
      <p v-for="n in nums2">{{n}}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have 2 buttons, which append numbers to 2 separate arrays as we click it by running append or appendTo2 functions respectively.

Then since we set handler to 'watch' , the watch method in methods is called, so we’ll see that “nums changed” is logged if we click any of the buttons.

Conclusions

Off the shelf CSS frameworks are great for reducing styling effort needed for us. Some frameworks have component libraries made from it so that we don’t have to spend time styling them ourselves.

We should use watchers for watching values changes deeply nested objects and array. It has options to start watching if the component loads and also has options to watch deeply nested objects, which computed properties can’t do.