I've got an 'Album' component that is being used on a page. I am trying to create something whereby only one album can be toggled at a given time. So if an album is already open when clicking another it would close.
Component being mounted recursively in the 'parent' app.
<album v-for="(album, index) in albums"
:key="index"
#expand="setAlbumContainerHeight"
#action="removeFromCollection"
:albumDetails="album"
page="collection">
</album>
The component itself.
<template>
<div v-on:click="toggleExpand" ref="album" class="album">
<img class="album-artwork" alt="album-artwork" :src="albumDetails.cover_url">
<div ref="expandContent" class="expanded-content">
<div class="left-block">
<div class="title">{{ albumDetails.title }}</div>
<div class="artist">{{ albumDetails.artist }}</div>
<!-- Search page specific -->
<div v-if="page === 'search'" class="info">{{ albumDetails.year }}<br> <span v-for="genre in albumDetails.genre"> {{ genre }}</span><br> {{ albumDetails.label }}</div>
<!-- Collection page specific -->
<div v-if="page === 'collection'" class="info">{{ albumDetails.year }}<br> <span v-for="genre in albumDetails.genres"> {{ genre.genre }}</span><br> {{ albumDetails.label }}</div>
<!-- Search page specific -->
<button v-if="page === 'search'" v-on:click.stop="addToCollection" class="add-collection">Add to collection</button>
<!-- Collection page specific -->
<button v-if="page === 'collection'" v-on:click.stop="removeFromCollection" class="add-collection">Remove from collection</button>
</div>
<div v-if="page === 'search'" class="right-block">
<div class="a-side">
<p v-for="track in albumDetails.track_list_one" class="track">{{ track }}</p>
</div>
<div class="b-side">
<p v-for="track in albumDetails.track_list_two" class="track">{{ track }}</p>
</div>
</div>
<div v-if="page === 'collection'" class="right-block">
<div class="a-side">
<p v-for="track in trackListOne" class="track">{{ track.track }}</p>
</div>
<div class="b-side">
<p v-for="track in trackListTwo" class="track">{{ track.track }}</p>
</div>
</div>
<img class="faded-album-artwork" alt="faded-album-artwork" :src="albumDetails.cover_url">
</div>
</div>
</template>
<script>
module.exports = {
name: 'Album',
props: ['albumDetails', 'page'],
data: function () {
return {
expanded: false,
expandedContentHeight: 0,
trackListOne: [],
trackListTwo: []
}
},
mounted() {
if (this.albumDetails.tracks) {
this.getTrackListOne(this.albumDetails.tracks);
this.getTrackListTwo(this.albumDetails.tracks);
}
},
methods: {
toggleExpand() {
if (!this.expanded) {
this.expanded = true;
this.$refs.expandContent.style.display = 'flex';
this.expandedContentHeight = this.$refs.expandContent.clientHeight;
let height = this.$refs.album.clientHeight + this.expandedContentHeight;
this.$emit('expand', height, event);
} else {
this.expanded = false;
this.$refs.expandContent.style.display = 'none';
let height = 'initial';
this.$emit('expand', height, event);
}
},
addToCollection() {
this.$emit('action', this.albumDetails.cat_no);
},
removeFromCollection() {
this.$emit('action', this.albumDetails.cat_no);
},
getTrackListOne(tracks) {
let halfWayThough = Math.floor(tracks.length / 2);
this.trackListOne = tracks.slice(0, halfWayThough);
},
getTrackListTwo(tracks) {
let halfWayThough = Math.floor(tracks.length / 2);
this.trackListTwo = tracks.slice(halfWayThough, tracks.length);
},
},
}
</script>
<style scoped lang="scss">
#import './styles/album';
</style>
The component itself is storing its state with a simple 'expanded' data attribute. Currently this means that the component works nicely when a user is opening and closing each album, however, problems occur when they are opening several at a time. Due to absolute positioning I am manually having to set the height of the container. The aim is to have only one open at a time. I am having trouble thinking of a way to store and track what album is currently open - I'm also unsure how the parent can manage the stage each album / open and closing them where. Even if I know which album is open in the parent how can I trigger individual child components methods?
I've tried to be as clear as possible, please let me know if there's anything else I can make clearer.
I'm not quite sure what you mean by recursive, as far as I can see you're not referencing the album component within itself recursively.
I'd go with this approach:
expanded is a property of your component.
On a click the component emits a this.$emit('expand', albumID).
In your parent you listen for the #expand event and assign your expandedAlbumID to the event payload.
The last remaining piece of the puzzle is now to pass your expanded property like :expanded="expandedAlbumID == album.id"
Full working example: https://codesandbox.io/s/o5p4p45m9
Related
I made an object to manage the candidate education in online marketplace
let education ={
tabs:{},
render:function(){
document.querySelector('#education-tabs').innerHTML = ''
let container =``;
Object.values(this.tabs).map((tab)=>{
container += `
<div class="education-tab-container">
<div class="education-tab-right">
<div class="big-title">
${tab.faculty}
</div>
<div class="medium-title">
${tab.department}
</div>
<div class="small-title">
${tab.university}
</div>
</div>
<div class="education-tab-left">
<div class="date">
${tab.from || ''} ${tab.to || ''}
</div>
<div class="close" data-id="${tab.id}">
<span>x</span>
</div>
</div>
</div>
`
})
document.querySelector('#education-tabs').innerHTML = container
},
deleteTab:function(id){
delete this.tabs[id];
this.render();
},
add:async function(payload){
this.tabs = {...this.tabs,[payload.id]:payload}
this.render();
}
}
This updates the UI every time I add a new tab to this object.
The problem is when I try to remove a tab: sometimes I get the data-id attribute of the tab that I made and sometimes it returns null.
here is my function
const triggerListener = ()=>{
$("#education-tabs").find(".education-tab-left .close").click((e)=>{
if(e.target.getAttribute('data-id')){
alert(e.target.getAttribute('data-id'))
}
});
so is there a way to solve this problem?
I have two routes that render the same component but with different data coming from an API.
This component has a child component called <base-section> that has a v-if directive that checks if a specific slot has content or not (because if it has no content, I don't want the slot to show).
However, there might be more than one instance of this child component on the same parent component, and therefore, if one of the instances has content in the slot, but the other one doesn't, VUE will automatically assume that all slots have content.
Therefore, I would like to know if there is any way of checking the specific slot content of each instance and then compare it with the data coming from the API. Please find my code below:
Parent component (Content.vue)
<template>
<base-section
v-for="entry in this.entries"
:key="entry.title"
lang="lang-markup"
:title="entry.title"
>
<template v-slot:content>
<span v-html="entry.content"></span>
</template>
<template v-slot:examples>
<div v-html="entry.examples"></div>
</template>
<template v-slot:code>
{{ entry.code }}
</template>
</base-section>
</template>
Child component (BaseSection.vue)
<template>
<hr class="my-6" />
<h4 class="text-salmon">{{ title }}</h4>
<section>
<div class="section-sm txt-justify" v-if="this.$slots.content">
<slot name="content"></slot>
</div>
<span class="medal bg-light text-dark code-medal">Examples</span>
<div class="section-sm border-light-1 mb-3">
<slot name="examples"></slot>
</div>
<span class="medal text-dark code-medal">Code</span>
<pre :class="lang + ' border-light-1 bg-light'">
<code><slot name="code"></slot></code>
</pre>
</section>
</template>
The data coming from the API follows this structure:
getData() {
const url = this.apiUrl + this.$route.name + this.apiToken
fetch(url)
.then((collection) => collection.json())
.then((collection) => {
const entries = [];
this.entries = [];
for (const id in collection.entries) {
if (collection.entries[id].Version === this.byteVersion) {
entries.push({
title: collection.entries[id].Title,
content: collection.entries[id].Content,
examples: collection.entries[id].Examples,
code: collection.entries[id].Code,
});
}
}
this.entries = entries;
});
}
Thank you very much for your help!
Regards,
T.
Maybe you can pass the "entry.content" into your BaseSection component. and v-if the entryContent.
Parent component (Content.vue)
<template>
<base-section
v-for="entry in this.entries"
:key="entry.title"
lang="lang-markup"
:title="entry.title"
:entryContent="entry.content"
>
<template v-slot:content>
<span v-html="entry.content"></span>
</template>
<template v-slot:examples>
<div v-html="entry.examples"></div>
</template>
<template v-slot:code>
{{ entry.code }}
</template>
</base-section>
</template>
Child component (BaseSection.vue)
<div class="section-sm txt-justify" v-if="entryContent">
<slot name="content"></slot>
</div>
Or you can v-if your content template
<template v-slot:content v-if="entry.content">
<span v-html="entry.content"></span>
</template>
I am learning to deal with VueJS and made a simple todo app. It works well, but I want to store data locally and make it persistent even if there is a page reload.
This is the code produced following instruction of a few useful tutorials (leaving CSS outside to ease readability):
<template>
<div class="main-container">
<div class="header md-elevation-4">
<h1 class="md-title">{{ header }}</h1>
</div>
<div class="todo-list">
<div class="row"></div>
<div class="row">
<!-- add new todo with enter key -->
<md-field class="todo-input">
<md-input
v-model="currentTodo"
#keydown.enter="addTodo()"
placeholder="Add a todo! It's easy!"
/>
</md-field>
<!-- for all todos, set class of edited todos -->
<ul class="todos">
<div class="list-div">
<li v-for="todo in todos" :key="todo.id">
<!-- binds checkbox to todo model after each instance; -->
<input
class="toggle-todo"
type="checkbox"
v-model="todo.completed"
/>
<!-- starts editing process on double click -->
<span
class="todo-item-label"
:class="{ completed: todo.completed }"
#dblclick="editTodo(todo)"
v-if="!todo.edit"
>{{ todo.label }}</span
>
<!-- concludes editing with enter click -->
<input
v-else
class="todo-item-edit"
type="text"
v-model="todo.label"
#keyup.enter="completedEdit(todo)"
/>
<!-- deletes todos using removeTodo method -->
<i
class="material-icons"
alt="remove toDo"
#click="removeTodo(todo)"
>delete</i
>
</li>
</div>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
name: "RegularToolbar",
data() {
return {
header: "A VueJS ToDo App",
todos: [],
currentTodo: "",
completed: false, // add a completed property
editedToDo: null // add a edited property
};
},
methods: {
addTodo() {
this.todos.push({
id: this.todos.length,
label: this.currentTodo,
completed: false, // initializes property
edit: false // initializes property
});
this.currentTodo = "";
},
mounted() {
// console.log('App mounted!');
if (localStorage.getItem("todos"))
this.todos = JSON.parse(localStorage.getItem("todos"));
},
watch: {
todos: {
handler() {
// console.log('Todos changed!');
localStorage.setItem("todos", JSON.stringify(this.todos));
},
deep: true
}
},
removeTodo(todo) {
// allows users to remove todos
var index = this.todos.indexOf(todo);
this.todos.splice(index, 1);
},
editTodo(todo) {
todo.edit = true;
},
completedEdit(todo) {
todo.edit = false;
}
}
};
</script>
<style lang="scss" scoped></style>
As it is, all the JS part referring to mount and watch does not work. I am able to add new todos and delete them as I wish, but it does not retain the data if the user reloads the page.
Could some of the colleagues spot what I am missing here?
It's a problem of code organization:
Your mounted and watch sections are currently inside the methods section, which will prevent them from firing as expected.
Move those out into their own sections (both should be at the same level as methods) and you should be all set.
With those edits and nothing else, I've got your code working in a fiddle here: https://jsfiddle.net/ebbishop/dc82unyb/
I am having a weird issue with my single file Vue component where when I update an unrelated variable (Vue.js variable), all of my inputs (stuff I typed in, not the elements themselves.) disappear.
I have worked with Vue single file components for a few months now and I have never ran into something like this. Here is the weird part, the variable gets updated successfully as expected, but if I include the variable inside of the template at all that is when all the inputs disappear.
The function is looking up 'agents', then letting the user know how many records have been found and whether or not he/she would like to view them. If the user clicks on the "View" link, then they are shown a bootstrap-modal which shows them the records so that they could select one.
Here is what I have already tried:
Removing all ids from the inputs and using only refs="" to get the values.
changing the 'agents' variable name. Thought maybe it was conflicting with some rogue global or something.
Double checked that the parent component and this component was not being re-rendered. I did that by putting console.log() comments in the mounted() function and as expected it is only rendering once.
Watched the key using Vue dev tools extension to make sure the key was not being changed somehow.
Executed the searchAgent() function in a setTimeout(()=>{},5000) to see whether my use of _.debounce was causing issues.
Used jquery to fetch the values from the inputs instead of refs.
Assign the new records to a local variable agentsArray, then pass that into a function which assigns it to the vue variable 'agents' (its basically a needlessly longer route to the same thing but I thought WHY NOT TRY IT)
Double checked all my uses of 'this' to make sure that I was not accidentally using the wrong this and causing some unknown bug.
Using V-model, but using that doesn't help because I would still have to include the 'agents' inside of the modal in the template.
Using a v-if statement to render the modal HTML in the template only after 'agents' is not an empty array.
Update: Based on a suggestion, removed the function from inside of $(document).ready() inside of the mounted() function.
Template:
<template>
<div class="Q mb-0">
<i class="far fa-question-circle"></i>
<center>
<p class="display-1">{{title}}</p>
{{prefix}} is Representing Themselves Skip This Step.
<div id="searchResults" class="hidden" style="margin-top:5px;">
<a id="searchResultsText" class="SkipStepStyle"></a>
<a
id="viewSearchResults"
style="font-weight: bold;"
class="hidden SkipStepStyle"
v-on:click="displayAgents"
>
View
</a>
</div>
<form class="mt-2 BuyerSellerAgentInfo">
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="NameFirst"
type="text"
:name="prefix+'sAgent_NameFirst'"
placeholder="FIRST NAME"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_NameFirst'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="NameLast"
type="text"
:name="prefix+'sAgent_NameLast'"
placeholder="LAST NAME"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_NameLast'].Answer"
>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="Email"
type="text"
:name="prefix+'sAgent_Email'"
placeholder="EMAIL ADDRESS"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Email'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="Phone"
type="text"
:name="prefix+'sAgent_Phone'"
maxlength="14"
placeholder="PHONE #"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Phone'].Answer"
>
</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">
<input
ref="Brokerage"
type="text"
:name="prefix+'sAgent_Brokerage'"
placeholder="AGENT'S BROKERAGE"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_Brokerage'].Answer"
>
</div>
<div class="form-group col-md-6">
<input
ref="License"
type="text"
:name="prefix+'sAgent_License'"
placeholder="AGENT'S LICENSE #"
class="AnswerChoice"
:value="currentAnswers[prefix+'sAgent_License'].Answer"
>
</div>
</div>
<input
class="AnswerChoice"
type="hidden"
:name="prefix+'sAgent_ID'"
:value="currentAnswers[prefix+'sAgent_ID'].Answer || '1'"
>
<input
class="AnswerChoice"
type="hidden"
:name="prefix+'sAgent_BrokerageID'"
:value="currentAnswers[prefix+'sAgent_BrokerageID'].Answer || '1'"
>
</form>
</center>
<div v-if="agents.length > 0" class="modal" id="AgentPopup">
<div class="vertical-alignment-helper">
<div class="modal-dialog vertical-align-center">
<div class="modal-content">
<div class="modal-body">
<center>
<h5 class="d-inline-block mb-3">Select {{prefix}}'s Agent:</h5>
</center>
<button v-on:click="displayCategories" type="button" class="close shadow" data-dismiss="modal">×</button>
<ul>
<li v-for="agent in agents">{{ agent.NameFull || agent.NameFirst+' '+agent.NameLast }}</li>
<li class="border-0">{{prefix}}’s agent is not in this list</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
Script:
import _ from 'lodash';
export default {
name: "AgentInformation",
props: {
friendlyIndex: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
answerChoices:{
type: Array,
default: () => []
},
currentAnswers: {
type: Object,
default: () => {},
},
prefix: {
type: String,
default: '',
},
token: {
type: String,
default: '',
},
},
methods: {
debounceFunction(func,timer){
let vueObject = this;
return _.debounce(()=>{
vueObject[func]();
},timer);
},
displayCategories(){
$('.categories').show();
},
displayAgents(){
$('.categories').hide();
$('#AgentPopup').modal({backdrop:'static',keyboard:false});
},
searchAgent() {
let vueObject = this;
console.log('calling searchAgent()');
let agentSearchRoute = correctVuexRouteURL(vueObject.$store.getters.routeName('search.agent'));
if (!agentSearchRoute) genericError('Agent Search Route Not Found. Error code: a-s-001');
else
{
let dataObject = {
NameFirst: this.$refs.NameFirst.value,
NameLast: this.$refs.NameLast.value,
Email: this.$refs.Email.value,
Phone: this.$refs.Phone.value,
License: this.$refs.License.value,
_token: this.token,
};
console.log(dataObject);
vueObject.$http.post(agentSearchRoute, dataObject).then((r) => {
let status = r.body.status;
if (status == 'success')
{
vueObject.agents = r.body.agents;
let searchResultsContainer = $('#searchResults');
let searchResultsText = $('#searchResultsText');
let viewSearchResultsLink = $('#viewSearchResults');
let agentCount =
vueObject.agents.length;
searchResultsContainer.removeClass('hidden');
if(agentCount > 0)
{
let rText = agentCount > 1 ? 'records' :
'record';
searchResultsText.text(agentCount+' '+rText+'
found.');
viewSearchResultsLink.removeClass('hidden');
}
else
{
if (!viewSearchResultsLink.hasClass('hidden'))
viewSearchResultsLink.addClass('hidden');
searchResultsText.text('No records found.');
}
}
});
}
},
},
data(){
return {
agents: [],
}
},
mounted() {
let vueObject = this;
console.log('mounted');
$(document).ready(function(){
$('#phone').mask('(###)-###-####');
$('.AnswerChoice').on('input', () => {
let searchAgent =
vueObject.debounceFunction('searchAgent',500);
searchAgent();
});
});
}
}
It seems that the issue is the template does not like the 'agents' variable to be inside of it. When I remove the modal container or just the references to 'agents' it works as expected. If I change the variable name it does not solve the issue.
Any thoughts on the solution? Am I missing something blatantly obvious and stupid?!
Edit: Something I forgot to add, I don't think affects this in any way but it is worth mentioning. This component is rendered dynamically inside of the parent.
Rendering the component:
<component
v-for="(component,i) in selectedView"
:is="component['Component']"
v-bind="bindAttributes(component)"
:key="component.ID"
>
</component>
Changing agents will cause the whole template to be re-run. Not just the bits that mention agents, everything in that template will be updated.
When a user types into one of your <input> elements you aren't storing that value anywhere. You've got a :value to poke the value in but you aren't updating it when the value changes. The result will be that when Vue re-renders everything it will jump back to its original value.
You should be able to confirm this by setting the initial values within currentAnswers to be something other than empty. You should find that whenever agents changes it jumps back to those initial values.
The solution is just to ensure that your data is kept in sync with what the user types in. Typically this would be done using v-model but that's a bit tricky in this case because you're using a prop for the values and you shouldn't really be mutating a prop (one-way data flow). Instead you should use events to communicate the required changes up to whichever component owns that data.
Here is a simple test case to demonstrate the issue in isolation:
new Vue({
el: '#app',
data () {
return {
count: 0,
value: 'initial'
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<input :value="value">
<button #click="count++">Click count: {{ count }}</button>
</div>
I'm using Vue v2
I'm trying to change only the properties of the selected element. See, when the response is marked after the click, it should change to a red color with a text that says 'Unmark'. And vice versa: if the button is clicked again (which now would say 'Unmark'), it should change to a green color and the text would be 'Mark'. Alas, it works.... Nevertheless, my code applies the change to every element inside the v-for; I only want that to happen to the selected element.
I've thought about using a Component to check if somethings changes, but first I'd like to see if there's a solutions for this. ANy help will be appreciated
Here's my code:
<div class="search-results">
<div class="activity-box-w" v-for="user in users">
<div class="box">
<div class="avatar" :style="{ 'background-image': 'url(' + user.customer.profile_picture + ')' }">
</div>
<div class="info">
<div class="role">
#{{ '#' + user.username }}
</div>
<div>
<div>
<p class="title">#{{ user.customer.name }}
#{{user.customer.lastname}}
</p>
</div>
</div>
</div>
<div class="time">
<input type="button" class="btn btn-sm btn-primary" v-on:click.prevent="markUser(user)" v-model="text"
v-bind:class="[{'green-border':notMarked}, {'red-border':marked}]" v-cloak v-if="!action"
:disabled="action"></input>
</div>
</div>
</div>
Now for the script:
new Vue({
el: '#engage-panel',
data: {
users: [],
mark: {'id' : '', 'marks' : ''},
text: 'Mark', //Migth change to Unmark later on
action: false,
marked: null,
notMarked: null,
},
methods:
{
markUser: function(user){
this.mark.id = user.id;
this.action= true;
Vue.http.headers.common['X-CSRF-TOKEN'] = $('meta[name="csrf-token"]').attr('content');
this.$http.put('/mark/user', this.mark).then(response => {
if(response.body === "marked")
{
this.mark.marks="true";
this.text = 'Unmark';
this.marked= true;
this.notMarked= false;
this.action= false;
}else{
this.mark.marks="false";
this.text = 'Mark';
this.marked= false;
this.notMarked= true;
this.action= false;
}
}).catch(e => {
this.action= false;
});
}
}
You can use $event.target on click if you just need to toggle css class.
fiddle here
But it's true that it's easier if a user has a status like marked = true/false for example, you just need to bind class like :
<input :class="{ 'green-border': user.marked, 'red-border': !user.marked }">
The issue my code applies the change to every element you met is caused by every user in v-for="user in users" uses one same object to indicates it is marked or not.
If your users data has one property like status to save current status (like unmark, mark etc), it is very simple, just change to next status when click mark button.
If your users data doesn't have that property, you need to create one dictionary, then save the users already clicked as key, the status for the user will be the value.
Below is one demo:
app = new Vue({
el: "#app",
data: {
users1: [{'name':'abc', 'status':'none'},
{'name':'xyz', 'status':'none'}],
users2: [{'name':'abc'}, {'name':'xyz'}],
selectedUsers: {}
},
methods: {
getNextStatusBaseOnRoute: function (status) {
if(status ==='marked') return 'marked'
let routes = {'none':'unmark', 'unmark':'marked'}
return routes[status]
},
markUser1: function (item) {
item.status = this.getNextStatusBaseOnRoute(item.status)
},
markUser2: function (item) {
let status = item.name in this.selectedUsers ? this.selectedUsers[item.name] : 'none'
// remember to use vue.$set when adding new property to one object
this.$set(this.selectedUsers, item.name, this.getNextStatusBaseOnRoute(status))
}
}
})
.marked {
background-color:green;
}
.unmark {
background-color:yellow;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<h2>Case 1: </h2>
<div v-for="(item, index1) in users1" :key="'1'+index1">
<span>{{item.name}}:</span><span :class="[item.status]">{{item.status}}</span><button #click="markUser1(item)">Mark</button>
</div>
<h2>Case 2: </h2>
<div v-for="(item, index2) in users2" :key="'2'+index2">
<span>{{item.name}}:</span><span :class="[item.name in selectedUsers ? selectedUsers[item.name] : 'none']">{{item.name in selectedUsers ? selectedUsers[item.name] : 'none'}}</span><button #click="markUser2(item)">Mark</button>
</div>
</div>
For Vue3, you can also store the index of the selected element
<ul role="list" class="">
<li class="relative" v-for="(image, index) of images" :class="selectedImage == index? 'border-indigo-500 border-2': 'border-transparent'" >
<div #click="selectedImage = index" class="">
<img :src="image" alt="" class="object-cover pointer-events-none group-hover:opacity-75">
</div>
</li>
</ul>