Angular PrimeNg Using autocomplete and passing REST object - autocomplete

I have an issue with PrimeNg autocomplete :
When i type any research, I obtain [Object Object] in the input text.
First I have an API call for getting a data set :
ngOnInit(): void {
this.getCategories();
}
private getCategories(): void {
const response = this.apiService.getCategories().subscribe(
(data) => {
this.categories = data as CategoriesModel[];
}
);
console.log('Get categories');
console.log('response ', response);
}
It allows me to retrive my data correctly (here is a sample) :
[
{
"id": "1",
"categoryName": "Name1",
"docDescription": "Description1 ..."
},
{
"id": "2",
"categoryName": "Name2",
"docDescription": "Description2"
}..
]
Now I try to handle my array of javascript objects in order to filter them :
I defined member variables in my component :
categories: CategoriesModel[];
filteredCategories: CategoriesModel[];
category: CategoriesModel;
I add this code into the HTML template:
<p-autoComplete
[(ngModel)]="category"
[suggestions]="filteredCategories"
(completeMethod)="filterCategories($event)"
[size]="30"
[minLength]="1" placeholder="Hint: type a letter"
[dropdown]="true">
<ng-template let-category pTemplate="item.categoryName">
<div class="ui-helper-clearfix" style="border-bottom:1px solid #D5D5D5">
{{category.id}}
<div style="font-size:18px;float:right;margin:10px 10px 0 0">{{category.categoryName}}</div>
</div>
</ng-template>
</p-autoComplete>
<span style="margin-left:50px">Category: {{category?.categoryName||'none'}}</span>
Now I try to use a filter method that will show in list results :
filterCategories(event): void {
this.filteredCategories = [];
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < this.categories.length; i++) {
this.category = this.categories[i];
if (this.category.categoryName.toLowerCase().indexOf(event.query.toLowerCase()) === 0) {
console.log(this.category.categoryName);
this.filteredCategories.push(this.category);
}
}
}

I finally solved this by modifying the template :
<p-autoComplete
[(ngModel)]="category"
[suggestions]="filteredCategories"
field = "categoryName"
(completeMethod)="filterCategories($event)"
[size]="30"
[minLength]="1" placeholder="Hint: type a letter"
[dropdown]="true">
<ng-template let-category pTemplate="categoryName">
<div class="ui-helper-clearfix" style="border-bottom:1px solid #D5D5D5">
{{category.id}} {{category.categoryName}}
</div>
</ng-template>
</p-autoComplete>

Related

Adding a wp: featured image to loop when using a headless CMS via the Wordpress API and Vue.js

