Tag: Dynamically

Dynamically Switching From One HTML Element to Another in Vue

A friend once contacted me asking if I had a way to dynamically change one HTML element into another within Vue’s template block. For instance, shifting a <div> element to a <span> element based on some criteria. The trick was to do this without relying on a series of v-if and v-else code.

I didn’t think much of it because I couldn’t see a strong reason to do such a thing; it just doesn’t come up that often. Later that same day, though, he reached out again and told me he learned how to change element types. He excitedly pointed out that Vue has a built-in component that can be used as a dynamic element in the very way that he needed.

This small feature can keep code in the template nice and tidy. It can reduce v-if and v-else glut down to a smaller amount of code that’s easier to understand and maintain. This allows us to use methods or computed methods to create nicely-coded, and yet more elaborate, conditions in the script block. That’s where such things belong: in the script, not the template block.

I had the idea for this article mainly because we use this feature in several places in the design system where I work. Granted it’s not a huge feature and it is barely mentioned in the documentation, at least as far as I can tell. Yet it has potential to help render specific HTML elements in components.

Vue’s built-in <component> element

There are several features available in Vue that allow for easy dynamic changes to the view. One such feature, the built-in <component> element, allows components to be dynamic and switched on demand. In both the Vue 2 and the Vue 3 documentation, there is a small note about using this element with HTML elements; that is the part we shall now explore.

The idea is to leverage this aspect of the <component> element to swap out common HTML elements that are somewhat similar in nature; yet with different functionality, semantics, or visuals. The following basic examples will show the potential of this element to help with keeping Vue components neat and tidy.

Buttons and links are often used interchangeably, but there are big differences in their functionality, semantics, and even visuals. Generally speaking, a button (<button>) is intended for an internal action in the current view tied to JavaScript code. A link (<a>), on the other hand, is intended to point to another resource, either on the host server or an external resource; most often web pages. Single page applications tend to rely more on the button than the link, but there is a need for both.

Links are often styled as buttons visually, much like Bootstrap’s .btn class that creates a button-like appearance. With that in mind, we can easily create a component that switches between the two elements based on a single prop. The component will be a button by default, but if an href prop is applied, it will render as a link.

Here is the <component> in the template:

<component   :is="element"   :href="href"   class="my-button" >   <slot /> </component>

This bound is attribute points to a computed method named element and the bound href attribute uses the aptly named href prop. This takes advantage of Vue’s normal behavior that the bound attribute does not appear in the rendered HTML element if the prop has no value. The slot provides the inner content regardless whether the final element is a button or a link.

The computed method is simple in nature:

element () {   return this.href ? 'a' : 'button'; }

If there’s an href prop,. then an <a> element is applied; otherwise we get a <button>.

<my-button>this is a button</my-button> <my-button href="https://www.css-tricks.com">this is a link</my-button>

The HTML renders as so:

<button class="my-button">this is a button</button> <a href="https://www.css-tricks.com" class="my-button">this is a link</a>

In this case, there could be an expectation that these two are similar visually, but for semantic and accessibility needs, they are clearly different. That said, there’s no reason the two outputted elements have to be styled the same. You could either use the element with the selector div.my-button in the style block, or create a dynamic class that will change based on the element.

The overall goal is to simplify things by allowing one component to potentially render as two different HTML elements as needed — without v-if or v-else!

Ordered or unordered list?

A similar idea as the button example above, we can have a component that outputs different list elements. Since an unordered list and an ordered list make use of the same list item (<li>) elements as children, then that’s easy enough; we just swap <ul> and <ol>. Even if we wanted to have an option to have a description list, <dl>, this is easily accomplished since the content is just a slot that can accept <li> elements or <dt>/<dd>combinations.

The template code is much the same as the button example:

<component   :is="element"   class="my-list" >   <slot>No list items!</slot> </component>

Note the default content inside the slot element, I’ll get to that in a moment.

There is a prop for the type of list to be used that defaults to <ul>:

props: {   listType: {     type: String,     default: 'ul'   } }

Again, there is a computed method named element:

element () {   if (this.$  slots.default) {     return this.listType;   } else {     return 'div';   } }

