Related
I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});
I'm creating a quiz that requires every answer to be answered. The problem is that you should be able to skip questions if you don't have an answer. I'm trying to set a default answer everytime I press next so when I try to skip one I don't have to answer for it to have a value. The default value I want is each time the last value of my array.
next and previous question
SetQuestion(question) {
if (this.questionNumber >= 0) {
let oldAnswerButton = document.querySelectorAll('.filter_anwser');
// Deletes old question when the next question is clicked
for (let answerButton of oldAnswerButton) {
answerButton.style.display = 'none';
}
}
this.questionNumber = question;
let q = this.quiz[question];
// Check if your at the last question so the next button will stop being displayed.
if (this.questionNumber === Quiz.length - 1) {
this.nextbtn.style.display = 'none';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'grid';
} else if (this.questionNumber === 0) {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'none';
this.resultbtn.style.display = 'none';
} else {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'none';
}
// Displays Question
this.questionName.textContent = q.questionText;
this.questionName.id = "questionID";
return q;
console.log(this.getLink())
console.log(this.tmp)
}
IntoArray() {
const UrlVar = new URLSearchParams(this.getLink())
this.UrlArray = [...UrlVar.entries()].map(([key, values]) => (
{[key]: values.split(",")}
)
);
}
NextQuestion() {
// let quizUrl = this.url[this.questionNumber];
let question = this.SetQuestion(this.questionNumber + 1);
let pre = question.prefix;
let prefixEqual = pre.replace('=', '');
let UrlArr = this.UrlArray;
let UrlKeys = UrlArr.flatMap(Object.keys)
let answers = question.chosenAnswer.slice(0, -1);
this.clicked = true;
// Displays answers of the questions
for (let y = 0; y < answers.length; y++) {
let item = answers[y];
// Display answer buttons
if (UrlKeys.includes(prefixEqual)) {
console.log("exists");
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
} else {
let btn = document.createElement('button');
btn.value = item.id;
btn.classList.add("filter_anwser", pre)
btn.id = 'answerbtn';
btn.textContent = item.name;
this.button.appendChild(btn);
}
// let quizUrl = control.url[control.questionNumber];
// // console.log(this.tmp);
// if (quizUrl === undefined) {
// quizUrl.push(question.prefix[y] + '');
// }
// if (quizUrl === undefined){
// this.tmp.push('');
// }
}
this.IntoArray();
}
PrevQuestion() {
let question = this.SetQuestion(this.questionNumber - 1);
let answers = question.chosenAnswer.slice(0, -1);
// Displays answers of the questions
for (let y = 0; y < answers.length; y++) {
let item = answers[y];
// Display answer buttons
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
}
this.IntoArray();
}
Link creator:
/**
* Returns the parameters for the URL.
*
* #returns {string}
*/
getLink() {
this.tmp = [];
for (let i = 0; i < this.url.length; i++) {
// Check if question is from the same quiz part and adds a , between chosen answers and add the right prefix at the beginning
if (this.url[i].length > 0) {
this.tmp.push("" + Quiz[i].prefix + this.url[i].join(","))
// console.log(this.url)
}
if (this.url[i].length === 0) {
this.tmp.push("");
}
}
/// If answers are from different quiz parts add a & between answers.
return "" + this.tmp.join("&");
// console.log(this.url[i].prefix);
};
Answer click event
control.button.addEventListener("click", function (e) {
const tgt = e.target;
control.clicked = true;
// clear the url array if there's nothing clicked
if (control.url.length === control.questionNumber) {
control.url.push([]);
}
let quizUrl = control.url[control.questionNumber];
// Check if a button is clicked. Changes color and adds value to the url array.
if (quizUrl.indexOf(tgt.value) === -1) {
quizUrl.push(tgt.value);
e.target.style.backgroundColor = "orange";
// Check if a button is clicked again. If clicked again changes color back and deletes value in the url array.
} else {
quizUrl.splice(quizUrl.indexOf(tgt.value), 1);
e.target.style.backgroundColor = "white";
}
console.log(control.getLink());
console.log(quizUrl)
})
array:
class QuizPart {
constructor(questionText, chosenAnswer, prefix, questionDescription) {
this.questionText = questionText;
this.chosenAnswer = chosenAnswer;
this.prefix = prefix;
this.questionDescription = questionDescription;
}
}
class ChosenAnswer {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
let Quiz = [
new QuizPart('Whats your size?', [
new ChosenAnswer('6595', '41'),
new ChosenAnswer('6598', '42'),
new ChosenAnswer('6601', '43'),
new ChosenAnswer('', ''),
], 'bd_shoe_size_ids=',
'The size of your shoes is very important. If you have the wrong size, they wont fit.'),
new QuizPart('What color would you like?', [
new ChosenAnswer('6053', 'Red'),
new ChosenAnswer('6044', 'Blue'),
new ChosenAnswer('6056', 'Yellow'),
new ChosenAnswer('6048', 'Green'),
new ChosenAnswer('', ''),
], 'color_ids=',
'Color isn t that important, It looks good tho.'),
new QuizPart('What brand would you like?', [
new ChosenAnswer('5805', 'Adidas'),
new ChosenAnswer('5866', 'Nike'),
new ChosenAnswer('5875', 'Puma'),
new ChosenAnswer('', ''),
], 'manufacturer_ids=',
'Brand is less important. Its just your own preference'),
]
I tried giving the array's in link creator and my eventlistener a default value and replacing it when I get and actual value from one of my buttons, but it just doesn't work. Can anybody help me?
I understand, that it might be a bit far from what you expect for an answer - but why don't you have a look at a reactive tool, like Vue? It has all the tools that you might need for such a task, and maybe more:
the whole quiz can be abstracted to a simple array of objects (the questions)
next, prev, set default answer becomes a breeze
easy to extend (with questions)
simple to update (template, features, etc.)
Vue.component('QuizQuestion', {
props: ['data', 'selected'],
computed: {
valSelected: {
get() {
return this.selected
},
set(val) {
this.$emit('update:selected', val)
}
},
},
template: `
<div>
{{ data.text }}<br />
{{ data.description }}<br />
<div class="quiz-options">
<label
v-for="val in data.options"
:key="val[0]"
>
<input
type="radio"
:name="data.text"
:value="val"
v-model="valSelected"
/>
{{ val[1] }}
</label>
</div>
</div>
`
})
new Vue({
el: "#app",
computed: {
currentQuestion() {
return this.questions[this.current]
},
hasPrev() {
return !!this.current
},
hasNext() {
return this.current < this.questions.length - 1
},
},
data() {
return {
current: 0,
questions: [{
text: 'Whats your size?',
description: 'The size of your shoes is very important. If you have the wrong size, they wont fit.',
options: [
['6595', '41'],
['6598', '42'],
['6601', '43'],
['', ''],
],
selected: null,
}, {
text: 'What color would you like?',
description: 'Color isn\'t that important, It looks good tho.',
options: [
['6053', 'Red'],
['6044', 'Blue'],
['6056', 'Yellow'],
['6048', 'Green'],
['', ''],
],
selected: null,
}, {
text: 'What brand would you like?',
description: 'Brand is less important. Its just your own preference',
options: [
['5805', 'Adidas'],
['5866', 'Nike'],
['5875', 'Puma'],
['', ''],
],
selected: null,
}, ],
}
},
methods: {
selectDefault() {
this.questions[this.current] = {
...this.questions[this.current],
selected: this.questions[this.current].options.slice(-1)[0],
}
},
getPrev() {
if (this.hasPrev) {
if (!this.currentQuestion.selected) {
this.selectDefault()
}
this.current -= 1
}
},
getNext() {
if (this.hasNext) {
if (!this.currentQuestion.selected) {
this.selectDefault()
}
this.current += 1
}
},
},
template: `
<div>
<quiz-question
:data="currentQuestion"
:selected.sync="currentQuestion.selected"
/><br />
<button v-if="hasPrev" #click="getPrev">PREV</button>
<button v-if="hasNext" #click="getNext">NEXT</button>
<button v-if="!hasNext">RESULT</button>
</div>
`
})
.quiz-options {
display: flex;
flex-direction: column;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
EDIT
But, if frameworks/libraries are not to be used, here's a more OOP approach:
class Quiz {
constructor(questions) {
this._current = 0
this._questions = questions
}
get current() {
return this._current
}
set current(val) {
this._current = val
}
get hasNext() {
return this.current < this.questions.length - 1
}
get hasPrev() {
return !!this.current
}
get questions() {
return this._questions
}
get next() {
this.current = this.hasNext ? this.current + 1 : this.current
return this.currentQuestion
}
get prev() {
this.current = this.hasPrev ? this.current - 1 : this.current
return this.currentQuestion
}
get currentQuestion() {
return this.questions[this.current]
}
}
class Question {
constructor({
text,
description,
options,
prefix,
}) {
this.text = text
this.desc = description
this.prefix = prefix
this._options = options.map(([key, val]) => ({
id: key,
value: [key, val],
selected: false,
}))
}
get options() {
return this._options
}
set options(newOptions) {
this._options = newOptions
}
get selected() {
return this.options.find(({
selected
}) => !!selected)
}
set selected(selectedVal) {
this.options = [...this.options.map(({
value: [key, val],
selected,
...rest
}) => {
return {
...rest,
value: [key, val],
selected: key === selectedVal
}
})]
}
get lastOption() {
return this.options.slice(-1)[0]
}
setDefault() {
if (!this.selected) {
this.selected = this.lastOption.id
}
}
}
const urlParser = (quiz) => {
return quiz.questions.map(({
prefix,
selected = {
value: ['']
}
}) => {
const s = selected.value[0] ? selected.value[0] : ''
return `${prefix}${s}`
}).join('&')
}
const qArr = [{
text: 'text1',
description: 'desc1',
options: [
['1_1', '11'],
['1_2', '12'],
['1_3', '13'],
],
prefix: 'prefix_1_',
},
{
text: 'text2',
description: 'desc2',
options: [
['2_1', '21'],
['2_2', '22'],
['2_3', '23'],
],
prefix: 'prefix_2_',
},
{
text: 'text3',
description: 'desc3',
options: [
['3_1', '31'],
['3_2', '32'],
['3_3', '33'],
],
prefix: 'prefix_3_',
},
]
const getOptionsHtml = ({
text,
options
}) => {
let html = ''
options.forEach(({
id,
value,
selected
}, i) => {
html += `
<label>
<input
class="question-input"
type="radio"
name="${text}"
value="${value[0]}"
${selected ? 'checked' : ''}
/>
${value[1]}
</label>
`
})
return html
}
const getSingleQuestionHtml = (q) => {
const optionsHtml = getOptionsHtml({
text: q.text,
options: q.options
})
return `
${q.text}<br />
${q.desc}<br />
${optionsHtml}
`
}
const registerEventHandlers = ({
container,
question
}) => {
const radioBtns = document.querySelectorAll('.question-input')
radioBtns.forEach((input, i) => {
input.addEventListener('change', function(e) {
question.selected = e.target.value
})
})
}
const updateHtml = ({
container,
question
}) => {
container.innerHTML = getSingleQuestionHtml(question)
registerEventHandlers({
container,
question
})
};
const updateContainer = (container) => (question) => updateHtml({
container,
question
});
const setElDisplay = ({
el,
display
}) => {
if (display) {
el.classList.add("d-inline-block")
el.classList.remove("d-none")
} else {
el.classList.remove("d-inline-block")
el.classList.add("d-none")
}
}
const updateBtnVisibility = ({
btnNext,
btnPrev,
btnResult,
quiz
}) => () => {
setElDisplay({
el: btnNext,
display: quiz.hasNext
})
setElDisplay({
el: btnResult,
display: !quiz.hasNext
})
setElDisplay({
el: btnPrev,
display: quiz.hasPrev
})
}
(function() {
const quiz = new Quiz(qArr.map(q => new Question(q)))
const container = document.getElementById('quiz-container')
updateQuizContainer = updateContainer(container)
updateQuizContainer(quiz.currentQuestion)
const btnPrev = document.getElementById('btn-prev')
const btnNext = document.getElementById('btn-next')
const btnResult = document.getElementById('btn-result')
const updateBtns = updateBtnVisibility({
btnPrev,
btnNext,
btnResult,
quiz
})
updateBtns()
btnPrev.addEventListener('click', function() {
if (quiz.hasPrev) {
quiz.currentQuestion.setDefault()
}
updateQuizContainer(quiz.prev)
updateBtns()
})
btnNext.addEventListener('click', () => {
if (quiz.hasNext) {
quiz.currentQuestion.setDefault()
}
updateQuizContainer(quiz.next)
updateBtns()
})
btnResult.addEventListener('click', function() {
quiz.currentQuestion.setDefault()
console.log(urlParser(quiz))
})
})();
.d-inline-block {
display: inline-block;
}
.d-none {
display: none;
}
<div id="quiz-container"></div>
<div id="quiz-controls">
<button id="btn-prev" class="d-inline-block">
PREV
</button>
<button id="btn-next" class="d-inline-block">
NEXT
</button>
<button id="btn-result" class="d-none">
RESULT
</button>
</div>
<div id="result"></div>
I'm brand new to Angular and typescript and still trying to make it through but now I can't.
I bought a template for Angular (VEX) and I would like to integrate data from firebase into a datatable already present in the template.
In the template, this table is fed by static data and I would like to replace that with my call to firebase.
I am really lost and I would like to understand how I can do to get there.
Here is the manage-users-component.ts
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '#angular/core';
import { Observable, of, ReplaySubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { Customer } from './interfaces/customer.model';
import { MatTableDataSource } from '#angular/material/table';
import { MatPaginator } from '#angular/material/paginator';
import { MatSort } from '#angular/material/sort';
import { MatDialog } from '#angular/material/dialog';
import { TableColumn } from '../../../#vex/interfaces/table-column.interface';
import { aioTableData, aioTableLabels } from '../../static-data/aio-table-data';
import { CustomerCreateUpdateComponent } from './customer-create-update/customer-create-update.component';
import icEdit from '#iconify/icons-ic/twotone-edit';
import icDelete from '#iconify/icons-ic/twotone-delete';
import icSearch from '#iconify/icons-ic/twotone-search';
import icAdd from '#iconify/icons-ic/twotone-add';
import icFilterList from '#iconify/icons-ic/twotone-filter-list';
import { SelectionModel } from '#angular/cdk/collections';
import icMoreHoriz from '#iconify/icons-ic/twotone-more-horiz';
import icFolder from '#iconify/icons-ic/twotone-folder';
import { fadeInUp400ms } from '../../../#vex/animations/fade-in-up.animation';
import { MAT_FORM_FIELD_DEFAULT_OPTIONS, MatFormFieldDefaultOptions } from '#angular/material/form-field';
import { stagger40ms } from '../../../#vex/animations/stagger.animation';
import { FormControl } from '#angular/forms';
import { UntilDestroy, untilDestroyed } from '#ngneat/until-destroy';
import { MatSelectChange } from '#angular/material/select';
import icPhone from '#iconify/icons-ic/twotone-phone';
import icMail from '#iconify/icons-ic/twotone-mail';
import icMap from '#iconify/icons-ic/twotone-map';
import firebase from 'firebase';
import { UserManageService } from '../../services/user-manage.service';
#UntilDestroy()
#Component({
selector: 'vex-manage-users',
templateUrl: './manage-users.component.html',
styleUrls: ['./manage-users.component.scss'],
animations: [
fadeInUp400ms,
stagger40ms
],
providers: [
{
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,
useValue: {
appearance: 'standard'
} as MatFormFieldDefaultOptions
}
]
})
export class ManageUsersComponent implements OnInit, AfterViewInit {
layoutCtrl = new FormControl('boxed');
/**
* Simulating a service with HTTP that returns Observables
* You probably want to remove this and do all requests in a service with HTTP
*/
subject$: ReplaySubject<Customer[]> = new ReplaySubject<Customer[]>(1);
data$: Observable<Customer[]> = this.subject$.asObservable();
customers: Customer[];
#Input()
columns: TableColumn<Customer>[] = [
{ label: 'Checkbox', property: 'checkbox', type: 'checkbox', visible: true },
{ label: 'ShipTo', property: 'extId', type: 'text', visible: true },
{ label: 'uid', property: 'uid', type: 'text', visible: true },
{ label: 'Compagny', property: 'compagny', type: 'text', visible: true },
{ label: 'Name', property: 'name', type: 'text', visible: true, cssClasses: ['font-medium'] },
{ label: 'First Name', property: 'firstName', type: 'text', visible: false },
{ label: 'Last Name', property: 'lastName', type: 'text', visible: false },
{ label: 'Email', property: 'email', type: 'text', visible: true },
{ label: 'Phone', property: 'phone', type: 'text', visible: true },
{ label: 'Role', property: 'role', type: 'text', visible: true },
{ label: 'Actions', property: 'actions', type: 'button', visible: true }
];
pageSize = 10;
pageSizeOptions: number[] = [5, 10, 20, 50];
dataSource: MatTableDataSource<Customer> | null;
selection = new SelectionModel<Customer>(true, []);
searchCtrl = new FormControl();
labels = aioTableLabels;
icPhone = icPhone;
icMail = icMail;
icMap = icMap;
icEdit = icEdit;
icSearch = icSearch;
icDelete = icDelete;
icAdd = icAdd;
icFilterList = icFilterList;
icMoreHoriz = icMoreHoriz;
icFolder = icFolder;
#ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
#ViewChild(MatSort, { static: true }) sort: MatSort;
constructor(private dialog: MatDialog,
private usersManageService: UserManageService ) {
}
get visibleColumns() {
return this.columns.filter(column => column.visible).map(column => column.property);
}
/**
* Example on how to get data and pass it to the table - usually you would want a dedicated service with a HTTP request for this
* We are simulating this request here.
*/
getData() {
return of(aioTableData.map(customer => new Customer(customer)));
}
ngOnInit() {
const users = this.usersManageService.getUsers();
console.log(users);
this.getData().subscribe(customers => {
this.subject$.next(customers);
});
console.log(aioTableData);
this.dataSource = new MatTableDataSource();
this.data$.pipe(
filter<Customer[]>(Boolean)
).subscribe(customers => {
this.customers = customers;
this.dataSource.data = customers;
});
this.searchCtrl.valueChanges.pipe(
untilDestroyed(this)
).subscribe(value => this.onFilterChange(value));
}
ngAfterViewInit() {
this.dataSource.paginator = this.paginator;
this.dataSource.sort = this.sort;
}
createCustomer() {
this.dialog.open(CustomerCreateUpdateComponent).afterClosed().subscribe((customer: Customer) => {
/**
* Customer is the updated customer (if the user pressed Save - otherwise it's null)
*/
if (customer) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
this.customers.unshift(new Customer(customer));
this.subject$.next(this.customers);
}
});
}
updateCustomer(customer: Customer) {
this.dialog.open(CustomerCreateUpdateComponent, {
data: customer
}).afterClosed().subscribe(updatedCustomer => {
/**
* Customer is the updated customer (if the user pressed Save - otherwise it's null)
*/
if (updatedCustomer) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
const index = this.customers.findIndex((existingCustomer) => existingCustomer.uid === updatedCustomer.uid);
this.customers[index] = new Customer(updatedCustomer);
this.subject$.next(this.customers);
}
});
}
deleteCustomer(customer: Customer) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
this.customers.splice(this.customers.findIndex((existingCustomer) => existingCustomer.uid === customer.uid), 1);
this.selection.deselect(customer);
this.subject$.next(this.customers);
}
deleteCustomers(customers: Customer[]) {
/**
* Here we are updating our local array.
* You would probably make an HTTP request here.
*/
customers.forEach(c => this.deleteCustomer(c));
}
onFilterChange(value: string) {
if (!this.dataSource) {
return;
}
value = value.trim();
value = value.toLowerCase();
this.dataSource.filter = value;
}
toggleColumnVisibility(column, event) {
event.stopPropagation();
event.stopImmediatePropagation();
column.visible = !column.visible;
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
trackByProperty<T>(index: number, column: TableColumn<T>) {
return column.property;
}
// onLabelChange(change: MatSelectChange, row: Customer) {
// const index = this.customers.findIndex(c => c === row);
// this.customers[index].labels = change.value;
// this.subject$.next(this.customers);
// }
}
Here is the Firebase Realtime database "users" that I would like to insert in my datatable
Here is the function I imaginated for do the job but I dont know where to put it into
getUsers() {
firebase.database().ref('/users').once('value').then((snapshot) => {
const users = snapshot.val();
return users;
});
}
I am completely stuck and I thank very much in advance anyone who can help me.
I made some progress in my case and I was able to recover the values I wanted.
The array returned by my function matches the array of the example template.
getData() {
const users = [];
firebase.database().ref('/users').once('value').then((snapshot) => {
users.push(snapshot.val()) ;
const users2 = users[0];
const mapped = Object.keys(users2).map(key => (users2[key]));
console.log(mapped.map(customer => new Customer(customer)));
// console.log(aioTableData.map(customer => new Customer(customer)));
return of(mapped.map(customer => new Customer(customer)));
});
// return of(aioTableData.map(customer => new Customer(customer)));
The problem is that in ngOnInit, when the call to the function is done, it expects a subscribe and I don't know how to do it.
ngOnInit() {
this.getData().subscribe(customers => {
this.subject$.next(customers);
});
this.dataSource = new MatTableDataSource();
this.data$.pipe(
filter<Customer[]>(Boolean)
).subscribe(customers => {
this.customers = customers;
this.dataSource.data = customers;
});
this.searchCtrl.valueChanges.pipe(
untilDestroyed(this)
).subscribe(value => this.onFilterChange(value));
}
An idea?
i am using react-quill as my editor and recently i configured my image handler function to pass props to the handler and after making the change my editor behaves weirdly and when ever i type something on the other input fields my editor comes into focus and automatically words are typed in it
below is the code for my editor
please any help or suggestion will be greatly appreciated.
Component
} from 'react';
// import {
// Editor
// } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import axios from 'axios'
import {
API_URL
} from './../../api_url'
// import * as Icons from 'images/icons';
import * as loadImage from 'blueimp-load-image';
import {
key
} from '../../assets/encryptionkey'
import globalStyles from '../../stylesheets/ui.css'
import blogStyles from './blogs.css'
import bootstrapStyles from '../../stylesheets/bootstrap/css/bootstrap.min.css'
import fontAwesomeStyles from '../../stylesheets/font-awesome/css/font-awesome.min.css'
import actionIconsStyles from '../../stylesheets/action_icons.css'
import cx from 'classnames'
import './editor.css';
import s from './editor.css';
//import CKEditor from '#ckeditor/ckeditor5-react';
//import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
//import ReactQuill, { Quill } from "react-quill";
//var Image = Quill.import('formats/image');
//Image.className = 'custom-class-to-image';
//Quill.register(Image, true);
export default class BlogEditor extends Component {
constructor( loader ) {
super();
this.state = {
editorHtml: '', theme: 'snow',
text:''
}
this.handleChange = this.handleChange.bind(this)
// var that=this;
if (typeof window !== 'undefined') {
this.ReactQuill = require('react-quill')
const ReactQuill=this.ReactQuill;
var Image = ReactQuill.Quill.import('formats/image');
Image.className = 'blog-content-image';
ReactQuill.Quill.register(Image, true);
// ReactQuill.Quill.setContents(editor.clipboard.convert(html));
}
}
componentWillReceiveProps(){
//debugger
let clearContent=this.props.clearContent
if(clearContent){
// this.editorRef.setEditorContents(this.editorRef.getEditor(), '<h1>test</h1>');
}
}
handleChange(value) {
//debugger
this.setState({ text: value })
// this.props.changeInEditor(value)
}
imageHandler({ val, componentProps }) {
// debugger
let self=this
let image;
let image_extension;
const Cryptr = require('cryptr');
const cryptr = new Cryptr(key);
const users = localStorage.getItem('users') ? JSON.parse(cryptr.decrypt(localStorage.getItem('users'))) : {}
// console.log(users[users.lastLoginId])
let loggedinUser = users[users.lastLoginId];
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.setAttribute("class", "Editor-mage");
input.click();
input.onchange = async () => {
//debugger
const file = input.files[0];
var ValidImageTypes = ["image/gif", "image/jpeg", "image/png", "image/jpg", "image/GIF", "image/JPEG", "image/PNG", "image/JPG"];
let file_type = file.type
let filename = file.name
let extension = filename.split('.').pop();
if(ValidImageTypes.indexOf(file_type) >= 0){
if(file.size<=500000&&file.size>=50000){
var fileToLoad = file
loadImage(fileToLoad, (canvas) => {
if(canvas){
// this.setState({
image=canvas.toDataURL()
image_extension=extension
//});
const res = new Promise(function(resolve, reject) {
axios({
method:'post',
url:API_URL+'api/v1/postblogimage',
headers:{
'x-access-handler':loggedinUser.token
},
data:{
image: image,
image_extension:image_extension,
userid:loggedinUser.userid
}
})
//axios.post(API_URL + 'api/v1/postblogimage', formData, config)
.then((response) => {
if (response.data.error == 'false' || response.data.error == false) {
if (response.data.status == 200 && response.data.message == "Image uploaded successfully") {
//debugger
const range = self.quill.getSelection(true);
// Insert temporary loading placeholder image
// this.quill.insertEmbed(range.index, 'image', `${window.location.origin}/images/loaders/placeholder.gif`);
// Move cursor to right side of image (easier to continue typing)
self.quill.setSelection(range.index + 1);
// Remove placeholder image
self.quill.deleteText(range.index, 1);
// Insert uploaded image
let url=response.data.data[0].imageURL;
self.quill.insertEmbed(range.index, 'image', url);
self.quill.pasteHTML(range.index, <img src={url} class="blog-image-content" alt="Responsive image"/>);
}
}else if(response.data.error == 'true' || response.data.status == '500')
componentProps.error('Sorry, Inappropriate image')
// }
}).catch((error) => {
// reject(Error("It broke"));
});
});
}
}, {orientation: true});
}
else{
componentProps.error(" Sorry, File size should be of size between 50 kb to 500kb")
}
}
else{
// this.setState({
// image_warning:'Invalid image type',
// image:'',
// image_extension:''
//})
// this.fileInput.value=''
}
};
}
render() {
const ReactQuill = this.ReactQuill
if (typeof window !== 'undefined' && ReactQuill) {
return (
<div className="editor-container">
<ReactQuill
ref={(el) => this.quillRef = el
}
onChange={this.handleChange}
placeholder={"share your thoughts"}
modules={{
toolbar: {
container: [
[{ header: '1' }, { header: [2,3, 4, 5, 6] }, { font: [] }],
[{ size: [ 'small', false, 'large', 'huge' ] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{ list: 'ordered' }, { list: 'bullet' }],
['link', 'image', 'video'],
['clean'],
['code-block'],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'align': [] }],
],
handlers: {
image: (val) => this.imageHandler({ val, componentProps: this.props })
// image: () => this.imageHandler
}
}
}}
/>
</div>
)
}
else {
return <textarea />;
}
}
}```
Each re-render modules object creating, useMemo fixed.
const modules = useMemo(() => ({
imageResize : {
parchment: Quill.import('parchment'),
modules: ['Resize', 'DisplaySize', 'Toolbar'],
},
toolbar: {
container: [
[{ header: [1, 2, 3, 4, false] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{ list: "ordered" },
{ list: "bullet" },
{ indent: "-1" },
{ indent: "+1" },
],
[{align: [ ]}],
["link", "image"],
["clean"],
],
handlers: {
image: () => {
imageHandler()
}
}
},
}), []);
I am trying to use React Data Grid in my project and I want to use row drag and drop and Drag Columns to Reorder features together.
I tried to do this by passing draggable: true for ReactDataGrid column property and wrapping the ReactDataGrid and Draggable.Container with DraggableHeader.DraggableContainer.
It makes column header moveable but it does not trigger onHeaderDrop action in DraggableContainer and it gave a console error Uncaught TypeError: Cannot read property 'id' of undefined.
I change example23-row-reordering.js according to the description above.
const ReactDataGrid = require('react-data-grid');
const exampleWrapper = require('../components/exampleWrapper');
const React = require('react');
const {
Draggable: { Container, RowActionsCell, DropTargetRowContainer },
Data: { Selectors },
DraggableHeader: {DraggableContainer}
} = require('react-data-grid-addons');
import PropTypes from 'prop-types';
const RowRenderer = DropTargetRowContainer(ReactDataGrid.Row);
class Example extends React.Component {
static propTypes = {
rowKey: PropTypes.string.isRequired
};
static defaultProps = { rowKey: 'id' };
constructor(props, context) {
super(props, context);
this._columns = [
{
key: 'id',
name: 'ID',
draggable: true
},
{
key: 'task',
name: 'Title',
draggable: true
},
{
key: 'priority',
name: 'Priority',
draggable: true
},
{
key: 'issueType',
name: 'Issue Type',
draggable: true
}
];
this.state = { rows: this.createRows(1000), selectedIds: [1, 2] };
}
getRandomDate = (start, end) => {
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toLocaleDateString();
};
createRows = (numberOfRows) => {
let rows = [];
for (let i = 1; i < numberOfRows; i++) {
rows.push({
id: i,
task: 'Task ' + i,
complete: Math.min(100, Math.round(Math.random() * 110)),
priority: ['Critical', 'High', 'Medium', 'Low'][Math.floor((Math.random() * 3) + 1)],
issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.floor((Math.random() * 3) + 1)],
startDate: this.getRandomDate(new Date(2015, 3, 1), new Date()),
completeDate: this.getRandomDate(new Date(), new Date(2016, 0, 1))
});
}
return rows;
};
rowGetter = (i) => {
return this.state.rows[i];
};
isDraggedRowSelected = (selectedRows, rowDragSource) => {
if (selectedRows && selectedRows.length > 0) {
let key = this.props.rowKey;
return selectedRows.filter(r => r[key] === rowDragSource.data[key]).length > 0;
}
return false;
};
reorderRows = (e) => {
let selectedRows = Selectors.getSelectedRowsByKey({rowKey: this.props.rowKey, selectedKeys: this.state.selectedIds, rows: this.state.rows});
let draggedRows = this.isDraggedRowSelected(selectedRows, e.rowSource) ? selectedRows : [e.rowSource.data];
let undraggedRows = this.state.rows.filter(function(r) {
return draggedRows.indexOf(r) === -1;
});
let args = [e.rowTarget.idx, 0].concat(draggedRows);
Array.prototype.splice.apply(undraggedRows, args);
this.setState({rows: undraggedRows});
};
onRowsSelected = (rows) => {
this.setState({selectedIds: this.state.selectedIds.concat(rows.map(r => r.row[this.props.rowKey]))});
};
onRowsDeselected = (rows) => {
let rowIds = rows.map(r => r.row[this.props.rowKey]);
this.setState({selectedIds: this.state.selectedIds.filter(i => rowIds.indexOf(i) === -1 )});
};
render() {
return (
<DraggableContainer
onHeaderDrop={()=>{console.log('column dropped');}}
>
<Container>
<ReactDataGrid
enableCellSelection={true}
rowActionsCell={RowActionsCell}
columns={this._columns}
rowGetter={this.rowGetter}
rowsCount={this.state.rows.length}
minHeight={500}
rowRenderer={<RowRenderer onRowDrop={this.reorderRows}/>}
rowSelection={{
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: this.onRowsSelected,
onRowsDeselected: this.onRowsDeselected,
selectBy: {
keys: {rowKey: this.props.rowKey, values: this.state.selectedIds}
}
}}/>
</Container>
</DraggableContainer>);
}
}
module.exports = exampleWrapper({
WrappedComponent: Example,
exampleName: 'Row Reordering',
exampleDescription: 'This examples demonstrates how single or multiple rows can be dragged to a different positions using components from Draggable React Addons',
examplePath: './scripts/example23-row-reordering.js'
});
I went through their documentation but could not find any place where they say these two features can't use together. But in their examples does not provide any example for this.Any example code for documentation describes how to use these two features together would be greatly appreciated.