hide the section if the umbraco content is empty - vue.js

I have created a description section for a website, and the description content is added through the Umbraco CMS. So now what i want is hide the description column if the Umbraco CMS content for this description section is empty or no content.
<CustomListVue
class="mt-8"
v-if="pageData.items"
v-show="showDetails"
:startCollapse="true"
:collapsable="true"
:data="{
title: 'Description',
}"
>
<ul class="desc-box">
<li
v-for="(i, index) in pageData.items.filter((x) => x.content)"
:key="'item_' + index"
class="desc"
v-html="i.content"
></li>
</ul>
</CustomListVue>
data() {
return {
showTnc: false,
};
},
computed: {
},

Related

two arrays that have matching content bind links from one to the other

I have 2 sets of data coming in from a Ajax request. One set only contains titles I own. The other set contains all the titles that exists plus their URL. What I am trying to do is bind the first set to URLs found in the 2nd set.
new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript" },
{ text: "Learn Vue" },
{ text: "Play around in JSFiddle"}
],
myURLs: [
{ text: "Learn JavaScript", url:"http://google.com" },
{ text: "Learn Vue", url:"http://google1.com" },
{ text: "Play around in JSFiddle", url:"http://google2.com" },
{ text: "Build something awesome",url:"http://google3.com" }
]
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>My Title + Hyperlinks:</h2>
<ol>
<li v-for="todo in todos">
<a v-bind:href="myURLs.url">{{ todo.text }}</a>
</li>
</ol>
</div>
You can use computed to create a dictionary
computed: {
myUrlDict() {
return this.myURLs.reduce((a, c) => {
a[c.text] = c.url
return a
}, {})
}
}
then use it in template
<li v-for="todo in todos">
<a v-bind:href="myUrlDict[todo.text]" v-if="myUrlDict[todo.text]">{{ todo.text }}</a>
</li>

Vue.js: Including same instance of component multiple times in page