In this case, we are testing if the default slot exists, meaning it has content to render. If it does, then the the list type passed through the listType prop is used. Otherwise, the element becomes a <div> which would show the “No list items!” message inside the slot element. This way, if there are no list items, then the HTML won’t render as a list with one item that says there are no items. That last aspect is up to you, though it is nice to consider the semantics of a list with no apparent valid items. Another thing to consider is the potential confusion of accessibility tools suggesting this is a list with one item that just states there are no items.

Just like the button example above, you could also style each list differently. This could be based on selectors that target the element with the class name, ul.my-list. Another option is to dynamically change the class name based on the chosen element.

This example follows a BEM-like class naming structure:

<component   :is="element"   class="my-list"   :class="`my-list__$  {element}`" >   <slot>No list items!</slot> </component>

Usage is as simple as the previous button example:

<my-list>   <li>list item 1</li> </my-list>  <my-list list-type="ol">   <li>list item 1</li> </my-list>  <my-list list-type="dl">   <dt>Item 1</dt>   <dd>This is item one.</dd> </my-list>  <my-list></my-list>

Each instance renders the specified list element. The last one, though, results in a <div> stating no list items because, well, there’s no list to show!

One might wonder why create a component that switches between the different list types when it could just be simple HTML. While there could be benefits to keeping lists contained to a component for styling reasons and maintainability, other reasons could be considered. Take, for instance, if some forms of functionality were being tied to the different list types? Maybe consider a sorting of a <ul> list that switches to a <ol> to show sorting order and then switching back when done?

Now we’re controlling the elements

Even though these two examples are essentially changing the root element component, consider deeper into a component. For instance, a title that might need to change from an <h2> to an <h3> based on some criteria.

If you find yourself having to use ternary solutions to control things beyond a few attributes, I would suggest sticking with the v-if. Having to write more code to handle attributes, classes, and properties just complicates the code more than the v-if. In those cases, the v-if makes for simpler code in the long run and simpler code is easier to read and maintain.

When creating a component and there’s a simple v-if to switch between elements, consider this small aspect of a major Vue feature.

Expanding the idea, a flexible card system

Consider all that we’ve covered so far and put it to use in a flexible card component. This example of a card component allows for three different types of cards to be placed in specific parts of the layout of an article:

  • Hero card: This is expected to be used at the top of the page and draw more attention than other cards.
  • Call to action card: This is used as a line of user actions before or within the article.
  • Info card: This is intended for pull quotes.

Consider each of these as following a design system and the component controls the HTML for semantics and styling.

In the example above, you can see the hero card at the top, a line of call-to-action cards next, and then — scrolling down a bit — you’ll see the info card off to the right side.

Here is the template code for the card component:

<component :is="elements('root')" :class="'custom-card custom-card__' + type" @click="rootClickHandler">   <header class="custom-card__header" :style="bg">     <component :is="elements('header')" class="custom-card__header-content">       <slot name="header"></slot>     </component>   </header>   <div class="custom-card__content">     <slot name="content"></slot>   </div>   <footer class="custom-card__footer">     <component :is="elements('footer')" class="custom-card__footer-content" @click="footerClickHandler">       <slot name="footer"></slot>     </component>   </footer> </component>

There are three of the “component” elements in the card. Each represents a specific element inside the card, but will be changed based on what kind of card it is. Each component calls the elements() method with a parameter identifying which section of the card is making the call.

The elements() method is:

elements(which) {   const tags = {     hero: { root: 'section', header: 'h1', footer: 'date' },     cta: { root: 'section', header: 'h2', footer: 'div' },     info: { root: 'aside', header: 'h3', footer: 'small' }   }   return tags[this.type][which]; }

There are probably several ways of handing this, but you’ll have to go in the direction that works with your component’s requirements. In this case, there is an object that keeps track of HTML element tags for each section in each card type. Then the method returns the needed HTML element based on the current card type and the parameter passed in.

For the styles, I inserted a class on the root element of the card based on the type of card it is. That makes it easy enough to create the CSS for each type of card based on the requirements. You could also create the CSS based on the HTML elements themselves, but I tend to prefer classes. Future changes to the card component could change the HTML structure and less likely to make changes to the logic creating the class.

