#app(v-bind:data-state="gallery")
form.ui-form(v-on:submit.prevent="transition({ type: 'SEARCH', query: this.query })")
input.ui-input(
placeholder="Search Flickr for photos..."
type="search"
v-model="query"
v-bind:disabled="gallery === 'loading'")
.ui-buttons
button.ui-button(
v-bind:disabled="gallery === 'loading'") {{searchText}}
button.ui-button(
v-if="gallery === 'loading'"
type="button"
v-on:click="transition({ type: 'CANCEL_SEARCH' })") Cancel
section.ui-items
span.ui-error(v-cloak v-if="gallery === 'error'") Uh oh, search failed.
img.ui-item(v-else
v-for="item in items"
v-bind:key="item"
v-bind:src="item"
v-on:click="transition({ type: 'SELECT_PHOTO', item })")
section.ui-photo-detail(
v-if="gallery === 'photo'"
v-on:click="transition({ type: 'EXIT_PHOTO' })")
img.ui-photo(v-bind:src="photo")
View Compiled
textarea
padding: 1rem
h1
text-align: center
font-weight: 100
#app
&:after
content: 'current state: ' attr(data-state)
position: fixed
bottom: .5rem
color: white
background-color: rgba(black, 0.4)
padding: .5rem 1rem
border-radius: 1rem
left: 50%
transform: translateX(-50%)
text-shadow: 0 0 .1rem black
pointer-events: none
.ui-form
display: flex
margin: 0 auto
width: auto
position: sticky
justify-content: center
padding: 1rem
button
padding: 0.5rem
margin-left: 0.5rem
.ui-items
display: grid
grid-gap: 1rem
padding: 1rem
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr))
grid-template-rows: minmax(160px, max-content)
.ui-item
object-fit: cover
width: 100%
height: 100%
.ui-photo-detail
position: absolute
left: 0
top: 0
right: 0
bottom: 0
display: flex
align-items: center
justify-content: center
background-color: alpha(#FFF, 80%)
.ui-photo
max-height: 100vh
max-width: 100vw
Vue.config.devtools = true
const transitions = {
start: {
SEARCH: 'loading'
},
loading: {
SEARCH_SUCCESS: 'gallery',
SEARCH_FAILURE: 'error',
CANCEL_SEARCH: 'gallery'
},
error: {
SEARCH: 'loading'
},
gallery: {
SEARCH: 'loading',
SELECT_PHOTO: 'photo'
},
photo: {
EXIT_PHOTO: 'gallery'
}
}
const app = new Vue({
el: '#app',
data() {
return {
gallery: 'start', // finite state
query: '',
items: [],
photo: ''
}
},
computed: {
searchText() {
return {
loading: 'Searching...',
error: 'Try search again',
start: 'Search'
}[this.gallery] || 'Search'
}
},
methods: {
command(nextState, action) { // Logic
switch (nextState) {
case 'loading':
this.search(action.query)
break
case 'gallery':
if (action.items) return { items: action.items }
break
case 'photo':
if (action.item) return { photo: action.item }
break
}
},
transition(action) {
const next = transitions[this.gallery][action.type]
next && Object.assign(app.$data, {
gallery: next,
...this.command(next, action)
})
},
search(query) {
const encodedQuery = encodeURIComponent(query)
const url = `https://dog.ceo/api/breeds/image/random/6`
fetch(url)
.then(response => response.json())
.then(data => {
this.transition({ type: 'SEARCH_SUCCESS', items: data.message })
})
.catch(error => {
this.transition({ type: 'SEARCH_FAILURE' })
})
}
}
})
View Compiled
This Pen doesn't use any external CSS resources.