Quantcast
Channel: Artur Piszek
Viewing all articles
Browse latest Browse all 24

WordPress Data Views: Basic setup

$
0
0

WordPress has some sweet, sweet new APIs. One of them I am super excited about is Data Views. Data Views is the API to introduce modern table views into WordPress and I was excited to try it out for my Personal OS plugin.

Example: Custom taxonomy

In my example, I wanted to create a “Bucketlist” page in my WP-Admin to render a custom taxonomy that would hold my Bucketlist. We will need 3 elements:

  • We will need to hook into PHP to create a new WP-Admin page and render React there
  • React code to display Data Views
  • A build process to compile this React code.

React Gutenberg code

I will start with the actual Gutenberg React code. I put my code together using this fantastic tutorial and the code from WordPress Storybook

import { Icon, __experimentalHStack as HStack } from '@wordpress/components';
import domReady from '@wordpress/dom-ready';
import { useState, useMemo, createRoot } from '@wordpress/element';
// Per https://github.com/WordPress/gutenberg/tree/trunk/packages/dataviews :
// Important note If you're trying to use the DataViews component in a WordPress plugin or theme and you're building your scripts using the @wordpress/scripts package, you need to import the components from @wordpress/dataviews/wp instead of @wordpress/dataviews.
import { DataViews, filterSortAndPaginate } from '@wordpress/dataviews/wp';
import { __ } from '@wordpress/i18n';
import { useEntityRecords } from '@wordpress/core-data';
import './style.scss';
import { trash, flag } from '@wordpress/icons';

function NotebookAdmin() {
	const [ view, setView ] = useState( {
		type: 'table',
		search: '',
		page: 1,
		perPage: 100,
		fields: [ 'name', 'description', 'flags', 'count' ],
		layout: {},
		filters: [],
		sort: {
			order: 'asc',
			orderby: 'name',
		},
	} );

	// Our setup in this custom taxonomy.
	const fields = [
		{
			label: __( 'Name', 'your-textdomain' ),
			id: 'name',
			enableHiding: false,
			enableGlobalSearch: true,
			type: 'string',
		},
		{
			label: __( 'Description', 'your-textdomain' ),
			id: 'description',
			enableSorting: false,
			enableGlobalSearch: true,
			type: 'string',
		},
		{
			label: __( 'Flags', 'your-textdomain' ),
			id: 'flags',
			header: (
				<HStack spacing={ 1 } justify="start">
					<Icon icon={ flag } />
					<span>{ __( 'Flags', 'your-textdomain' ) }</span>
				</HStack>
			),
			type: 'array',
			render: ( { item } ) => {
				return item.meta?.flag?.join( ', ' ) || '';
			},
			enableSorting: false,
		},
		{
			label: __( 'Count', 'your-textdomain' ),
			id: 'count',
			enableSorting: true,
			enableGlobalSearch: false,
			type: 'number',
		},
	];

	// We will use the entity records hook to fetch all the items from the "notebook" custom taxonomy
	const { records } = useEntityRecords( 'taxonomy', 'notebook', {
		per_page: -1,
		page: 1,
		hide_empty: false,
	} );

	// filterSortAndPaginate works in memory. We theoretically could pass the parameters to backend to filter sort and paginate there.
	const { data: shownData, paginationInfo } = useMemo( () => {
		return filterSortAndPaginate( records, view, fields );
	}, [ view, records ] );

	return (
		<DataViews
			getItemId={ ( item ) => item.id.toString() }
			paginationInfo={ paginationInfo }
			data={ shownData }
			view={ view }
			fields={ fields }
			onChangeView={ setView }
			actions={ [
				{
					id: 'delete',
					label: __( 'Delete', 'your-textdomain' ),
					icon: trash,
					callback: async ( items ) => {
						// Implement delete functionality
						console.log( 'Delete items:', items );
					},
				},
			] }
			defaultLayouts={ {
				table: {
					// Define default table layout settings
					spacing: 'normal',
					showHeader: true,
				},
			} }
		/>
	);
}

domReady( () => {
	const root = createRoot( document.getElementById( 'bucketlist-root' ) );
	root.render( <NotebookAdmin /> );
} );

In your plugin you have to import the components from @wordpress/dataviews/wp instead of @wordpress/dataviews. I expect this is a recent change and it will be further evolve. You also need to add

“@wordpress/dependency-extraction-webpack-plugin”: “^6.14.0”

To your devpependencies (check at the end of this post)