The card also supports a background image on the header for the hero card. This is done with a simple computed placed on the header element: bg. This is the computed:

bg() {   return this.background ? `background-image: url($  {this.background})` : null; }

If an image URL is provided in the background prop, then the computed returns a string for an inline style that applies the image as a background image. A rather simple solution that could easily be made more robust. For instance, it could have support for custom colors, gradients, or default colors in case of no image provided. There’s a large number of possibilities that his example doesn’t approach because each card type could potentially have their own optional props for developers to leverage.

Here’s the hero card from this demo:

<custom-card type="hero" background="https://picsum.photos/id/237/800/200">   <template v-slot:header>Article Title</template>   <template v-slot:content>Lorem ipsum...</template>   <template v-slot:footer>January 1, 2011</template> </custom-card>

You’ll see that each section of the card has its own slot for content. And, to keep things simple, text is the only thing expected in the slots. The card component handles the needed HTML element based solely on the card type. Having the component just expect text makes using the component rather simplistic in nature. It replaces the need for decisions over HTML structure to be made and in turn the card is simply implemented.

For comparison, here are the other two types being used in the demo:

<custom-card type="cta">   <template v-slot:header>CTA Title One</template>   <template v-slot:content>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</template>   <template v-slot:footer>footer</template> </custom-card>  <custom-card type="info">   <template v-slot:header>Here's a Quote</template>   <template v-slot:content>“Maecenas ... quis.”</template>   <template v-slot:footer>who said it</template> </custom-card>

Again, notice that each slot only expects text as each card type generates its own HTML elements as defined by the elements() method. If it’s deemed in the future that a different HTML element should be used, it’s a simple matter of updating the component. Building in features for accessibility is another potential future update. Even interaction features can be expanded, based on card types.

The power is in the component that’s in the component

The oddly named <component> element in Vue components was intended for one thing but, as often happens, it has a small side effect that makes it rather useful in other ways. The <component> element was intended to dynamically switch Vue components inside another component on demand. A basic idea of this could be a tab system to switch between components acting as pages; which is actually demonstrated in the Vue documentation. Yet it supports doing the same thing with HTML elements.

This is an example of a new technique shared by a friend that has become s surprisingly useful tool in the belt of Vue features that I’ve used. I hope that this article carries forward the ideas and information about this small feature for you to explore how you might leverage this in your own Vue projects.


The post Dynamically Switching From One HTML Element to Another in Vue appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,

How to Add Commas Between a List of Items Dynamically with CSS

Imagine you have a list of items. Say, fruit: Banana, Apple, Orange, Pear, Nectarine

We could put those commas (,) in the HTML, but let’s look at how we could do that in CSS instead, giving us an extra level of control. We’ll make sure that last item doesn’t have a comma while we’re at it.

I needed this for a real project recently, and part of the requirements were that any of the items in the list could be hidden/revealed via JavaScript. The commas needed to work correctly no matter which items were currently shown.

One solution I found rather elegant solution is using general sibling combinator. We’ll get to that in a minute. Let’s start with some example HTML. Say you start out with a list of fruits:

<ul class="fruits">   <li class="fruit on">Banana</li>   <li class="fruit on">Apple</li>   <li class="fruit on">Orange</li>   <li class="fruit on">Pear</li>   <li class="fruit on">Nectarine</li> </ul>

And some basic CSS to make them appear in a list:

.fruits {   display: flex;   padding-inline-start: 0;   list-style: none; }  .fruit {   display: none; /* hidden by default */ }  .fruit.on { /* JavaScript-added class to reveal list items */   display: inline-block; }

Now say things happen inside this interface, like a user toggles controls that filter out all fruits that grow in cold climates. Now a different set of fruits is shown, so the fruit.on class is manipulated with the classList API.

So far, our HTML and CSS would create a list like this:

BananaOrangeNectarine

Now we can reach for that general sibling combinator to apply a comma-and-space between any two on elements:

.fruit.on ~ .fruit.on::before {   content: ', ';  }

Nice!

