How to validate child component using vue ant design? - javascript

I have this component DynamicSelect (child compoenent) and I'm using it in an another component (parent) but when I try to validate my childe component, it deliver always the value as null so the validation is always false
DynamicSelect Component:
<template>
<a-select
:showSearch="true"
:placeholder=placeholder
:value="selectedValue"
#search="searchRegex($event)"
#change="$emit('changed-item', setChangedItem($event))"
#select="$emit('selected-item', setSelectedItem($event))"
:filterOption="filterOption"
>
<a-select-option
v-for="(item,idx) in dropdownData"
:value="idx"
:key="idx"
>{{item.text}}</a-select-option>
</a-select>
</template>
<script>
export default {
name: "DynamicSelect",
data(){
return{
dropdownData: [],
copyDropdownData:[],
selectedValue: undefined
}
},
props:{
//Input data collection
dataSrc:Array,
//Placeholder for input field
placeholder: String,
//if true the dropdown will be automatically cleared after element selected
resetAfterSelect: false,
// List of id to filter the dropdown list
lookFor:Array,
// Data to display in the dropdown if not set, lookFor list will be displayed
displayedValues: Array,
//Default Value
defaultValues:String,
},
beforeMount(){
this.checkDefaultVariable();
},
watch:{
dataSrc:function(newVar,oldVar) { // watch it
this.checkDefaultVariable()
}
},
methods:{
//Search for search term in the data collection 'lookFor' elements to set the dropdown list
async searchRegex(term){
if(term.length>2) {
let searchTerm = new RegExp(term.toUpperCase());
this.dropdownData = await this.filterData(searchTerm);
this.copyDropdownData = JSON.parse(JSON.stringify(this.dropdownData));
}
else
{
this.dropdownData = [];
this.copyDropdownData = [];
}
},
filterData(searchTerm){
return this.dataSrc.filter(x => {
let filtered= [];
for (let i=0; i<this.lookFor.length;i++){
if(x[this.lookFor[i]])
{
if(searchTerm.test(x[this.lookFor[i]].toUpperCase()))
{
let text = '';
if(this.displayedValues !== undefined)
{
for (let k=0; k<this.displayedValues.length;k++)
{
text += x[this.displayedValues[k]];
if(k < this.displayedValues.length-1)
text += ', '
}
}
else {
for (let k=0; k<this.lookFor.length;k++)
{
text += x[this.lookFor[k]];
if(k < this.lookFor.length-1)
text += ', '
}
}
x.text = text;
filtered.push(x);
}
}
}
return filtered.length>0
});
},
// just a logger
logger(event){
console.log(event);
},
async checkDefaultVariable(){
if (this.defaultValues !== '' && this.defaultValues !== undefined && this.dataSrc.length>0 ){
// console.log('DATA',this.dataSrc);
await this.searchRegex(this.defaultValues);
let selected = await this.setSelectedItem(0);
this.$emit('selected-item', selected)
}
},
// return the selected Item as an Object
setSelectedItem(id){
// console.log('ON SELECT');
let temp = JSON.parse(JSON.stringify(this.dropdownData[id]));
delete temp.text;
if(this.resetAfterSelect)
{
this.dropdownData = [];
this.selectedValue = undefined;
}
else {
this.selectedValue = id;
}
return temp
},
setChangedItem(id){
let temp = JSON.parse(JSON.stringify(this.copyDropdownData[id]));
delete temp.text;
if(this.resetAfterSelect)
{
this.copyDropdownData = [];
this.selectedValue = undefined;
}
else {
this.selectedValue = id;
}
return temp
},
// search in the dropdown list
filterOption(input, option) {
let searchTerm = new RegExp(input.toUpperCase());
if(searchTerm.test(this.dropdownData[option.key].text.toUpperCase()))
return true;
else {
for(let i=0;i<this.lookFor.length;i++){
if(searchTerm.test(this.dropdownData[option.key][this.lookFor[i]].toUpperCase()))
return true;
else if(i >= this.lookFor.length)
return false;
}
}
}
}
}
</script>
parent component:
<template>
<dynamic-select
:dataSrc="users"
placeholder="Lastname, Firstname"
#selected-item="onSelectUser($event)"
#changed-item="onSelectUser($event)"
:lookFor="['lastname','firstname']"
v-decorator="['contact', {valuePropName:'selectedValue',
rules: [{ required: true,
validator: userExists,
message: 'Error'}]}]"
>
</dynamic-select>
</template>
<script>
.
.
.
methods: {
userExists(rule, value, callback) {
console.log('VALUE', value); //always undefined
console.log('RULES',rule);
console.log('CALLBACK',callback)
return value !== null && value !== undefined && value.length > 2;
},
onSelectUser(user) {
console.log("user: " , user); // set with the selected value
}
},
.
.
.
</script>
I expect that the child component returns the selected value like when emitting an event, I also tried with models but it hasn't helped
thanks :)

