npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tuimedia/vue-page

v2.0.11

Published

Vue components and vuex module for rendering TuiPageBundle component-based content

Readme

TuiPage Vue renderer

Components and Vuex module for rendering and editing TuiPageBundle content.

[TOC]

Setup

  • yarn add @tuimedia/vue-page
  • Install the plugin in your main.js:
import TuiPage from '@tuimedia/vue-page';
Vue.use(TuiPage);
  • Add the TuiPage vuex module to your store, e.g. store.js:
import Vue from 'vue';
import Vuex from 'vuex';
import TuiPage from '@tuimedia/vue-page/dist/tui-page-vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    TuiPage,
  },
});

export default store;
  • Register your content components globally:
// Global lazy-loading component registration
Vue.component('MyGreeting', () => import('@/components/MyGreeting'));
Vue.component('MyImage', () => import('@/components/MyImage'));
  • Drop a <TuiPage url="/api/pages/my-test-page" state="live" language="en-GB"/> component where you want the page rendered.

  • Alternatively you can fetch the page from the API yourself and set the :data="" property, or call the TuiPage/setPage(data) action from the Vuex store. This can be useful if your page component needs access to the page object for more than just displaying the content.

  • If your project has vue-meta installed, you'll find that relevant properties in your page's metadata object will be applied - i.e. if your page has a metadata title property, that will become the page title. Read the vue-meta docs to see what properties can be set.

TuiPage component

  • url - the URL on the API for the page you want to render. Commonly you'll want this to be a computed property that uses a router parameter to fetch the page slug and then prepends the API base URL.
  • state (optional, defaults to 'live') - the page state you want to retrieve (defaults to live)
  • data - an alternative to url if you want to fetch the page object yourself and use it outside the component.
  • language - a language code you want the page to be rendered in

Metadata helper

It's quite common to need to access page metadata from outside the TuiPage component, for example when displaying a list of pages. In that case, accessing the translatedMetadata properties can be quite verbose. As an alternative, you can use the useTranslatedMetadata helper to generate methods for the fields you need to access. For example:

export default {
  props: {
    page: {
      type: Object,
      required: true,
    },
  },
  methods: {
    ...useTranslatedMetadata(['title']),
  },
};

This creates a vuePageTitle(page, language) method you can access in your templates:

<template>
  <div>{{ vuePageTitle(page, 'en-GB') }}</div>
</template>

Handling page state

The pageState state property goes through the following lifecycle:

  • initialised - the component has loaded but no page has been loaded or set
  • loading - a page is being retrieved
  • loaded or error - the page has been retrieved (and there was an error)
  • unloading - the page state is resetting

You can watch this state property to perform various actions, and also modify it manually using the setPageState action.

Showing content while loading

