How to fetch folder structure from Nuxt content? - vue.js

I'm trying to fetch my content structure to display the list on the homepage:
my folder is like that :
content /
-- | A /
---- | an_article.md
-- | B /
---- | another_one.md
-- | C /
---- | etc.md
And I would like to use these folders as categories for my website. So later they are displayed as a list (A, B, C) on the homepage. I know I can fetch articles but I don't know for folders...
Here is an exemple on how it looks in html, css, jquery. It will look something like that:
$('.sub-menu ul').hide();
$(".sub-menu a").click(function () {
event.preventDefault();
$(this).parent(".sub-menu").children("ul").slideToggle(200);
$(this).parent('.sub-menu').siblings().find('ul').slideUp(200);
});
body {
font-size: 1em;
font-family: arial;
}
a {
text-decoration: none;
}
.menu {
width: 50%;
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
li {
}
li:not(.sub-menu):last-child {
}
.sub-menu li {
border-top: 1px solid black;
}
li.sub-menu {
margin-left: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
<ul class="menu">
<li class='sub-menu'> <a href='#'>A</a>
<ul>
<li class='sub-menu'>
<img src="https://picsum.photos/200/100" alt=""> <br>
<a href='#'>Project related to A</a>
</ul>
</li>
<li class='sub-menu'> <a href='#'>B</a>
<ul>
<li class='sub-menu'>
<img src="https://picsum.photos/200/200" alt=""> <br>
<a href='#'>Another project related to B</a>
</ul>
</li>
<li class='sub-menu'> <a href='#'>C</a>
<ul>
<li class='sub-menu'>
<img src="https://picsum.photos/210/200" alt=""> <br>
<a href='#'>Again another project related to C</a>
</ul>
</li>
</ul>
</body>
This is what I use so far to fetch the articles published.
export default {
async asyncData ({ $content, params }) {
const articles = await $content('A', params.slug)
.only(['title', 'description', 'img', 'slug'])
.sortBy('createdAt', 'asc')
.fetch()
return {
articles
}
}
}

I saw that you answer got answered there: https://stackoverflow.com/a/73437532/8816585
With the following code
<template>
<div class="container">
<div v-for="(filteredArticles, categoryKey) in groupedCategories" :key="categoryKey">
<h2>{{ categoryKey }}</h2>
<list-item v-for="article in filteredArticles" :key="article.slug">
<template #title>
{{ article.title }}
</template>
<template #content>
<div> {{ article.description }}</div>
</template>
<br>
</list-item>
<hr>
</div>
</div>
</template>
<script>
export default {
async asyncData ({ $content }) {
const articles = await $content('', { deep: true })
.only(['title', 'description', 'img', 'slug', 'cat', 'dir'])
.sortBy('createdAt', 'asc')
.fetch()
return { articles }
},
computed: {
groupedCategories () {
return this.articles.reduce((finalObject, obj) => {
const directory = obj.dir
finalObject[directory] ?? (finalObject[directory] = [])
finalObject[directory].push(obj)
return finalObject
}, {})
}
}
}
</script>

Related

Vue cli 3 props (parent to child) child never update after the variable in parent has changed

I tried to make a chat app using Vue CLI 3 and I have finished making a real-time chat room. Then, I tried to give a citing function to it which users can cite the message before and reply to it. So, I manage to pass the cited message to the child component by props. The cited message was NULL by default. After the user clicked some buttons, I expected the value of the "cited message" would change and the new value would be passed to the child through props (automatically updated). But, in fact, it didn't.
When I was browsing the internet, I did find several questions about updating the child component when props values change. So, I tried watch:, created(), update(), but none of them worked.
I once tried to directly add an element <p> in the child component and put {{cited_message}} in it to see what was inside the variable. Then, the Vue app crashed with a white blank page left (but the console didn't show any error).
For convenience, I think the problem is around:
<CreateMessage:name="name":cited_message="this.cited_message"#interface="handleFcAfterDateBack"/>
OR
props: ["name","cited_message"],
watch: { cited_message: function (newValue){ this.c_message = newValue; } },
You can ctrl+F search for the above codes to save your time.
Parent component:
<template>
<div class="container chat">
<h2 class="text-primary text-center">Real-time chat</h2>
<h5 class="text-secondary text-center">{{ name }}</h5>
<div class="card" style="min-height: 0.8vh">
<div class="card-body">
<p class="text-secondary nomessages" v-if="messages.length == 0">
[no messages yet!]
</p>
<div class="messages" v-chat-scroll="{ always: false, smooth: false }">
<div v-for="message in messages" :key="message.id">
<div v-if="equal_name(message)">
<div class="d-flex flex-row">
<div class="text-info">
[ {{ message.name }} ] : {{ message.message }}
</div>
<div class="btn-group dropright">
<a
class="btn btn-secondary btn-sm dropdown-toggle"
href="#"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
</a>
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<button
#click="get_cited_message(message)"
:key="message.id"
class="dropdown-item"
href="#"
>
Cite
</button>
</div>
</div>
<div class="text-secondary time">
<sub>{{ message.timestamp }}</sub>
</div>
</div>
<!--below is for cited message-->
<div v-if="message.cited_message" class="d-flex flex-row">
Cited : {{ message.cited_message }}
</div>
</div>
<div v-else>
<div class="d-flex flex-row-reverse">
<div class="text-info">
[ {{ message.name }} ] : {{ message.message }}
</div>
<div class="text-secondary time">
<sub>{{ message.timestamp }}</sub>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card-action">
<CreateMessage
:name="name"
:cited_message="this.cited_message"
#interface="handleFcAfterDateBack"
/>
</div>
</div>
</div>
</template>
<script>
import CreateMessage from "#/components/CreateMessage";
import fb from "#/firebase/init.js";
import moment from "moment";
export default {
name: "Chat",
props: {
name: String,
},
components: {
CreateMessage,
},
methods: {
equal_name(message) {
if (message.name == this.name) {
return true;
} else {
return false;
}
},
get_cited_message(message) {
this.cited_message = message.message;
console.log(this.cited_message);
},
handleFcAfterDateBack(event) {
console.log("data after child handle: ", event);
},
},
data() {
return {
messages: [],
cited_message: null,
};
},
created() {
let ref = fb.collection("messages").orderBy("timestamp");
ref.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
change.type = "added";
if (change.type == "added") {
let doc = change.doc;
this.messages.push({
id: doc.id,
name: doc.data().name,
message: doc.data().message,
timestamp: moment(doc.data().timestamp).format(
"MMMM Do YYYY, h:mm:ss a"
),
cited_message: doc.data().cited_message,
});
}
});
});
},
};
</script>
<style>
.chat h2 {
font-size: 2.6em;
margin-bottom: 0px;
}
.chat h5 {
margin-top: 0px;
margin-bottom: 40px;
}
.chat span {
font-size: 1.2em;
}
.chat .time {
display: block;
font-size: 0.7em;
}
.messages {
max-height: 300px;
overflow: auto;
text-align: unset;
}
.d-flex div {
margin-left: 10px;
}
</style>
Child component:
<template>
<div class="container" style="margin-bottom: 30px">
<form #submit.prevent="createMessage()">
<div class="form-group">
<input
type="text"
name="message"
class="form-control"
placeholder="Enter your message"
v-model="newMessage"
/>
<p v-if="c_message" class="bg-secondary text-light">Cited: {{c_message}}</p>
<p class="text-danger" v-if="errorText">{{ errorText }}</p>
</div>
<button class="btn btn-primary" type="submit" name="action">
Submit
</button>
</form>
</div>
</template>
<script>
import fb from "#/firebase/init.js";
import moment from "moment";
export default {
name: "CreateMessage",
props: ["name","cited_message"],
watch: {
cited_message: function (newValue){
this.c_message = newValue;
}
},
data() {
return {
newMessage: "",
errorText: null,
c_message: null
};
},
methods: {
createMessage() {
if (this.newMessage) {
fb.collection("messages")
.add({
message: this.newMessage,
name: this.name,
timestamp: moment().format(),
cited_message: this.c_message
})
.then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
})
.catch((err) => {
console.log(err);
});
this.newMessage = null;
this.errorText = null;
} else {
this.errorText = "Please enter a message!";
}
},
},
beforeMount(){
this.c_message = this.cited_message;
}
};
</script>
Side-note: In the parent component, I only made the dropdown menu for the messages on the left-hand side. If this thread solved, I would finish the right-hand side.
It is solved. I think the problem is that the child component didn't re-render when the variable in parent component updated. Only the parent component was re-rendered. So, the props' values in the child remained the initial values. In order to solve this, binding the element with v-bind:key can let the Vue app track the changes of the variable (like some kind of a reminder that reminds the app to follow the changes made on the key). When the variable(key) changes, the app will be noticed and the new value will be passed to the child.
E.g.
Original
<CreateMessage
:name="name"
:cited_message="this.cited_message"
#interface="handleFcAfterDateBack"
/>
Solved
<CreateMessage
:name="name"
:cited_message="this.cited_message"
#interface="handleFcAfterDateBack"
:key="this.cited_message"
/>
Even though the problem is solved, I don't know whether I understand the problem clearly. If I made any mistakes, please comment and let me know.

