Contentful Tags not rendering on page - graphql

I'm creating article cards, and I want the tags to get rendered within the card. I'm using NextJS and GraphQLClient to fetch data from Contentful. Everything gets rendered but not the Tags.
This is my query:
export async function getArticles() {
const articlesQuery = gql`
{
articleCollection {
items {
title
slug
excerpt
date
contentfulMetadata {
tags {
name
id
}
}
featuredImage {
title
url
width
height
}
}
}
}
`;
return graphQLClient.request(articlesQuery);
}
This is the results using GraphiQL:
This is my article page where I want the tags to appear on the article post cards:
export async function getStaticProps() {
const articles = await getArticles();
return {
props: {
articles: articles.articleCollection.items,
},
};
}
export default function ArticlesPage({ articles }) {
return (
<Wrapper>
<div>
<div>
<div/>
</div>
<div>
<div>
{articles.map((article) => (
<Link key={article.slug} href={`/articles/${article.slug}`}>
<div key={article.title}>
<div>
<img
src={article.featuredImage.url}
alt=""
/>
</div>
<div>
<div>
<p>
<a href={article.contentfulMetadata.tags.name}>
{article.contentfulMetadata.tags.name}
</a>
</p>
<a>
<p>
{article.title}
</p>
<p>
{article.excerpt}
</p>
</a>
</div>
<div>
<div>
</div>
<div>
<p>
</p>
</div>
</div>
</div>
</div>
</Link>
))}
</div>
</div>
</div>
</Wrapper>
);
}
Can you help me spot what I'm doing wrong?

Contentful DevRel here.
contentfulMetadata.tags is an array. The provided snippet template assumes tags is an object.
Try iterating over the array and render each tag seperately.
article.contentfulMetadata.tags.map((tag) => {
return <a href={tag.name}>
{tag.name}
</a>;
})

Related

Livewire nestable is resetting order on drop

using the following library to add sorting and nesting https://www.hesamurai.com/nested-sort/latest, I'm also using the pre-rendered list option to populate the items (https://www.hesamurai.com/nested-sort/latest/pre-rendered).
Now my html structure looks like the following:
<div x-data class="pageContent__content__components mb10">
<span class="subTitle">Menu items</span>
<ol x-init="initSort($refs.draggable)" x-ref='draggable' id="draggable"
class='nested-sort nested-sort--enabled'>
#foreach($navigation->navigation_items as $item)
<li wire:key='{{ $item->id }}' draggable='true'
data-id="{{ $item->id }}"> {{ $item->navigatable->page_name ?? $item->navigatable->blog_title }}
<i wire:click="showAlert('{{ $item->id }}')"
class="fa-solid fa-trash-can"></i>
</li>
#if($item->children->count() > 0)
<ol data-id="{{ $item->id }}">
#foreach($item->children as $child)
<li draggable='true'
data-id="{{ $child->navigatable->id }}"> {{ $child->navigatable->page_name ?? $child->navigatable->blog_title }}
<i wire:click="showAlert('{{ $child->navigatable->id }}')"
class="fa-solid fa-trash-can"></i>
</li>
#endforeach
</ol>
#endif
#endforeach
</ol>
</div>
With the scripts being included at the bottom of the page
#push('scripts')
#once
<script src="https://cdn.jsdelivr.net/npm/nested-sort#5/dist/nested-sort.umd.min.js"></script>
<script type='text/javascript'>
function initSort(ref) {
return {
nested: new NestedSort({
el: ref,
actions: {
onDrop: function(data) {
#this.
call('saveMenuOrder', data);
},
},
}),
};
}
</script>
#endonce
#endpush
Now, adding and removing items to the list works as intended, but reordering or nesting any item causes livewire to reset the list to it's initial state.
If I where to add a wire:ignore to the <ol> that technically fixes the issue of livewire updating the DOM, but that also means that, when adding or removing items it no longer updates the list without manually refreshing the page.
My backend component looks like this:
// the model containing the items to be displayed (via a HasMany relation)
public NavigationModel $navigation;
// the method that is called everytime the onDrop action fires (the $data array contains the new order of elements)
public function saveMenuOrder(array $data): void
{
foreach ($data as $menuItem) {
$menuItemObject = $this->navigation->navigation_items->find(
$menuItem['id']
);
$menuItemObject->order = $menuItem['order'];
if (isset($menuItem['parent'])) {
$menuItemObject->parent_id = $menuItem['parent'];
} else {
$menuItemObject->parent_id = null;
}
$menuItemObject->save();
}
}
And that's basically it for the component, all I want is for livewire to update the list without messing up the DOM elements created by the library.
any ideas?
alpineJS is also installed, if that's a better solution that's also accepted.
thanks!
--- Edit
What I currenly have:
I converted the laravel foreach to an alpine x-for:
<div class="pageContent__content" style="grid-area: unset">
<div x-data='initSort($refs.draggable)'
wire:ignore
class="pageContent__content__components mb10">
<span class="subTitle">Menu items</span>
<ol class='nested-sort' x-ref='draggable' id="draggable">
<template x-for="item in items">
<li draggable="true" :data-id='item.id'>
<div class='nested-sort-item'>
<div class='nested-sort-item__text' x-text='item.text' />
<div class='nested-sort-item__actions'>
<button type='button' class='nested-sort-item__actions__button'
#click='$wire.showAlert(item.id)'
>
<i class='fas fa-trash-alt'></i>
</button>
</div>
</div>
</li>
</template>
</ol>
</div>
</div>
and rewrote the init function:
function initSort(ref) {
return {
navigation_items: #js($this->navigation_items),
get items() {
return this.navigation_items;
},
init() {
new NestedSort({
el: ref,
actions: {
onDrop: function(data) {
},
},
});
},
};
}
Now I can't seem to figure out how to access the navigation_items inside of my onDrop action, simply using this.navigation_items or this.items console.logs undefined.

