My vue component is like this :
<a :href="baseUrl+'/message/inbox'"
:class="{ 'active': currentPath === '/message/inbox' }"
>
Message
</a>
If they meet the conditions then the message menu will be active
But, I want to make it like this :
<a :href="baseUrl+'/message/inbox'"
:class="{ 'active': currentPath in array ('/message/inbox', '/message/inbox/detail') }"
>
Message
</a>
So it will check currentPath in the array
How can I do it?
Update :
If I have menu again like this :
<a :href="baseUrl+'/store/sale'"
:class="{ 'active': currentPath in array ('/store/sale', '/store/sale/detail') }"
>
Sale
</a>
Or more menu
How to implement it?
Update 2
<a :href="baseUrl+'/message/inbox'"
:class="{ 'active': isActive }"
>
Message
</a>
<a :href="baseUrl+'/store/sale'"
:class="{ 'active': isActiveSale }"
>
Message
</a>
computed: {
isActive () {
return ['/message/inbox', '/message/inbox/detail'].indexOf(this.currentPath) > -1
},
isActiveSale () {
return ['/store/sale', '/store/sale/detail'].indexOf(this.currentPath) > -1
}
}
You can use computed properties :
computed: {
currentPathInInbox: function() {
var arrayInbox = ['/message/inbox', '/message/inbox/detail'];
return arrayInbox.indexOf(this.currentPath) > -1;
}
}
and in template :
:class="{ 'active': currentPathInInbox }"
or with no computed properties :
:class="{ 'active': (currentPath === '/message/inbox' || (currentPath === '/message/inbox/detail') }"
UPDATED :
I think you need component :
Vue.component( 'linkWithPath', {
template: '<div><a :href="baseUrl + relativeUrl"' +
':class="{ \'active\': isActive }">' +
'<slot>Link name</slot></a></div>',
props: {
baseUrl: { type: String },
currentPath: { type: String, default: '' },
relativeUrl: { type: String }
},
computed: {
isActive: function() {
return [ this.relativeUrl, this.relativeUrl + '/detail'].indexOf(this.currentPath) > -1;
}
}
});
Vue.component( 'listOfLinksWithPath', {
template: '<div><link-with-path v-for="menuItem in menuArray"' +
':key="menuItem" :base-url="baseUrl" :current-path="currentPath"' +
':relative-url="menuItem.url">{{ menuItem.name }}</link-with-path></div>',
props: {
baseUrl: { type: String },
currentPath: { type: String },
menuArray: { type: Array }
}
});
new Vue({
el: "#app",
data: function() {
return {
baseUrl: 'http://www.CHANGETHISURL.com',
currentPath: '/message/inbox',
menuArray: [ { name: 'Message', url: '/message/inbox' },
{ name: 'Sale', url: '/store/sale' } ]
}
},
methods: {
changeCurrentPath: function() {
this.currentPath = '/store/sale'
}
}
});
a.active{
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.5/vue.js"></script>
<div id="app">
<p>Link is active when it is red.</p>
<list-of-links-with-path :base-url="baseUrl" :menu-array="menuArray" :current-path="currentPath"></list-of-links-with-path>
<br />
<button #click="changeCurrentPath" type="button">Change current path</button>
<br />
currentPath : {{ currentPath }}
</div>
Add a computed property.
computed: {
isActive () {
return ['/message/inbox', '/message/inbox/detail'].indexOf(this.currentPath) > -1
}
}
So you'll be able to use:
<a :href="baseUrl+'/message/inbox'"
:class="{ 'active': isActive }"
>
Message
</a>
Related
In my application I have a list of documents that I display in a table, each document has a specific type, which is specified in json file as enum values.
I can already display all the documents without any problems, but now I have tried to create an input where the user can choose from the list of enum values, and when doing so only the documents with the selected enum type will be shown in the table.
This does sorta work, but the problem is that I have somehow created an infinite update loop, which causes the application to randomly stop working.
This is the template. I am using a custom made template component, but that is not related to the issue.
<template>
<b-container>
<div #dragover.prevent.stop #drop.prevent.stop="onDropEvent">
<b-card-header header-tag="header" class="p-1" role="tab">
<b-button-toolbar justify>
<b-button
:class="showCollapse ? 'collapsed' : null"
#click="showCollapse = !showCollapse"
variant="outline-info"
class="flex-grow-1"
>
<span class="float-left">{{ folderName }}</span>
<span class="float-right">
{{
search ?
$t('documentCountFiltered', {filtered: filteredDocuments.length, count: documents.length}) :
$tc('documentCount', documents.length)
}}
</span>
</b-button>
<template v-if="!inherited && mode !== 'READ_ONLY'">
<label class="document-uploader btn btn-sm btn-outline-primary ml-2">
<span>
<i class="fas fa-fw fa-file-upload"></i>
{{ $t('uploadFiles') }}
</span>
<input type="file" multiple #change="selectFiles">
</label>
<b-dropdown
v-if="mode !== 'READ_ONLY' && mode !== 'RESTRICTED'"
size="sm" variant="outline-primary" class="ml-2 template-dropdown" no-caret right>
<template slot="button-content">
<i class="fas fa-fw fa-file-medical"></i> {{ $t('createDocument') }}
</template>
<b-dropdown-header v-if="companyTemplates.length !== 0" id="dropdown-header-templates">
{{ $t('companyTemplates') }}
</b-dropdown-header>
<b-dropdown-item v-if="companyTemplates.length !== 0" v-for="template of companyTemplates"
#click="emitWordDocumentCreateSelectEvent(template)">
{{ template.name }} <i v-if="template.freeTextEnabled" class="fa-fw fas fa-paragraph"></i>
</b-dropdown-item>
<b-dropdown-divider
v-if="companyTemplates.length !== 0 && companyFormBuilderTemplates.length > 0"></b-dropdown-divider>
<b-dropdown-header v-if="companyFormBuilderTemplates.length > 0" id="dropdown-header-inheritedDocuments">
{{ $t('formBuilderTemplates') }}
</b-dropdown-header>
<b-dropdown-item
v-for="template of companyFormBuilderTemplates"
#click="emitDocumentCreateSelectEvent(template)">
{{ template.name }}
</b-dropdown-item>
<b-dropdown-divider
v-if="globalTemplates.length !== 0"></b-dropdown-divider>
<b-dropdown-header v-if="globalTemplates.length > 0" id="dropdown-header-globalDocuments">
{{ $t('globalTemplates') }}
</b-dropdown-header>
<b-dropdown-item
v-for="template of globalTemplates"
#click="emitGlobalDocumentCreateSelectEvent(template)">
{{ template.name }}
</b-dropdown-item>
</b-dropdown>
</template>
</b-button-toolbar>
</b-card-header>
<b-form-group class="mt-4">
<w-b-form-select v-model="filteredDocumentType">
<template>
<b-form-select-option :value="null" disabled>-- {{ $t('selectDocumentByType') }} --</b-form-select-option>
</template>
<b-form-select-option :value="'all'">({{ $t('all') }})</b-form-select-option>
<option v-for="documentType in availableDocumentTypes" :key="documentType" :value="documentType">
{{ $t('model.document.types.' + documentType) }}
</option>
</w-b-form-select>
</b-form-group>
<b-collapse v-model="showCollapse">
<common-table
:fields="fields"
:items="filteredDocuments"
primary-key="id"
sort-by="creationDateTime"
sort-desc
>
<template slot="head(select)">
<check-all-checkbox :list="filteredDocuments" #change="handleTag(documents, $event)" property="selected"/>
</template>
<template slot="cell(select)" slot-scope="data">
<w-b-form-checkbox v-model="data.item.selected" v-if="data.item.selected != null"
#change="handleTag([data.item], $event)" data-test-id="check-box"/>
</template>
<template slot="cell(name)" slot-scope="data">
<div class="d-flex">
<div style="flex: 2 0 0">
<b-dropdown :text="data.item.name" variant="link" toggle-class="name-cell">
<b-dropdown-item #click="viewFile(data.item)" v-if="data.item.type != 'EMAIL'"><i
class="fas fa-eye"></i> {{ $t('showDocument') }}
</b-dropdown-item>
<b-dropdown-item #click="downloadFile(data.item)"><i class="fas fa-file-download"></i> {{
$t('download')
}}
</b-dropdown-item>
</b-dropdown>
</div>
<div style="flex: 1 0 0" v-if="data.item.uploading">
<b-progress :animated="!data.item.error" striped class="h-100">
<b-progress-bar
:value="100"
:variant="data.item.error ? 'danger' : 'primary'"
:label="data.item.error ? 'Error' : 'Uploading...'"
/>
</b-progress>
</div>
</div>
</template>
</common-table>
</b-collapse>
<p-d-f-j-s-viewer ref="pdf-viewer"/>
</div>
</b-container>
</template>
My script
<script>
import CheckAllCheckbox from '#/components/CheckAllCheckbox';
import CommonTable from '#/components/common/CommonTable';
import CommonInput from '#/components/common/CommonInput';
import {applianceService} from '#/services/appliance';
import PDFJSViewer from '#/components/PDFJSViewer';
import axios from '#/config/axios';
import {documentService} from '#/services/document';
import documentTypes from '#/models/document/type';
import {propertyFacilityService} from '#/services/property-facility';
import CommonCollapsible from '#/components/common/CommonCollapsible';
export default {
props: {
documents: Array,
documentOwnerType: String,
companyTemplates: Array,
companyFormBuilderTemplates: Array,
globalTemplates: Array,
inherited: Boolean,
startCollapsed: {
type: Boolean,
default: false
},
mode: String,
search: String,
applianceId: String,
propertyFacilityId: String,
noteId:String
},
components: {
CheckAllCheckbox,
CommonTable,
CommonCollapsible,
CommonInput,
PDFJSViewer
},
data() {
return {
documentTypes,
showCollapse: true,
filteredDocumentType: null
};
},
computed: {
filteredDocuments() {
console.log(this.filteredDocumentType)
console.log(this.documents)
if (this.filteredDocumentType === null || this.filteredDocumentType === 'all') {
return (this.documents ?? []).filter(document =>
(document.name.toUpperCase().includes(this.search.toUpperCase()) ||
this.$t(`model.document.types.${document.type}`).toUpperCase().includes(this.search.toUpperCase())))
}
else {
return (this.documents ?? []).filter(document =>
(document.name.toUpperCase().includes(this.search.toUpperCase()) ||
this.$t(`model.document.types.${document.type}`).toUpperCase().includes(this.search.toUpperCase())) &&
document.type === this.filteredDocumentType)
}
},
folderName() {
if (this.inherited) {
return this.$t('sharedDocuments');
} else if (this.documentOwnerType === 'COMPANY') {
return this.$t('companyDocuments');
} else {
return this.$t('documents');
}
},
availableDocumentTypes() {
return this.documentTypes.sort((a, b) => this.getDocumentTypeText(a).localeCompare(this.getDocumentTypeText(b)));
},
fields() {
return [
{
key: 'select',
thStyle: 'width: 1%'
},
{
key: 'name',
label: this.$t('name'),
sortable: true
},
{
key: 'inherited',
label: this.$t('inherited'),
formatter: inherited => this.$t(inherited ? 'yes' : 'no'),
sortable: true,
hide: this.inherited
},
{
key: 'sharedOnTC',
label: this.$t('sharedOnTC'),
formatter: sharedOnTC => this.$t(sharedOnTC ? 'yes' : 'no'),
sortable: true,
hide: this.sharedOnTC
},
{
key: 'type',
label: this.$t('type'),
formatter: type => this.$t('model.document.types.' + type),
sortable: true,
sortByFormatted: true
},
{
key: 'tags',
label: this.$t('tags'),
formatter: tags => tags.join(', '),
sortable: true,
sortByFormatted: true
},
{
key: 'signed',
label: this.$t('signed'),
formatter: sharedOnTC => this.$t(sharedOnTC ? 'yes' : 'no'),
sortable: true,
sortByFormatted: true
},
{
key: 'creationDateTime',
label: this.$t('created'),
sortable: true,
template: {type: 'date', format: 'L LT'}
},
{
key: 'changedDateTime',
label: this.$t('changed'),
sortable: true,
template: {type: 'date', format: 'L LT'}
},
{
key: 'actions',
hide: this.inherited || this.mode === 'READ_ONLY',
template: {
type: 'actions',
cell: [
{
icon: 'fa-edit',
tooltip: this.$t('edit'),
if: this.mode !== 'RESTRICTED',
disabled: data => data.item.uploading || !data.item.documentTemplateId || data.item.signed,
action: data => this.emitDocumentEditTemplateSelectEvent(data.item)
},
{
icon: 'fa-cog',
tooltip: this.$t('documentSettings'),
disabled: data => data.item.uploading,
action: data => this.emitDocumentEditSelectEvent(data.item)
},
{
icon: 'fa-trash',
variant: 'outline-danger',
disabled: data => data.item.uploading,
action: data => this.emitDocumentDeleteSelectEvent(data.item)
}
]
}
}
].filter(field => !field.hide);
}
},
methods: {
getDocumentTypeText(type) {
return this.$t(`model.document.types.${type}`);
},
selectFiles(event) {
this.emitFileUploadEvent(event.target.files);
event.target.value = '';
},
onDropEvent(event) {
this.emitFileUploadEvent(event.dataTransfer.files);
},
downloadFile(document) {
documentService.downloadDocument(document.id)
.catch(error => {
console.error(error);
});
},
viewFile(document) {
documentService.getPublicDownloadToken(document.id).then(result => {
let fileName = `${axios.defaults.baseURL}/file/public/${result.data}/download`;
this.$refs['pdf-viewer'].show(fileName);
}).catch(error => {
console.error(error);
});
},
emitGlobalDocumentCreateSelectEvent(template) {
this.$emit('document-global-create-select', template);
},
emitDocumentCreateSelectEvent(template) {
this.$emit('document-create-select', template);
},
emitWordDocumentCreateSelectEvent(template) {
this.$emit('document-word-create-select', template);
},
emitDocumentEditSelectEvent(document) {
documentService.getDocument(document.id).then(result => {
document = result.data;
this.$emit('document-edit-select', document);
});
},
emitDocumentEditTemplateSelectEvent(document) {
this.$emit('document-edit-template-select', document);
},
emitDocumentDeleteSelectEvent(document) {
this.$emit('document-delete-select', document);
},
emitFileUploadEvent(files) {
if (files && files.length) {
this.$emit('file-upload', [...files]);
}
},
handleTag(documents, selected) {
if (this.applianceId) {
applianceService.updateDocuments(this.applianceId, {
documentIds: documents.map(doc => doc.id), selected: selected
}).then(({data: documents}) => {
documents.forEach(doc => {
this.documents.splice(this.documents.findIndex(d => d.id === doc.id), 1,
Object.assign(doc, {selected: selected}));
});
});
}
if (this.propertyFacilityId) {
propertyFacilityService.updateDocuments(this.propertyFacilityId, {
documentIds: documents.map(doc => doc.id), selected: selected
}).then(({data: documents}) => {
documents.forEach(doc => {
this.documents.splice(this.documents.findIndex(d => d.id === doc.id), 1,
Object.assign(doc, {selected: selected}));
});
});
}
}
},
created() {
this.showCollapse = !this.startCollapsed;
}
};
</script>
I have a dropdown that I want to give a validation.
So here is my dropdown component:
<template>
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected.name }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div v-if="defaultValue != ''">{{defaultValue}}</div>
<div v-for="(option, i) of options" :key="i" #click=" selected = option; open = false; $emit('input', option);">
{{ option.name }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
options: {
type: Array,
required: true,
},
defaultValue: {
type: String,
required: false,
default: "Choose an option",
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
open: false,
selected: this.setDefaultValue(),
};
},
mounted() {
this.$emit("input", this.selected);
},
methods: {
setDefaultValue () {
if (this.defaultValue == '' && this.options.length > 0) {
return this.options[0];
}
return { name: this.defaultValue};
}
}
};
</script>
and in my parent component, I am using this dropdown, the fetched value from API call and filled with variations. So what I am trying to do is if the value is not selected (default: "Choose an option"), I want to give an error message which is saying, the dropdown is mandatory.
<Dropdown
:options="getVariations"
class="select"
#input="getSelected"
/>
<script>
import Dropdown from "../components/Dropdown";
import apiHelper from "../helpers/apiHelper";
export default {
components: {Dropdown},
data() {
return {
selected: "",
variationId: "",
selectedObject: null
};
},
computed: {
getVariations() {
return this.product.attributes.variations
}
},
methods: {
getSelected(opt) {
this.selectedObject = opt;
this.selected = opt.description;
this.variationId = opt.id;
}
},
};
</script>
This is a simple example of what you are asking for:
// v-model property
selectedVModel: null
// html
<select id="testselect" v-model="selectedVModel" required>
<option :value="null">Default</option>
<option value="bar">Option 1</option>
<option value="foo">Option 2</option>
<option value="baz">Option 3</option>
</select>
<div v-if="!selectedVModel">Error! Choose something!</div>
You can handle the error inside of your child component. In this example, if the v-model as selectedVModel is empty, the <div> with v-if="!selectedVModel" will be shown. As selectedVModel is null it will automatically select <option :value="null">Default</option>. If you chosse one of the options selectedVModel get´s a value so v-if="!selectedVModel" results into false, no error then. If you select Default again, the value turns to null again which results into true at v-if="!selectedVModel", so the error will be visible again.
Combine this simple example with your code and let me know if it helped you.
If you want to validate in your parent component, try to check selectedObject.id :
Vue.component('Dropdown', {
template: `
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected.name }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div v-if="defaultValue != ''">{{defaultValue}}</div>
<div v-for="(option, i) of options" :key="i" #click=" selected = option; open = false; $emit('input', option);">
{{ option.name }}
</div>
</div>
</div>
`,
props: {
options: {
type: Array,
required: true,
},
defaultValue: {
type: String,
required: false,
default: "Choose an option",
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
open: false,
selected: this.setDefaultValue(),
};
},
mounted() {
this.$emit("input", this.selected);
},
methods: {
setDefaultValue () {
if (this.defaultValue == '' && this.options.length > 0) {
return this.options[0];
}
return { name: this.defaultValue};
}
}
})
new Vue({
el: '#demo',
data() {
return {
selected: "",
variationId: "",
selectedObject: null,
product: {
attributes: {
variations: [{id: 1, name: 'name1', description: 'desc1'}, {id: 2, name: 'name2', description: 'desc2'},{id: 3, name: 'name3', description: 'desc3'},]
}
}
};
},
computed: {
getVariations() {
return this.product.attributes.variations
}
},
methods: {
getSelected(opt) {
this.selectedObject = opt;
this.selected = opt.description;
this.variationId = opt.id;
},
submit() {
if (!this.selectedObject.id) alert('pls select option')
else console.log(this.selectedObject)
}
},
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="submit">submit</button>
<Dropdown
:options="getVariations"
class="select"
#input="getSelected"
/>
</div>
<template>
<div>
<el-form label-position="top" :model="notificationEmails" ref="emailForm">
<el-form-item
v-for="(item, index) in notificationEmails.emails"
:key="index"
:label="getLabel(index)"
:for="`${item.id}_${index}`"
:prop="'emails.' + index + '.id'"
:rules="{
required: true,
type: 'email',
message: 'Not valid email',
trigger: ['blur', 'change']
}"
>
<el-row>
<el-col :span="6">
<el-input v-model="item.id" type="email" :id="`${item.id}_${index}`" />
</el-col>
<el-col :span="2" style="padding-left: 18px">
<span v-if="index === 0" tabindex="0" #click="addEmail" #keyup.enter.stop="addEmail">
<i aria-hidden="true" class="icon-add-circle-outline" />
<span class="screen-reader">{{$t('a11y.settings.soldTo.notif.action.addEmail')}}</span>
</span>
<span
v-else
tabindex="0"
#click="deleteEmail(item.id)"
#keyup.enter.stop="deleteEmail(item.id)"
>
<i class="icon-subtract-circle-outline" aria-hidden="true" />
<span class="screen-reader">{{$t('a11y.settings.soldTo.notif.action.deleteEmail')}}</span>
</span>
</el-col>
</el-row>
</el-form-item>
</el-form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'EmailWidget',
data() {
return {
notificationEmails: {
emails: []
}
};
},
props: ['passEmail'],
watch: {
passEmail: {
handler(newVal) {
this.notificationEmails.emails = newVal;
},
deep: true
},
notificationEmails: {
handler() {
this.$refs.emailForm.validate(async validate => {
if (validate) {
await this.$store.dispatch('settings/GENERIC', {
module: 'common',
propKey: 'validEmail',
propValue: true
});
} else {
await this.$store.dispatch('settings/GENERIC', {
module: 'common',
propKey: 'validEmail',
propValue: false
});
}
});
},
deep: true
}
},
methods: {
addEmail() {
this.notificationEmails.emails.push({
id: '',
priority: this.notificationEmails.emails.length + 1
});
// this.emails = [...this.emails, { id: '', priority: this.emails.length + 1 }];
},
deleteEmail(email) {
// this.emails = this.emails.filter(item => item.id !== email);
let index = 0;
for (let i = 0; i < this.notificationEmails.emails.length; i += 1) {
if (this.notificationEmails.emails[i].id === email) {
index = i;
break;
}
}
this.notificationEmails.emails.splice(index, 1);
},
getLabel(index) {
return index === 0 ? this.$t('settings.soldTo.notif.email') : '';
}
},
};
</script>
<style lang="scss">
i:hover {
cursor: pointer;
}
</style>
Some problems about the validation in dynamic add or delete the emails in el-form of Vue. When I add a new email, the validation cannot work. When I delete email. it shows
Error: please transfer a valid prop path to form item!
There is no issue when I edit the email.
I change the props according to enter code here the official document, but it still shows error.
I have an array of names that I loop over using v-for I'm trying to filter these results when a user starts typing in a search box.
I've added my code below for reference if I do my loop as v-for="entry in entries" then it output the array but doesn't work with the computed and filteredList function
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredList">
{{ entry.name }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
search: '',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredList() {
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
Try to move the search prop in to data option like this:
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
search: '',
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredList() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
Also add a check if the search prop is empty to return the full entries list.
Demo fiddle
Struggling to sort out how to get a selected value from a Typeahead component to pass back to the parent component. I'm allowing the user to search from a variety of data to link a record to a post. Once the user clicks one of the typeahead drop-down records, I pass the item to the sendlink method - I've checked that the data passes ok. When I do the emit using the selected-link event, I'm not getting the data in the parent component.
PostList.vue
<template>
<div>
<div v-if='posts.length === 0' class="header">There are no posts yet!</div>
<form action="#" #submit.prevent="createPost()" class="publisher bt-1 border-fade bg-white" autocomplete="off">
<div class="input-group">
<input v-model="post.content" type="text" name="content" class="form-control publisher-input" placeholder="What's the lastest?" autofocus>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">Post</button>
</span>
</div>
<span class="publisher-btn file-group">
<i class="fa fa-camera file-browser"></i>
<input type="file">
</span>
</form>
<div #click="doit" v-on:selected-link="onSelectedLink">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3">
</typeahead>
<post v-for="post in posts"
:key="post.id"
:post="post"
#post-deleted="deletePost($event)">
</post>
</div>
</template>
<script>
var axios = require("axios");
import post from './PostItem.vue';
import typeahead from './Typeahead.vue';
export default {
components: {
post,
typeahead
},
props: ['postableId', 'postableType', 'model'],
data: function() {
return {
modellink: {
"name": "n/a",
"description": "",
"id": null,
"model": "n/a"
},
post: {
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
},
posts: [
{
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
}
]
};
},
created() {
this.fetchPostsList();
},
methods: {
onSelectedLink: function (talink) {
alert(JSON.stringify(talink, null, 4));
this.link = talink
},
doit() {
alert(JSON.stringify(this.modellink, null, 4));
},
fetchPostsList() {
if( this.postableId ) {
axios.get('/api/' + this.postableType + '/' + this.postableId + '/posts').then((res) => {
this.posts = res.data;
});
} else {
axios.get('/api/post').then((res) => {
//alert(JSON.stringify(res.data[0], null, 4));
this.posts = res.data;
});
}
},
createPost() {
axios.post('api/post', {content: this.post.content, user_id: Laravel.userId, vessel_id: Laravel.vesselId })
.then((res) => {
this.post.content = '';
// this.post.user_id = Laravel.userId;
// this.task.statuscolor = '#ff0000';
this.edit = false;
this.fetchPostsList();
})
.catch((err) => console.error(err));
},
deletePost(post) {
axios.delete('api/post/' + post.id)
.then((res) => {
this.fetchPostsList()
})
.catch((err) => console.error(err));
},
}
}
</script>
Typeahead.vue
<template>
<div>
<input
v-model="query"
#blur="reset"
type="text"
class="SearchInput"
:placeholder="placeholder">
<transition-group name="fade" tag="ul" class="Results">
<li v-for="item in items" :key="item.id">
<span #click="sendlink(item)">
<strong>{{ item.name }}</strong> - <small>{{ item.model }}</small><br>
<small>{{ item.description }}</small>
</span>
</li>
</transition-group>
<p v-show="isEmpty">Sorry, but we can't find any match for given term :( </p>
</div>
</template>
<script>
var axios = require("axios");
export default {
name: 'Typeahead',
props: {
modellink: {
type: Object,
required: false
},
source: {
type: [String, Array],
required: true
},
filterKey: {
type: String,
required: true
},
startAt: {
type: Number,
default: 3
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
items: [],
query: '',
taitem: ''
}
},
computed: {
lookup() {
if(this.query.length >= this.startAt) {
axios.get(this.source + '/' + this.query).then((res) => {
this.items = res.data;
return res.data;
});
}
},
isEmpty() {
if( typeof this.lookup === 'undefined' ) {
return false
} else {
return this.lookup.length < 1
}
}
},
methods: {
sendlink: function (taitem) {
this.$emit('selected-link', taitem);
},
reset() {
this.query = ''
}
}
}
</script>
In your PostList.vue, move the v-on:selected-link="onSelectedLink" from the div to typeahead like below. When emitting an event from child to parent, the listener on the parent needs to be on the child component tag for it to work.
<div #click="doit">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3"
v-on:selected-link="onSelectedLink">
</typeahead>