Check box selection issue in vue.js

Please look the below image
I have two users and their allowed channels.
My issue is :
When I click one channel of a user, the same channel is also get checked of other user. For example:
When I click Commissions of user Deepu, the channel Commissions of user Midhun also get checked.
I have to avoid that, the clicked channel of the user should only selected, not other user's.
I tried this
<v-data-table
:headers="headers"
:items="UserChannels"
:loading="datatableloading"
class="elevation-1"
>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.username }}</td>
<td class="text-xs-left">
<ul class="channel-listitems">
<li v-for="item in Channels">
<input type="checkbox" class="channel-checkbox" :value="item.id" v-model="checkedChannels">
{{ item.channel_name }}
</li>
</ul>
<br>
<span>Checked names: {{ checkedChannels }}</span>
</td>
</template>
</v-data-table>
I am using Vue.js for doing this.
You can create mapping dict for each user Channels selection.
Please refer following codepen - https://codepen.io/Pratik__007/pen/oNgaXeJ?editors=1010
In data
checkedChannels:{},
Created
created () {
console.log(this.Channels)
let vm = this;
this.Users.map(function(item){
vm.$set(vm.checkedChannels,item['name'],[])
return item;
})
},
The official Vue docs states, you can use v-model on multiple checkboxes using the same array. ( Docs : https://v2.vuejs.org/v2/guide/forms.html#Checkbox )
Also working example : https://codesandbox.io/s/hungry-kepler-t7mkw
Select elements for array with checkboxes and v-model
<template>
<div id="app">
<!-- First, iterate over all users -->
<div v-for="(user, index) in users" class="user">
<p class="name">{{ user.username }} - Checked Channels {{user.channels}}</p>
<!-- Create checkbox for each available channel, and v-model it to user.channels -->
<div class="channel">
<label v-for="(channel, index) in availableChannels">
{{channel}}
<input type="checkbox" v-bind:value="channel" v-model="user.channels">
</label>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
users: [
{
username: "User1",
channels: ["channel1", "channel2"]
},
{
username: "User2",
channels: ["channel3"]
}
],
availableChannels: [
"channel1",
"channel2",
"channel3",
"channel4",
"channel5"
]
};
}
};
</script>
<style>
.user {
min-height: 100px;
display: flex;
align-items: center;
border: 1px solid black;
margin-bottom: 10px;
justify-items: center;
}
.name,
.channel {
flex: 1;
}
.channel {
min-height: 100px;
display: flex;
flex-direction: column;
}
</style>

