Categories
BootstrapVue

BootstrapVue — Dynamic Tabs and Time Pickers

To make good looking Vue apps, we need to style our components.

To make our lives easier, we can use components with styles built-in.

In this article, we look at how to create dynamic tabs.

Also, we look at how to add a time picker to our app.

Dynamic Tabs

We can add tabs to a page dynamically.

For instance, we can write:

<template>
  <div id="app">
    <b-tabs>
      <b-tab v-for="(i, index) in tabs" :key="i" :title="`Tab ${i}`">
        <div class="p-3">
          Tab {{ i }}
          <b-button @click="closeTab(index)">Close tab</b-button>
        </div>
      </b-tab>

      <template v-slot:tabs-end>
        <b-nav-item @click.prevent="newTab" href="#">
          <b>+</b>
        </b-nav-item>
      </template>
    </b-tabs>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      tabs: []
    };
  },
  methods: {
    newTab() {
      this.tabs = [...this.tabs, this.tabs.length + 1];
    },
    closeTab(index) {
      this.tabs.splice(index, 1);
    }
  }
};
</script>

We populate the tabs-end slot with a b-nav-item component to let us display a link to let users add a new tab.

To add a new tab, the newTab method is called to add a new tab.

It just adds a new number to tabs .

The tabs are rendered by looping through tabs and render them as b-tab components.

To remove a tab, we have the closeTab method to remove an entry by index with splice .

Then the updated set of tabs will be rendered.

Time

We can display a time picker with the b-time component.

For example, we can write:

<template>
  <div id="app">
    <b-time v-model="value" locale="en" @context="onContext"></b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  },
  methods: {
    onContext(ctx) {
      console.log(ctx)
    }
  }
};
</script>

We added the b-time component which displays a time picker.

The selected time is bound to the value state via v-model .

locale is set as 'en' to set the locale to English.

onContext is called when a time component selected.

The ctx parameter is an object with the formatted time, locale, and time parts.

Disabled

We can make it non-interactive by setting the disabled prop:

<template>
  <div id="app">
    <b-time v-model="value" disabled></b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  }
};
</script>

Read-Only

We can make it focusable but prevent the user from selecting a time with the readonly prop:

<template>
  <div id="app">
    <b-time v-model="value" readonly></b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  }
};
</script>

Styling

There are various ways we can style a time component.

Enable the Seconds Spin Button

We can add the show-seconds prop to show the seconds spin button.

It’s not shown by default.

For example, we can write:

<template>
  <div id="app">
    <b-time v-model="value" show-seconds></b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  }
};
</script>

Hiding the Top Selected Time Header

The hide-header prop will hide the selected time:

<template>
  <div id="app">
    <b-time v-model="value" hide-header></b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  }
};
</script>

Border and Padding

We can add the class prop to apply styles we want:

<template>
  <div id="app">
    <b-time v-model="value" class="border rounded p-2"></b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  }
};
</script>

Default Slot

The default slot lets us add content to the bottom of the time picker.

For example, we can write:

<template>
  <div id="app">
    <b-time v-model="value" class="border rounded p-2">
      <b-button variant="outline-danger" @click="value = ''">Clear time</b-button>
    </b-time>
    <p>{{value}}</p>
  </div>
</template>
<script>
export default {
  name: "App",
  data() {
    return {
      value: ""
    };
  }
};
</script>

We added a b-button to the default slot to reset value to an empty string.

Conclusion

We can make tabs dynamic by rendering b-tab with v-for .

Also, BootstrapVue comes with a time picker that we can let the user select a time.

Categories
BootstrapVue

BootstrapVue — More Tabs Customizations

To make good looking Vue apps, we need to style our components.

To make our lives easier, we can use components with styles built-in.

We look at how to customize the tabs that we added, including how to navigate tabs with code via v-model and refs methods.

Add Custom Classes to nav-tab or Pills

We can set our own class with the title-link-class prop.

For example, we can write:

<template>  
  <div id="app">  
    <b-tabs>  
      <b-tab title="Tab 1" title-link-class="tab">Tab 1</b-tab>  
      <b-tab title="Tab 2" title-link-class="tab">Tab 2</b-tab>        
    </b-tabs>  
  </div>  
</template>  
<script>  
export default {  
  name: "App"    
};  
</script>  
<style>  
.tab {  
  color: red  
}  
</style>

We added the tab class, which we set as the value of title-link-class .

Because of the class’s style, we’ll see that the nav text is red when we hover over it.

Lazy Load Tab Content

We can add the lazy prop to lazy load tab content.

This means that the content is loaded only when it’s shown.

For example, we can write:

<template>  
  <div id="app">  
    <b-tabs>  
      <b-tab title="Tab 1">Tab 1</b-tab>  
      <b-tab title="Tab 2" lazy>Tab 2</b-tab>  
    </b-tabs>  
  </div>  
</template>  
<script>  
export default {  
  name: "App"  
};  
</script>

Now the 2nd tab’s content only loads when it’s being shown.

We can make all tabs lazy by putting lazy in b-tabs :

<template>  
  <div id="app">  
    <b-tabs lazy>  
      <b-tab title="Tab 1">Tab 1</b-tab>  
      <b-tab title="Tab 2">Tab 2</b-tab>  
    </b-tabs>  
  </div>  
</template>  
<script>  
export default {  
  name: "App"  
};  
</script>

Keyboard Navigation

We can navigate tabs with the keyboard.

Left or up activates the previous non-disabled tab.

Right or down activates the next non-disabled tab.

Shift+Left or Shift+Up activates the first non-disabled tab.

Home activates the first non-disabled tab.

Shift+Rigbt or Shift+Down activates the last non-disabled tav.

End activates the last non-disabled tab.

Tab moves focus to the active tab component.

Shift+Tab moves focus to the previous control on the page.

We can disable keyboard navigation with the no-key-nav prop.

Then the behavior will be the same as the regular behavior with the Tab key.

Tab will move to the next button or control on the page.

Shift+tab moves to the previous button or control on the page.

Enter or Space activates the currently focused button’s tab.

Programmatically Activate and Deactivate Tabs

We can add the active prop to active a tab.

Also, we can use the activate to activate a tab.

And the deactivate method to deactivate a tab.

For example, we can write:

<template>  
  <div id="app">  
    <b-tabs>  
      <b-tab title="Tab 1">Tab 1</b-tab>  
      <b-tab ref="tab2" title="Tab 2">Tab 2</b-tab>  
    </b-tabs>  
  </div>  
</template>  
<script>  
export default {  
  name: "App",  
  mounted() {  
    this.$refs.tab2.activate();  
  }  
};  
</script>

We added a ref to the 2nd tab.

Then in the mounted hook, which is run when the component is loaded, we called the activate method on it.

Tab 2 will then be the active tab when the component is loaded.

v-model

We can add v-model to b-tabs . The value will be the currently active tab.

The first tab is 0, the 2nd tab is 1, and so on.

For example, we can write:

<template>  
  <div id="app">  
    <b-tabs v-model="tab">  
      <b-tab title="Tab 1">Tab 1</b-tab>  
      <b-tab title="Tab 2">Tab 2</b-tab>  
    </b-tabs>  
    <b-button @click="next">Next</b-button>  
  </div>  
</template>  
<script>  
export default {  
  name: "App",  
  data() {  
    return {  
      tab: 0  
    };  
  },  
  methods: {  
    next() {  
      this.tab = (this.tab + 1) % 2;  
    }  
  }  
};  
</script>

We added the Next button which calls next when clicked.

In next , we updated this.tabs with the next tab’s index.

It’ll go back to the first when we click next when the 2nd tab is displayed.

this.tab is bound to the b-tabs component’s tab index with the v-model directive.

Therefore, when we click Next, we’ll cycle through the tabs.

Conclusion

We can control how tabs are displayed programmatically.