You might be thinking: why not just apply commas to all the list items and remove it from the last with something like :last-child or :last-of-type. The trouble with that is the last child might be “off” at any given time. So what we really want is the last item that is “on,” which isn’t easily possible in CSS, since there is nothing like “last of class” available. Hence, the general sibling combinator trick!

In the UI, I used max-width instead of display and toggled that between 0 and a reasonable maximum value so that I could use transitions to push items on and off more naturally, making it easier for the user to see which items are being added or removed from the list. You can add the same effect to the pseudo-element as well to make it super smooth.

Here’s a demo with a couple of examples that are both slight variations. The fruits example uses a hidden class instead of on, and the veggies example has the animations. SCSS is also used here for the nesting:

I hope this helps others looking for something similar!


The post How to Add Commas Between a List of Items Dynamically with CSS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Filtering Lists Dynamically With Vue on the Server Side is Easier Than You’d Think

I recently attended the ARTIFACT conference in Austin, TX, and was inspired by a few talks about accessibility through the lens of site performance. It became clear to me that there is this tendency to rely on big JavaScript frameworks to handle the work — like React, Vue, and Angular — but that can be overkill in some cases. That is, negatively affecting site performance, and thus accessibility. At the same time, these frameworks can make development easier and more efficient for developers. My big takeaway from the conference was to see how a fast, performant experience can be balanced with my own development process.

This was on my mind as I was building a list-filtering feature on a project a few days after the conference. Pretty standard stuff: I needed a list of posts and some category filtering. I was using CraftCMS for front-end routes and templating as well as some Vue components here and there for some added JavaScript juiciness. Not a full-on “single page app” but more like a sprinkle of Vue.

The typical way one might approach this is to:

  1. render the page with an empty div using Craft/Twig
  2. mount a Vue component to that div
  3. make an Ajax call from the Vue component to an API to gather the posts as JSON
  4. render the posts and tie in the filtering.

Since the posts are held as an array within Vue, dynamic list rendering is a pretty cut and dry task.

Simple. Done, right? Well… that extra Ajax request means the user is presented with no content on the initial load depending on the user’s network, which might take some time. We could add a loading indicator, but maybe we can do better?

Preferably, the posts are rendered on the initial page request from the CMS.

But how do we get the static HTML “hooked up” with Vue for the filtering?

After taking a step back to rethink the problem, I realized I could use v-if directives to achieve the same thing with some inline JavaScript from Twig (“in the loop”). Below, I’ll show how I went about it.

My original project used CraftCMS, but I’m going to do the demos below in WordPress. This is just a concept. It can be applied to CraftCMS/Twig or any other CMS/templating engine combo.

First we need a filtering UI. This will likely go above the start of the loop in an archive template.

<?php $  terms = get_terms( [   'taxonomy' => 'post_tag', // I used tags in this example, but any taxonomy would do   'hide_empty' => true,   'fields' => 'names' ] );  if(!empty($  terms)): ?>   <div>     Filter:      <ul class="filters">       <li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''">All</button></li>       <?php foreach($  terms as $  term): ?>       <li class="filters__item">         <button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $  term; ?>'}" @click="tag = '<?php echo $  term; ?>'"><?php echo $  term; ?></button>       </li>       <?php endforeach; ?>     </ul>     <p aria-live="polite">Showing posts tagged {{ tag ? tag : 'all' }}.</p>   </div> <?php endif; ?>

Following along with the code, we get some tags from WordPress with get_terms() and output them in a foreach loop. You’ll notice the button for each tag has some Vue directives we’ll use later.

We have our loop!

    <div class="posts">       <?php       // Start the Loop.       while ( have_posts() ) : the_post();              <article id="post-<?php the_ID(); ?>"           <?php post_class(); ?>           v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(),  ['fields' => 'names'])); ?>.includes(tag) || tag === ""'         >           <header class="entry-header">             <h2><?php the_title(); ?></h2>           </header>                <div class="entry-content">             <?php the_excerpt(); ?>           </div>         </article>            // End the loop.       endwhile; ?>     </div>

This is a pretty standard WordPress loop for posts. You’ll notice some Vue directives that make use of PHP outputting CMS content.

Aside from some styling, all that’s left is the Vue “app.” Are you ready for it? Here it is:

new Vue({   el: '#filterablePosts',   data: {     'tag': ''   } });

Yes, really, that’s all that’s needed in the JavaScript file to get this to work!

So, what’s going on here?

Well, instead of some JSON array of posts that gets fed into Vue, we output the posts on the initial page load with WordPress. The trick is to use PHP to output what’s needed in the Vue directives: v-if and :class.

What’s happening on the filter buttons is an onclick event handler (@click) that sets the Vue variable “tag” to the value of the WordPress post tag.

@click="tag = '<?php echo $  term; ?>'"

Also, if that Vue variable equals the value of the button (in the :class directive), it adds an active class for the button. This is just for styling.

:class="{'filters__button--active': tag === '<?php echo $  term; ?>'}"

For the list of articles, we conditionally display them based on the value of the Vue “tag” variable:

v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(),  ['fields' => 'names'])); ?>.includes(tag) || tag === ""'