Would like to know how to implement the Wordpress Featured Image to multiple vue.js/nuxt.js based headless-CMS approaches using the Wordpress restful API.
Initially, I followed this immensely helpful tutorial headless-cms with nuxt and created a headless CMS via the wordpress api, and of course applied it to my use-case (here is a link to a live version nuxt-headless-cms-webapp. Unfortunately, I have been unable to figure out how to include a post's featured image as it is not covered in this particular tutorial. I then did a bit of research and ended up piecing together another project (vue.js), in which I was able to implement featured images. That being said I would like guidance with regard to implementing my working code in terms of the wp-featured image, to the original tutorial's project (as nuxt provides better routing and SEO options from my understanding. Thank you in advanced for any help!
First, here is the axios http request syntax found in the original tutorial (nuxt) project's index.js:
const siteURL = "https://indvillage.com"
export const state = () => ({
posts: [],
tags: []
})
export const mutations = {
updatePosts: (state, posts) => {
state.posts = posts
},
updateTags: (state, tags) => {
state.tags = tags
}
}
export const actions = {
async getPosts({ state, commit, dispatch }) {
if (state.posts.length) return
try {
let posts = await fetch(
`${siteURL}/wp-json/wp/v2/posts?_embed`
).then(res => res.json())
posts = posts
.filter(el => el.status === "publish")
.map(({ id, slug, title, excerpt, date, tags, content }) => ({
id,
slug,
title,
excerpt,
date,
tags,
content
}))
commit("updatePosts", posts)
} catch (err) {
console.log(err)
}
},
async getMedia({ state, commit }) {
if (state.media.length) return
try {
let media= await fetch(
`${siteURL}/wp-json/wp/v2/media?_embed`
).then(res => res.json())
commit("updatePosts", media)
} catch (err) {
console.log(err)
}
},
async getTags({ state, commit }) {
if (state.tags.length) return
let allTags = state.posts.reduce((acc, item) => {
return acc.concat(item.tags)
}, [])
allTags = allTags.join()
try {
let tags = await fetch(
`${siteURL}/wp-json/wp/v2/tags?page=1&per_page=40&include=${allTags}`
).then(res => res.json())
tags = tags.map(({ id, name }) => ({
id,
name
}))
commit("updateTags", tags)
} catch (err) {
console.log(err)
}
}
}
Next, we have the index.vue page where the above logic is implemented.
template>
<div>
<app-masthead></app-masthead>
<div class="posts">
<main>
<div class="post" v-for="post in sortedPosts" :key="post.id">
<h3>
<a :href="`blog/${post.slug}`">{{ post.title.rendered }}</a>
</h3>
<small>{{ post.date | dateformat }}</small>
<div v-html="post.excerpt.rendered"></div>
<a :href="`blog/${post.slug}`" class="readmore slide">Read more ⟶</a>
</div>
</main>
<!--<aside>
<h2 class="tags-title">Tags</h2>
<div class="tags-list">
<ul>
<li
#click="updateTag(tag)"
v-for="tag in tags"
:key="tag.id"
:class="[tag.id === selectedTag ? activeClass : '']"
>
<a>{{ tag.name }}</a>
<span v-if="tag.id === selectedTag">✕</span>
</li>
</ul>
</div>
</aside>-->
</div>
</div>
</template>
<script>
import AppMasthead from "#/components/AppMasthead.vue";
export default {
components: {
AppMasthead
},
data() {
return {
selectedTag: null,
activeClass: "active"
};
},
computed: {
posts() {
return this.$store.state.posts;
_embed = true;
},
tags() {
return this.$store.state.tags;
},
sortedPosts() {
if (!this.selectedTag) return this.posts;
return this.posts.filter(el => el.tags.includes(this.selectedTag));
}
},
created() {
this.$store.dispatch("getPosts");
},
methods: {
updateTag(tag) {
if (!this.selectedTag) {
this.selectedTag = tag.id;
} else {
this.selectedTag = null;
}
}
}
};
Here is a link to my project with working wordpress featured images! https://indvillage.netlify.app/
And here is the logic associated with the axious http request I used.
The question is how, do I include my logic in terms of the wp featured image to the initial nuxt tutorial without breaking things:
export default {
data() {
return {
postsUrl: "https://indvillage.com/wp-json/wp/v2/posts",
queryOptions: {
per_page: 6,
page: 1,
_embed: true
},`
posts: []
};
},
methods: {
// Get recent posts from wp
getRecentMessages() {
axios
.get(this.postsUrl, { params: this.queryOptions })
.then(response => {
this.posts = response.data;
console.log("Posts retrieved!");
})
//document.getElementById("test").id = "testing";
.catch(error => {
console.log(error);
});
},
getPostDate(date) {
return moment(date).format("111");
},
},
mounted() {
this.getRecentMessages();
}
}
Next, here is the App.vue template that displays the parsed information:
<template id="app">
<body>
<div class="row container">
<!-- looping through and displaying the array bound posts in HTML -->
<div class="col s4 m4" v-for="(post, index) in posts" :key="index" :id="'post'+index">
<div class="card" id="test">
<div class="card-image">
<!-- rendering the post's wp:featuredmedia in the image portion of the html/css card -->
<img
v-if="post._embedded['wp:featuredmedia']"
:src="post._embedded['wp:featuredmedia'][0].source_url"
/>
</div>
<!-- rendering the post.excerpt to the html/css card container -->
<div class="card-content" v-html="post.excerpt.rendered"></div>
<div class="card-action">
<!-- rendering the post title to the action portion of the html/css card -->
{{ post.title.rendered }}
</div>
</div>
</div>
</body>
</template>
Please let me know if anyone has any suggestions with regard to implementing wp:featuredmedia to the code derived from the first project/tutorial (nuxt.js)
Thanks again! Feel free to email with further questions

Vuetify datatable reload when performing CRUD operations

I have a simple vuetify datatable that performs CRUD operations. I am using axios and a mongo database. When I add a new item, the information is correctly displayed in the client side and posted in the mongo database. However, I cannot access to updated information of the mongo database, particularly the id that mongo created, unless I reload the webpage. I am newcomer in Vue, please be patient. A simplified version of the problem:
axios
.post('http://localhost:5000/dessert', {
name: this.editedItem.name
})
console.log(this.editedItem.name) // I CAN ACCES WITHOUT PROBLEM
console.log(this.editedItem._id) // I NEED TO RELOAD THE WEBPAGE IN ORDER TO ACCES THIS ELEMENT. THE ID THAT MONGO CREATED.
Vue file:
<template>
<v-data-table
:headers="headers"
:items="desserts"
sort-by="calories"
class="elevation-1"
>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>My CRUD</v-toolbar-title>
<v-divider
class="mx-4"
inset
vertical
></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on }">
<v-btn color="primary" dark class="mb-2" v-on="on">New Item</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.name" label="Dessert name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.calories" label="Calories"></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.action="{ item }">
<v-icon
small
class="mr-2"
#click="editItem(item)"
>
edit
</v-icon>
<v-icon
small
#click="deleteItem(item)"
>
delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</template>
<script>
import axios from 'axios'
export default {
data: () => ({
dialog: false,
headers: [
{
text: 'Dessert (100g serving)',
value: 'name',
},
{ text: 'Calories', value: 'calories' },
{ text: 'Actions', value: 'action', sortable: false },
],
desserts: [],
editedIndex: -1,
editedItem: {
name: '',
calories: 0,
},
defaultItem: {
name: '',
calories: 0,
},
}),
mounted() {
this.fetchItems()
},
computed: {
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
},
created () {
this.initialize()
},
methods: {
fetchItems(){
axios
.get('http://localhost:5000/dessert')
.then(response => (this.desserts = response.data.data))
},
editItem (item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.editedID = this.editedItem._id
this.name = this.editedItem.name
this.calories = this.editedItem.calories
this.dialog = true
},
deleteItem (item) {
const index = this.desserts.indexOf(item)
this.deletedItem = Object.assign({}, item)
console.log(this.deletedItem)
this.deletedID = this.deletedItem._id
console.log(this.deletedID)
if (confirm("Do you really want to delete?")) {
axios.delete(`http://localhost:5000/dessert/${this.deletedID}`);
this.desserts.splice(index, 1);
}
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () { // Edit Item
if (this.editedIndex > -1) {
Object.assign(this.desserts[this.editedIndex], this.editedItem)
axios.delete(`http://localhost:5000/dessert/${this.editedItem._id}`)
axios
.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
})
// New Item
} else {
this.desserts.push(this.editedItem)
axios.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
})
}
this.close()
},
},
}
</script>
Python file:
from flask import Flask
from flask import jsonify
from flask import request
from flask_pymongo import PyMongo
from flask_cors import CORS
from bson.objectid import ObjectId
app = Flask(__name__)
#CORS(app)
# instantiate
app.config.from_object(__name__)
# enable CORS
CORS(app, resources={r'/*': {'origins': '*'}})
app.config['MONGO_DBNAME'] = 'restdb'
app.config['MONGO_URI'] = 'mongodb://localhost:27017/restdb'
mongo = PyMongo(app)
#app.route('/dessert', methods=['POST'])
def add_dessert():
dessert = mongo.db.desserts
name = request.json['name']
calories = request.json['calories']
dessert_id = dessert.insert({
'name': name,
'calories': calories
})
new_dessert = dessert.find_one({'_id': dessert_id })
output = {
'name' : new_dessert['name'],
'calories' : new_dessert['calories']
}
return jsonify({'result' : output})
#app.route('/dessert', methods=['GET'])
def get_all_desserts():
dessert = mongo.db.desserts
output = []
for s in dessert.find():
s['_id'] = str(s['_id'])
output.append({'_id' : s['_id'],
'name' : s['name'],
'calories' : s['calories']
})
return jsonify({'data' : output})
#app.route('/dessert/<dessert_id>', methods=['GET'])
def get_one_dessert(dessert_id):
dessert = mongo.db.desserts
s = dessert.find_one({"_id" : ObjectId(dessert_id)})
s['_id'] = str(s['_id'])
if s:
output = {'_id' : s['_id'], 'name' : s['name'], 'calories' : s['calories']}
else:
output = "No such name"
return jsonify({'result' : output})
#app.route('/dessert/<dessert_id>', methods=['DELETE'])
def delete_one_dessert(dessert_id):
dessert = mongo.db.desserts
s = dessert.find_one({"_id" : ObjectId(dessert_id)})
s['_id'] = str(s['_id'])
dessert.remove({"_id" : ObjectId(dessert_id)})
if s:
output = {'_id' : s['_id'], 'name' : s['name'], 'calories' : s['calories']}
else:
output = "No such name"
return jsonify({'result' : output})
if __name__ == '__main__':
app.run(debug=True)
If I understood it correctly, you want to be able to see in the front-end the newly added item, including the generated ID, after posting it to backend, right?
So, you can just simply call the fetchItems() once you finish posting the new item. It will automaticly update the array of the shown items, including the newly added ID.
The ID property is created when the item is added to the database, so it's not possible to have it unless the back-end gives it back to the front-end.
axios.post('http://localhost:5000/dessert', {
name: this.editedItem.name,
calories: this.editedItem.calories
}).then(response => {
this.fetchItems()
})
That means, once finishing the POST, fetchItems() again.

