Developing an Apple Music Search into Contentful using UI Extensions

Headless CMS customization

Image for post
Image for post

Apple Music Search

const app = new Vue({
el: '#extension',
template: `
<section>
<input v-model="query" @keyup.enter="search" />
</section>
`,
data() {
return {
music: null,
query: ""
}
},
methods: {
search() {
this.music.api.search(this.query, {
limit: 5,
types: 'songs'
})
.then(data => {
console.log(data)
})
.catch(console.error)
}
},
mounted() {
document.addEventListener('musickitloaded', () => {
MusicKit.configure({
developerToken: 'YOUR_DEVELOPER_TOKEN'
})

this.music = MusicKit.getInstance()
})
}
})

Adding an Extension

Image for post
Image for post

Integrating UI Extension SDK

data() {
return {
extension: null,
...
}
},
...
mounted() {
window.contentfulExtension.init(extension => {
this.extension = extension

this.extension.window.startAutoResizer()
})
...
}
Vue.component('song', {
props: ['song', 'set'],
template: `
<div class="song" @click="setSong">
<div class="artwork">
<img :src="song.artwork" height="40" width="40" />
</div>
<div class="title">
<h1>{{ song.name }}</h1>
<h2>{{ song.artist }}</h1>
</div>
<div class="actions" v-if="set">
<button @click="removeSong">
<cf-icon name="delete"><svg width="14" height="14" viewBox="-1 -1 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="currentColor"><path d="M4.846 7l-3.77-3.77L0 2.155 2.154 0 3.23 1.077 7 4.847l3.77-3.77L11.845 0 14 2.154 12.923 3.23 9.153 7l3.77 3.77L14 11.845 11.846 14l-1.077-1.077L7 9.153l-3.77 3.77L2.155 14 0 11.846l1.077-1.077z"></path></g></svg></cf-icon>
</button>
</div>
</div>
`,
methods() {
setSong() {
if (!this.set) {
this.$emit('set-song')
}
},
removeSong() {
this.$emit('remove-song')
}
}
}
search() {
this.music.api.search(this.query, {
limit: 5,
types: 'songs'
})
.then(data => {
this.songs = data.songs.data.map(song => {
return {
id: song.id,
artist: song.attributes.artistName,
name: song.attributes.name,
artwork: song.attributes.artwork.url,
url: song.attributes.url,
isrc: song.attributes.isrc,
preview: song.attributes.previews[0].url
})
.catch(console.error)
}
methods: {
set(song) {
this.extension.field.setValue(song)
},
remove() {
this.extension.field.removeValue()
}
}
template: `
<section>
<template v-if="extension">
<template v-if="extension.field.getValue()">
<song :song="extension.field.getValue()" set="true" v-on:remove-song="remove"></song>
</template>
<template v-else>
<input v-model="query" placeholder="Apple Music Search" @keyup.enter="search" />

<div id="songs">
<song v-for="song in songs" :song="song" v-on:set-song="set(song)"></song>
</div>
</template>
</template>
</section>
`

Styling the UI Extension

#songs{
margin-top: 0.5em;
}
#songs .song{
align-items: center;
border-bottom: 1px solid #E5EBEC;
cursor: pointer;
display: flex;
height: 60px;
padding: 0 0.5em;
}
#songs .song:hover{
background: #f7f9fa;
}
#songs .song .artwork{
background: #E5EBEC;
height: 40px;
margin-right: 0.5em;
width: 40px;
}
#songs .song .title{
flex: 1;
}
#songs .song .title h1{
font-weight: bold;
}
#songs .song .actions button{
appearance: none;
-webkit-appearance: none;
background: none;
border: 0;
cursor: pointer;
}
#songs .song .actions button:hover{
color: #536171;
}
svg{
color: #8091a5;
transition: color 200ms ease-in-out;
}

Written by

I develop websites for rock 'n' roll bands and get paid in sex and drugs. Previously Silva Artist Management, SoundCloud, and Songkick. Currently: Available

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store