You can easily communicate between components
Vue.config.debug = true;
// Parent
let App = new Vue({
el: "#the-parent",
data(){
return{ msg: "Nothing.." };
},
methods:{
receivedFromChild(request){
this.msg = request;
}
},
// Children
components: {
'children': {
template: `
<div><button #click="request">Send to parent!</button>` + `<input type="text" v-model="text"></div>`,
props: [ 'childrenRequest' ],
data() {
return {
text: 'this is value'
}
},
methods: {
request(){
console.log('work!');
this.$emit('received', this.text);
}
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="the-parent">
<h3>The children want me to say: {{ msg }}</h3>
<children #received="receivedFromChild"></children>
</div>

Related

Vuex store creates a new instance when imported in different modules

I'm struggling for a few days with importing my Vuex store into multiple different modules. For some reason it seems that my store is creating new instances for every import. In one Vue object i am setting a value to the store, but for an unknown reason to me it is not accessible in another Vue object. I have 3 files: store.js, addresses.js, and relation.js. In the store.js file the following code is present:
const Vue = require('vue').default;
const Vuex = require("vuex");
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
addresses: {
address: 'test'
}
},
mutations: {
setAddress(state, payload) {
state.addresses.address = payload;
}
},
actions: {
setAddress(state, payload) {
state.commit('setAddress', payload);
}
},
getters: {
getAddress(state) {
return state.addresses.address;
}
},
});
export default store;
My addresses.js contains:
import agent from '../agent.js';
const Vue = require("vue").default;
import __ from '../translations/translate';
import Swal from '../../../app-assets/vendors/js/extensions/sweetalert2.all.min'
import store from "../store";
var address = new Vue({
el: '#address-form',
data: {
address: {
id: null,
postal_code: '',
house_number: '',
house_number_addition: '',
street_name: '',
place: '',
country: defaultAddressCountry[0],
latitude: null,
longitude: null,
extra_address_line: null,
safety_instructions: null,
notes: null,
po_box_number: '',
is_po_box: false
},
address_countries: addressCountries,
inputErrors: {
postal_code: '',
house_number: '',
street_name: '',
place: '',
po_box_number: '',
}
},
methods: {
completeAddress: function(){
let self = this;
if(this.address.postal_code && this.address.house_number && this.address.postal_code.length > 3 && this.address.house_number.length > 0){
$('#complete-address i').addClass('rotate');
agent.Address.completeAddress(this.address.postal_code, this.address.house_number.toString() + this.address.house_number_addition, this.address.country)
.then(function(response){
self.address.street_name = response.data.address.street;
self.address.place = response.data.address.locality;
self.address.latitude = response.data.location.latitude;
self.address.longitude = response.data.location.longitude;
$('#complete-address i').removeClass('rotate');
self.validateFields();
}).catch(function(){
$('#complete-address').addClass('text-color-red');
setTimeout(function(){
$('#complete-address').removeClass('text-color-red');
}, 1000)
$('#complete-address i').removeClass('rotate');
self.address.street_name = null;
self.address.place = null;
self.address.latitude = null;
self.address.longitude = null;
});
}else{
if(!this.address.postal_code || this.address.postal_code.length <= 4){
$('#input-postal-code').addClass('has-error');
}
if(!this.address.house_number || this.address.house_number.length == 0){
$('#input-house-number').addClass('has-error');
}
setTimeout(function(){
$('#input-postal-code, #input-house-number').removeClass('has-error');
}, 500)
}
},
gotoMaps: function () {
open('https://maps.google.com/?q=' + this.address.latitude + ', ' + this.address.longitude);
},
sanitizeFields: function(){
this.address.postal_code = this.address.postal_code.replace(/[^0-9a-z]/gi, '').toUpperCase().substr(0, 6);
this.address.house_number = this.address.house_number.replace(/[^0-9]+/g, '').substr(0, 4);
this.address.house_number_addition = this.address.house_number_addition.replace(/[^0-9a-z]/gi, '').toUpperCase().substr(0, 2);
},
validateFields: function(){
let has_errors = false;
this.inputErrors.postal_code = '';
if(this.address.postal_code.length < 5){
this.inputErrors.postal_code = __('invalid input');
has_errors = true;
}
if(this.address.postal_code.length === 0){
this.inputErrors.postal_code = __('required');
has_errors = true;
}
this.inputErrors.house_number = '';
if(this.address.house_number.length === 0 && !this.address.is_po_box){
this.inputErrors.house_number = __('required');
has_errors = true;
}
this.inputErrors.street_name = '';
if(this.address.street_name.trim().length === 0 && !this.address.is_po_box){
this.inputErrors.street_name = __('required');
has_errors = true;
}
this.inputErrors.place = '';
if(this.address.place.trim().length === 0){
this.inputErrors.place = __('required');
has_errors = true;
}
this.inputErrors.po_box_number = '';
if(this.address.po_box_number.trim().length === 0 && this.address.is_po_box){
this.inputErrors.po_box_number = __('required');
has_errors = true;
}
return !has_errors;
},
saveAddress: function(){
if(this.address.is_po_box){
this.address.street_name = '';
this.address.house_number = '';
this.address.house_number_addition = '';
this.address.safety_instructions = null;
this.address.extra_address_line = null;
this.address.notes = null;
}else{
this.address.po_box_number = '';
}
if(this.validateFields()){
let self = this;
if(this.address.id) {
agent.Address.update(this.address);
}else{
agent.Address.create(this.address).then(function (result) {
if(result.status)
self.address = result.data;
}).catch(function(error){
if(error.response.status === 409){
let house_number = [error.response.data.house_number];
if(error.response.data.house_number_addition){
house_number.push(error.response.data.house_number_addition);
}
Swal.fire({
title: __('Existing address was found'),
icon: 'info',
html: '' +
'<table style="text-align:left;" class="table table-bordered">' +
'<tr>' +
'<td>'+__('Address')+'</td>' +
'<td>'+error.response.data.street_name+' ' + house_number.join('-') + '</td>' +
'</tr>' +
'<tr>' +
'<td>'+__('Postal code')+'</td>' +
'<td>'+error.response.data.postal_code+'</td>' +
'</tr>' +
'<tr>' +
'<td>'+__('Place')+'</td>' +
'<td>'+error.response.data.place+'</td>' +
'</tr>' +
'<tr>' +
'<td>'+__('Country')+'</td>' +
'<td>'+error.response.data.country+'</td>' +
'</tr>' +
'</table>',
customClass: 'swal-wide',
showCancelButton: true,
confirmButtonText: __('Use this address'),
cancelButtonText: __('No, create a new one'),
}).then(function(result){
if(result.value === true){
store.dispatch('setAddress', self.address)
console.log(store.state.addresses.address)
}
});
}
});
}
}
}
},
watch: {
address: {
handler(){
if(this.address.is_po_box){
$('.no-po-box').hide();
$('#extra-address-info').hide();
$('#show-address-extra').attr('data-expanded', 'false');
$('#show-address-extra').find('i').removeClass('icon-chevron-up').addClass('icon-chevron-down');
$('.is-po-box').show();
}else{
$('.no-po-box').show();
$('.is-po-box').hide();
}
this.sanitizeFields();
},
deep: true
}
},
computed: {
showMapsIcon: function(){
return this.address.latitude && this.address.longitude;
}
},
created(){
$(document).on('click', '.address-selector', function(){
$('#address-modal').modal();
});
$(document).on('click', '#show-address-extra', function(){
if($(this).attr('data-expanded') == 'false') {
$('#extra-address-info').slideDown();
$(this).attr('data-expanded', 'true');
$(this).find('i').removeClass('icon-chevron-down').addClass('icon-chevron-up');
}else{
$('#extra-address-info').slideUp();
$(this).attr('data-expanded', 'false');
$(this).find('i').removeClass('icon-chevron-up').addClass('icon-chevron-down');
}
});
$('.dataTable').DataTable();
},
delimiters: ['[[' , ']]']
});
export default address;
My relation.js file:
import agent from '../../agent.js'
const Vue = require("vue").default;
import store from "../../store";
var relation = new Vue({
el: '#upsert-relation-form',
data: {
relation: {
id: null,
name: null,
is_supplier: false,
is_customer: true,
is_prospect: false,
is_debtor: true,
general_email_address: null,
general_phone_number: defaultCountryCode,
communication_language: languages.find(x => x.code == defaultLanguage[0])['code'],
account_manager: null,
tags: [],
industries: [],
},
languages: languages,
account_managers: [
{
id: 1,
name: 'Piet de Vries'
},
{
id: 2,
name: 'Willem Aardappel'
}
]
},
computed: {
visiting_address(){
console.log(store.getters.getAddress);
return store.getters.getAddress;
}
},
methods: {
completeAddress: function(address){
console.log(address);
}
},
created(){
let self = this;
$(document).on('click', '.tags-container', function(){
$(this).find('input').focus();
});
$(document).on('click', '.tags-container li a.remove', function(){
let field = $(this).closest('.tags-container').attr('data-field');
let val = $(this).closest('li').text().trim();
let index = self.relation[field].findIndex(function(tag){
return tag.value == val;
});
self.relation[field].splice(index, 1);
});
$(document).on('keyup', '.tags-container input', function(e){
let field = $(this).closest('.tags-container').attr('data-field');
if(e.keyCode === 13){
let tagValue = $(this).val().trim().toLowerCase().replace(/[^a-zA-Z 0-9]+/g, '');
if(tagValue.length < 3){
return;
}
let index = self.relation[field].findIndex(function(tag){
return tag.value == tagValue;
});
if(index !== -1){
$(this).val('');
return;
}
self.relation[field].push({
id: null,
value: tagValue
});
$(this).val('');
}
});
$(document).on('keydown', '.tags-container input', function(e){
let field = $(this).closest('.tags-container').attr('data-field');
if(e.keyCode === 8 && $(this).val().trim().length === 0){
self.relation[field].pop();
}
});
},
delimiters: ['[[' , ']]']
});
export default relation;
In addresses.js I see that the value is correctly set to the store. But in the relation.js file it still gets the original data set on initiation of the store.
It's supposed to be done the other way around, you import the module in your vuex and not the vuex in your module.
So you should have a "Master module" that looks like this:
import Vue from 'vue';
///////////Vuex et store
import Vuex from 'vuex';
/////////////////Modules
import media from "./modules/media";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
}
,
actions: {
},
modules: {
media
}
})
and then your module should simply be like so:
const state = {
};
const getters = {
};
const mutations = {
};
const actions = {
};
export default {
namespaced: true,
state,
getters,
actions,
mutations
};
The namespaced part is optional and here is the link to the doc:
https://vuex.vuejs.org/fr/guide/modules.html
In the doc it's organised in a single file, the way I presented it correspond to one file for the "Master Module" which is your store really, and then a file by module.

