I am using dropzone in this example, it accepts a file and when the file is uploaded, I provide a preview template. The preview template then needs to render a new dropzone component "import-widget" in this example from the "import widget"
var Dropzone = require('dropzone')
Dropzone.autoDiscover = false
Dropzone.autoProcessQueue = false
export default {
data(){
return{
collection: false,
}
},
props: {
id: {
type: String,
required: true
},
method: {
type: String,
required: false
},
url: {
type: String,
required: true
},
clickable: {
type: Boolean,
default: true
},
acceptedFileTypes: {
type: String
},
thumbnailHeight: {
type: Number,
default: 200
},
thumbnailWidth: {
type: Number,
default: 200
},
showRemoveLink: {
type: Boolean,
default: true
},
maxFileSizeInMB: {
type: Number,
default: 2
},
maxNumberOfFiles: {
type: Number,
default: 5
},
autoProcessQueue: {
type: Boolean,
default: true
},
useFontAwesome: {
type: Boolean,
default: false
},
useCustomDropzoneOptions: {
type: Boolean,
default: false
},
dropzoneOptions: {
type: Object
}
},
events:{
processQueue: function(data){
this.processQueue();
}
},
methods: {
removeAllFiles: function () {
this.dropzone.removeAllFiles(true)
},
processQueue: function () {
console.log(this.dropzone.getQueuedFiles());
this.dropzone.processQueue();
console.log('Dropzone widget dispatching updateUploadCollection');
this.$dispatch('updateUploadCollection',this.collection);
}
},
computed: {
cloudIcon: function () {
if (this.useFontAwesome) {
return '<i class="fa fa-cloud-upload"></i>'
} else {
return '<i class="material-icons"></i>'
}
},
doneIcon: function () {
if (this.useFontAwesome) {
return '<i class="fa fa-check"></i>'
} else {
return ' <i class="material-icons"></i>'
}
},
errorIcon: function () {
if (this.useFontAwesome) {
return '<i class="fa fa-close"></i>'
} else {
return ' <i class="material-icons">error</i>'
}
}
},
ready () {
console.log('Dropzone Widget Ready');
var element = document.getElementById(this.id)
if (!this.useCustomDropzoneOptions) {
this.dropzone = new Dropzone(element, {
clickable: this.clickable,
thumbnailWidth: this.thumbnailWidth,
thumbnailHeight: this.thumbnailHeight,
maxFiles: this.maxNumberOfFiles,
maxFilesize: this.maxFileSizeInMB,
dictRemoveFile: 'Remove',
addRemoveLinks: false,
acceptedFiles: this.acceptedFileTypes,
autoProcessQueue: this.autoProcessQueue,
dictDefaultMessage: this.cloudIcon +'<br>Click Here To Add Files To Upload',
previewTemplate: '<div class="row dz-preview dz-file-preview"><div class="col-xs-1" data-dz-remove>x</div><div style="word-wrap: break-word;" class="col-xs-2 dz-filename"><span data-dz-name></span></div><div class="col-xs-2 dz-image"><img width="100%" data-dz-thumbnail /></div><div class="col-xs-4"><import-widget id="import-widget2" method="post" url="test/url"></import-widget></div></div>',
previewsContainer: '#previews',
})
} else {
this.dropzone = new Dropzone(element, this.dropzoneOptions)
}
// Handle the dropzone events
var vm = this
this.dropzone.on('addedfile', function (file) {
vm.$forceUpdate();
vm.$emit('vdropzone-fileAdded', file)
})
this.dropzone.on('removedfile', function (file) {
vm.$emit('vdropzone-removedFile', file)
})
this.dropzone.on('success', function (file, response) {
console.log('dropzone success response');
console.log(response);
if (!vm.collection) {
vm.$set('collection', []);
}
vm.collection.push(response);
vm.$emit('vdropzone-success', file, response)
});
this.dropzone.on('error', function (file, error, xhr) {
vm.$emit('vdropzone-error', file, error, xhr)
})
this.dropzone.on('sending', function(file, xhr, formData){
console.log('appending: ' + Laravel.csrfToken);
formData.append("_token", Laravel.csrfToken);
vm.$emit('vdropzone-sending', file, xhr, formData)
})
},
}
<form :method="method" :action="url" class="dropzone" :id="id">
</form>
<div class="row">
<div id="previews" class="col-sm-12">
</div>
</div>
See "previewTemplate" .. in here you can see that I've inserted component. The problem is that the system treats this as html instead of a vue component. What is the best way around this?
Related
I am struggling re-writing some older code I made in vue. I have a form which allows the user to enter multiple email recipients. The problem is that the array in which these emails are stored outputs them as one single item.
How do I seperate these email addresses?
This is my code. I know that Vue has something called Split, but I am not sure where I could use it here (or if it would change anything).
<template>
<b-form-group :label="label">
<vue-tags-input
v-model="inputValue"
:tags="tags"
:autocomplete-min-length="0"
:add-on-key="[',', 13]"
:autocomplete-items="filteredAutocompleteItems"
:placeholder="$t('addEmailAddressesHere')"
data-test-id="send-doc-email-to"
#before-adding-tag="addTag"
#tags-changed="tagsChanged"
class="mw-100"
/>
</b-form-group>
</template>
<script>
import {propertyService} from '#/services/property';
import {tenancyService} from '#/services/tenancy';
import {unitService} from '#/services/unit';
import {userService} from '#/services/user';
import VueTagsInput from '#johmun/vue-tags-input';
export default {
components: {
VueTagsInput
},
props: {
value: Array,
label: String,
entityId: String,
entityType: String,
prefill: {
type: Boolean,
default: false
},
asNotification: {
type: Boolean,
default: false
},
includeUser: {
type: Boolean,
default: false
}
},
data() {
return {
inputValue: '',
autocompleteItems: []
};
},
computed: {
tags() {
return (this.value || []).map(this.setText);
},
filteredAutocompleteItems() {
return this.autocompleteItems.filter(autocompleteItem =>
autocompleteItem.text.toUpperCase().includes(this.inputValue.toUpperCase()));
}
},
methods: {
addTag({tag, addTag}) {
if (!tag.recipients) {
tag.recipients = [{emailAddress: tag.text}];
}
addTag(tag);
},
setText(tag) {
tag.text = [tag.description, tag.recipients.map(recipient => recipient.emailAddress).join(', ')].filter(Boolean).join(' | ');
return tag;
},
tagsChanged(newTags) {
this.$emit('input', newTags);
},
load() {
switch (this.entityType) {
case 'TENANCY':
userService.getCurrentUser().then(userResult => {
tenancyService.getTenants(this.entityId).then(result => {
const defaultTags = [];
const recipients = result.data
.map(tenant => tenant.legalEntity)
.filter(legalEntity => legalEntity.email || (!legalEntity.email && this.asNotification ? legalEntity.name : null))
.map(legalEntity => ({
emailAddress: legalEntity.email || (!legalEntity.email && this.asNotification ? legalEntity.name.concat(' ', `(${this.$t('letterMail').toLowerCase()})`) : null),
legalEntityId: legalEntity.id
}));
if (recipients.length) {
defaultTags.push(this.setText({description: this.$t('tenants'), recipients}));
}
this.autocompleteItems.push(...defaultTags);
if (this.includeUser) {
defaultTags.push(this.setText({
description: this.$t('user'),
recipients: [{emailAddress: userResult.data.email}]
}));
}
if (this.prefill) {
this.tagsChanged(defaultTags);
}
tenancyService.getUnits(this.entityId).then(result =>
result.data.forEach(unitTenancy => this.addPropertyContactsToAutocompleteItems(unitTenancy.unit.propertyId)));
});
});
break;
case 'UNIT':
unitService.get(this.entityId).then(result =>
this.addPropertyContactsToAutocompleteItems(result.data.propertyId));
break;
case 'PROPERTY':
this.addPropertyContactsToAutocompleteItems(this.entityId);
break;
}
},
addPropertyContactsToAutocompleteItems(propertyId) {
propertyService.listContacts(propertyId).then(result => {
this.autocompleteItems.push(...result.data
.filter(contact => contact.email)
.map(contact => this.setText({
description: contact.profession ? this.$t(`model.contact.professions.${contact.profession}`) : null,
recipients: [{emailAddress: contact.email, legalEntityId: contact.id}]
}))
);
});
}
},
created() {
this.load();
}
};
</script>
Currently i am trying to get the value of altLanguages and output that dynamically in a JSON, so it can be injected into the head. The altLanguages are the meta attribute values that should be added before rendering the page to avoid the error (altLanguages is undefined). Anyone know how to do that.
<template>
<header class="site-header">
<router-link to="/" class="logo">Example Site</router-link>
<nav>
<ul>
<li v-for="menuLink in menuLinks" :key="menuLink.id">
<prismic-link :field="menuLink.link">{{ $prismic.richTextAsPlain(menuLink.label) }}</prismic-link>
</li>
</ul>
</nav>
<alternate-languages :altLanguages="altLanguages" />
<!-- <alternate-content :altLanguages="altLanguages" /> -->
</header>
</template>
<script>
export default {
props: {
id: { type: String, default: "" },
altLanguages: { type: Array, default: () => ([]) }
},
data() {
return {
menuContent: [],
menuLinks: [],
// altLanguages: []
};
},
methods: {
async getMenu(lang) {
//Query to get menu content
const menuContent = await this.$prismic.client.getSingle("menu", {
lang: lang
});
this.menuContent = menuContent;
this.menuLinks = menuContent.data.menu_links;
}
},
created() {
// this.getLanguages(this.id);
this.getMenu(this.$route.params.lang);
},
watch: {
$route(to, from) {
this.getMenu(to.params.lang);
}
}
// beforeRouteUpdate(to, from, next) {
// console.log("new");
// this.getMenu(to.params.lang);
// next();
// }
};
</script>
//expected output
export default {
data: function () {
return {
title: 'My Title'
}
},
// Usage with context the component
head: {
// To use "this" in the component, it is necessary to return the object through a function
title: function () {
return {
inner: this.title
}
},
meta: [
// altLanguages should be output in here.....
{ name: 'description', content: 'My description', id: 'desc' }
]
}
...
}
}
I have a custom table with actions via a modal popup that set values on Rows. Things are mostly working great (Updates to Foo and Bar get sent to the backend and are set in a database, reload of the page pulls the data from the database and shows foo/bar were correctly set). The only not-great part is on setting of Foo, the value in the table does not get updated. Bar gets updated/is reactive. (the template uses foo.name and bar.id). Does anyone have any ideas on how to get Foo to update in the table? I've changed the moustache template to use {{ foo.id }} and it updates, but I need foo.name.
<template>
<div>
<c-dialog
v-if="foo_modal"
title="Set Foo"
:actions="foo_modal_options.actions"
#cancel="foo_modal = null">
<slot>
<div class="form-group">
<label>Foo:</label>
<select class="form-control" v-model="foo_modal.thing.foo.id">
<option v-for="foo in foos" :key="foo.id" :value="foo.id">{{ foo.name }}</option>
</select>
</div>
</slot>
</c-dialog>
<c-dialog
v-if="bar_modal"
title="Set Rod/Stick"
:actions="bar_modal_options.actions"
#cancel="bar_modal = null">
<slot>
<div class="form-group">
<label>Rod:</label>
<select class="form-control" v-model="bar_modal.thing.rod.id">
<option v-for="bar in bars" :key="bar.id" :value="bar.id" v-if="bar.type === 'rod'">{{ bar.id }}</option>
</select>
</div>
<div class="form-group">
<label>Stick:</label>
<select class="form-control" v-model="bar_modal.thing.stick.id">
<option v-for="bar in bars" :key="bar.id" :value="bar.id" v-if="bar.type === 'stick'">{{ bar.id }}</option>
</select>
</div>
</slot>
</c-dialog>
<c-table-paginated
class="c-table-clickable"
:rows="grid.data"
:columns="grid.columns"
:actions="grid.actions"
:key="componentKey">
</c-table-paginated>
</div>
</template>
<script>
import fooModal from '../../components/fooModal.vue';
import barModal from '../../components/barModal.vue';
import CTablePaginated from "../../components/custom/cTable/cTablePaginated";
import cTooltip from '../../components/custom/cTooltip/cTooltip.vue';
import cDialog from '../../components/custom/cDialog/cDialog.vue';
import moment from 'moment';
export default {
components: { CTablePaginated, cTooltip, cDialog },
methods: {
loadData() {
let that = this;
that.$http.get('/things', { params: that.param || {} })
.then(function (things) {
that.things = things.data;
that.grid.data = that.things;
});
},
setBar(thing_id, options, cb) {
let that = this;
this.$http.patch(`/things/${thing_id}`, { rod_id: options.rod, stick_id: options.stick })
.then(function () {
cb();
});
},
setFoo(thing_id, options, cb) {
let that = this;
this.$http.patch(`/things/${thing_id}`, { foo_id: options.foo_id })
.then(function () {
cb();
})
},
},
data() {
return {
componentKey: 0,
things: null,
foos: [],
bars: [],
foo_modal: null,
foo_modal_options: {
actions: [
{
label: "Save",
class: "btn-primary",
action: (function (ctx) {
return function () {
const thing = ctx.foo_modal.thing;
const options = {
foo_id: thing.foo.id,
};
ctx.setFoo(thing.id, options, function () {
ctx.foo_modal = null;
});
}
})(this)
},
{
label: "Cancel",
action: (function (ctx) {
return function () {
ctx.foo_modal = null;
}
})(this)
}
]
},
bar_modal: null,
bar_modal_options: {
actions: [
{
label: "Save",
class: "btn-primary",
action: (function (ctx) {
return function () {
const thing = ctx.bar_modal.thing;
const options = {
rod: thing.rod.id,
stick: thing.stick.id
};
ctx.setBar(thing.id, options, function () {
ctx.bar_modal = null;
});
}
})(this)
},
{
label: "Cancel",
action: (function (ctx) {
return function () {
ctx.bar_modal = null;
}
})(this)
}
]
},
grid: {
data: [],
columns: [
{
label: "Foo",
value: function (row) {
if (!row.foo) return "No foo set";
return `${row.foo.name }`;
}
},
{
label: "Rod/Stick",
value: function (row) {
if (!row.rod && !row.stick) return "No rod/stick set";
if (!row.rod) return `No rod set/${row.stick.id}`;
if (!row.stick) return `${row.rod.id}/no stick set`;
return `${row.rod.id}/${row.stick.id}`;
}
}
],
actions: [
{
label: "Set Foo",
visible: function (thing) {
return !thing.active;
},
action: (function (ctx) {
return function (thing) {
if (!thing.foo) thing.foo = {};
ctx.foo_modal = {
thing: thing
};
}
})(this)
},
{
label: "Set Bar",
visible: function (thing) {
return !thing.active;
},
action: (function (ctx) {
return function (thing) {
if (!thing.rod) thing.rod = {};
if (!thing.stick) thing.stick = {};
ctx.bar_modal = {
thing: thing
};
}
})(this)
},
],
}
};
},
props: {
title: {
type: String
},
param: {
type: Object,
required: true
},
events: {
type: Object,
required: true
}
},
created() {
let that = this;
this.loadData();
this.$http.get('/bars')
.then(function (bars) {
that.bars = bars.data;
});
this.$http.get('/foos')
.then(function (foos) {
that.foos = foos.data;
});
},
}
</script>
There are two possibilities you can try both if any one of them can help you.
You can set value by using Vuejs this.$set method for deep reactivity. Click here.
You can use this.$nextTick(()=>{ // set your variable here }). Click here.
I am new to VueJS and after reading this doc section and this question, I can't figure how to change dynamically the prop active|positive|intermediary|negative and pulse of the following component (it could be another): vue-status-indicator
eg: with user.status = positive and the following wrong code :
<span v-for="user in users" :key="user.id">
<status-indicator {{ user.status }}></status-indicator>
</span>
What is the correct syntax to set theses type of props ?
You could do something like this.. I had to write a wrapper for it to make it functional..
[CodePen Mirror]
Edit To be clear - you cannot interpolate inside an attribute.. This has to do with boolean attributes in Vue..
This:
<status-indicator active pulse />
...is the same exact thing as doing this:
<status-indicator :active="true" :pulse="true" />
The "wrapper" component I wrote allows you to supply a string to set the status (like you are wanting to do):
<v-indicator status="active" pulse></v-indicator>
<!-- OR -->
<v-indicator status="positive" pulse></v-indicator>
<!-- OR -->
<v-indicator status="intermediary" pulse></v-indicator>
<!-- OR -->
<v-indicator status="negative" pulse></v-indicator>
Here is the full "wrapper" component, in .vue format: (added a validator for the 'status' prop)
<template>
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</template>
<script>
export default {
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
</script>
Snippet:
const vIndicator = {
template: "#v-indicator",
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
new Vue({
el: '#app',
components: {
vIndicator
},
data: {
currentStatus: '',
isPulse: '',
},
computed: {
currentJson() {
let cj = {
currentStatus: this.currentStatus,
isPulse: this.isPulse,
};
return JSON.stringify(cj, null, 2);
}
},
mounted() {
let statuses = ["active", "positive", "intermediary","negative"];
let c = 0;
let t = 0;
this.currentStatus = statuses[c];
this.isPulse = true;
setInterval(() => {
t = c + 1 > 3 ? t + 1 : t;
c = c + 1 > 3 ? 0 : c + 1;
this.currentStatus = statuses[c];
this.isPulse = (t % 2 == 0) ? true : false;
}, 2000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-status-indicator#latest/dist/vue-status-indicator.min.js"></script>
<link href="https://unpkg.com/vue-status-indicator#latest/styles.css" rel="stylesheet"/>
<div id="app">
<p>Will alternate status as well as pulsing (pulse changes after each full loop)</p>
<!--
[status]active|positive|intermediary|negative
[pulse]true|false
-->
<v-indicator :status="currentStatus" :pulse="isPulse"></v-indicator>
<pre>{{ currentJson }}</pre>
</div>
<!-- WRAPPER COMPONENT -->
<script type="text/x-template" id="v-indicator">
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</script>
I'm trying to make use of the main component inside another with pre-defined properties.
Here's what I'm trying to achieve, but I'm just getting an empty div as a result.
<template>
<call-dialog-link
:id="id"
:url=url"
message="Are you sure you wish to remove this record?"
label="Remove"
css-classes="alert"
></call-dialog-link>
</template>
<script>
import CallDialogLink from './CallDialogLink.vue';
export default {
props: {
id: {
type: String,
required: true
},
url: {
type: String,
required: true
}
},
components: {
'call-dialog-link': CallDialogLink
}
};
</script>
Here's the CallDialogLink component
<template>
<span class="clickAble" :class="cssClasses" v-text="label" #click="clicked()"></span>
</template>
<script>
export default {
props: {
id: {
type: String,
required: true
},
url: {
type: String,
required: true
},
message: {
type: String,
required: true
},
label: {
type: String,
required: true
},
cssClasses: {
type: String,
required: false
}
},
mounted() {
window.EventHandler.listen('remove-dialog-' + this.id + '-called', (data) => {
window.location.reload(true);
});
},
methods: {
clicked() {
window.EventHandler.fire('top-confirm', {
id: 'remove-dialog-' + this.id,
message: this.message,
url: this.url
});
}
}
};
</script>
Any idea what I'm doing wrong?
I believe there is typo in your code.
<template>
<call-dialog-link
:id="id"
:url="url" // didn't open the double quote here
message="Are you sure you wish to remove this record?"
label="Remove"
css-classes="alert"
></call-dialog-link>
</template>