diceline-chartmagnifierquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

Use a hidden iframe to submit a form via Javascript for legacy applications

To prevent the page from being redirected or refreshed, we will create a javascript iframe inside which we will create the form:

methods: {
  hiddenIframe(fields) {
    let customHiddenIframeName = 'iframeId'
    if (!document.getElementById(customHiddenIframeName)) {
      let iFrame = document.createElement('iframe')
      iFrame.id = customHiddenIframeName
      iFrame.name = customHiddenIframeName
      iFrame.src = 'about:blank'
      iFrame.style.display = 'none'
      document.body.appendChild(iFrame)
     }

     let form = document.createElement('form')
     form.method = 'POST'
     form.action =
        'https://webto.salesforce.com/servlet/servlet.WebToCase?encoding=UTF-8'
     form.setAttribute('target', customHiddenIframeName)
      
     for (let fieldName in fields) {
       let input = document.createElement('input')
       input.name = fieldName
       input.value = fields[fieldName]
       input.id = fieldName
       input.setAttribute('type', 'hidden')
       form.appendChild(input)
     }

     document.body.appendChild(form)
     form.submit()
  }
},

created() {
  this.hiddenIframe({
    email: 'example@test.com',
    name: 'John Doe',
    subject: 'Form',
    description: 'Form description'
   })
}

Vuex composition helpers utility package

One big advantage of the Composition API over the Options API is that it lets us group our variables and methods how we want. Most of the time we group them together by feature/functionality, as they use/call one another and it's convenient to have them grouped together this way.

For all apps where we use Vuex for state management, we probably take advantage of the mapState, mapGetters, mapActions and mapMutations binding helpers. Unfortunately, this does not work as expected with the Composition API. But there is also a way of how we can replicate this behavior and also maintain the structure of our components as described at the beginning as an advantage.

We have to install the vuex-composition-helpers utility package:

npm i vuex-composition-helpers@next

and use it as follows in our component:

<script setup lang="ts">

import { useState, useActions, useGetters, useMutations } from 'vuex-composition-helpers'

const { globalMessage, onlineUsers } = useState(['globalMessage', 'onlineUsers'])
const { getGlobalMessage } = useGetters(['getGlobalMessage'])
const { UPDATE_MESSAGE, ADD_USER } = useMutations(['UPDATE_MESSAGE', 'ADD_USER'])
const { updateMessage, addOnlineUser } = useActions(['updateMessage', 'addOnlineUser'])


</script>

DefineEmits and DefineProps compiler macros with default values in TypeScript

This is how we:


a. use the defineEmits() and defineProps() compiler macros with Composition API's Script Setup and TypeScript

b. pass default values to our component's props

<script setup>
const props = withDefaults(
  defineProps<{
    message: string, 
    isActive: boolean
  }>(), 
  {
    message: 'Default message', 
    isActive: false
  }
);

const emit = defineEmits<{
  (e: "customEventName", payload?: object) : void
}>();
</script>

Script setup syntactic sugar for the Composition API in SFCs

Problem: When using the Composition API, we need to return all (reactive) variables from the setup() method in order to use them in the template section. This can make components huge, harder to read and maintain and also cause unwanted bugs if we forget to return any variables used in the template. More than that, we can use variables only by accessing their "value" property.

This is the basic structure of a component that we want to improve:

<script>
//Long list of imports 

import Child1 from 'child1-location';
import Child2 from 'child2-location';
import { ref } from 'vue';

export default defineComponent({
  name: 'CustomComponent',
  components: { Child1, Child2 },
		
  setup() {
	const variable1 = ref(1);
	const variable2 = ref(2);
	//...
	const variable100 = ref(100);

	//altering variables
	variable1.value = 'new value for variable1';

	return {
		variable1,
		variable2,
		.
		.
		.
		variable100,
	}
  }
});
</script>

<template>
	<Child1 />
	<Child2 />
	{{ variable1 }}
	...
	{{ variable100 }}
</template>

Solution: The structure and performance of this component can be improved by using the Script Setup syntactic sugar. 
The Script Setup is a compile-time syntactic sugar that is recommended to be used in SFCs with the Composition API. Let's rewrite the component from above using the Script Setup:

<script setup> 
	
import Child1 from 'child1-location';
import Child2 from 'child2-location';
import { ref } from 'vue';
	
const variable1 = ref(1);
const variable2 = ref(2);
//...
const variable100 = ref(100);

variable1 = 'new value for variable1';
</script>

<template>
        <Child1 />
	<Child2 />
	{{ variable1 }}
	...
	{{ variable100 }}
</template>

Important differences and advantages: 



  1. We don't need to return any variables from our script in order to use them in the template, reducing the number of lines and increasing readability and operability
  2. We don't need to use .value to access and modify reactive variable values
  3. We can directly use imported Child Components in our template without registering them
  4. Better runtime performance due to the behind the hood implementation of the template (as a render function without an intermediate proxy)
  5. TypeScript support for defining props and emitted events (will be described in a future TIL)

File validation in vueJS with vee-validate

As we know, "v-model" does not work on an input that has a type of "file". So to validate a file is a little bit tricky.

For this validation, we will use vee-validate -> https://vee-validate.logaretm.com/v4/

To do this, you need to add a method that triggers the "on change" event on your input and to add on the "ValidationProvider" a ref.

import { ValidationProvider } from 'vee-validate/dist/vee-validate.full.esm'

<ValidationProvider ref="provider" rules="required">
 <input
 ref="file"
 type="file"
 @change="onFileAdd"
 />
</ValidationProvider>

Also you need to go and create the "onFileAdd" method , which will contain the following code:

export default {
 data() {
  return {
    fileData: '',
  }
 }

 methods: {
  async onFileAdd(e){
   const { valid } = await this.$refs.provider.validate(e)
   if (valid) {
    const file = this.$refs.file.files[0]
    this.fileData = file
   }
  }
 }
}

The "fileData" in the data object is the file that will be sent when the form is submitted.

Commit your changes. That's all.