In this tutorial I’m going to show you how to build a photo gallery with a ready-to-use template from , powered by the API, hosted on the Cosmic JS App Server. HTML5UP Cosmic JS TL;DR View demo View the codebase on GitHub Prerequisites You’ll need node JS and npm. Make sure you already have them before you start. Getting Started First of all we’ll need to install VueJS CLI and start the new project. Run the following commands to do this: npm install -g vue-clivue init webpack vuejs-photo-gallerycd vuejs-photo-gallerynpm install After you’ll setup this project you’ll be able to run cd vuejs-photo-gallerynpm run dev And play with your app in browser Doing everything using the existing git repo First of all, you have to be sure you have node > 6.x installed, than run the following commands: npm install -g vue-cligit clone https://github.com/cosmicjs/vuejs-photo-gallery.gitcd vuejs-photo-gallerynpm installnpm run dev Browser window will open automatically once you’ll run the last command Setting up Cosmic JS library First of all, install Cosmic JS Angular/JavaScript library npm install cosmicjs --save Now you should be able to import Cosmic object and perform Cosmic JS API calls like following: import Cosmic from 'cosmicjs';const bucket = { slug: 'your-bucket-slug' }; Cosmic.getObjects({ bucket }, (err, res) => { console.log(res.objects);}); Setting up things with Cosmic JS Create the bucket and remeber the bucket name ( in our case): vuejs-photo-gallery Than create a new object type named Photo. We also need a way to store the picture itself. Please enter the “Metafields Template” tab and add “Image/File” type metafield with key . This metafield will store the image. We don’t need anything more, so just set the name and save object type. After save you’ll be redirected to ‘New Photo’ page. Create some photos using this page and save them - we'll use them as test data. image The only thing left is to set site-wide things, such as title, tagline, social icons and footer text. Let’s create one more object type named Global. And add the following metafields: Tagline — Plain Text Area Twitter — Plain Text Input Instagram — Plain Text Input Github — Plain Text Input Email — Plain Text Input Footer — Plain Text Area VueJS environments We want to pick our bucket name automatically on deploy. In this case we’ll need configuration file, which we’ll populate with correct data during deploy. Create to match the following: src/config.js Config = { bucket: 'vuejs-photo-gallery'}; module.exports = Config; Prepare assets Download the template ZIP and unzip it somewhere. In our case we have the following content: - this is our HTML markup, we'll move it to Vue components later. - this is sample images folder. We don't need it, our images will be served from Cosmic JS servers - other assets such as CSS, fonts, javascript files. We'll need CSS and fonts. Let's ignore Javascript for now, since we're planning to use VueJS. Let's copy and folders to folder inside our project. This will allow us to add these files to the build automatically as static assets. index.html images assets assets/css assets/fonts static Prepare index.html Now it’s time to include our assets to . Add the following to the section: index.html head <meta name="viewport" content="width=device-width, initial-scale=1" /><!--[if lte IE 8]><script src="assets/js/ie/html5shiv.js"></script><![endif]--><link rel="stylesheet" href="static/css/main.css" /><!--[if lte IE 8]><link rel="stylesheet" href="assets/css/ie8.css" /><![endif]--><!--[if lte IE 9]><link rel="stylesheet" href="assets/css/ie9.css" /><![endif]--><noscript><link rel="stylesheet" href="static/css/noscript.css" /></noscript> This will include our static assets. Once we’ll implement correct markup, appearance will be automatically set via CSS. VueJS components Looking into our page, we can define components we’ll need: and - for this will be very simple components, which will display global data which will be loaded on app startup - this component will display photos thumbnails - this component will display the big photo and provide prev/next navigation. Please note - we're creating component's templates markup using template we downloaded before (doing copy-paste from index.html and applying VueJS directives). Header Footer Thumbs Viwer component: Header <template> <header id="header"> <h1>{{ header }}</h1> <div v-html="text"></div> <ul class="icons"> <li><a :href="twitter" class="icon fa-twitter"><span class="label">Twitter</span></a></li> <li><a :href="instagram" class="icon fa-instagram"><span class="label">Instagram</span></a></li> <li><a :href="github" class="icon fa-github"><span class="label">Github</span></a></li> <li><a :href="email" class="icon fa-envelope-o"><span class="label">Email</span></a></li> </ul> </header></template> <script>import {EventBus} from '../event_bus'; export default { name: 'app-header', created() { EventBus.$on('global_loaded', (obj) => { this.header = obj.title; this.text = obj.metafield.tagline.value; this.twitter = obj.metafield.twitter.value; this.instagram = obj.metafield.instagram.value; this.github = obj.metafield.github.value; this.email = 'mailto:' + obj.metafield.email.value; }); }, data () { return { text: null, twitter: '', instagram: '', github: '', email: '', header: '' } }}</script> component is very similar: Footer <template> <footer id="footer"> <div v-html="text"></div> </footer></template> <script>import {EventBus} from '../event_bus'; export default { name: 'app-footer', created() { EventBus.$on('global_loaded', (obj) => { this.text = obj.metafield.footer.value; }); }, data () { return { text: null } }}</script> Both components uses to receive data from parent component. I'll tell about the Event bus later in this post. EventBus Thumbs component This component is more complicated than previous two: <template> <section id="thumbnails"> <article v-for="(item, index) in items" v-bind:class="{ 'active': activeIndex == index }"> <a class="thumbnail" v-on:click="selectImage(item, index)"> <img v-bind:src="item.metafield.image.imgix_url" alt="" /> </a> <h2>{{ item.title }}</h2> <div v-html="item.content"></div> </article> </section></template> <script>import Cosmic from 'cosmicjs';import * as Config from '../config';import {EventBus} from '../event_bus'; const bucket = { slug: Config.bucket }; export default { name: 'thumbs', props: ['bus'], created() { Cosmic.getObjectType({ bucket }, { type_slug: 'photos' }, (err, res) => { this.items = res.objects.all; EventBus.$emit('loaded', this.items[0]); }); EventBus.$on('move', (dir) => { this.activeIndex = this.activeIndex + dir; if (dir > 0 && this.activeIndex >= this.items.length) { this.activeIndex = 0; } if (dir < 0 && this.activeIndex < 0) { this.activeIndex = this.items.length - 1; } EventBus.$emit('loaded', this.items[this.activeIndex]); }); }, data () { return { items: [], activeIndex: 0 } }, methods: { selectImage (itm, index) { EventBus.$emit('loaded', itm); this.activeIndex = index; } }}</script> On component creation we’re fetching our photos list and subscribes to Event bus ‘move’ event. Than we just render these photos and emit a new event each time when user selects a new photo. Viewer component <template> <div id="viewer"> <div class="inner"> <div class="nav-next" v-on:click="selectNext()"></div> <div class="nav-previous" v-on:click="selectPrev()"></div> </div> <div class="slide active" v-if="img"> <div class="caption"> <h2>{{ img.title }}</h2> <div v-html="img.content"></div> </div> <div class="image" v-bind:style='{ backgroundImage: "url(" + img.metafield.image.imgix_url + ")" }'> </div> </div> </div></template> <script>import {EventBus} from '../event_bus'; export default { name: 'viewer', props: ['bus'], created() { EventBus.$on('loaded', (obj) => { this.img = obj; }); }, data () { return { img: null } }, methods: { selectNext() { EventBus.$emit('move', 1); }, selectPrev() { EventBus.$emit('move', -1); } }}</script> This component subscribes to event via Event bus. This event means that uses selected a new photo and we have to show it bigger size; Another task of this component is to notify Thumbs component when users click prev/next buttons. Component uses Event bus for this purpose also. loaded Event bus Event bus follows publish-subscribe pattern and allows us setup communication between Thumbs and Viewer components. Both components are on the same level (no parent-child relationship), so we need something more complicated than simple event emission. Event bus implementation is very easy ( ): src/event_bus.js import Vue from 'vue';export const EventBus = new Vue(); This event bus is used to fire events in one component (using ) and subscribe on them in another component (using ). EventBus.$emit EventBus.$on Concatenating everything together Now it’s time to concat everything with component: App <template> <div> <div id="main"> <app-header></app-header> <thumbs></thumbs> <app-footer></app-footer> </div> <viewer></viewer> </div></template> <script>import AppHeader from './components/AppHeader'import AppFooter from './components/AppFooter'import Thumbs from './components/Thumbs'import Viewer from './components/Viewer'import Vue from 'vue';import Cosmic from 'cosmicjs';import * as Config from './config';import {EventBus} from './event_bus'; const bucket = { slug: Config.bucket }; export default { name: 'app', components: { AppFooter, AppHeader, Thumbs, Viewer }, created() { Cosmic.getObjectType({ bucket }, { type_slug: 'globals' }, (err, res) => { EventBus.$emit('global_loaded', res.objects.all[0]); console.log(res.objects.all[0]); }); },}</script> This component loads globals data on creation and notify and components via . AppHeader AppFooter EventBus Deploy to Cosmic JS servers Cosmic JS has some requirements for deploying apps: it must be in public git repo depending on your platform must be met Specific requirements In our case we actually have HTML5 app, so we’ll need some additional software. Prepare config Create a file in your project directory: prepare.js var fs = require('fs'); var str = ` Config = { bucket: '${process.env.COSMIC_BUCKET}' }; module.exports = Config;`;fs.writeFile("./src/config.js", str, function(err) { if(err) { return console.log(err); } console.log("The file was saved!");}); This script will rewrite application config file (see more info above) file to use your Cosmic JS bucket write key and bucket name. Modify package.json VueJS CLI adds some packaged on as . We have to move them all into to make our scripts work in Cosmic JS servers. package.json devDependencies dependencies Prepare software We’ll also need something to serve our Angular app. We’ll use Express framework: npm install --save express Add the following to your package.json: { ... "scripts": { ... "start": "node app.js" }, ...} The main point is to have command defined in the section (you can safely replace default angular command). This is the command which will be run to start our app. So now we have the only thing left - create the file: start scripts start app.js const express = require('express')const app = express() app.use(express.static('./dist')); app.listen(process.env.PORT, function () {}); This is a simple Express app which serves dir as dir of static files. Please take note - app listens on port specified via environment variable, it's important to run apps on Cosmic JS App Server. dist PORT Build VueJS app for production We’ll use to do this (dokku section): app.json predeploy { "scripts": { "dokku": { "predeploy": "node prepare.js && npm run build" } }} This script will be executed before we’ll launch our express app to build the VueJS app for production. Run it! Now you can enter ‘Deploy Web App’ page in your Cosmic JS Dashboard. Simply enter your repo URL and click ‘Deploy to Web’ — deploy process will be started and app become ready in a couple of minutes. Conclusion Using Cosmic JS App Server allows quickly deploy the application to hosting using a git repo and don’t worry about server configuration and software installation — everything will be done by Cosmic JS servers. This article originally appeared on the . Cosmic JS Blog