Quick Bookmark-able Prototyping with Vue.js and Visual Studio Code
By Dudley Ablorh-Bryan, UX Developer @ Chewy
Working on the Chewy Design System team generally involves switching between engineering new UI components and constructing prototypes made from existing shared components within our Chewy Design System.
While I won’t be discussing design systems in detail, there is certainly rewarding reading out there for those looking to get up to speed. In short, a design system and UI library share styling and/or component logic between the design, prototype and production code while letting each stage focus on what it does best. And when it comes to where you develop, I’ll be focusing on a couple of helpful shortcuts using Visual Studio Code. For brevity, this article assumes your familiarity with words like “components” or “prototypes” and that you have a reasonable grasp of frameworks like Vue.js and their respective tooling.
What makes a good prototype?
We want our prototypes to be fast to create, yet forward-thinking. They should enable teams to communicate and work through issues before production development, such as user flows, tricky new interactions, accessibility and basic content and data flow. Now, consider an app that features search results or displays specific resource information. In a recent project, we made the decision to reflect the user’s choices in the page URL. This gave teams an excellent way to share various states in a single-page application during the prototyping phase of the project.
When teams are able to share and bookmark multiple scenarios (inside the same test interface), prototypes excel as a tool for communicating. In the early phases of a project, it’s especially important to have practical discussions around a working example.
Various teams and stakeholders will have the opportunity to marshal critical input around a tangible application. We’re creating a simple search page in this article, but imagine how a prototype like this would be helpful for an interactive grade book, product comparison widget or an interface that helps customers conduct complex product searches.
In this post, we will generate a simple, bookmark-able prototype that relies on an external REST API using Vue.js. Afterwards, we’ll review how it fits into a general prototype workflow.
Route Params
Let’s preview what your application URL might look like when a user searches for certain terms with certain parameters.
Example 1:/search?q=labrador&subject=dogs
Depending on the server configuration of your deployment environment (where you deploy your app and give others access), the request above may look a bit different:
Example 2:/#/search?q=labrador&subject=dogs
Example 1, using URL params, may look more familiar than Example 2, which uses hash params (notice how everything comes after the hash). Hash params however, require no additional server configuration and may be easier to release in your organization. Thankfully, Vue.js handles most of the work for you so that you don’t have to change much code to take advantage of reading parameters from both configurations.
Get Your Data Early
Prototype work often begins when we receive technical requirements from stakeholders and design mockups that detail how the app should look and behave. In our example, we have been tasked with creating a pet-centric search tool using the Open Library Search API. We already save time by using a working endpoint, so we won’t even need to generate mock data or artificially force that data to conditionally show different information.
Our search query mentioned above looks like this when sent to the JSON API:
http://openlibrary.org/search.json?title=labrador&subject=dogs
Note: Another useful piece of information about Open Library is where to find images. If the
cover_id
attribute is present, we should be able to display a book cover by referencing this information. We will use this attribute for our thumbnail paths.
https://covers.openlibrary.org/w/id/9144885-L.jpg
For the purpose of initial testing, consider saving a static copy of the JSON response locally until you’re ready to interact with the UI you’re creating. This will cut down on how often you hit the API as you get started and protect you from quota limits. Or, if it’s an internal API, it will shield you from unexpected API deployments or build errors that could disrupt your development timeline.
In a typical Vue.js project, we can just save our test data here /public/assets/data/search_example.json
and reach it with any XML Http Requests (XHRs) later.
Scaffolding Your App
From here, there are lots of options to quickly leap into a running app. We’ll use vue create
via Vue CLI so that we’re up and running in seconds. If you see recurring patterns, consider creating custom templates for Vue CLI. You might also consider using third-party tools like Vetur, Yeoman or even Angular Schematics to quickly set up scaffolding through generators or templates that jumpstart your new prototypes via the command line.
For now, we’ll follow this basic sequence of commands. First, fire up VS Code and open the folder you intend to develop in. Launch the inline terminal and type:
$ vue create routing-demo-app
Choose “Manually”, select the “Routing” option and hit enter. Then, use “n” for history mode and the default for everything else. A “Y” for history mode would represent a project that conforms to that first example we discussed around routing. Next, change directories and add the following packages:
$ cd routing-demo-app
$ yarn add axios ant-design-vue
$ yarn add node-sass sass-loader
$ yarn
$ yarn serve
Using yarn serve
should present you with a URL for visiting your running boilerplate application. Open `src/main.js` and add the following lines after `import router from ‘./router’`:
import axios from 'axios'; // http clientimport Antd from 'ant-design-vue'; // your ui library
import 'ant-design-vue/dist/antd.css'; // ui library styles
Vue.use(Antd); // setting up UI libraryVue.prototype.$http = axios; // setting up axios
These lines import the packages you added earlier from the command line and set us up to have access to Axios throughout any component in the application. As we’ll see, Axios helps to normalize the interface for http requests through the browser, making them dead simple to use.
Now, inside `router/index.js`, replace the routing:
const routes = [
{
path: '/',
name: 'home',
component: Home
}
]
Next, remove the “nav” div from `src/App.vue` (we won’t need it) and add a :key
attribute to the router-view component. Make sure you add the colon, as it tells Vue to process the reference being passed. We want it to look like this:
<router-view :key="$route.fullPath"/>
That’s it for set-up!
While at Chewy we use our own internal UI library and design system, we will use “AntD ” in this example so you can follow along. With a good UI library, developers don’t have to worry about color palettes, UI styles or many concerns related to accessibility. Those features and solutions would already be baked into simple-to-use stylesheets and convenient reusable web components.
Templating
Let’s go to `src/views/Home.vue` and delete everything inside of the DIV tag with the “class” of “home”. Your template snippet should now look like this:
<div class="home"></div>
Now, change this into a form element by selecting “div” from the root template element and typing “form”. Notice how VS Code will automatically change the closing tag for you. Set your cursor inside this element and add the following inside the tag:
<form class="home">
<p>
<a-input-search name="q" v-model="q" class="searchbox"
@search="onSearch" enterButton="Search"></a-input-search>
</p>
<p>
<a-radio-group v-model="petType">
<a-radio-button
v-for="petType in getPetTypes"
:key="petType.value" :value="petType.value">
{{petType.label}}</a-radio-button>
</a-radio-group>
</p>
<hr>
<a-table :columns="columns" :dataSource="results"></a-table></form>
If you’re using Visual Studio Code, you may want to experiment with the power of Emmet, which comes enabled by default with this IDE. It allows you to use shortcuts (as shown below) to expand into full HTML.
p>a-input-search[enterButton=Search]
Wiring it Up
Inside the <script> tag, add your menu item data:
const petTypes = 'All,Dogs,Cats,Birds';
Next you’ll add the column descriptions for the table component. This array takes advantage of how the table component is structured by allowing developers to add both the name of the header and instructions on how to render the cells below them in one fell swoop. Here, we specify a template to use for the “title” cells (we will add this shortly) and join the array of values returned by “author_name” and “subject” in the response data:
const columns = [
{
title: 'Title',
key: 'title',
scopedSlots: { customRender: 'title' },
width: '20%'
},
{
title: 'Date',
dataIndex: 'first_publish_year',
width: '10%'
},
{
title: 'Author(s)',
dataIndex: 'author_name',
width: '20%',
customRender: row => `${row ? row.join(', '): ''}`
},
{
title: 'Subject',
key: 'subject',
customRender: row => `${row.subject ?
row.subject.join(' '): ''}`
},
];
Add the following attributes inside export default
:
data: () => {
return {
dataSource: process.env.VUE_APP_DATA_SOURCE || 'remote',
results: [],
columns,
petType: 'all',
q: ''
};
},
created: {
// TBD
},
methods: {
onSearch: () => {
// TBD
}
},
computed: {
getPetTypes: () => {
const pts = petTypes.split(',');
return pts.map(pt => Object({
value: pt.toLowerCase(),
label: pt
})
)
}
}
Now, append these styles below your script tag and add the “lang” attribute so that you can take advantage of the scss support we added earlier.
<style lang="scss">
.home {
width: 80%;
margin: 30px auto;
.searchbox { width: 60%; }
.thumb { text-align: center; }
}
</style>
Personally, I really appreciate how simple it is to change and edit templates, code and styling in Vue’s signature (and entirely optional) “single file component” format.
That said, the final three steps are all located in this same document. We need to add our custom template for one of the table cells, add our code to perform the search action (the “onSearch” method), and add our code to retrieve the data from the API (which we will place in our “created” lifecycle hook).
Add the following slot content to the template inside the table component:
<a-table :columns="columns" :dataSource="results">
<div class="thumb" slot="title" slot-scope="row">
<img v-if="row['title']" :src="showThumb(row)" width="50" /><br />
<strong>{{ row.title }}</strong>
</div>
</a-table>
Our onSearch method is short and sweet, firing when the user hits the “search” button or “enter”. It takes the data bound to our form and updates the route based on the user’s choices. Meanwhile, we use a special method to render the thumbnail path for each item in our result set depending on whether an image is present (a custom “default” image was added to the assets directory for these cases).
methods: {
onSearch() {
const routeQuery = {};
routeQuery['q'] = this.q;
routeQuery['petType'] = this.petType;
this.$router.replace({
path: this.$route.path,
query: routeQuery
});
}
},
showThumb(row) {
return row['cover_i'] ?
`//covers.openlibrary.org/w/id/${row['cover_i']}-S.jpg`:
`/assets/default-S.jpg`;
}
Finally, what happens in the “created” life cycle hook makes it all work. Back when we set the “key” value of the router-view to the full path of the route, it ensures the component will be re-rendered each time the route changes (even just the parameters). In other words, each time the user’s search options are submitted, the “created” hook is called and the latest results are assigned to our results property.
created() {
if(this.$route.query['q'] || this.$route.query['petType']) {
this.q = this.$route.query['q'];
this.petType = this.$route.query['petType'];
this.$http
.get(this.dataSource === 'remote' ?
`http://openlibrary.org/search.json?title=${this.q}
&subject=${(this.petType==='all'?'pets':this.petType)}`:
'/assets/search_example.json')
.then(response => {
this.results = response.data.docs;
});
}
},
Also note that I’ve created a data property which pulls from an environment variable that determines whether the static JSON file should be called. This setting helps keep things stable as I work through how best to format the templates. It not only protects me from API related issues, but also means that I can develop while completely offline (if I find myself working on functionality that doesn’t require dynamic data). You can change this environment variable to “local” by placing an “.env” file at the root of your project directory and adding this line:
VUE_APP_DATA_SOURCE=local
You’ll need to re-run yarn serve
to see this take effect. You can change environment variables by pointing to environment-specific .env files at build time during your project deployment or by passing override values directly via the build commands, allowing you to change key aspects of your app as needed.
And we are done! Try clicking around your new prototype. Test different keywords and pet types. Paste the resulting link in a different browser and see the same search results appear.
Try this entire process a few times from scratch using Vue CLI and different project names, and see how long it takes you to get an entirely new prototype up and running. As presented here, it should easily take less than ten minutes. Experiment with shaving off more steps and tuning the workflow to suit your own style.
Conclusion
Prototyping can often be a thankless task. At times it can even seem like a speed bump to releasing short iterative changes or completing time-sensitive launches. However, prototypes more often serve as a shield against wasted effort retooling production-ready code. They are also invaluable tools for conducting user research and provide ample opportunities for discovering hidden yet vital business requirements early on.
Basic prototyping workflow:
- Requirements and Designs
- Gather Your Data
- Build and Test Your Prototype
- Deployment
- Discussion (rinse and repeat)
Done right, rapid, collaboration-friendly working examples are truly a welcome blueprint for success!
by Dudley Ablorh-Bryan
UX Developer @ Chewy
If you have any questions about careers at Chewy, please visit https://www.chewy.com/jobs