-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new Categories block #2102
Add new Categories block #2102
Conversation
fdbdbd5
to
f694b1f
Compare
Codecov Report
@@ Coverage Diff @@
## master #2102 +/- ##
==========================================
- Coverage 22.3% 22.01% -0.29%
==========================================
Files 137 139 +2
Lines 4291 4356 +65
Branches 722 730 +8
==========================================
+ Hits 957 959 +2
- Misses 2815 2870 +55
- Partials 519 527 +8
Continue to review full report at Codecov.
|
f604a79
to
380769b
Compare
This works great! Thanks so much for working on it. This PR feels very very ready to go. It doesn't show empty categories by default, it seems. Is the same behavior as the widget? |
blocks/library/categories/data.js
Outdated
@@ -0,0 +1,13 @@ | |||
/** | |||
* Returns a Promise with the categories or an error on failure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically it returns a jqXHR
which implements the jQuery.Deffered
interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 86afe7a
blocks/library/categories/index.js
Outdated
]; | ||
} | ||
|
||
componentWillUnmount() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not prescribed, but general convention that render
should be last method defined in a component class. I tend to prefer to keep lifecycle methods together toward the top of the class.
What do you think? Maybe we should document this, since I find I'm recommending it fairly often.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, updated in 2af85c8.
blocks/library/categories/index.js
Outdated
} | ||
|
||
toggleDisplayAsDropdown() { | ||
const { displayAsDropdown } = this.props.attributes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor (Stylistic): We could avoid nested property access by including attributes
in the destructure of this.props
:
const { attributes, setAttributes } = this.props;
const { displayAsDropdown } = attributes;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 30ba95e
this.categoriesRequest = getCategories(); | ||
|
||
this.categoriesRequest | ||
.then( categories => this.setState( { categories } ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we confirm whether this callback will be invoked if the request is aborted while in-flight? At that point the component would no longer be mounted either, so setState
could cause trouble.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure - it will not be invoked, as the deferred .then
is executed only if the request is resolved.
blocks/library/categories/index.js
Outdated
getCategories( parentId = null ) { | ||
const { categories } = this.state; | ||
if ( ! categories.length ) { | ||
return []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we return categories;
here? Why do we need this condition anyways? I doubt Array#filter
on an empty array has much overhead to be worried about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, good call about returning categories here - updated in 0fa3f29. I'm more inclined to keep the condition though - no need to continue if categories are not loaded yet.
blocks/library/categories/index.js
Outdated
|
||
return ( | ||
<li key={ category.id }> | ||
<a href={ category.link } target="_blank">{ category.name.trim() || __( '(Untitled)' ) }</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious since I've dealt with issues before: How well does this handle entities, like an ampersand &
? Might need to do some unescaping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good call, done in 428d791.
blocks/library/categories/index.js
Outdated
|
||
return [ | ||
<option key={ category.id }> | ||
{ new Array( level * 3 ).fill( '\xa0' ) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't include a polyfill (yet). This will error in IE11:
Try instead:
Array.apply( null, new Array( level * 3 ) ).map( () => '\xa0' );
Or with Lodash:
_.times( 5, () => '\xa0' );
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, updated in aa5d4ad.
blocks/library/categories/index.js
Outdated
{ category.name.trim() || __( '(Untitled)' ) } | ||
{ | ||
!! showPostCounts | ||
? ` ( ${ category.count } )` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above we don't include spaces in parentheses around category.count
, but here we do. Should we be consistent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course! Updated in f1f7f0b.
lib/blocks/categories.php
Outdated
); | ||
|
||
if ( ! empty( $attributes['displayAsDropdown'] ) ) { | ||
$id = 'wp-block-categories-' . wp_rand(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why wp_rand
? Uniqueness? We're not guaranteed unique values between two subsequent wp_rand
calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I've updated it to use an increasing static counter, see c501a4b
lib/blocks/categories.php
Outdated
location.href = "<?php echo home_url(); ?>/?cat=" + dropdown.options[ dropdown.selectedIndex ].value; | ||
} | ||
} | ||
dropdown.onchange = onCatChange; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this apply here: https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-onchange.md ?
I could see it being frustrating to use by keyboard if it navigated by keying through the list of options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm more inclined to keep the behavior we have for the core widget here - I like keeping things consistent. If we update the core widget to use onBlur
, I'd be happy to update this one as well.
export function getCategories() { | ||
const categoriesCollection = new wp.api.collections.Categories(); | ||
|
||
const categories = categoriesCollection.fetch(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this not introduce the possibility of there being many requests when there is a large deeply-nested category tree?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really - this one will be called only per a Categories block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. I was confusing this getCategories
function with the getCategories
method on the component, and the latter is called recursively.
gutenberg.php
Outdated
@@ -27,4 +27,5 @@ | |||
|
|||
// Register server-side code for individual blocks. | |||
require_once dirname( __FILE__ ) . '/lib/blocks/latest-posts.php'; | |||
require_once dirname( __FILE__ ) . '/lib/blocks/categories.php'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to be changed in light of #2014.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was fixed with a combination of rebase and f23a5da.
blocks/library/categories/index.js
Outdated
/** | ||
* WordPress dependencies | ||
*/ | ||
import { Component } from 'element'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note with #2172, these need to be updated to prefix the dependencies with @wordpress/
. You will need to perform a rebase against the latest version of master and apply your changes:
git fetch origin
git rebase origin/master
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Rebased and updated in 515dad4.
f63ded5
to
2cc2804
Compare
@aduth @westonruter thanks so much for the constructive feedback! I've addressed it all, feel free to have a new look. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me for a first iteration on categories.
I'm happy to give this one a final round of testing and merge it, if everyone agrees it's good. |
Purpose
This PR introduces a new Categories dynamic block, fixes #1790.
Features
The new block comes with the following alignment options:
none
,left
,right
,center
,full
. It also supports the following attributes, which are similar to the coreCategories
widget:false
, which displays an unordered list.false
, indicates these are totally hidden.false
, meaning a flat, non-hierarchical list.The server-side rendering part takes advantage of what the core widget uses -
wp_list_categories()
for the unordered list, andwp_dropdown_categories
for the dropdown.Preview
List view, no post counts, no hierarchy
List view, with post counts, no hierarchy
List view, no post counts, with hierarchy
List view, with post counts, with hierarchy
Dropdown view, without post counts, without hierarchy
Dropdown view, with post counts, without hierarchy
Dropdown view, without post counts, with hierarchy
Dropdown view, with post counts, with hierarchy
Block control options
Questions
The script for redirecting users to a category once they change it in the dropdown field in the frontend - I've borrowed it from the Categories core widget. I felt it would make sense to be consistent with what we have in the core, but if you folks have better suggestions, I'm all ears! Thanks!