Getting and Display Records from API

I am trying to get list of tracks related to an album and i am having problem displaying the records using my API. How can i get to display all the tracks under an album and it will give me the Track ID, Track Title etc.
<ul class="tracklist">
<li class='tracklistRow' v-for="album in albums">
<div class='trackCount'>
<img class='play' src='/assets/images/icons/play-white.png' onclick='playSong()'>
<img class='pause' src='/assets/images/icons/pause.png' style='display:none;' onclick='pauseSong()'>
<span class='trackNumber'>{{ album.track.id }}</span>
</div>
<div class='trackInfo'>
<span class='trackName'>{{ album.track.track.title }}</span>
</div>
<div class='trackOptions'>
<img class='optionsButton' src='/assets/images/icons/more.png'>
</div>
<div class='trackDuration'>
<span class='duration'>{{ album.track.track.duration }}</span>
</div>
</li>
<div class="rLabel" v-if="album">
© {{ album.albumDetails.recordlabel }}
</div>
</ul>
<script>
import axios from 'axios';
export default {
name: 'album',
props:['id'],
data: function(){
return {
album: null,
albums: []
}
},
methods: {
getData(){
var that = this
axios.get('http://api.localhost/api/album/'+this.id)
.then(function (response){
that.album = response.data.data;
})
}
},
mounted: function(){
this.getData()
}
}
</script>