If you you import directly from @wordpress/dataviews you will get errors like these (click to expand)
column-header-menu.js:105 Uncaught TypeError: Cannot read properties of undefined (reading 'Group')
    at HeaderMenu (column-header-menu.js:105:44)
    at renderWithHooks (react-dom.js?ver=18.3.1:15496:20)
    at updateForwardRef (react-dom.js?ver=18.3.1:19255:22)
    at beginWork (react-dom.js?ver=18.3.1:21685:18)
    at HTMLUnknownElement.callCallback (react-dom.js?ver=18.3.1:4151:16)
    at Object.invokeGuardedCallbackDev (react-dom.js?ver=18.3.1:4200:18)
    at invokeGuardedCallback (react-dom.js?ver=18.3.1:4264:33)
    at beginWork$1 (react-dom.js?ver=18.3.1:27500:9)
    at performUnitOfWork (react-dom.js?ver=18.3.1:26606:14)
    at workLoopSync (react-dom.js?ver=18.3.1:26515:7)
react-dom.js?ver=18.3.1:18714 The above error occurred in the <AddFilterMenu> component:

    at AddFilterMenu (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:352:3)
    at div
    at FiltersToggle (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:742:3)
    at div
    at http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14024:47
    at UnforwardedView (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14903:3)
    at UnconnectedHStack (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:32221:23)
    at div
    at http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14024:47
    at UnforwardedView (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:14903:3)
    at UnconnectedHStack (http://localhost:8901/wp-includes/js/dist/components.js?ver=490baf0…:32221:23)
    at div
    at DataViews (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:2415:3)
    at BucketlistAdmin (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:14023:87)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
3
react-dom.js?ver=18.3.1:18714 The above error occurred in the <ForwardRef(HeaderMenu)> component:

    at HeaderMenu (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:3894:3)
    at th
    at tr
    at thead
    at table
    at ViewTable (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:4363:3)
    at DataViewsLayout (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:1526:69)
    at div
    at DataViews (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:2415:3)
    at BucketlistAdmin (http://localhost:8901/wp-content/plugins/personalos/modules/bucketlist/js/build/admin.js?ver=5a41136…:14023:87)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
react-dom.js?ver=18.3.1:26972 Uncaught TypeError: Cannot read properties of undefined (reading 'Item')
    at add-filter.js:32:31
    at Array.map (<anonymous>)
    at AddFilterMenu (add-filter.js:31:1)
    at renderWithHooks (react-dom.js?ver=18.3.1:15496:20)
    at mountIndeterminateComponent (react-dom.js?ver=18.3.1:20113:15)
    at beginWork (react-dom.js?ver=18.3.1:21636:18)
    at beginWork$1 (react-dom.js?ver=18.3.1:27475:16)
    at performUnitOfWork (react-dom.js?ver=18.3.1:26606:14)
    at workLoopSync (react-dom.js?ver=18.3.1:26515:7)
    at renderRootSync (react-dom.js?ver=18.3.1:26483:9)

PHP end

In PHP, you have to:

  • set up a WP-Admin page
  • enqueue your script
  • enqueue your style
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_scripts' ) );


public function add_admin_menu(): void {
	add_menu_page(
		'Bucketlist',
		'Bucketlist',
		'read',
		'bucketlist',
		array( $this, 'render_admin_page' ),
		'dashicons-list-view'
	);
}

public function render_admin_page(): void {
	?>
	<div class="wrap">
		<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
		<div id="bucketlist-root"></div>
	</div>
	<?php
}

public function enqueue_admin_scripts( string $hook ): void {
	if ( 'toplevel_page_bucketlist' !== $hook ) {
		return;
	}

	$asset_file = plugin_dir_path( __FILE__ ) . 'js/build/admin.asset.php';
	$asset = require $asset_file;

	wp_enqueue_script(
		'bucketlist-admin',
		plugin_dir_url( __FILE__ ) . 'js/build/admin.js',
		$asset['dependencies'],
		$asset['version'],
		false
	);

	wp_enqueue_style(
		'bucketlist-admin',
		plugin_dir_url( __FILE__ ) . 'js/build/style-admin.css',
		array(),
		$asset['version']
	);
}

Build code

This is my package.json. Remember to add `”@wordpress/dependency-extraction-webpack-plugin”: “^6.14.0″` in this specific version.

Otherwise, wp-scripts will treat dataviews as WordPress dependency and it won’t render in your app!

{
	"name": "bucketlist",
	"version": "1.0.0",
	"description": "Bucketlist module for PersonalOS",
	"scripts": {
		"build": "wp-scripts build js/src/admin.js --output-path=js/build",
		"start": "wp-scripts start js/src/admin.js --output-path=js/build"
	},
	"dependencies": {
		"@wordpress/components": "^25.0.0",
		"@wordpress/data": "^9.0.0",
		"@wordpress/dataviews": "^4.10.0",
		"@wordpress/element": "^5.0.0"
	},
	"devDependencies": {
		"@wordpress/scripts": "^30.7.0",
		"@wordpress/dependency-extraction-webpack-plugin": "^6.14.0"
	}
}

For style tweaks and the rest you can refer to this fantastic walkthrough

The post WordPress Data Views: Basic setup appeared first on Artur Piszek.


Viewing all articles
Browse latest Browse all 24

Trending Articles