Skip to content

Instantly share code, notes, and snippets.

@curtisbelt

curtisbelt/AppFlex.js

Last active Sep 7, 2019
Embed
What would you like to do?
Sample Vue Components
/*
The full project features a page builder on the backend, with various "Flexible Components"
the user can select from to build their page. Each of these corresponds to a Vue compoennt
on the frontend.
There are two ways to use the component:
- Manually use a specific Flex component
- Automatically load all Flex components (when used on page that supports it)
Single usage:
```
<AppFlex
component="FlexFormSendMessage"
:settings="{email: $getPageData('email')}"
/>
```
Automatic usage -- no props needed, AppFlex does the rest
```
<AppFlex />
```
Instead of importing all of the "Flex" vue components on a page, even if most of them may
not be used on that page - this AppFlex component will dynamically load them.
The parent component's data is $getPageData is made available here via `inject`, which is
made possible by the `provide` on the parent component.
```
async asyncData(ctx) {
return {
pageData: await ctx.app.$wp.loadPageData('posts')
};
},
provide() {
return {
$getPageData: this.$getPageData
};
},
methods: {
$getPageData(path, fallback = null) {
return this.$get(this.pageData, path, fallback);
}
}
```
*/
export default {
name: 'AppFlex',
inject: ['$getPageData'],
props: {
component: {
type: String,
default: ''
},
settings: {
type: Object,
default: () => {}
}
},
inheritAttrs: false,
render(h) {
if (this.component) {
const children = Object.keys(this.$slots).map(slot =>
h('template', { slot }, this.$slots[slot])
);
const loadComponent = this.loadComponents;
return h('keep-alive', [
h(
loadComponent.component,
{
props: {
settings: this.$get(loadComponent, 'settings', {})
},
attrs: this.$attrs,
on: this.$listeners,
scopedSlots: this.$scopedSlots
},
children
)
]);
} else if (this.$getPageData('components')) {
return h('keep-alive', [
h(
'div',
{ class: 'AppFlex' },
this.loadComponents.map(function(loaded) {
return h(loaded.component, {
props: {
settings: loaded.settings
}
});
})
)
]);
}
},
computed: {
loadComponents() {
if (this.component) {
return {
component: () => import(`~/components/shared/Flex/${this.component}`),
settings: this.settings
};
}
return this.$getPageData('components', []).map(component => {
return {
component: () => import(`~/components/shared/Flex/${component._is}`),
settings: component
};
});
}
}
};
<template>
<div
id="FlexFormSendMessage"
class="FlexFormSendMessage bg-gray-100 border-gray-300 border-t relative"
>
<form
class="px-6 py-6 md:px-4 max-w-2xl m-auto"
@submit.prevent="submit"
>
<div class="flex items-center my-6 text-3xl lg:text-4xl font-serif">
{{ $get(settings,'title','Send a Message') + ' ' }}
<span
v-if="$get(settings, 'email', '')"
class="text-lg text-brand-blue-200 mx-4 font-sans font-hairline tracking-tight"
>{{ $get(settings, 'email', '') }}
</span>
</div>
<div class="-mx-4 -my-2 flex flex-wrap justify-between w-full">
<div class="w-1/2 px-4 py-2">
<label
for="first_name"
class="block my-2 text-lg"
>
First Name *
</label>
<FormText
id="first_name"
v-model="body.first_name"
:required="true"
:sync-route-query="false"
class="w-full"
type="text"
name="first_name"
/>
</div>
<div class="w-1/2 px-4 py-2">
<label
for="last_name"
class="block my-2 text-lg"
>
Last Name *
</label>
<FormText
id="last_name"
v-model="body.last_name"
:required="true"
:sync-route-query="false"
class="w-full"
type="text"
name="last_name"
/>
</div>
<div class="w-1/2 px-4 py-2">
<label
for="email"
class="block my-2 text-lg"
>
Email *
</label>
<FormText
id="email"
v-model="body.email"
:required="true"
:sync-route-query="false"
class="w-full"
type="email"
name="email"
/>
</div>
<div class="w-1/2 px-4 py-2">
<label
for="phone"
class="block my-2 text-lg"
>
Phone Number
</label>
<FormText
id="phone"
v-model="body.phone_number"
v-mask="'(###) ###-####'"
:sync-route-query="false"
class="w-full"
name="phone"
placeholder="(000) 000-0000"
type="tel"
/>
</div>
<div class="w-full px-4 py-2">
<label
for="name"
class="block my-2 text-lg"
>
Your Message
</label>
<textarea
id="message"
ref="message"
v-model="body.message"
:required="true"
name="message"
class="pl-4 pt-2 h-12 w-full border border-gray-300 text-xs leading-loose"
/>
</div>
</div>
<div class="text-center">
<button
class="bg-brand-blue-300 text-white px-8 py-3 my-8 mx-auto text-center text-xs tracking-wide uppercase"
>
{{ $get(settings,'submit_label','Send') }}
</button>
</div>
</form>
<div
v-if="showSuccess"
class="SuccessMessage absolute inset-0 flex justify-center items-center"
>
<span class="p-4 text-lg bg-brand-blue-400 text-white">message sent</span>
</div>
</div>
</template>
<script>
import FormText from '~/components/shared/Form/FormText';
import { mask } from 'vue-the-mask';
export default {
name: 'FlexFormSendMessage',
components: {
FormText
},
directives: { mask },
props: {
settings: {
type: Object,
default: () => {}
}
},
inject: ['$getPageData'],
data() {
return {
showSuccess: false,
body: {
first_name: null,
last_name: null,
email: null,
phone_number: null,
message: null
}
};
},
methods: {
async submit() {
let contact,
contact_id,
property_id,
user_id = null;
if (this.$myTribusEmbed.isAuthenticated()) {
contact = this.$myTribusEmbed.getContact();
contact_id = contact.id;
}
if (this.$getPageData('listing')) {
property_id = this.$getPageData('id');
}
if (this.$getPageData('display_name')) {
user_id = this.$getPageData('id');
}
let response = await this.$displet.postMessage({
id: null,
contact_id,
property_id,
user_id,
requested_showing_at: null,
body: JSON.stringify(this.body)
});
if (response) {
this.showSuccess = true;
window.setTimeout(() => {
this.showSuccess = false;
}, 2000);
}
}
}
};
</script>
<style lang='pcss' scoped>
textarea {
color: #d5d5d5;
height: 280px;
}
input::placeholder {
color: #d5d5d5;
}
</style>
<template>
<div class="FlexMap">
<h3 class="font-serif text-brand-blue-400 text-4xl font-hairline text-center my-4">
{{ $get(settings, 'title', '') }}
</h3>
<AppContainer
class="my-4"
v-html="$get(settings, 'text', '')"
/>
<div class="w-full flex justify-end">
<a
class="ttext-sm uppercase font-hairline text-brand-blue-400 p-2"
target="blank"
:href="'https://www.google.com/maps/dir/?api=1&destination=' + encodeURIComponent($get(settings, 'map.address'))"
>
get directions
</a>
</div>
<div
class="relative z-20"
style="height: 397px"
>
<ClientOnly>
<l-map
ref="MyMap"
:center="center"
:zoom="10"
>
<l-tile-layer url="https://{s}.tile.osm.org/{z}/{x}/{y}.png" />
<l-marker
:lat-lng="center"
/>
</l-map>
</ClientOnly>
</div>
</div>
</template>
<script>
export default {
name: 'FlexMap',
props: {
settings: {
type: Object,
default: () => {}
}
},
data() {
return {
center: [this.$get(this.settings, 'map.lat', 0), this.$get(this.settings, 'map.lng', 0)],
community: null
};
},
mounted() {
this.$wp
.posts({
id: this.settings.community_post_id
})
.then(data => {
this.community = this.$get(data, 'results[0]');
});
this.$nextTick(() => {
if (this.$refs.MyMap) {
let map = this.$refs.MyMap.mapObject;
map.scrollWheelZoom.disable();
map.on('focus', function() {
map.scrollWheelZoom.enable();
});
map.on('blur', function() {
map.scrollWheelZoom.disable();
});
}
});
}
};
</script>
<template>
<div class="UiPagination flex">
<div
class="hidden md:block mr-1 relative border border-gray-300 text-xs bg-white px-2 py-2 cursor-pointer select-none"
:class="{'opacity-50 cursor-not-allowed': currentPage <= 1}"
@click="goToPage(currentPage-1)"
>
<AppIcon
icon="chevron-left"
class="h-full text-3xs pointer-events-none"
/>
</div>
<FormSelect
:choices="pageChoices"
name="page"
:value="1"
:disabled="totalPages<=1"
:sync-route-query="true"
/>
<div
class="hidden md:block ml-1 relative border border-gray-300 text-xs bg-white px-2 py-2 cursor-pointer select-none"
:class="{'opacity-50 cursor-not-allowed': currentPage >= totalPages}"
@click="goToPage(currentPage+1)"
>
<AppIcon
icon="chevron-right"
class="h-full text-3xs pointer-events-none"
/>
</div>
</div>
</template>
<script>
import FormSelect from '~/components/shared/Form/FormSelect';
export default {
name: 'UiPagination',
components: {
FormSelect
},
inject: ['$getPageData'],
computed: {
currentPage() {
return parseInt(this.$route.query.page ? this.$route.query.page : 1);
},
totalPages() {
let totalLimit = this.$getPageData('total', 0) > 9500 ? 9500 : this.$getPageData('total', 0);
return Math.ceil(totalLimit / this.$getPageData('__params.per_page'));
},
pageChoices() {
let choices = [];
for (let i = 1; i <= this.totalPages; i++) {
choices.push({
label: i.toLocaleString(),
value: i
});
}
return choices;
}
},
methods: {
goToSelectedPage(event) {
this.goToPage(event.target.value);
},
goToPage(pageNumber, event) {
if (pageNumber > this.totalPages || pageNumber < 1) {
return;
}
if (pageNumber == 1) {
pageNumber = undefined;
}
this.$router.push({
query: { ...this.$route.query, page: pageNumber }
});
}
}
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment