How to update the text of a Tinymce text component? - vue.js

I have the following situation, I have a listing where if I press brings me the text to the text editor, the issue arises when I want to add more text and this is bugged in an endless cycle killing Vue, I do not know if it is wrongly done or not.
Tinymce Component
<template>
<div>
<editor
:id="uuid"
api-key="xxxxxxxx"
:init="editorInit"
v-model="content"
/>
</div>
</template>
<script>
import Editor from '#tinymce/tinymce-vue'
export default {
name: "tinymce",
components: {
'editor': Editor
},
props: ['resultsEditor','clearcomponent','uuidEditor','size','setContentText'],
data() {
return {
uuid: this.uuidEditor,
content: null,
editorInit: {
height: this.size,
menubar: false,
entity_encoding: 'raw',
language: 'es',
plugins: ['image', 'advlist', 'autolink', 'lists', 'link', 'charmap', 'preview', 'anchor',
'searchreplace', 'visualblocks', 'code', 'fullscreen', 'insertdatetime', 'media', 'table',
'help', 'wordcount'
],
toolbar: 'undo redo | image | blocks | bold | alignleft aligncenter alignright alignjustify | bullist numlist | table ', // | removeformat', //outdent indent => incrementar sangría
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
branding: false,
relative_urls: false,
remove_script_host : false,
document_base_url : window.location.origin,
automatic_uploads: true,
resize_img_proportional: true,
setup: function(editor) {
editor.on('init', function(args) {
editor = args.target;
editor.on('NodeChange', function(e) {
if (e && e.element.nodeName.toLowerCase() == 'img') {
var width = 0;
var height = 0;
width = e.element.width
height = e.element.height
if (width > 800) {
height = height / (width / 800);
width = 800;
}
tinyMCE.DOM.setAttribs(e.element, {
'width': width,
'height': height
});
}
});
});
},
images_upload_handler: (blobInfo) => {
return new Promise((success, failure) => {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', route('upload_image_no_attach'));
xhr.onload = function() {
var json;
if (xhr.status != 200) {
reject('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
reject('Invalid JSON: ' + xhr.responseText);
return;
}
// success(json.location);
if (xhr.status === 200) {
const location =
`${window.location.origin}/opportunity/preview/${json.location}`;
success(location)
}
};
formData = new FormData();
formData.append('image', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
});
}
}
}
},
watch: {
content: function (newValue, oldValue){
this.resultsEditor(this.content)
},
clearcomponent: function(){
if (this.clearcomponent) {
tinyMCE.get(this.uuidEditor).setContent('');
this.content = null
}
},
setContentText: function(){
// console.log(this.setContentText)
if (this.setContentText != '') {
tinyMCE.get(this.uuidEditor).setContent(this.setContentText);
this.content = this.setContentText //Problem to update data to other component.
}
}
}
}
</script>
Modal Template Component
<!-- LISTADO DE PLANTILLAS -->
<b-list-group class="mt-2 search-width scroll-custom-template" v-if="listTemplates.length > 0">
<b-list-group-item
v-for="data in filterByList" :key="data.id"
class="with-coursor"
:active="textID == data.id"
#click="selectTemplate(data)"
>{{ data.name }}</b-list-group-item>
</b-list-group>
<!-- END LISTADO DE PLANTILLAS -->
</div>
<div class="col-sm-9 col-md-9 col-lg-9">
<tinymce
uuidEditor="template_opportunity"
size="450"
:setContentText="text"
:clearcomponent="this.clearContent"
:resultsEditor="onResultsEditor"
></tinymce>
</div>
onResultsEditor(text){
// this.text = text //It writes the text in it from left to left and not from left to right.
},
Example:
This is my original template that I select:
Hello world, you are in europe :D.
This is what it writes me when I fill in the input and I want to update.
The word is: Nuevo
oveuHello world, you are in europe :D.N

Related

Sorting multiple columns based on headers in Vue.js

I want to apply sorting on firstname, lastname and email but i did not undestand How We sort table in ascending and descnding order by clicking on column name in vuejs. I am tried lot of concepts on stackoverflow and other resources but they didn't seem to work . This is very silly question but I am stopped on this from many days.I'm new to Vue.js and any help would be appreciated. Image
<template>
<div class="h-full flex flex-col">
<ListFilter
ref="filter"
:class="{ 'mb-8': hasFilters || search }"
:filter="filter"
:has-filters="hasFilters"
:has-quick-filters="hasQuickFilters"
:query="query"
:search="search"
:search-placeholder="searchPlaceholder"
:filter-classes="filterClasses"
:initial-values="initialFilter"
:apply-filter="values => applyFilter(filterMapper(values))"
:apply-search="applySearch"
:clear-search="clearSearch"
>
<template #filters="props">
<slot
name="filters"
:reset="props.reset"
:filter="filter"
/>
</template>
<template #quickfilter>
<slot
name="quickfilter"
:apply-filter="applyFilter"
/>
</template>
<template #loader>
<loader
:loading="loading"
:backdrop="true"
/>
</template>
</ListFilter>
<!-- <div>
</div> -->
<vuetable
ref="vuetable"
:row-class="rowClass"
:api-mode="false"
:fields="columns"
:data-manager="dataManager"
:css="css.table"
:track-by="trackBy"
:sort-order="innerSortOrder"
:detail-row-component="detailRow"
:detail-row-options="detailOptions"
:no-data-template="noDataTemplate"
pagination-path="pagination"
#vuetable:row-clicked="handleRowClicked"
#vuetable:cell-clicked="props => $emit('cell-clicked', props)"
#vuetable:pagination-data="onPaginationData"
>
<template #empty-result>
<slot name="empty-result" />
</template>
<template #actions="props">
<div v-if="hasActions">
<ListActions :record="props.rowData">
<slot
name="actions"
:record="props.rowData"
/>
</ListActions>
</div>
</template>
<template #inline-actions="props">
<div v-if="hasInlineActions">
<InlineActions :record="props.rowData">
<slot
name="inline-actions"
:record="props.rowData"
/>
</InlineActions>
</div>
</template>
<template #detail-toggler="props">
<div v-if="hasDetailRow">
<DetailToggler
:row-data="props.rowData"
:row-index="props.rowIndex"
>
<template #default="detailProps">
<slot
name="detail-toggler"
:record="props.rowData"
:is-open="detailProps.isOpen"
:toggle-row="detailProps.toggleRow"
/>
</template>
</DetailToggler>
</div>
</template>
</vuetable>
<div
v-if="!infinityScroll"
class="pt-2"
>
<vuetable-pagination
ref="pagination"
:css="css.pagination"
#vuetable-pagination:change-page="onChangePage"
/>
</div>
</div>
</template>
<script>
import { Vuetable, VuetablePagination } from 'vue3-vuetable';
import AuthMixin from '#/components/auth/AuthMixin';
import ModalNavigation from '#/mixins/ModalNavigation';
// import Loader from '#/components/ui/Loader';
import { throttle } from 'lodash-es';
import ListFilter from '#/components/auth/list/ListFilter';
import NotifyMixin from '#/mixins/NotifyMixin';
const css = {
table: {
tableClass: 'table-auto w-full table',
tableBodyClass: '',
tableHeaderClass: 'px-4 py-2',
tableWrapper: 'overflow-x-auto flex-1',
loadingClass: 'loading',
ascendingIcon: 'blue chevron up icon',
descendingIcon: 'blue chevron down icon',
ascendingClass: 'sorted-asc',
descendingClass: 'sorted-desc',
sortableIcon: 'grey sort icon',
handleIcon: 'grey sidebar icon',
detailRowClass: 'bg-blue-100',
},
pagination: {
wrapperClass: 'flex justify-center py-4',
activeClass: 'active',
disabledClass: '',
pageClass: 'btn-paging',
linkClass: 'btn-paging',
paginationClass: '',
paginationInfoClass: 'pagination-info',
dropdownClass: '',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
},
};
export default {
components: {
// Loader,
Vuetable,
VuetablePagination,
ListFilter,
},
mixins: [NotifyMixin, AuthMixin, ModalNavigation],
props: {
css: {
type: Object,
default() {
return css;
},
},
},
data() {
return {
// loading: false,
query: '',
// filter: this.filterMapper(this.initialFilter),
sort: undefined,
showFilters: false,
loading: false,
scrollContainer: null,
innerSortOrder: this.sortOrder || [],
data: [],
columns: [
{
name: 'first_name',
title: 'First Name',
class: 'w-1/4',
},
{
name: 'last_name',
title: 'Last Name',
class: 'w-1/4',
},
{
name: 'email',
title: 'Email',
class: 'w-1/4',
},
{
name: 'phone_number',
title: 'Phone Number',
class: 'w-1/4',
},
{
name: 'communities',
title: 'Communities',
class: 'w-1/4',
},
{
label: 'communities',
field: 'Communities',
class: 'w-1/4',
formatter(value) {
// console.log('ROW', value);
let communities = [];
value.community_has_leasing_agents.forEach(function (row) {
communities.push('<span class="tag">' + row.community_id + '</span>');
});
// console.log('communities=>', communities);
return communities.join('');
},
},
],
};
},
computed: {
search() {
return true;
},
searchPlaceholder() {
return 'Search by name, community, email';
},
hasFilters() {
// return !!this.$slots.filters;
return true;
},
hasQuickFilters() {
// return !!this.$slots['quickfilter'];
return true;
},
hasActions() {
// return !!this.$slots.actions;
return true;
},
hasInlineActions() {
// return !!this.$slots['inline-actions'];
return true;
},
hasDetailRow() {
// return this.detailRow !== "";
return true;
},
hasClickRowListener() {
// return this.$attrs['row-clicked'];
return true;
},
nonClickableRow() {
return (
this.onRowClick === 'none' &&
!this.hasClickRowListener &&
!(this.$route.params?.passFlowTo && this.$route.params?.passFlowTo !== this.$route.name)
);
},
detailOptions() {
return {
...this.detailRowOptions,
vuetable: this.$refs.vuetable,
};
},
},
async mounted() {
console.log('== Leasing Agents Index ==');
console.log(this.profile);
await this.getLeasingAgentsFromAPI();
},
created() {
if (!this.community) {
this.notifyError('please select a community to continue, then refresh the browser');
}
},
methods: {
async getLeasingAgentsFromAPI() {
console.log('LEASING::getLeasingAgentsFromAPI()');
this.loading = true;
return await this.$calendarDataProvider
.get('calendarGetLeasingAgents', {
customer_id: this.profile.customerId,
})
.then(res => {
console.log('LEASING::getLeasingAgentsFromAPI() result:');
console.log(res);
this.data = res;
return res;
// return this.getEvents(res);
})
.catch(err => console.log(err.message))
.finally(() => {
this.loading = false;
});
},
rowClass(dataItem) {
const classes = ['table-row', 'row'];
if (this.nonClickableRow) {
classes.push('table-row-nonclickable');
}
if (this.hasDetailRow && this.$refs.vuetable?.isVisibleDetailRow(dataItem[this.trackBy])) {
classes.push('table-row-detail-open');
}
return classes.join(' ');
},
toggleFilters() {
this.showFilters = !this.showFilters;
},
applySearch(value) {
this.query = value;
},
clearSearch() {
this.query = '';
},
applyFilter(values) {
this.filter = values;
this.$refs.vuetable?.changePage(1);
this.$refs.vuetable?.resetData();
},
doSearch: throttle(
function () {
if (this.query.length > 2 || this.query.length === 0) {
this.reload();
}
},
500,
{ trailing: true }
),
onPaginationData(paginationData) {
if (!this.infinityScroll) {
this.$refs.pagination.setPaginationData(paginationData);
}
},
onChangePage(page) {
this.$refs.vuetable.changePage(page);
},
reload() {
this.$refs.vuetable.resetData();
this.$refs.vuetable.refresh();
},
getSort({ sortField, direction }) {
// sortField: 'type&bundle.name', - sort by two fields
const fields = sortField.split('&');
if (fields.length > 1) {
return fields.map(item => {
const [fieldName, dir = direction] = item.split('|');
return `${fieldName},${dir}`;
});
}
return `${sortField},${direction}`;
},
async dataManager(/*sortOrder = [], pagination = { current_page: 1 }*/) {
// allow static data
// if (this.data) {
// return {
// data: this.data,
// };
// }
const data = await this.getLeasingAgentsFromAPI();
return {
data: data,
};
// const prevData = this.$refs.vuetable?.tableData ?? [];
// const { pageSize: size, query, filter, sort: oldSort } = this;
// const sort = sortOrder[0] ? this.getSort(sortOrder[0]) : undefined;
// this.loading = true;
// return this[this.dataProvider]
// .getList(this.resource, {
// page: pagination.current_page - 1,
// size,
// sort,
// query,
// ...this.requestParams,
// ...filter,
// })
// .then(this.responseMapper)
// .then(({ content: nextData, totalElements }) => {
// const newPagination = this.$refs.vuetable?.makePagination(totalElements, this.pageSize, pagination.current_page);
// this.sort = sort;
// this.innerSortOrder = sortOrder;
// return {
// pagination: newPagination,
// data: this.infinityScroll && oldSort === sort ? [...prevData, ...nextData] : nextData,
// };
// })
// .catch(error => {
// this.notifyError(error.message);
// return {
// pagination: this.$refs.vuetable?.tablePagination,
// data: this.$refs.vuetable?.tableData,
// };
// })
// .finally(() => (this.loading = false));
},
handleRowClicked({ data, ...rest }) {
if (this.$route.params?.passFlowTo && this.$route.params?.passFlowTo !== this.$route.name) {
this.$router.push({
name: this.$route.params?.passFlowTo,
params: {
...this.$route.params,
[this.routeIdParam]: data[this.trackBy],
},
});
} else {
switch (this.onRowClick) {
case 'edit':
this.$router.replace({ path: `${this.basePath}/${data[this.trackBy]}` });
break;
case 'details':
this.$router.replace({ path: `${this.basePath}/${data[this.trackBy]}/details` });
break;
default:
this.$emit('row-clicked', { data, ...rest });
}
}
},
handleScrollContainer(e) {
if (this.$refs.vuetable.tablePagination && e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight <= 2) {
this.onChangePage('next');
}
},
},
};
</script>
<style scoped>
.tag {
display: inline-flex;
align-items: center;
overflow: hidden;
margin-right: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
border-radius: 0.0625rem;
--bg-opacity: 1;
background-color: #eaf6ff;
background-color: rgba(234, 246, 255, var(--bg-opacity));
--text-opacity: 1;
color: #105d91;
color: rgba(16, 93, 145, var(--text-opacity));
font-family: 'FrankNew', sans-serif;
font-weight: 500;
}
</style>
I'm attaching my index.vue file and image.

How to move Tinymce's own methods to Vue?

Hi I am moving views from Laravel to Vue2, and work it as components and I have the problem that I don't know how to move 2 functions that I made in JS to Vue, which are setup and image_upload.
CODE JS
var tin = tinymce.init({
selector: 'textarea#description',
height: 300,
menubar: false,
entity_encoding: "raw",
language: 'es',
plugins: ['image', 'advlist', 'autolink', 'lists', 'link', 'charmap', 'preview', 'anchor',
'searchreplace', 'visualblocks', 'code', 'fullscreen', 'insertdatetime', 'media', 'table',
'help', 'wordcount'
],
toolbar: 'undo redo | image | blocks | bold | alignleft aligncenter alignright alignjustify | bullist numlist | table | customInsertData | preview', // | removeformat', //outdent indent => incrementar sangría
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
branding: false,
relative_urls: false,
remove_script_host : false,
document_base_url : window.location.origin,
automatic_uploads: true,
resize_img_proportional: true,
setup: function(editor) {
editor.on('init', function(args) {
editor = args.target;
editor.on('NodeChange', function(e) {
if (e && e.element.nodeName.toLowerCase() == 'img') {
width = e.element.width;
height = e.element.height;
if (width > 800) {
height = height / (width / 800);
width = 800;
}
tinyMCE.DOM.setAttribs(e.element, {
'width': width,
'height': height
});
}
});
});
},
images_upload_handler: (blobInfo) => {
return new Promise((success, failure) => {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', '{{ route('upload_image_no_attach') }}');
var token = '{{ csrf_token() }}';
xhr.setRequestHeader("X-CSRF-TOKEN", token);
xhr.onload = function() {
var json;
if (xhr.status != 200) {
reject('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
reject('Invalid JSON: ' + xhr.responseText);
return;
}
// success(json.location);
if (xhr.status === 200) {
const location =
`${window.location.origin}/opportunity/preview/${json.location}`;
success(location)
}
};
formData = new FormData();
formData.append('image', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
});
}
});
Vue
<template>
<div>
<editor
id="uuid"
:api-key="key"
:init="{
height: 300,
menubar: false,
entity_encoding: 'raw',
language: 'es',
plugins: ['image', 'advlist', 'autolink', 'lists', 'link', 'charmap', 'preview', 'anchor',
'searchreplace', 'visualblocks', 'code', 'fullscreen', 'insertdatetime', 'media', 'table',
'help', 'wordcount'
],
toolbar: 'undo redo | image | blocks | bold | alignleft aligncenter alignright alignjustify | bullist numlist | table ', // | removeformat', //outdent indent => incrementar sangría
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
branding: false,
relative_urls: false,
remove_script_host : false,
document_base_url : window.location.origin,
automatic_uploads: true,
resize_img_proportional: true,
}"
v-model="content"
/>
</div>
</template>
I tried placing the setup as in the JS but it didn't work and as a method I can't think how to call it in the editor.

Handle interaction between vue fields

I have prepared a functional code example in JSFiddle of VUE field interaction.
https://jsfiddle.net/JLLMNCHR/2a9ex5zu/6/
I have a custom autocomplete component that works properly, a normal input field, and a 'Load' button which objetive is to load the value entered in the normal input in the autocomplete field.
This 'load' button is not working.
HTML:
<div id="app">
<p>Selected: {{test1}}</p>
<br>
<div>
<label>Test1:</label>
<keep-alive>
<autocomplete v-model="test1" v-bind:key="1" :items="theItems">
</autocomplete>
</keep-alive>
</div>
<br>
<label>Display this in 'test1':</label>
<input type="text" v-model=anotherField>
<button type="button" v-on:click="loadField()">Load</button>
<br>
<br>
<button type="button" v-on:click="displayVals()">Display vals</button>
</div>
<script type="text/x-template" id="autocomplete">
<div class="autocomplete">
<input type="text" #input="onChange" v-model="search"
#keyup.down="onArrowDown" #keyup.up="onArrowUp" #keyup.enter="onEnter" />
<ul id="autocomplete-results" v-show="isOpen" class="autocomplete-results">
<li class="loading" v-if="isLoading">
Loading results...
</li>
<li v-else v-for="(result, i) in results" :key="i" #click="setResult(result)"
class="autocomplete-result" :class="{'is-active':i === arrowCounter}">
{{ result }}
</li>
</ul>
</div>
</script>
VUE.JS:
const Autocomplete = {
name: "autocomplete",
template: "#autocomplete",
props: {
items: {
type: Array,
required: false,
default: () => []
},
isAsync: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
isOpen: false,
results: [],
search: "",
isLoading: false,
arrowCounter: 0
};
},
methods: {
onChange() {
// Let's warn the parent that a change was made
this.$emit("input", this.search);
// Is the data given by an outside ajax request?
if (this.isAsync) {
this.isLoading = true;
} else {
// Let's search our flat array
this.filterResults();
this.isOpen = true;
}
},
filterResults() {
// first uncapitalize all the things
this.results = this.items.filter(item => {
return item.toLowerCase().indexOf(this.search.toLowerCase()) > -1;
});
},
setResult(result) {
this.search = result;
this.$emit("input", this.search);
this.isOpen = false;
},
onArrowDown(evt) {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter() {
this.search = this.results[this.arrowCounter];
this.isOpen = false;
this.arrowCounter = -1;
},
handleClickOutside(evt) {
if (!this.$el.contains(evt.target)) {
this.isOpen = false;
this.arrowCounter = -1;
}
}
},
watch: {
items: function(val, oldValue) {
// actually compare them
if (val.length !== oldValue.length) {
this.results = val;
this.isLoading = false;
}
}
},
mounted() {
document.addEventListener("click", this.handleClickOutside);
},
destroyed() {
document.removeEventListener("click", this.handleClickOutside);
}
};
new Vue({
el: "#app",
name: "app",
components: {
autocomplete: Autocomplete
},
methods: {
displayVals() {
alert("test1=" + this.test1);
},
loadField() {
this.test1=this.anotherField;
}
},
data: {
test1: '',
anotherField: '',
theItems: [ 'Apple', 'Banana', 'Orange', 'Mango', 'Pear', 'Peach', 'Grape', 'Tangerine', 'Pineapple']
}
});
Any help will be appreciated.
See this new fiddle where it is fixed.
When you use v-model on a custom component you need to add a property named value and watch it for changes, so it can update the local property this.search.

How to change prop dynamically in vue-status-indicator?

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>

Creating Vue Search Bar | How to hide/show data based on input?

I am creating a dynamic search bar that will filter a sidebar full of names based on user input. However, I am having trouble temporarily hiding and showing data based on the search bar's value on keyup. What is the best way to achieve this the "Vue way"?
On keyup, I want to filter through all of the this.people data and only show names that contain the value of the search input.
Below is what my code looks like
Vue.component('sidebar',{
props: ['people', 'tables'],
data: () => {
return {
fullName: ''
}
},
computed: {
computed() {
return [this.people, this.tables].join()
}
},
template:
`
<div id="sidebarContain" v-if="this.people">
<input id="sidebar-search" type="text" placeholder="Search..." #keydown="searchQuery">
<select id="sidebar-select" #change="sidebarChanged">
<option value="AZ">A-Z</option>
<option value="ZA">Z-A</option>
<option value="notAtTable">No Table</option>
<option value="Dean's Guest">Dean's Guest</option>
<option value="BOO | VIP">BOO | VIP</option>
</select>
<div v-for="person in people" :class="[{'checked-in': isCheckedIn(person)}, 'person']" :id="person.id" :style="calcRegColor(person)">
<span v-if="person.table_name">{{person.first_name + ' ' + person.last_name + ' - ' + person.table_name}}</span>
<span v-else>{{person.first_name + ' ' + person.last_name}}</span>
</div>
</div>
`,
methods: {
isCheckedIn(person) {
return person.reg_scan == null ? true : false;
},
isHidden(person)
{
console.log("here");
},
calcRegColor(person)
{
switch(person.registration_type)
{
case "Dean's Guest" :
return {
color: 'purple'
}
break;
case "BOO | VIP" :
return {
color: 'brown'
}
break;
case "Student" :
return {
color: 'green'
}
break;
case "Faculty":
case "Staff":
return {
color: 'blue'
}
break;
case "Alumni Club Leader":
return {
color: 'gold'
}
break;
case "Table Guest" :
return {
color: 'pink'
}
break;
default:
return {
color: 'black'
}
}
}
},
watch: {
computed() {
console.log("People and Tables Available");
}
}
});
var app = new Vue({
el: '#main',
data: {
tables: {},
people: [],
currentAlerts: [],
lastDismissed: []
},
methods: {
loadTables() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'users/getTableAssignments/' + event_id
}).done(data => {
this.tables = data;
});
},
loadPeople() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'users/getParticipants2/' + event_id
}).done(data => {
this.people = data;
this.sortSidebar(this.people);
});
},
loadCurrentAlerts() {
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'alerts/getAlerts/' + event_id
}).done(data => {
this.currentAlerts = data;
});
},
loadLastDismissed(num = 15)
{
$.ajax({
method: 'POST',
dataType: 'json',
url: base_url + 'alerts/getLastDismissed/' + event_id + '/' + num
}).done(data => {
this.lastDismissed = data;
});
},
setRefresh() {
setInterval(() => {
console.log("Getting People and Tables");
this.loadPeople();
this.loadTables();
}, 100000);
},
makeTablesDraggable() {
$(document).on("mouseenter", '.table', function(e){
var item = $(this);
//check if the item is already draggable
if (!item.is('.ui-draggable')) {
//make the item draggable
item.draggable({
start: (event, ui) => {
console.log($(this));
}
});
}
});
},
makePeopleDraggable() {
$(document).on("mouseenter", '.person', function(e){
var item = $(this);
//check if the item is already draggable
if (!item.is('.ui-draggable')) {
//make the item draggable
item.draggable({
appendTo: 'body',
containment: 'window',
scroll: false,
helper: 'clone',
start: (event, ui) => {
console.log($(this));
}
});
}
});
}
makeDroppable() {
$(document).on("mouseenter", ".dropzone, .table", function(e) {
$(this).droppable({
drop: function(ev, ui) {
console.log("Dropped in dropzone");
}
});
});
}
},
mounted() {
this.loadTables();
this.loadPeople();
this.loadCurrentAlerts();
this.loadLastDismissed();
this.setRefresh();
this.makeTablesDraggable();
this.makePeopleDraggable();
this.makeDroppable();
}
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
</head>
<div id="app">
<sidebar :people="people" :tables="tables"></sidebar>
</div>
You can change the people property in sidebar into a computed property, which will be calculated based on user's input.
So change the sidebar code to
<div v-for="person in filteredPeople" :class="[{'checked-in': isCheckedIn(person)}, 'person']" :id="person.id" :style="calcRegColor(person)">
<span v-if="person.table_name">{{person.first_name + ' ' + person.last_name + ' - ' + person.table_name}}</span>
<span v-else>{{person.first_name + ' ' + person.last_name}}</span>
</div>
and add a computed property
computed: {
filteredPeople () {
// Logic to filter data
}
}
A foolish aproach that I use, without computed properties:
JS:
new Vue({
el: '#app',
data: {
list: [],
filteredList: []
},
mounted(){
this.filteredList = this.list
},
methods: {
filter(e){
if(e.target.value === '') this.filteredList = this.list
else {
this.filteredList = []
this.list.forEach(item=>{
if(list.name.includes(e.target.value)) this.filteredList.push(item)
})
}
}
}
})
The "name" property of list object can be changed to whatever property you want to look for.
HTML:
<input #keyup="filter" id="search" type="search" required>