Also, keyboard navigation is supported by BootstrapVue tabs.

We can also add custom classes to tab controls.

Categories
BootstrapVue

BootstrapVue — More Table Customizations

To make good looking Vue apps, we need to style our components. To make our lives easier, we can use components with styles built-in. Today we’ll look at how to customize table contents, including headers and footers.

Rendering Custom Headers and Footers

We can customize the render of headers and footers by populating slots.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items" foot-clone>
      <template v-slot:head(firstName)="data">
        <span>{{ data.label.toUpperCase() }}</span>
      </template>

<template v-slot:foot()="data">
        <span>{{ data.label.toUpperCase() }}</span>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

With the head slot, we can format the header. We can pass in the field name for the field we want to format. Or we can leave it blank to format all column headers. Likewise, we can do the same for footer.

Add Additional Rows top the Header

We can add more rows to the header.

To do that, we populate the thead-top slot.

Inside the slot, we use the b-tr component to add a table row.

Then inside it, we add our b-th components to add the headers.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items">
      <template v-slot:thead-top="data">
        <b-tr>
          <b-th>
            <span>Name</span>
          </b-th>
          <b-th>Surname</b-th>
        </b-tr>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

Now we have the table header with the cells Name and Surname on top of the table.

Custom Footer

We can create a custom footer. To add a custom footer, we can populate the custom-foot slot.

We can write:

<template>
  <div id="app">
    <b-table :items="items">
      <template v-slot:custom-foot="data">
        <b-tr>
          <b-td>Name</b-td>
          <b-td>Surname</b-td>
        </b-tr>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

We populate the custom-foot slot with the b-tr and b-td components to make our own footer row. It won’t be rendered if we have the foot-clone prop set.

Custom Empty Table Rendering

We can render something of our choice if we have an empty table. To customize the content, we can populate the empty and emptyfiltered slot.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items" show-empty>
      <template v-slot:empty="scope">
        <h4>{{ scope.emptyText }}</h4>
      </template>
      <template v-slot:emptyfiltered="scope">
        <h4>{{ scope.emptyFilteredText }}</h4>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: []
    };
  }
};
</script>

Then we see ‘There are no records to show’ since there’s no data in items .

We can customize the text with some props:

`<template>
  <div id="app">
    <b-table :items="items" show-empty empty-text="nothing to see">
      <template v-slot:empty="scope">
        <h4>{{ scope.emptyText }}</h4>
      </template>
      <template v-slot:emptyfiltered="scope">
        <h4>{{ scope.emptyFilteredText }}</h4>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: []
    };
  }
};
</script>

We set the empty-text prop to 'nothing to see' , so that’s the value of scope.emptyText and it’s displayed.

There’s also the empty-html prop to set the HTML content for empty text.

The value will be available in scope.emptyHtml .

emptyFilterHtml is set by the empty-filtered-html .

emptyFilteredText is set by the empty-filtered-text .

Sticky Headers

We can make the header sticky with the sticky-header prop. The value can be set to the maximum height of the table in pixels.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items" sticky-header head-variant="light"></b-table>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" },
        { firstName: "james", lastName: "jones" },
        { firstName: "james", lastName: "jones" },
        { firstName: "james", lastName: "jones" },
        { firstName: "james", lastName: "jones" },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

Then the header will always stay on the top.

Conclusion

We can make headers sticky. Also, we can customize the layout of the header and footer.

Categories
BootstrapVue

BootstrapVue — Popovers

To make good looking Vue apps, we need to style our components.

To make our lives easier, we can use components with styles built-in.

We look at how to customize popovers.

Variants and Custom Class

We can change the variants and custom classes to customize our popups.

To change to a styling variant, we can use the variant prop.

For example, we can write:

<template>
  <div id="app" class="text-center">
    <b-button id="popover-button" href="#">Button</b-button>
    <b-popover target="popover-button" variant="danger" triggers="focus">
      <template v-slot:title>Danger!</template>danger
    </b-popover>
  </div>
</template>

<script>
export default {};
</script>

<style>
#app {
  margin: 200px;
}
</style>

We changed the variant to danger , so we’ll see that the title and content are red.

We can add custom classes with the custom-class prop on b-popover .

For example, we can write:

<template>
  <div id="app" class="text-center">
    <b-button id="popover-button" href="#">Button</b-button>
    <b-popover target="popover-button" custom-class="popover-class" triggers="focus">
      <template v-slot:title>Danger!</template>danger
    </b-popover>
  </div>
</template>

<script>
export default {};
</script>

<style>
#app {
  margin: 200px;
}

.popover-class {
  background: orange
}
</style>

We added the popover-class class and set it as the value of custom-class .

Therefore, the background is now orange.

Show and Hide Popovers Programmatically

We can show and hide popovers programmatically with the show prop.

It takes an expression to indicate whether we want to show the popover or not.

For instance, we can write:

<template>
  <div id="app" class="text-center">
    <b-button id="popover-button" href="#" @click="show = !show">toggle</b-button>
    <b-popover :show="show" target="popover-button">
      <template v-slot:title>title</template>content
    </b-popover>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: false
    };
  }
};
</script>

<style>
#app {
  margin: 200px;
}
</style>

The toggle button toggles the popover on and off.

We have the click handler on b-button to do the toggling by toggling show between true and false .

The show prop is set to show so that when the show state changed, the popover will be toggled on or off.

We can also assign a ref to the b-popover component and emit the open and close events to open and close the popover respectively.

For example, we can write:

<template>
  <div id="app" class="text-center">
    <b-button @click="onOpen">Open</b-button>
    <b-button @click="onClose">Close</b-button>
    <b-button id="popover-button" variant="primary">button with popover</b-button>
    <b-popover ref="popover" target="popover-button" title="title">content</b-popover>
  </div>
</template>

<script>
export default {
  methods: {
    onOpen() {
      this.$refs.popover.$emit("open");
    },
    onClose() {
      this.$refs.popover.$emit("close");
    }
  }
};
</script>

<style>
#app {
  margin: 200px;
}
</style>

We have the Open and Close buttons, which references the ref of the popover to emit the open and close events respectively.

That will open and close the popover.

We also have the ‘button with popover’ button that’s associate with the popover.

Now when we click Open, the popover will open.

When we click Close, the popover will close.

The popover is displayed beside the ‘button with popover’ button since the id and target match.

Also, we can make the popover show on the initial render.

We can just add the show prop to do that:

<template>
  <div id="app">
    <b-button id="popover-button" variant="primary">Button</b-button>

    <b-popover show target="popover-button" title="title">content</b-popover>
  </div>
</template>

<script>
export default {};
</script>

We have the b-popover with the show prop to show the popover when the page loads.

Programmatically Disabling Popover

We can use the disabled prop to disable a popover.

For example, we can write:

<template>
  <div id="app">
    <b-button id="popover-button" variant="primary">Button</b-button>

    <b-popover disabled target="popover-button" title="title">content</b-popover>
  </div>
</template>

<script>
export default {};
</script>

Then we won’t see the popover no matter what we do.

v-b-popover Directive

We can add modifiers to the v-b-popover directive to change the placement.

For instance, we can write:

<template>
  <div id="app" class="text-center">
    <b-button v-b-popover.hover.top="'Popover'" title="Title">Button</b-button>
  </div>
</template>

<script>
export default {};
</script>

<style>
#app {
  margin: 200px;
}
</style>

to place the popover on top of the button with the top modifier.

And the hover modifier lets us change the button to show the popover on hover.

Other possible modifiers are right , left , or bottom to change the placement according to those names.

Conclusion

We can create popovers by in the way we want.

The placement can be changed. Showing and hiding of popovers can be done programmatically.

Categories
BootstrapVue

BootstrapVue — Table Customizations

To make good looking Vue apps, we need to style our components.

To make our lives easier, we can use components with styles built-in.

We look at how to customize table contents.

Colgroups

We can group table columns into colgroups.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items">
      <template v-slot:table-colgroup="scope">
        <col
          v-for="field in scope.fields"
          :key="field.key"
          :style="{ width: field.key === 'firstName' ? '120px' : '180px' }"
        >
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

We have the col component that populates the table-colgroup slot to loop through the fields.

This way, we can style each column with the style prop.

Busy State

b-table lets us pass in the busy prop to flag the table if it’s busy.

This way, we can display something if the table is busy.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items" busy>
      <template v-slot:table-busy>
        <div>
          <b-spinner></b-spinner>
          <strong>Loading...</strong>
        </div>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

We have the busy prop on b-table and populated the table-busy slow with a spinner and the ‘Loading’ text.

Therefore, instead of seeing the table’s content, we’ll see what’s in the slot instead.

Custom Data Rendering

We can render data in a custom way.

For example, we can write:

<template>
  <div id="app">
    <b-table :items="items">
      <template v-slot:cell(firstName)="data">{{ data.value }}</template>

<template v-slot:cell(lastName)="data">
        <b>{{ data.value }}</b>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

We populate the slots for the columns.

The column’s data can be obtained with data .

v-slot:cell(firstName)=”data” gets the firstName property and assigned it to data .

v-slot:cell(lastName)=”data” does the same for lastName .

And the value property has the value of each property.

Therefore, we can render each piece of data our way.

data in the example above has many properties.

index has the row number.

item has the raw record of each entry.

value is the value for the given key.

unformatted has raw values before passing through the formatter function.

field has the normalized field definition object.

detailsShowing is true if the row-details scoped slot is visible.

toggleDetails can be called to toggle the visibility of the row-details scope slot.

rowSelected is true if the row is selected.

selectRow selects the current row when called.

unselectRow unselects the current row when called.

Display Raw HTML

We can display raw HTML.

To do that we just populate the slot with an element that has the v-html directive.

For instance, we can write:

<template>
  <div id="app">
    <b-table :items="items">
      <template v-slot:cell(lastName)="data">
        <span v-html="data.value"></span>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [{ firstName: "alex", lastName: "<b>green</b>" }]
    };
  }
};
</script>

We have a span that has the v-html directive set to data.value which has our lastName ‘s value.

So the HTML code will be rendered without sanitization.

However, we have to be careful so that we don’t get attacked with cross-site scripting attacks.

Formatter Callback

We can add a formatted to out fields array entries to format our cells.

For instance, we can write:

<template>
  <div id="app">
    <b-table :items="items" :fields="fields"></b-table>
  </div>
</template>
<script>
export default {
  data() {
    return {
      fields: [
        {
          key: "name",
          label: "Full Name",
          formatter: "fullName"
        }
      ],
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  },
  methods: {
    fullName(value, key, item) {
      return `${item.firstName} ${item.lastName}`;
    }
  }
};
</script>

We have the fullName method.

It takes the item parameter, which has the object in items , and return the firstName and lastName properties combined together.

The name of it is set as the value of formatter in our field’s array entry.

Therefore, now we have one Full Name column that has both field’s values displayed together.

We can also write:

<template>
  <div id="app">
    <b-table :items="items" :fields="fields"></b-table>
  </div>
</template>
<script>
export default {
  data() {
    return {
      fields: [
        {
          key: "name",
          label: "Full Name",
          formatter(value, key, item) {
            return `${item.firstName} ${item.lastName}`;
          }
        }
      ],
      items: [
        { firstName: "alex", lastName: "green" },
        {
          firstName: "may",
          lastName: "smith"
        },
        { firstName: "james", lastName: "jones" }
      ]
    };
  }
};
</script>

We moved the formatted function to fields entry object instead of having it in methods .

Photo by Marylou Fortier on Unsplash

Conclusion

We can format table cells with formatted functions.

Also, we can customize cell layout and formatting with slots or raw HTML.

We can set the busy state to display something when the table’s data is loading.