The PHP function json_encode allows us to output an array of post tags as JavaScript, which means we can use .includes() to see if the Vue “tag” variable is in that array. We also want to show the article if no tag is selected.

Here it is put together using the Twenty Nineteen theme template archive.php as a base:

<?php get_header(); ?>   <section id="primary" class="content-area">     <main id="main" class="site-main">       <?php if ( have_posts() ) : ?>         <header class="page-header">           <?php the_archive_title( '<h1 class="page-title">', '</h1>' ); ?>         </header>          <div class="postArchive" id="filterablePosts">           <?php $  terms = get_terms( [               'taxonomy' => 'post_tag',               'hide_empty' => true,               'fields' => 'names'           ] );            if(!empty($  terms)): ?>             <div class="postArchive__filters">               Filter:                <ul class="postArchive__filterList filters">                 <li class="filters__item"><button class="filters__button" :class="{'filters__button--active': tag === ''}" @click="tag = ''" aria-controls="postArchive__posts">All</button></li>                    <?php foreach($  terms as $  term): ?>                   <li class="filters__item">                     <button class="filters__button" :class="{'filters__button--active': tag === '<?php echo $  term; ?>'}" @click="tag = '<?php echo $  term; ?>'" aria-controls="postArchive__posts"><?php echo $  term; ?></button>                   </li>                 <?php endforeach; ?>                  </ul>                  <p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>             </div>           <?php endif; ?>              <div class="postArchive__posts">               <?php               // Start the Loop.               while ( have_posts() ) : the_post(); ?>                 <article                   id="post-<?php the_ID(); ?>"                   <?php post_class(); ?>                   v-if='<?php echo json_encode(wp_get_post_tags(get_the_ID(), ['fields' => 'names'])); ?>.includes(tag) || tag === ""'                 >                   <header class="entry-header">                     <h2><?php the_title(); ?></h2>                   </header>                            <div class="entry-content">                       <?php the_excerpt(); ?>                   </div>                  </article>               <?php endwhile; // End the loop. ?>           </div>         </div>               <?php       // If no content, include the "No posts found" template.       else :         get_template_part( 'template-parts/content/content', 'none' );       endif; ?>     </main>   </section>  <?php get_footer();

Here’s a working example on CodePen

See the Pen
Dynamic List Filtering in Vue using Server-side data fetching
by Dan Brellis (@danbrellis)
on CodePen.

Bonus time!

You may have noticed that I added in an aria-live="polite" notifier below the filter button list to let assistive tech users know the content has changed.

<p aria-live="polite">Showing {{ postCount }} posts tagged {{ tag ? tag : 'all' }}.</p>

To get the postCount Vue variable, we add some extra JavaScript to our Vue component:

new Vue({   el: '#filterablePosts',   data: {     'tag': '',     'postCount': '' },   methods: {     getCount: function(){       let posts = this.$  el.getElementsByTagName('article');       return posts.length;   }   },   beforeMount: function(){     this.postCount = this.getCount();   },   updated: function(){     this.postCount = this.getCount();   } });</p>

The new method getCount is used to select the article elements in our component div and return the length. Before the Vue component mounts we get the count to add to our new Vue postCount variable. Then, when the component updates after the user selects a tag, we get the count again and update our variable.

CSS-Tricks

, , , , , , , ,
[Top]