Set the loading slot to display content while the page is being loaded. If you aren't using the url property of the TuiPage component, then you'll need to manage the pageState manually by calling the setPageState action (the setPage action will set the pageState to loading and then loaded, but obviously it won't accurately reflect that your content is loading).

<TuiPage :url="pageUrl" state="live" language="en-GB">
  <template #loading>
    <div>Loading…</div>
  </template>
</TuiPage>

Handling errors

If you use the url property on the TuiPage component, an axios http request will be made to fetch that URL. If that request fails, you can provide an error template to display something useful to users. For example:

<TuiPage :url="pageUrl" state="live" language="en-GB">
  <template #error="{ error }">
    <div v-if="error.response && error.response.status === 403">
      <h2>Access denied</h2>
      <p>You do not currently have access to this page.</p>
    </div>
    <div v-else>
      <h2>Error</h2>
      <p>An unexpected error occurred: {{ error }}</p>
    </div>
  </template>
</TuiPage>

The error property is an axios error object.

Your content components

The only requirement for your custom components is that they accept a data prop, which will contain the processed configuration and translated content for that component. A minimal component might look like this:

<template>
  <div>{{ data.greeting }}</div>
</template>

<script>
export default {
  props: {
    data: {
      type: Object,
      required: true,
    },
  },
};
</script>

You'll also need to create a JSON Schema file describing the properties of your component, put it somewhere that will exist in the built site (e.g. public/schemas/) so that the TuiPageBundle API can read it. These schemas are used to validate and sanitise input. A generic schema exists for all content components, so you don't need to (and shouldn't) include id, component, languages or styles in your schema file. An example schema for the minimal component above would look like this:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://api.example.com/MyGreeting.schema.json#",
  "type": "object",
  "properties": {
    "greeting": {
      "title": "Title text",
      "type": "string",
      "minLength": 1,
      "maxLength": 1024
    }
  },
  "required": [
    "greeting"
  ]
}

Check the JSON Schema specification for options. Use the "contentMediaType": "text/html" to allow (some) HTML in your fields. The JSON Schema Validator is pretty handy too.

Once you've created a schema file for your component, it should be declared in the TuiPageBundle configuration (usually api/config/packages/tui_page.yaml):

tui_page:
  components:
    MyGreeting: { schema: '%kernel.project_dir%/public/schemas/MyGreeting.schema.json' }

During development, you might find it neater to mount your schema folder onto the docker container so that you don't have to keep copying the schemas every time you change them. Open up your docker-compose.yml file and add a volume a bit like this:

  app:
    # ...
    volumes:
      # ...
      - ./frontend/public/schemas:/var/www/app/public/schemas:cached

Getting raw component content and accessing the page

The content of the page is stored within a Vuex module. Import the map… convenience methods from Vuex to access various raw and processed aspects of it. Unless you're building a CMS, you probably won't need more than setLanguage.

<script>
import { mapGetters, mapState, mapActions } from `vuex`;

export default {
  // ...
  computed: {
    ...mapState('TuiPage', ['page', 'language', 'pageState']),
    ...mapGetters('TuiPage', ['translatedRows', 'translatedBlock', 'translatedMetadata']),
  },
  methods: {
    ...mapActions('TuiPage', [
      'setLanguage',
      'setPage',
      'loadPage',
      'unsetPage',
      'setPageState',
      'addRow',
      'setRow',
      'setRowLangData',
      'deleteRow',
      'swapRow',
      'addRowBlock',
      'deleteRowBlock',
      'swapRowBlock',
      'setBlock',
      'setBlockData',
      'setBlockLangData',
      'deleteBlock',
      'setSlug',
      'setMetadata',
    ]),
  },
};
</script>

Layout components

TuiPage provides two layout components which handle a wide range of layouts: PageRowSingle and PageRowFlex. PageRowFlex arranges an array of content blocks in a flexbox container, collapsing them into a vertical stack at a configurable breakpoint, while PageRowSingle displays only a single block (you can technically give it multiple blocks, but it'll only show the first one that's enabled for the given language).

PageRowSingle

Shows a single block component in a div. Takes no options, and displays only the first block in the blocks property. This is good for building simple Medium-style articles - a single column of stacked content blocks.

PageRowFlex

Show an arbitrary number of blocks.

Options:

  • wrap (boolean): whether the flex row should wrap
  • minWidth (enum: none, mobile, tablet, desktop): sets the breakpoint where the row starts behaving as a flexbox container. This defaults to tablet - 600px. desktop is 900px. Setting none or mobile will always flex.

Creating a custom layout component

You can create your own custom layout components if these aren't up to the task. They need to accept a data prop which at a minimum will contain:

{
  component: 'YourComponentName',
  blocks: ['array', 'of', 'block ids'],
}

The blocks property will be filtered to remove blocks that aren't available in the currently-selected language. Additional properties are also allowed; if the languages array is present, rows that don't list the currently selected language will be removed.

An optional "row-index" prop is also set with the array index of the current layout component. To use it, define it in your component:

export default {
  props: {
    data: {
      type: Object,
      required: true,
    },
    rowIndex: {
      type: Number,
      required: true,
    },
  },
};

To get the actual block data suitable for passing to the block's component data property, map the translatedBlock getter from the TuiPage vuex module:

import { mapGetters } from 'vuex';

export default {
  // ...
  computed: {
    blocksForLanguage() {
      return this.data.blocks.map(blockId => this.translatedBlock(blockId));
    },
    ...mapGetters('TuiPage', ['translatedBlock']),
  }
};

Your custom row can have custom properties and language data, just like blocks do, and these are merged into the data prop in exactly the same way as for blocks.

Vuex page actions

setLanguage(languageCode)

Set the current page language. This will update the output of the translatedX getters. Note that if the language given is not in the list of available languages for the given page, the page's default language will be used instead, regardless of this value.

createPage()

Create an empty page structure.

setPage(data)

Set the page object directly.

setPageState(state)

Set the page loading state. Must be one of initialised, loading, loaded, unloading or error, otherwise behaviour will be undefined.

loadPage(url)

Fetch the given URL and call setPage with its contents

unsetPage()

Remove page data from the store

setSlug(string)

Set the url slug for the given page

setMetadata({ value, key = null })

Set one or all non-translated metadata properties. If key is not supplied, value is assumed to be an object and replaces all properties.

Vuex row actions

addRow({ component, blocks, languages, … })

Add a row to the end of the page layout array. Component should be PageRowSingle, PageRowFlex, or the name of a custom globally-registered row component. Blocks should be an array of block ids, languages an array of supported languages for the row, and any other properties will be passed through, in case you need to configure your row object.

setRow({ row, index })

Replace the row at the given index.

`setRowLangData({ language, id, value, key = null })

Alias of setBlockLangData (see below). Note that unlike other row actions, this one requires the row ID, not the row index.

deleteRow(index)

Deletes a row and all of its blocks (calls deleteBlock for each of its block ids)

swapRow(index)

Swaps the given (0-indexed) row with the next one. Does nothing if the given row index or the next numbered index don't exist.

addRowBlock({ index, blockId })

Append a block to the given row.

deleteRowBlock({ rowIndex, blockIndex })

Remove a block from a row. Note: this does not delete the block itself - call deleteBlock(blockId) to do that.

swapRowBlock({ rowIndex, blockIndex })

Swap the a block's position in a row the next block (if it exists; does nothing otherwise).

Vuex block actions

setBlock({ id, component, languages, styles, … })

Add or replace a block with the given id. Pass only non-translatable properties (use setBlockLangData to set translatable content).

Properties:

  • id (string) id of the block
  • component (string) name of the globally registered Vue component used to render this block
  • languages (array[string]) an array of language codes this block is available in
  • styles (object, optional) CSS styles to apply to this component

setBlockData({ id, key, value })

Set an individual property on the given block

setBlockLangData({ language, id, value, key = null })

Set either an individual property or all properties for the given block and language. Leave key null and set value to an object to overwrite all language data for that block.

Tip: use the id metadata to set translatable page metadata properties like title

deleteBlock(id)

Remove block and language data with the given id. Note that this does not remove references to this block in any rows. You should call deleteRowBlock() on any rows containing this block before calling deleteBlock.

Contributing

Use npm run release to publish a new version. This runs a bunch of checks, bumps the version and manages the git & npmjs releases.