Bootstrap-vue: Auto-select first hardcoded <option> in <b-form-select>

I'm using b-form-select with server-side generated option tags:
<b-form-select :state="errors.has('type') ? false : null"
v-model="type"
v-validate="'required'"
name="type"
plain>
<option value="note" >Note</option>
<option value="reminder" >Reminder</option>
</b-form-select>
When no data is set for this field I want to auto-select the first option in the list.
Is this possible? I have not found how to access the component's options from within my Vue instance.
your v-model should have the value of the first option.
example
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: 'a',
options: [
{ value: null, text: 'Please select an option' },
{ value: 'a', text: 'This is First option' },
{ value: 'b', text: 'Selected Option' },
{ value: { C: '3PO' }, text: 'This is an option with object value' },
{ value: 'd', text: 'This one is disabled', disabled: true }
]
}
}
}
</script>
You can trigger this.selected=${firstOptionValue} when no data is set.
what if we don't know what the first option is. The list is generated?
if you have dynamic data, something like this will work.
<template>
<div>
<b-form-select v-model="selected" :options="options" />
<div class="mt-3">Selected: <strong>{{ selected }}</strong></div>
</div>
</template>
<script>
export default {
data() {
return {
selected: [],
options: [],
};
},
mounted: function() {
this.getOptions();
},
methods: {
getOptions() {
//Your logic goes here for data fetch from API
const options = res.data;
this.options = res.data;
this.selected = options[0].fieldName; // Assigns first index of Options to model
return options;
},
},
};
</script>
If your options are stored in a property which is loaded dynamically:
computed property
async computed (using AsyncComputed plugin)
through props, which may change
Then you can #Watch the property to set the first option.
That way the behavior of selecting the first item is separated from data-loading and your code is more understandable.
Example using Typescript and #AsyncComputed
export default class PersonComponent extends Vue {
selectedPersonId: string = undefined;
// ...
// Example method that loads persons data from API
#AsyncComputed()
async persons(): Promise<Person[]> {
return await apiClient.persons.getAll();
}
// Computed property that transforms api data to option list
get personSelectOptions() {
const persons = this.persons as Person[];
return persons.map((person) => ({
text: person.name,
value: person.id
}));
}
// Select the first person in the options list whenever the options change
#Watch('personSelectOptions')
automaticallySelectFirstPerson(persons: {value: string}[]) {
this.selectedPersonId = persons[0].value;
}
}