How to dispatch a Vue computed property

I´m trying to dispatch an object which is created in a computed.
I can´t get it to work as I´m fairly new to vue.js
I want to dispatch the object "updateObject" to the vuex-store.
Tried with setters but didn´t work. I think if I can set the "varia" object to the same object like "updateObject" then I could maybe dispatch it?
Hope somebody can help me.
Here is my code:
<template>
<div class="detail">
<b-row align-v="center"><b-button variant="success" #click="submit()">submit</b-button></b-row>
// some more code...
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
var ergebnis = {}
res.forEach(key => {
if (this.FA[key]) {
ergebnis[key] = this.FA[key]
}
})
return ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
</script>
It tell´s me "this.ergebnis" is undefined
You can try it declaring "ergebnis" as global variable under data as
export default {
data () {
return {
subID: '',
res: '',
showAlert: true,
varia: null,
ergebnis : {}
}
},
computed: {
...mapState([
'FA',
'Main',
'Sub',
'layouttype'
]),
getVariable: function (Sub, layouttype) {
const subID = this.layouttype.sub_id
var filterObj = this.Sub.filter(function (e) {
return e.sub_id === subID
})
console.log(filterObj)
return filterObj
},
updateObject: {
// getterfunction
get: function () {
var len = this.getVariable.length
var res = []
for (var i = 0; i < len; i++) {
if (i in this.getVariable) {
var val = this.getVariable[i].variable
res.push(val)
}
}
console.log(res)
res.forEach(key => {
if (this.FA[key]) {
this.ergebnis[key] = this.FA[key]
}
})
return this.ergebnis
},
// setterfunction
set: function (value) {
this.varia = value
}
}
},
methods: {
submit () {
this.$store.dispatch('sendData', this.ergebnis)
}
}
}
Now ergebnis is accessible