vue+bulma Tabs Error:'openTab' is defined but never used

I would like to create a tab using vue and bulma, but I'm getting the following error:
error: 'openTab' is defined but never used (no-unused-vars) at
code:
<template>
<div>
<div class="tabs is-centered">
<ul>
<li class="tab is-active" onclick="openTab(e,'adult')"><a>Adult</a></li>
<li class="tab" onclick="openTab(e,'card')"><a>Card</a></li>
<li class="tab" onclick="openTab(e,'food')"><a>Food</a></li>
</ul>
</div>
<div id="adult" class="content-tab">
<img :src="require(`../../static/dst/${parentData.file_name}`)">
<div v-bind:style="{ color: `${parentData.font_color}`}">
{{parentData.info_text}}<br>
{{parentData.rate_adult}}{{parentData.part_name}}<br>
{{parentData.rate_part}}
</div>
</div>
<div id="card" class="content-tab" style="display: none">
<div :style="{ color: `${parentData.card_color}`}">
{{parentData.card_text}}
</div>
</div>
<div id="food" class="content-tab" style="display: none">
{{parentData.class_name}}
</div>
</div>
</template>
<script>
export default {
name: 'Child',
props: ['parentData'],
openTab(e, tabName) {
var i, x, tablinks;
x = document.getElementsByClassName("content-tab");
for (i = 0; i < x.length; i++) {
x[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tab");
for (i = 0; i < x.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" is-active", "");
}
document.getElementById(tabName).style.display = "block";
e.currentTarget.className += " is-active";
}
}
</script>
                               
I want to find the answer to this problem.
Tap function does not apply.
Process:
Upload an image
Return json contents
Check by tap
Can you tell me what the error is?
Your openTabs function should be part of methods. So:
props: {},
methods: {
openTab()
}
More information here: https://v2.vuejs.org/v2/guide/events.html#Method-Event-Handlers

Hide bootstrap row column based on the data

I am running one jquery kendo grid row template where i am showing some content with images.Below is the code :
<table id="grid" style="width:100%">
<colgroup>
<col class="photo" />
<col class="details" />
<col />
</colgroup>
<thead style="display:none">
<tr>
<th>
Details
</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="3"></td>
</tr>
</tbody>
</table>
<script id="rowTemplate" type="text/x-kendo-tmpl">
<tr>
<td style="width:30%">
div class="row">
<div id="dvImage" class="col-sm-4" style="width:118px">
#= imagelink #
</div>
<div class="col-sm-8" style="width:400px">
<span class="name" style="font-size:14px; color:green">#: Link #</span>
</div>
</div>
</td>
</tr>
</script>
<style>
.name {
display: block;
font-size: 1.3em;
}
.k-grid-header .k-header {
padding: 0px 20px;
}
.k-grid-content {
overflow-y: auto;
}
.k-grid tr td {
background: white !important;
border: 0 !important;
border-color: transparent;
}
.k pager-wrap {
border-width: 1px !important;
border-color: #ccc;
}
.k-block, .k-widget, .k-input, .k-textbox, .k-group, .k-content, .k-header, .k-filter-row > th, .k-editable-area, .k-separator, .k-colorpicker .k-i-arrow-s, .k-textbox > input, .k-autocomplete, .k-dropdown-wrap, .k-toolbar, .k-group-footer td, .k-grid-footer, .k-footer-template td, .k-state-default, .k-state-default .k-select, .k-state-disabled, .k-grid-header, .k-grid-header-wrap, .k-grid-header-locked, .k-grid-footer-locked, .k-grid-content-locked, .k-grid td, .k-grid td.k-state-selected, .k-grid-footer-wrap, .k-pager-wrap, .k-pager-wrap .k-link, .k-pager-refresh, .k-grouping-header, .k-grouping-header .k-group-indicator, .k-panelbar > .k-item > .k-link, .k-panel > .k-item > .k-link, .k-panelbar .k-panel, .k-panelbar .k-content, .k-treemap-tile, .k-calendar th, .k-slider-track, .k-splitbar, .k-dropzone-active, .k-tiles, .k-toolbar, .k-tooltip, .k-button-group .k-tool, .k-upload-files {
border-color: transparent;
}
.col-md-2 {
width:118px
}
.col-md-3 {
width:25%
}
</style>
In the above code i have Image and description which i am showing but for some of the rows i don't have image but still it's containing the space. So here i need that if image is null for particular row then it should hide that image column. I tried like this but did not get any luck.
Below is the code:
$("#grid").kendoGrid({
autoBind: false,
dataSource: {
transport: {
read: {
url: "/Home/GetSearchData",
type: "POST",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: { searchTerm: $('[id*=hdnHomeSearch]').val() }
},
parameterMap: function (data, operation) {
return kendo.stringify(data);
}
},
pageSize: 10,
schema: {
parse: function (data) {
debugger;
var items = [];
var chkCorrectVal = 0;
var context = $('#dvImage');
for (var i = 0; i < data.data.length; i++) {
if (data.data[i].CorrectValue != null && data.data[i].SearchValue != null) {
$("#spnSR")[i].innerHTML = "<b>" + "Get results for this text: " + "</b>" + data.data[i].CorrectValue;
$("#spnSV")[i].innerHTML = "<b>" + "Searched for this text: " + "</b>" + data.data[i].SearchValue;
chkCorrectVal = 1;
}
else {
if (chkCorrectVal == 0) {
$("#spnSR").hide();
$("#spnSV").hide();
}
}
if (!data.data[i].imagelink) {
var getContext = $(context[i]);
data.data[i].imagelink = "";
$(context[i]).addClass('hidden');
}
}
var product = {
data: data.data,
total: data.total
};
items.push(product);
return (items[0].data);
},
}
},
dataBound: function () {
DisplayNoResultFound($("#grid"));
},
serverPaging: true,
pageable: {
refresh: true,
pageSizes: true
},
rowTemplate: kendo.template($("#rowTemplate").html()),
});
});
One more example i am pasting here where i am trying to get the same results and that is working fine for me.
Below is the code:
<input type="submit" id="soltitle" value="#1"/>
<div class="row">
<div class="col-md-2" id="hell1">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"></h4>
</div>
<div id="timeline1" class="panel-collapse collapse">
<div class="panel-body">
<a href="#" class="thumbnail">
<img class="img-responsive" src="http://placehold.it/250x160" alt="Thumb11" />
</a>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title"></h4>
</div>
<div id="timeline1" class="panel-collapse collapse">
<div class="panel-body">
<a href="#" class="thumbnail">
<img class="img-responsive" src="http://placehold.it/250x160" alt="Thumb11" />
</a>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
$('#soltitle').click(function () {
$('#hell1')
// Find parent with the class that starts with "col-md"
// Change class to "col-md-3"
.closest('[class^="col-md"]')
.toggleClass('col-md-2 col-md-2 hidden')
// Find siblings of parent with similar class criteria
// - if all siblings are the same, you can use ".siblings()"
// Change class to "col-md-2"
//.siblings('[class^="col-md"]')
// .removeClass('col-md-3')
// .addClass('col-md-2');
});
});
</script>
in this example i am hiding first column in button click event and that is working fine.
The problem is that you toggle the class for all rows where the image does not exist. Each time you toggle those and toggle again, the second toggle negates the first one. If the rows where the if is evaluated to true is pair, then the last toggle was a negation.
It is not clear whether you need to hide the whole column if you find one such row, or you need to hide the column only for the affected row specifically. Also, your if is incorrect.
If you want to hide the whole column if at least such a row exists, then this might be the solution:
var shouldShow = true;
for (var i = 0; shouldShow && (i < data.data.length); i++) {
if (!data.data[i].imagelink) {
$(".imageClass").addClass('hidden');
shouldShow = false;
}
}
If you want to do it only for the affected row, then something like this might help you:
var context = $(".imageClass");
for (var i = 0; i < data.data.length; i++) {
if (!data.data[i].imagelink) {
$(context[i]).addClass('hidden');
}
}
The code assumes you have a single column having imageClass.
EDIT
It turned out that the .hidden class was not defined. There are two possible solutions, you can choose either of them.
Solution1: replace .addClass("hidden") with .hide()
Solution2: Add the following rule to the CSS code: .hidden {display: none;}