Return a value with highlighted color

Requirement: I have a fragment.xml file which I am extending. The form element is currently being processed with a formatter.js where I am validating some values based on some condition:
In Fragment, the formatter function is getting called correctly
<Text text="{
parts: [
{path: 'ZName1'},
{path: 'ZStatus1'}
],
formatter: '.Formatter.delivery'
}" >
Formatter:
delivery: function(iName, iStatus) {
var sResult = "";
if(iStatus === "A" ) {
sResult = iName ;
} else if(iStatus === "P") {
sResult = iName ;
} else {
sResult = iName ;
}
return sResult ;
}
In the output, I should get sResult highlighted either in green, yellow, or red based on the condition.
Binding on text will not work for highlighting the text. refer the example for alternative solution.
<Text id="id" text="{ZName1}" class="{parts: [{path: 'ZName1'},{path: 'ZStatus1'} ],
formatter : '.Formatter.delivery'}">
In Format.js file:
delivery: function(iName, iStatus) {
var idText = this.byId("id");
if(iStatus === "A" ) {
idText.removeStyleClass("classForYellowColor");
idText.removeStyleClass("classForRedColor");
return "classForGreenColor";
} else if(iStatus === "P") {
idText.removeStyleClass("classForGreenColor");
idText.removeStyleClass("classForRedColor");
return "classForYellowColor";
} else {
idText.removeStyleClass("classForGreenColor");
idText.removeStyleClass("classForYellowColor");
return "classForRedColor";
}
}
Instead of plain sap.m.Text, take advantage of sap.m.ObjectStatus which works exactly like Text but supports semantic colors (via state) out-of-the-box.
Run the following snippet to see the results:
sap.ui.getCore().attachInit(() => sap.ui.require([
"sap/m/List",
"sap/m/CustomListItem",
"sap/m/ObjectStatus", // instead of Text
"sap/ui/core/ValueState",
"sap/ui/model/json/JSONModel",
], (List, Item, ObjectStatus, ValueState, JSONModel) => new List().bindItems({
path: "/myData",
template: new Item().addContent(new ObjectStatus({
text: "{ZName1}",
state: {
path: "ZStatus1",
formatter: status =>
status === "A" ? ValueState.Success : // Green
status === "P" ? ValueState.Warning : // Yellow
status === "Z" ? ValueState.Error : // Red
ValueState.None
},
}).addStyleClass("sapUiSmallMargin")),
}).setModel(new JSONModel({
myData: [
{
ZName1: "Success",
ZStatus1: "A"
},
{
ZName1: "Warning",
ZStatus1: "P"
},
{
ZName1: "Error",
ZStatus1: "Z"
},
{
ZName1: "None",
ZStatus1: ""
},
],
})).placeAt("content")));
<script>
window["sap-ui-config"] = {
libs: "sap.ui.core, sap.m",
preload: "async",
theme: "sap_belize",
compatVersion: "edge",
"xx-waitForTheme": true,
"xx-async": true
}
</script>
<script id="sap-ui-bootstrap" src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"></script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
We can see green, yellow, and red depending on the condition.
In Fragment file:
<Text text="{parts: [{path: 'ZName1'},{path: 'ZStatus1'}],
formatter : '.Formatter.delivery'}" >
In CSS file:
.greenTxtHlight {
color: green;
}
.yellowTxtHlight {
color: yellow;
}
.redTxtHlight {
color: red;
}
In Formatter file:
delivery: function(sName, sStatus) {
switch(sStatus){
case "A":
this.addStyleClass("greenTxtHlight");
this.removeStyleClass("yellowTxtHlight");
this.removeStyleClass("redTxtHlight");
break;
case "P":
this.removeStyleClass("greenTxtHlight");
this.addStyleClass("yellowTxtHlight");
this.removeStyleClass("redTxtHlight");
break;
case "G"://Update this
this.removeStyleClass("greenTxtHlight");
this.removeStyleClass("yellowTxtHlight");
this.addStyleClass("redTxtHlight");
break;
}
return sName;
}