why component disappear when i change tab?

in my laravel project i have used Vuejs and Vuex.when page is loaded or refreshed then component is displayed but when i change tab and goto previous tab then component disappear.And when i goto next page and return back everything is fine.
below is component featured.vue
<template>
<div class="featured_slider slider">
<h4>featured</h4>
<div class="featured_slider_item" v-for="product in getFeaturedProducts">
<div class="border_active"></div>
<div class="product_item discount d-flex flex-column align-items-center
justify-content-center text-center">
<div class="product_image d-flex flex-column align-items-center justify-
content-center">
<img :src="'/Uploads/Products/'+product.id+'/cropped_'+product.image.name"
:alt="product.image.name"></div>
<div class="product_content">
<div class="product_price discount">Rs.{{product.price_old}}<span>Rs.
{{product.price_new}}</span></div>
<div class="product_name">
<div>{{product.title}}</div>`
</div>
<div class="product_extras">
<div class="product_color">
<input type="radio" checked
name="product_color"style="background:#b19c83">
<input type="radio" name="product_color"
style="background:#000000">
<input type="radio" name="product_color"
style="background:#999999">
</div>
<button class="product_cart_button">Add to Cart</button>
</div>
</div>
<div class="product_fav"><i class="fas fa-heart"></i></div>
<ul class="product_marks">
<li class="product_mark product_discount">-25%</li>
<li class="product_mark product_new">new</li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
data(){
return{
products:[]
}
},
computed:{
getFeaturedProducts(){
return this.$store.getters.featuredProducts;
}
},
created() {
return this.$store.dispatch('featuredProducts');
}
}
</script>
below is state.js
export default {
featuredProducts:[],
onSale:[]
}
below is getters.js
export const featuredProducts= state =>{
//console.log(state);
return state.featuredProducts
};
export const onSale= state =>{
//console.log(state);
return state.onSale
};
below is mutations.js
export const featuredProducts=(state,responseData)=>{
state.featuredProducts=responseData;
// console.log(responseData)
};
export const onSale=(state,responseData)=>{
state.onSale=responseData;
// console.log(responseData)
};
below is actions.js
import {sendGet} from "../../../utils/request";
export const featuredProducts =context=>{
sendGet('/product/products').then(response=>{
context.commit('featuredProducts',response.data.data)
});
};
export const onSale =context=>{
sendGet('/product/onsale').then(response=>{
context.commit('onSale',response.data.data)
});
};
Considering general file structure 'public>js>app.js', app.js
which include all compiled js, the vue dependencies and attach vue instance to to
the body of the page, your blade, eg index.blade.php you might have something like ::
<script type="text/javascript" src="js/app.js"></script>
In your blade template, change it into something like this, considering same file structure:
<script type="text/javascript" src="{{url('js')}}/app.js"></script>

Don't see a div into a vue component

I have a vue component with a div and a button and into another div I have two components
<script>
export default {
name: 'excursion-backend-component',
methods:{
doRedirection: function () {
window.location = APP_URL+"/excursiones/create";
}
},
mounted() {
console.log(APP_URL+"/excursiones/create");
console.log("aaaa");
}
}
</script>
<template>
<div class="container">
<h3>Adicionar excursión</h3>
<div style="text-align: right">
<a class="btn btn-primary" :href="doRedirection"><i class="fa fa-plus"></i> Adicionar</a>
</div>
<br>
<div>
<excursion-list-component></excursion-list-component>
<excursion-add-component></excursion-add-component>
</div>
</div>
</template>
But I don't see the button on the navigator.
What is wrong?
Here is how I see the page
:href="" expecting string with url to navigation, but you using method.
Use #click instead :href, if you want to use method.
<a #click="doRedirect">... </a>
Or, may be, move it to computed block
computed: {
excursionesUrl () {
return APP_URL+"/excursiones/create";
}
}

How to use Ajax to update RenderBody()

I am trying to work with ajax in order to update only a partial view and not the whole view. In the following example I want to refresh the page-content section.
this is my layout.html.
<body>
<div id="container" class="effect">
#Html.Partial("_head")
<div class="boxed">
<section id="content-container">
#if (ViewData["Errors"] != null)
{
<div class="panel" style="background-color:white;">
<div class="panel-heading">
<h3 class="panel-title">
Page title
</h3>
</div>
</div>
}
<div id="page-content" class="container">
#RenderBody()
</div>
</section>
</div>
</div>
</body>
Partial view _head contains the menu with href links for example:
<a id="a1" href="">Menu Item 1</a>
In layout I am trying to use the following:
<script>
$(document).ready(function () {
$("#a1").click(function () {
A1();
});
function A1( ) {
$.ajax({
url: '#Url.Action("PartialViewMethodFromController", "HomeController")',
success: function (response) {
$('#page-content').html(response);
}
});
}
});
</script>
And my HomeController
public ActionResult PartialViewMethodFromController()
{
var model = service.GetAll();
return PartialView("PartialViewMethodFromController", model);
}
It kinda works - the partialview is refreshing for a moment and the partialview is show correctly and then the whole page goes on and refreshes.
Am I missing something crucial here?

Passing the Div id to another vue component in laravel

I created a simple real-time chat application using vue js in laravel.
I am having a problem with the automatic scroll of the div when there is a new data.
What I want is the div to automatically scroll down to the bottom of the div when there is a new data.
Here is my code so far.
Chat.vue file
<template>
<div class="panel-block">
<div class="chat" v-if="chats.length != 0" style="height: 400px;" id="myDiv">
<div v-for="chat in chats" style="overflow: auto;" >
<div class="chat-right" v-if="chat.user_id == userid">
{{ chat.chat }}
</div>
<div class="chat-left" v-else>
{{ chat.chat}}
</div>
</div>
</div>
<div v-else class="no-message">
<br><br><br><br><br>
There are no messages
</div>
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid"></chat-composer>
</div>
</template>
<script>
export default {
props: ['chats','userid','adminid'],
}
</script>
ChatComposer.vue file
<template>
<div class="panel-block field">
<div class="input-group">
<input type="text" class="form-control" v-on:keyup.enter="sendChat" v-model="chat">
<span class="input-group-btn">
<button class="btn btn-primary" type="button" v-on:click="sendChat">Send Chat</button>
</span>
</div>
</div>
</template>
<script>
export default{
props: ['chats','userid','adminid'],
data() {
return{
chat: ''
}
},
methods: {
sendChat: function(e) {
if(this.chat != ''){
var data = {
chat: this.chat,
admin_id: this.adminid,
user_id: this.userid
}
this.chat = '';
axios.post('/chat/sendChat', data).then((response) => {
this.chats.push(data)
})
this.scrollToEnd();
}
},
scrollToEnd: function() {
var container = this.$el.querySelector("#myDiv");
container.scrollTop = container.scrollHeight;
}
}
}
</script>
I am passing a div id from the Chat.vue file to the ChatComposer.vue file.
As you can see in the ChatComposer.vue file there is a function called scrollToEnd where in it gets the height of the div id from Chat.vue file.
When the sendchat function is triggered i called the scrollToEnd function.
I guess hes not getting the value from the div id because I am getting an error - Cannot read property 'scrollHeight' of null.
Any help would be appreciated.
Thanks in advance.
the scope of this.$el.querySelector will be limited to only ChatComposer.vue hence child component can not able to access div of parent component #myDiv .
You can trigger event as below in ChatComposer
this.$emit('scroll');
In parent component write ScollToEnd method and use $ref to assign new height
<chat-composer v-bind:userid="userid" v-bind:chats="chats" v-bind:adminid="adminid" #scroll="ScollToEnd"></chat-composer>
..

Resources