Filter tasks doesn't work - Todo list with MVC pattern using es6 classes

I'm trying to write a todo list project with pure JS using MVC pattern. It's my first project with MVC and I have a real problem that I can't solve.
I have three buttons, each button gets a value as filter value (in Model class) for complete, active and all task. default value for filter is 0 that refers to all button.
when complete button is active, new todo adds to page, but that page is only for complete todo and new todo must add to all pages while complete page is still active.
I write some methods for handle it, but they doesn't work and I can't understand where is the problem.
how can I solve it?
this is my code:
class Model {
constructor() {
this.todoS = [];
this.filter = 0;
}
bindTodoListChanged(callback) {
this.onTodoListChanged = callback;
}
_commit(todoS) {
this.onTodoListChanged(todoS);
}
addTodo(todoText) {
var todo = {
id:
this.todoS.length > 0
? this.todoS[this.todoS.length - 1].id + 1
: 0,
text: todoText,
complete: false
};
this.todoS.push(todo);
this._commit(this.todoS);
}
toggleTodo(id) {
this.todoS = this.todoS.map(todo =>
todo.id === id
? {
id: todo.id,
text: todo.text,
complete: !todo.complete
}
: todo
);
this._commit(this.todoS);
}
filterTodo(filter) {
this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
}
class View {
constructor() {
this.form = document.querySelector("#taskForm");
this.input = document.getElementById("taskInput");
this.list = document.querySelector("#taskList");
this.filterBtnS = document.getElementById("filterButtons");
this.allBtn = document.querySelector(".all");
this.activeBtn = document.querySelector(".active");
this.completeBtn = document.querySelector(".complete");
}
createElement(tag, className) {
var element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
var element = document.querySelector(selector);
return element;
}
get _todoText() {
return this.input.value;
}
_resetInput() {
this.input.value = "";
}
displayTodoS(todoS) {
// Faster way for clear tasks
while (this.list.firstChild) {
this.list.removeChild(this.list.firstChild);
}
if (todoS.length !== 0) {
todoS.forEach(todo => {
var li = this.createElement("li", "task"),
span = this.createElement("span");
li.id = todo.id;
var checkbox = this.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.complete;
if (todo.complete) {
var strike = this.createElement("s");
strike.textContent = todo.text;
span.innerHTML = "";
span.append(strike);
} else {
span.textContent = todo.text;
}
li.append(checkbox, span);
this.list.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener("submit", e => {
e.preventDefault();
if (this._todoText) {
handler(this._todoText);
this._resetInput();
}
});
}
bindToggleTodo(handler) {
this.list.addEventListener("change", event => {
if (event.target.type === "checkbox") {
var id = +event.target.parentElement.id;
handler(id);
}
});
}
bindFilterTodo(handler) {
this.filterBtnS.addEventListener("click", e => {
var filter = +e.target.getAttribute("value");
handler(filter);
});
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.bindTodoListChanged(this.onTodoListChanged);
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindToggleTodo(this.handleToggleTodo);
this.view.bindFilterTodo(this.handleFilterTodo);
this.onTodoListChanged(this.model.todoS);
}
onTodoListChanged = todoS => {
this.view.displayTodoS(todoS);
};
handleAddTodo = todoText => {
this.model.addTodo(todoText);
};
handleToggleTodo = id => {
this.model.toggleTodo(id);
};
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
}
var app = new Controller(new Model(), new View());
<div id="main">
<h2>Task List</h2>
<form id="taskForm">
<input
id="taskInput"
placeholder="New task..."
autocomplete="off"
/>
<input class="submit" type="submit" value="Add Task" />
</form>
<div id="filterButtons" class="buttons">
<div class="all" value="0">All</div>
<div class="active" value="1">Active</div>
<div class="complete" value="2">Completed</div>
</div>
<ul id="taskList"></ul>
</div>
The issue is that in filterTodo, you just filter the tasks but not _commit the change.
So
Store the filter in the model
Move the filter to _commit (so it will keep filter for any other actions such as add todo)
class Model {
constructor() {
this.todoS = [];
this.filter = 0;
}
bindTodoListChanged(callback) {
this.onTodoListChanged = callback;
}
_commit(todoS = this.todoS) {
this.onTodoListChanged(todoS.filter(todo => {
if (this.filter === 0) return true;
return this.filter === 1 ? !todo.complete : todo.complete;
}));
}
addTodo(todoText) {
var todo = {
id:
this.todoS.length > 0
? this.todoS[this.todoS.length - 1].id + 1
: 0,
text: todoText,
complete: false
};
this.todoS.push(todo);
this._commit(this.todoS);
}
toggleTodo(id) {
this.todoS = this.todoS.map(todo =>
todo.id === id
? {
id: todo.id,
text: todo.text,
complete: !todo.complete
}
: todo
);
this._commit(this.todoS);
}
filterTodo(filter) {
this.filter = filter;
this._commit();
}
}
class View {
constructor() {
this.form = document.querySelector("#taskForm");
this.input = document.getElementById("taskInput");
this.list = document.querySelector("#taskList");
this.filterBtnS = document.getElementById("filterButtons");
this.allBtn = document.querySelector(".all");
this.activeBtn = document.querySelector(".active");
this.completeBtn = document.querySelector(".complete");
}
createElement(tag, className) {
var element = document.createElement(tag);
if (className) element.classList.add(className);
return element;
}
getElement(selector) {
var element = document.querySelector(selector);
return element;
}
get _todoText() {
return this.input.value;
}
_resetInput() {
this.input.value = "";
}
displayTodoS(todoS) {
// Faster way for clear tasks
while (this.list.firstChild) {
this.list.removeChild(this.list.firstChild);
}
if (todoS.length !== 0) {
todoS.forEach(todo => {
var li = this.createElement("li", "task"),
span = this.createElement("span");
li.id = todo.id;
var checkbox = this.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = todo.complete;
if (todo.complete) {
var strike = this.createElement("s");
strike.textContent = todo.text;
span.innerHTML = "";
span.append(strike);
} else {
span.textContent = todo.text;
}
li.append(checkbox, span);
this.list.append(li);
});
}
}
bindAddTodo(handler) {
this.form.addEventListener("submit", e => {
e.preventDefault();
if (this._todoText) {
handler(this._todoText);
this._resetInput();
}
});
}
bindToggleTodo(handler) {
this.list.addEventListener("change", event => {
if (event.target.type === "checkbox") {
var id = +event.target.parentElement.id;
handler(id);
}
});
}
bindFilterTodo(handler) {
this.filterBtnS.addEventListener("click", e => {
var filter = +e.target.getAttribute("value");
handler(filter);
});
}
}
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.model.bindTodoListChanged(this.onTodoListChanged);
this.view.bindAddTodo(this.handleAddTodo);
this.view.bindToggleTodo(this.handleToggleTodo);
this.view.bindFilterTodo(this.handleFilterTodo);
this.onTodoListChanged(this.model.todoS);
}
onTodoListChanged = todoS => {
console.log(todoS);
this.view.displayTodoS(todoS);
};
handleAddTodo = todoText => {
this.model.addTodo(todoText);
};
handleToggleTodo = id => {
this.model.toggleTodo(id);
};
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
}
var app = new Controller(new Model(), new View());
<div id="main">
<h2>Task List</h2>
<form id="taskForm">
<input id="taskInput" placeholder="New task..." autocomplete="off" />
<input class="submit" type="submit" value="Add Task" />
</form>
<div id="filterButtons" class="buttons">
<div class="all" value="0">All</div>
<div class="active" value="1">Active</div>
<div class="complete" value="2">Completed</div>
</div>
<ul id="taskList"></ul>
</div>
https://stackblitz.com/edit/js-1u6dxi
I suspect the biggest issues in the returning of values. For example this:
handleFilterTodo = filter => {
this.model.filterTodo(filter);
};
This code does not return anything, it only calls this.model.filterTodo. In the corresponding method filterTodo, you are creating a new array by using this.todoS.filter but you do not return it anywhere:
filterTodo(filter) {
this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
You could do something like what you did with toggleTodo and the map function here:
filterTodo(filter) {
this.todoS = this.todoS.filter(todo => {
if (filter === 0) return true;
return filter === 1 ? !todo.complete : todo.complete;
});
}
...but that would only work once, as setting the filter would remove the other todos from your database.
From my understanding of the code (without trying it out), I'd probably set the filter only and whenever _commit gets called, pass the filtered version of the todos based on the selected filter
constructor() {
...
this.possibleFilters = {
0: () => true,
1: todo => !todo.completed,
2: todo => todo.completed
};
}
filterTodo(filter) {
this.filter = filter;
}
_commit(todoS) {
const selectedFilter = this.possibleFilters[this.filter];
this.onTodoListChanged(todoS.filter(selectedFilter));
}

item.tolowercase is not a function in ionic 3

When writing the name of the building in the searchbar the ionic returns me this error item.tolowercase is not a function in ionic
How should I proceed to correct this detail?
This is a image example
This is home.ts
export class HomePage {
searchQuery: string = '';
items: object[];
constructor() {
this.initializeItems();
}
initializeItems() {
this.items = [
{ nome:'Abaeté', rua:'Rua Andira - 396', imagem:"assets/img/4Wj6RBrjQPGiTMhV6T9W_Abaete.JPG" },
];
}
getItems(ev: any) {
// Reset items back to all of the items
this.initializeItems();
// set val to the value of the searchbar
const val = ev.target.value;
// if the value is an empty string don't filter the items
if (val && val.trim() != '') {
this.items = this.items.filter((item) => {
return (item.toLowerCase().indexOf(val.toLowerCase()) > -1);
})
}
}
Try to convert the items to string-
return (item.toString().toLowerCase().indexOf(val.toString().toLowerCase()) > -1);

React not rendering search results from API onChange

I am still new to React and have been doing small projects lately to get better. I am currently working on a nutrition webpage that sets calorie goals and obtains foods from an API. This project consists of 2 components FoodItem and Main.
The Main component calculates calories and displays the search results from the API. Here lies the problem. When the search bar first receives a name, it displays nothing. However, it displays the intended search results after a backspace (deleting one letter from the word). This is seen in the screenshots.
Full word:
After deleting one letter:
Here is the function responsible for displaying the Search Results:
updateResult(name) {
console.log(name);
if (name == "") {
this.setState({
foodResult: []
})
return;
}
let result = [];
let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=' + name;
fetch(url)
.then(
function(response) {
return response.json();
}
).then(function(jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({
name: jsonData.hints[i].food.label,
calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
})
}
})
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem name ={foods[i].name} calories ={foods[i].calories} updateFoods = {this.displayEatenFoods} isEaten = {false} checkItem = {this.checkItem}/>)
}
}
console.log(result);
this.setState({
foodResult: result
});
}
Full code:
import React from "react";
import ReactDOM from "react-dom";
//////////////////
let foods = [];
function removeDuplicates(arr) {
var unique = [];
for (let i = 0; i < arr.length; i++) {
let current = arr[i].name;
let add = true;
for (let i = 0; i < unique.length; i++) {
if (current == unique[i].name) add = false;
}
if (add) unique.push(arr[i]);
}
return unique;
}
///////////////////
class FoodItem extends React.Component {
constructor(props) {
super(props);
this.state = { addDone: false, disable: false };
this.addEaten = this.addEaten.bind(this);
}
addEaten() {
if (this.props.updateFoods(this.props.name))
this.setState({ addDone: false, disable: true });
else this.setState({ addDone: true });
}
render() {
if (this.props.isEaten) {
return (
<div>
{this.props.name}
  Calories :{this.props.calories}
</div>
);
}
if (!this.state.addDone) {
return (
<div>
{this.props.name}
  Calories :{this.props.calories}
 
<button primary onClick={this.addEaten} disabled={this.state.disable}>
Eat
</button>
</div>
);
}
return null;
}
}
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
goal: " ",
remaining: " ",
goalEntered: false,
foodSearch: "",
foodResult: [],
EatensFoods: [],
allowance: " ",
calories: ""
};
this.setGoal = this.setGoal.bind(this);
this.changeGoal = this.changeGoal.bind(this);
this.changeFoodSearch = this.changeFoodSearch.bind(this);
this.displayEatenFoods = this.displayEatenFoods.bind(this);
this.checkItem = this.checkItem.bind(this);
this.changeCalorieSearch = this.changeCalorieSearch.bind(this);
}
changeGoal(event) {
this.setState({ goal: event.target.value });
}
setGoal(event) {
this.setState({ goalEntered: true, remaining: this.state.goal });
event.preventDefault();
}
changeFoodSearch(event) {
this.setState({ foodSearch: event.target.value });
this.updateResult(event.target.value);
}
changeCalorieSearch(event) {
this.setState({ calories: event.target.value });
}
updateResult(name) {
console.log(name);
if (name == "") {
this.setState({ foodResult: [] });
return;
}
let result = [];
let url =
"https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=" +
name;
fetch(url)
.then(function(response) {
return response.json();
})
.then(function(jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({
name: jsonData.hints[i].food.label,
calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
});
}
});
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem
name={foods[i].name}
calories={foods[i].calories}
updateFoods={this.displayEatenFoods}
isEaten={false}
checkItem={this.checkItem}
/>
);
}
}
console.log(result);
this.setState({ foodResult: result });
}
displayEatenFoods(name) {
let tempEaten = [];
let disableFlag = false;
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase() == name.toUpperCase()) {
if (this.checkItem(foods[i].calories, foods[i].name)) {
tempEaten.push(
<FoodItem
name={foods[i].name}
calories={foods[i].calories}
updateFoods={this.displayEatenFoods}
isEaten={true}
checkItem={this.checkItem}
/>
);
} else {
disableFlag = true;
}
}
}
tempEaten = removeDuplicates(tempEaten);
tempEaten = this.state.EatensFoods.concat(tempEaten);
this.setState({ EatensFoods: tempEaten });
return disableFlag;
}
checkItem(cal, name) {
let newRemainder = this.state.remaining - cal;
if (newRemainder < 0) {
this.setState({ allowance: "You can't eat " + name });
return false;
}
this.setState({
remaining: newRemainder,
allowance: "You can eat " + name
});
return true;
}
render() {
if (!this.state.goalEntered) {
return (
<center>
<form onSubmit={this.setGoal}>
<label>
Please Enter your desired calories
<input
type="text"
value={this.state.goal}
onChange={this.changeGoal}
/>
</label>
<input type="submit" value="OK" />
</form>
</center>
);
}
return (
<div>
<center>
<h1>Maximum Calories:{this.state.goal}</h1>
<h2>Remaining Calories:{this.state.remaining}</h2>
<h3>{this.state.allowance}</h3>
<form>
<label>
Search foods
<input
type="text"
placeholder="Enter Name"
value={this.state.foodSearch}
ref={a => {
this.searchValue = a;
}}
onChange={this.changeFoodSearch}
/>
<input
type="text"
placeholder="Calories,Min+,Max,Min-Max"
value={this.state.calories}
onChange={this.changeCalorieSearch}
/>
</label>
</form>
{this.state.foodResult}
<h2>Eaten Foods:</h2>
{this.state.EatensFoods}
</center>
</div>
);
}
}
ReactDOM.render(<Main />, document.getElementById("root"));
First of all I can't examine all your code if there should be some best practices instead of your logic but your problem is your updateResult function doing an async job but you are not waiting it to finish. This is your main problem. Deleting one word or deleting anything does not trigger the problem. Just type "o" then wait a little bit, then write anything and see the same problem occurs. Make your updateResult function async and put an await before your fetch.
async updateResult(name) {
console.log(name);
if (name == "") {
this.setState({ foodResult: [] })
return;
}
let result = [];
let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=somekeyhere&ingr=' + name;
await fetch(url)
.then(
function (response) {
return response.json();
}
).then(function (jsonData) {
for (let i = 0; i < jsonData.hints.length; i++) {
foods.push({ name: jsonData.hints[i].food.label, calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL) })
}
})
console.log(foods);
foods = removeDuplicates(foods);
for (let i = 0; i < foods.length; i++) {
if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
result.push(
<FoodItem name={foods[i].name} calories={foods[i].calories} updateFoods={this.displayEatenFoods} isEaten={false} checkItem={this.checkItem} />)
}
}
console.log(result);
this.setState({ foodResult: result });
}
Instead of making your function an async one, you can continue the code with another .then method or methods. Just do not forget to return what you need for those .then methods from the previous ones.

Categories

Resources