Dynamic number of tabs using ionic2-super-tabs tag

I do not have fixed number of tabs in my app page, so that using *ngFor in ionic2-super-tabs tag to get dynamic tabs as mentioned in below ts and html.
typescript :
import { AustraliaPage } from './australia';
import {CanadaPage} from './canada';
export class CountryPage {
Australia = AustraliaPage;
Canada= CanadaPage;
tabsLoaded = false;
ionViewDidLoad() {
this.testservice.getCoutrynames().subscribe(countries => {
this.tabs = countries;
// below is the output data of getCoutrynames() method
// "countries": [
// {
// "country_id": 1,
// "countryName": "Canada"
// },
// {
// "country_id": 2,
// "countryName": "Australia"
// }
// ]
this.tabsLoaded = true;
})
}
}
HTML :
<super-tabs scrollTabs="true" *ngIf="tabsLoaded">
<super-tab *ngFor="let tab of tabs" [root]="tab.countryName" title="tab.countryName"></super-tab>
</super-tabs>
But getting a below error.
Runtime Error
Uncaught (in promise): invalid link: Australia
Any help would be greatly appreciated.
You are setting the root to the string 'Australia' not the Australia object/page. Do something like this:
pages = {
Australia: AustraliaPage,
Canada: CanadaPage,
...
};
<super-tabs scrollTabs="true" *ngIf="tabsLoaded">
<super-tab *ngFor="let tab of tabs" [root]="pages[tab.countryName]" title="tab.countryName"></super-tab>
</super-tabs>