Skip to content
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

Update WCEU workshop interactivity example with WP 6.5 syntax #122

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,38 @@
* @package BlockDevelopmentExamples\Quiz1835fa
*/

$unique_id = substr( uniqid(), -5 );
$unique_id = wp_unique_id( 'quiz-' );

wp_interactivity_state(
'quiz-1835fa-project-store',
array(
'quizSelected' => null,
'openText' => __( 'Open menu' ),
'closeText' => __( 'Close menu' ),
'quizzes' => array(
'openText' => __( 'Open' ),
'closeText' => __( 'Close' ),
'selected' => null,
'isOpen' => false,
'toggleText' => __( 'Open' ),
'isActive' => false,
'inputAnswer' => '',
'quizzes' => array(
$unique_id => array(
'current' => null,
'correct' => $attributes['answer'],
),
),
'toggleText' => __( 'Open menu' ),
'isActive' => false,
'inputAnswer' => null,
)
);


$context = array(
'id' => $unique_id,
'answer' => null,
);
?>

<div
<?php echo wp_kses_data( get_block_wrapper_attributes() ); ?>
data-wp-interactive='{"namespace": "quiz-1835fa-project-store"}'
data-wp-context='{ "id": "<?php echo esc_attr( $unique_id ); ?>" , "answer": null }'
data-wp-interactive="quiz-1835fa-project-store"
data-wp-on--keydown="actions.closeOnEsc"
data-wp-watch="callbacks.log"
<?php echo wp_kses_data( wp_interactivity_data_wp_context( $context ) ); ?>
>
<div>
<strong>
Expand All @@ -49,15 +52,15 @@
<button
data-wp-on--click="actions.toggle"
data-wp-bind--aria-expanded="state.isOpen"
aria-controls="<?php echo esc_attr( $unique_id ); ?>"
data-wp-text="state.toggleText"
aria-controls="quiz-<?php echo esc_attr( $unique_id ); ?>"
>
</button>
data-wp-bind--disabled="state.showAnswers"
></button>
</div>

<div
data-wp-bind--hidden="!state.isOpen"
id="quiz-<?php echo esc_attr( $unique_id ); ?>"
id="<?php echo esc_attr( $unique_id ); ?>"
>
<?php if ( 'boolean' === $attributes['typeOfQuiz'] ) : ?>
<button
Expand Down Expand Up @@ -86,4 +89,4 @@

<?php endif; ?>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,55 +1,40 @@
import { store, getContext, getElement } from '@wordpress/interactivity';
/**
* WordPress dependencies
*/
import { store } from '@wordpress/interactivity';

const { state } = store( 'quiz-1835fa-project-store', {
state: {
get isOpen() {
const { id: quizId } = getContext();
return state.selected === quizId;
get answered() {
return Object.values( state.quizzes ).filter(
( v ) => v.current !== null
).length;
},
get toggleText() {
return state.isOpen ? state.closeText : state.openText;
get allAnswered() {
return state.answered === Object.keys( state.quizzes ).length;
},
get isActive() {
const { id: quizId, thisAnswer } = getContext();
return state.quizzes[ quizId ].current === thisAnswer;
get correct() {
return state.showAnswers
? Object.values( state.quizzes ).filter(
( v ) => v.current === v.correct
).length
: '?';
},
get inputAnswer() {
const { id: quizId } = getContext();
return state.quizzes[ quizId ].current || '';
get allCorrect() {
return state.correct === Object.keys( state.quizzes ).length;
},
},
actions: {
toggle: () => {
const { id: quizId } = getContext();
if ( state.selected === quizId ) {
state.selected = null;
} else {
state.selected = quizId;
}
},
closeOnEsc: ( event ) => {
const { ref } = getElement();
if ( event.key === 'Escape' ) {
state.selected = null;
ref.querySelector( 'button[aria-controls^="quiz-"]' ).focus();
}
},
answerBoolean: () => {
const { id: quizId, thisAnswer } = getContext();
const quiz = state.quizzes[ quizId ];
quiz.current = quiz.current !== thisAnswer ? thisAnswer : null;
},
answerInput: ( event ) => {
const { id: quizId } = getContext();
state.quizzes[ quizId ].current = event.target.value || null;
},
},
callbacks: {
focusOnOpen: () => {
const { ref } = getElement();
if ( state.isOpen ) {
ref.focus();
}
checkAnswers: () => {
state.showAnswers = true;
state.selected = null;
},
reset: () => {
state.showAnswers = false;
state.selected = null;
Object.values( state.quizzes ).forEach( ( quiz ) => {
quiz.current = null;
} );
},
},
} );
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* @package BlockDevelopmentExamples\QuizProgress1835fa
*/

wp_interactivity_state(
$state = wp_interactivity_state(
'quiz-1835fa-project-store',
array(
'showAnswers' => false,
Expand All @@ -26,21 +26,20 @@

<div
<?php echo wp_kses_data( get_block_wrapper_attributes() ); ?>
data-wp-interactive='{"namespace": "quiz-1835fa-project-store"}'
data-wp-interactive="quiz-1835fa-project-store"
>
<div>
<strong><?php echo wp_kses_data( __( 'Answered' ) ); ?></strong>:
<span data-wp-text="state.answered"></span> / <span data-wp-text="state.totalQuizzes"></span>
<span data-wp-text="state.answered"></span>/<?php echo count( $state['quizzes'] ); ?>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luisherranz the portion of code <?php echo count( $state['quizzes'] ); is throwing an error
I temporarily fixed the error by adding this and this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

state.quizzes.length is not dynamically changed in the client, so it should be read from a static PHP variable instead of a client getter.

Apart from that, we need that value in the server so it's properly server-side rendered.

What's the exact error that it throwed in production?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, it doesn't work right now because state.quizzes is an object, so it should be Object.keys(state.quizzes).length.

But, anyway, let's fix it properly 😄

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the error I get with <?php echo count( $state['quizzes'] ); ?> on line 33

Answered: /
Warning: Undefined array key "quizzes" in /var/www/html/wp-content/plugins/interactivity-api-quiz-1835fa/build/blocks/quiz-progress-1835fa/render.php on line 33

Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of type Countable|array, null given in /var/www/html/wp-content/plugins/interactivity-api-quiz-1835fa/build/blocks/quiz-progress-1835fa/render.php:33 Stack trace: #0 /var/www/html/wp-includes/blocks.php(519): require() #1 /var/www/html/wp-includes/class-wp-block.php(463): {closure}(Array, '', Object(WP_Block)) #2 /var/www/html/wp-includes/blocks.php(1705): WP_Block->render() #3 /var/www/html/wp-includes/blocks.php(1743): render_block(Array) #4 /var/www/html/wp-includes/class-wp-hook.php(324): do_blocks('<!-- wp:block-d...') #5 /var/www/html/wp-includes/plugin.php(205): WP_Hook->apply_filters('<!-- wp:block-d...', Array) #6 /var/www/html/wp-content/plugins/gutenberg/build/block-library/blocks/post-content.php(50): apply_filters('the_content', '<!-- wp:block-d...') #7 /var/www/html/wp-includes/class-wp-block.php(463): gutenberg_render_block_core_post_content(Array, '<!-- wp:block-d...', Object(WP_Block)) #8 /var/www/html/wp-includes/class-wp-block.php(443): WP_Block->render() #9 /var/www/html/wp-includes/blocks.php(1705): WP_Block->render() #10 /var/www/html/wp-includes/blocks.php(1743): render_block(Array) #11 /var/www/html/wp-includes/block-template.php(260): do_blocks('<!-- wp:templat...') #12 /var/www/html/wp-includes/template-canvas.php(12): get_the_block_template_html() #13 /var/www/html/wp-includes/template-loader.php(106): include('/var/www/html/w...') #14 /var/www/html/wp-blog-header.php(19): require_once('/var/www/html/w...') #15 /var/www/html/index.php(17): require('/var/www/html/w...') #16 {main} thrown in /var/www/html/wp-content/plugins/interactivity-api-quiz-1835fa/build/blocks/quiz-progress-1835fa/render.php on line 33
There has been a critical error on this website.

[Learn more about troubleshooting WordPress.](https://wordpress.org/documentation/article/faq-troubleshooting/)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, wait. I know what's happening. The quiz blocks need to be inner blocks of the quiz progress block. Can you change the blueprint to be like that? I'll make the PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quiz blocks need to be inner blocks of the quiz progress block.

@luisherranz In that case, do we need to set the namespace for the children as it is done here?

Also, are independent blocks unable to share namespace and state/store?

Copy link
Member Author

@luisherranz luisherranz Jul 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to set the namespace for the children as it is done here?

Yes, even though it's not strictly required, it's a good practice to always add the data-wp-interactive directive.

are independent blocks unable to share namespace and state/store?

What do you mean?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean?

I mean, can two blocks being at the same level (no inner blocks) in different parts of the HTML share namespace and state/store

For example, having two blocks registered by different plugins, can they share namespace (and store/actions) without a parent/child relationship?

<div id="block-registered-by-plugin-1" data-wp-interactive="common-project-namespace">
</div>
<div id="block-registered-by-plugin-1" data-wp-interactive="common-project-namespace">
</div>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, sure 🙂

</div>

<div>
<strong><?php echo wp_kses_data( __( 'Correct' ) ); ?></strong>:
<span data-wp-text="state.correct"></span>
<span data-wp-bind--hidden="!state.allCorrect">
<?php echo wp_kses_data( __( 'All correct, congratulations!' ) ); ?>
</span>
<div data-wp-bind--hidden="!state.allCorrect">
<?php echo wp_kses_data( __( 'All correct, congratulations! 🎉' ) ); ?>
</div>
</div>

<div>
<button
data-wp-bind--hidden="state.showAnswers"
Expand All @@ -60,6 +59,10 @@
<hr>

<div>
<?php echo wp_kses_data( $content ); ?>

<?php
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $content;
?>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,39 +1,64 @@
import { store } from '@wordpress/interactivity';
/**
* WordPress dependencies
*/
import { store, getContext, getElement } from '@wordpress/interactivity';

const { state } = store( 'quiz-1835fa-project-store', {
state: {
get answered() {
return Object.values( state.quizzes ).filter(
( v ) => v.current !== null
).length;
get isOpen() {
const { id } = getContext();
return state.selected === id;
},
get allAnswered() {
return state.answered === Object.keys( state.quizzes ).length;
get toggleText() {
return state.isOpen ? state.closeText : state.openText;
},
get correct() {
return state.showAnswers
? Object.values( state.quizzes ).filter(
( v ) => v.current === v.correct
).length
: '?';
get isActive() {
const { id, thisAnswer } = getContext();
return state.quizzes[ id ].current === thisAnswer;
},
get allCorrect() {
return state.correct === Object.keys( state.quizzes ).length;
},
get totalQuizzes() {
return Object.values( state.quizzes ).length;
get inputAnswer() {
const { id } = getContext();
return state.quizzes[ id ].current || '';
},
},
actions: {
checkAnswers: () => {
state.showAnswers = true;
state.selected = null;
toggle() {
const { id } = getContext();

if ( state.selected === id ) {
state.selected = null;
} else {
state.selected = id;
}
},
reset: () => {
state.showAnswers = false;
Object.values( state.quizzes ).forEach( ( quiz ) => {
closeOnEsc( event ) {
if ( event.key === 'Escape' ) {
state.selected = null;
const { ref } = getElement();
ref.querySelector( 'button[aria-controls^="quiz-"]' ).focus();
}
},
answerBoolean() {
const { id, thisAnswer } = getContext();
const quiz = state.quizzes[ id ];

if ( quiz.current !== thisAnswer ) {
quiz.current = thisAnswer;
} else {
quiz.current = null;
} );
}
},
answerInput( event ) {
const { id } = getContext();
state.quizzes[ id ].current = event.target.value || null;
},
},
callbacks: {
focusOnOpen() {
if ( state.isOpen ) {
const { ref } = getElement();
ref.focus();
}
},
},
} );
Loading