What I am trying to accomplish:
I have some filters that display on a page to filter the products that display on the page. In mobile, I want to hide these filters behind a button that, once pressed, will show the filters in a slide out menu from the side.
While I can duplicate the same components on the page twice, the components are not the exact same instance, that is, clicking on a filter will trigger that function to filter the products on the page, but it sets its own data attributes, which I am using to say "if data attribute 'selected' is true, add a 'selected' class to the component. When I resize the window, the other instance of the component does not have the 'selected' data attribute marked as 'true'.
I expect this, because, from the docs:
Notice that when clicking on the buttons, each one maintains its own, separate count. That’s because each time you use a component, a new instance of it is created.
...but what would be the best way to do this?
I played around with the idea of just setting a class 'mobile' on the component, and the .mobile css would style the components differently, but I need for it to break out where it is nested.
e.g.
<body>
<header>
<!-- desktop -->
<guitar-filters>
<header>
<!-- mobile -->
<guitar-filters>
</body
Here is my Vue component 'guitar-filters' that displays several components called 'instrument-filter':
Vue.component('guitar-filters', {
data: function() {
return {
isMobile: false
}
},
mounted: function() {
var comp = this;
this.setIsMobile();
window.addEventListener('resize', function() {
comp.setIsMobile();
});
},
methods: {
setIsMobile: function() {
this.isMobile = (window.innerWidth <= 900) ? true : false;
}
},
template: `
<ul class="filters" :class="{mobile: isMobile}">
<li>
All
</il>
<li>
Series
<ul>
<instrument-filter filter-by="series" filter="All">All</instrument-filter>
<instrument-filter filter-by="series" filter="Frontier">Frontier</instrument-filter>
<instrument-filter filter-by="series" filter="Legacy">Legacy</instrument-filter>
<instrument-filter filter-by="series" filter="USA">USA</instrument-filter>
</ul>
</li>
<li>
Body Shape
<ul>
<instrument-filter filter-by="bodyType" filter="All">All</instrument-filter>
<instrument-filter filter-by="bodyType" filter="Concert">Concert</instrument-filter>
<instrument-filter filter-by="bodyType" filter="Concertina">Concertina</instrument-filter>
<instrument-filter filter-by="bodyType" filter="Concerto">Concerto</instrument-filter>
<instrument-filter filter-by="bodyType" filter="Orchestra">Orchestra</instrument-filter>
</ul>
</li>
</ul>
`
});
The instrument-filter component:
Vue.component('instrument-filter', {
data: function() {
return {
selected: false
}
},
props : [
'filterBy',
'filter'
],
methods: {
addFilter: function() {
this.$root.$emit('addFilter',{filterBy: this.filterBy,filter: this.filter});
},
clearFilter: function() {
this.$root.$emit('clearFilter',{filterBy: this.filterBy,filter: this.filter});
}
},
template: `
<li :class="{ 'selected' : selected }" #click="selected = !selected; selected ? addFilter() : clearFilter()"><slot></slot></li>
`
});
.css:
ul.filters > li > ul > li.selected::before {
content: "✔️";
...
}
The goal is to have a filter have the 'selected' class in both instances. If I click on 'concert' body shape, and then resize the window to mobile breakpoint, the other instance of that filter component will be selected also.
EDIT: I could hack this. I could move one instance of the component with javascript, but I'm learning Vue, and want to do this the Vue way and best practices.
There's a number of different ways you can handle this. It looks like you've started down the event bus path. Another option could be to use shared app state (see Vuex).
What I've done is similar to shared state, but just using app (same would apply to a common parent component) data. The shared object is passed to both instances of the component. If an item is selected, the appropriate entry is toggled. Since the object is shared, both components stay in sync.
If there was no common parent component, you'd have to look at events or state.
Take a look and see if that helps.
Vue.component('guitar-filters', {
props: [ 'data' ],
data: function() {
return {
isMobile: false
}
},
mounted: function() {
var comp = this;
this.setIsMobile();
window.addEventListener('resize', function() {
comp.setIsMobile();
});
},
methods: {
setIsMobile: function() {
this.isMobile = (window.innerWidth <= 900) ? true : false;
}
},
template: `
<ul class="filters" :class="{mobile: isMobile}">
<li>
All
</il>
<li>
Series
<instrument-filters :list="data.seriesFilters"/>
</li>
<li>
Body Shape
<instrument-filters :list="data.bodyFilters"/>
</li>
</ul>
`
});
Vue.component('instrument-filters', {
props : [ 'list', ],
methods: {
toggle(toggleItem) {
let itemInList = this.list.find((item) => item.value === toggleItem.value);
itemInList.selected = !itemInList.selected;
},
},
template: `
<ul>
<li v-for="item in list" :class="{ 'selected' : item.selected }" #click="toggle(item)">{{ item.label }}</li>
</ul>
`
});
new Vue({
el: "#app",
data: {
filterData: {
seriesFilters: [
{ label: 'All', value: 'All', selected: false },
{ label: 'Frontier', value: 'Frontier', selected: false },
{ label: 'Legacy', value: 'Legacy', selected: false },
{ label: 'USA', value: 'USA', selected: false },
],
bodyFilters: [
{ label: 'All', value: 'All', selected: false },
{ label: 'Concert', value: 'Concert', selected: false },
{ label: 'Concertina', value: 'Concertina', selected: false },
{ label: 'Concerto', value: 'Concerto', selected: false },
{ label: 'Orchestra', value: 'Orchestra', selected: false },
],
}
},
});
ul {
margin-left:20px;
}
ul > li {
cursor: pointer;
}
ul.filters > li > ul > li.selected::before {
content: "✔️";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<header>
<!-- desktop -->
<guitar-filters :data="filterData" />
</header>
<!-- mobile -->
<guitar-filters :data="filterData" />
</div>
Fiddle: https://jsfiddle.net/nigelw/jpasfkxb

VueJS replace data of component // same route problem

i have a '/posts' route. This 'posts' route has a created() function which fetches data from an API via GET and outputs the data to the page.
Now I have a navbar which is included on every page. This navbar now has an input field where I can search certain posts by tags. This tag based search function is already working and runs via POST to an api.
Now the problem:
I write some tags into input field in the navigation and search for them. If I'm currently not at the posts route, the search works fine and I get directed to the posts route and see the tag related posts.
If I write some tags in the navbar input field and press the search button, WHILE i'm already on the posts route, nothing happens.
So if I'm in any other route then '/posts', the tag based search works great.
Thats why I think, the problem is, that I'm already on the '/posts' route. But it should also work this way! So I need something like a route link that replaces/refresh the route content?
Here is my code:
Relevant part of my navbar component:
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<router-link to="/posts" class="nav-link">Posts</router-link>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<div v-for="(tag, index) in tags" class="ml-sm-2">
<h6><span class="badge badge-light" #click="removeSearchTags(index)">{{ tag }}</span></h6>
</div>
<input class="form-control ml-1 mr-sm-2" type="text" v-model="tag" v-on:keyup.enter="pushToTags"
placeholder="Search Gaming, Youtube, DrunkSlut" aria-label="Search">
<router-link :to="{name: 'posts', params: { searchTags: tags }}" reload>
<button type="button" v-if="this.tags.length > 0"
class="btn btn-outline-light my-2 my-sm-0">Search
</button>
</router-link>
</form>
Whole posts component logic:
<script>
export default {
name: "posts",
data: function () {
return {
apiUrl: '/getPosts',
posts: '',
submitted: false,
first_page_url: '',
last_page_url: '',
current_page_url: '',
next_page_url: '',
prev_page_url: '',
lastPage: '',
current_page: '',
tags: [],
}
},
methods: {
getPosts: function (url) {
this.$http.get(url).then(function (data) {
this.posts = data.body.data;
this.first_page_url = data.body.first_page_url;
this.last_page_url = data.body.last_page_url;
this.next_page_url = data.body.next_page_url;
this.current_page = data.body.current_page;
this.prev_page_url = data.body.prev_page_url;
this.lastPage = data.body.last_page;
this.current_page_url = '/getPosts?page=' + this.current_page;
});
},
getPostByTags: function (url, tags) {
this.$http.post(url, {
tags: tags
}).then(function (data) {
this.posts = data.body.data;
this.first_page_url = data.body.first_page_url;
this.last_page_url = data.body.last_page_url;
this.next_page_url = data.body.next_page_url;
this.current_page = data.body.current_page;
this.prev_page_url = data.body.prev_page_url;
this.lastPage = data.body.last_page;
this.current_page_url = '/getPostByTags?page=' + this.current_page;
});
},
},
computed: {},
created() {
if (!this.$route.params.searchTags) {
this.getPosts(this.apiUrl);
} else {
this.getPostByTags('/getPostByTags', this.$route.params.searchTags);
}
},
}
</script>
The Main html file, where vueJS starts. There is only the navbar component, thats how it's included on any other route.
<div id="app">
<navbar></navbar>
<router-view></router-view>
</div>
Try to make your created as a watch
Something like:
watch: {
'$route.params.searchTags': {
deep: true,
immediate: true, // this triggers the watch on component creation, so you can remove the created hook content
handler() {
if (!this.$route.params.searchTags) {
this.getPosts(this.apiUrl);
} else {
this.getPostByTags('/getPostByTags', this.$route.params.searchTags);
}
}
}
}

Vue: add multiple classes inside a v-for, based on array

I need to add classes to an element, based on categories. Here is my example data:
data() {
return {
projects: [
{
title: 'Project 1',
categories: [{ name: 'featured' }, { name: 'category-1' }],
},
{
title: 'Project 2',
categories: [{ name: 'category-2' }],
},
],
};
},
What I need is to add the categories directly as classes on the wrapper div (with the v-for), that will render:
<div class="featured category-1">
<h3>Project 1</h3>
</div>
<div class="category-2">
<h3>Project 1</h3>
</div>
I'm not sure how to do this?
<div v-for="(project, index) in projects" :class="v-for="(category, index) in project.categories???">
{{project.title}}
</div>
Should I do this differently? I can't seem to figure it out. Thanks for your help!
It's simple:
<div v-for="project in projects" :class="classExtraction(project)">
<h3>
{{project.title}}
</h3>
</div>
You need a method that extracts classes from your project's categories:
methods: {
classExtraction(item) {
return item.categories.map(cat => cat.name);
}
}
http://jsfiddle.net/eywraw8t/371192/
Also, please note that you should use :key directive with v-for binding it to a unique property, preferably object's id:
https://v2.vuejs.org/v2/style-guide/#Keyed-v-for-essential

How to use query parameter in Vue search box?

I have a page with a search box on it using Vue. What I want to do is this: when a user comes from another page with a parameter in the URL (e.g., myurl.com/?windows), I capture the parameter and populate the search field to run the search on that string when the page loads. If there's no parameter, nothing happens.
I'm capturing the string from the URL with JavaScript, but don't see how to get it in the input to run the search.... I created a method but don't see how to apply it.
<div id="app">
<input type="text" v-model="search" placeholder="Search Articles" />
<div v-for="article in filteredArticles" v-bind:key="article.id" class="container-fluid to-edges section1">
<div class="row">
<div class="col-md-4 col-sm-12 section0">
<div class="section0">
<a v-bind:href="article.url" v-bind:title="toUppercase(article.title)">
<img class="resp-img expand section0"
v-bind:src="article.src"
v-bind:alt="article.alt"/>
</a>
</div>
<div>
<h3 class="title-sec">{{ article.title }}</h3>
<p>{{ article.description }}</p>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var pgURL = window.location.href;
var newURL = pgURL.split("?")[1];
console.log(newURL);
</script>
// Filters
Vue.filter('to-uppercase', function(value){
return value.toUpperCase();
});
new Vue({
el: "#app",
data: {
articles: [
{ id: 1, title: 'Trend Alert: Black Windows', category: 'Windows', description: 'Timeless, elegant, and universally flattering, black is an excellent color to add to any wardrobe – or any window. Get in the black with this chic design trend.', src: 'http://i1.adis.ws/i/stock/Trending_Polaroid_Black_Windows_2018_1?$trending-mobile$', url: '/{StorefrontContextRoot}/s/trending/trend-alert-black-windows', alt: 'Pantone Colors image' },
{ id: 2, title: 'Benefits of a Pass-Through Window', category: 'Windows', description: 'Whether you’re adding a pass-through window in order to enjoy an al fresco aperitif or for easier access to appetizers in the kitchen, we’re big fans of bringing the outdoors in.', src: 'http://i1.adis.ws/i/stock/polaroid_benefitsofapassthroughwindow655x536?$trending-mobile$', url: '/{StorefrontContextRoot}/s/trending/kitchen-pass-through-bar-window', alt: 'Benefits of a Pass-Through Window image' }, etc....
],
search: ''
},
methods: {
toUppercase: function(title){
return title.toUpperCase();
},
urlSearch: function(newURL) {
if (newURL) {
return this.search = newURL;
}
}
},
computed: {
filteredArticles: function() {
// returning updated array based on search term
return this.articles.filter((article) => {
return article.category.match(new RegExp(this.search, "i"));
});
}
}
})
You can call the urlSearch method during the mounted hook:
mounted() {
this.urlSearch(newURL)
},
methods: {
urlSearch(url) {
return this.search = url
}
},