I'm working to translate a custom shortcode to a Gutenberg-block but am stuck on two issues. This post first treats the current situation, the next heading shows the problems I'm running into in greater detail.
Current situation
I'm using the shortcode (syntax and code below) to display resume-items on my website. It includes fields for a title, organisation, location, duration, number of hours per week and/or GPA and outputs as follows. There are links to some of the attributes.
Syntax of the shortcode (removed part of the description for brevity) is as follows:
[cv-item naam='<a href="/" target="_blank" rel="noopener">Aerospace Engineering (MSc)</a>' organisatie='<a href="/" target="_blank" rel="noopener">Delft University of Technology</a>' locatie='Delft, The Netherlands' duur='09/2014 - current' status='open' cijfer='7.7/10' cijfer_datum='11/2018'] Double-track specialisation (180 EC, three years) in the [...] on the other. [/cv-item]
Shortcode definition is as follows:
function cv_item( $atts, $content = null ) {
$atts_completed = shortcode_atts( array(
'naam' => null,
'organisatie' => null,
'locatie' => null,
'duur' => null,
'uren' => null,
'status' => 'closed',
'cijfer' => null,
'cijfer_datum' => null,
), $atts );
$naam = $atts_completed['naam'];
$organisatie = $atts_completed['organisatie'];
$locatie = $atts_completed['locatie'];
$duur = $atts_completed['duur'];
$uren = $atts_completed['uren'];
$status = $atts_completed['status'];
$cijfer = $atts_completed['cijfer'];
$cijfer_datum = $atts_completed['cijfer_datum'];
static $id = 1;
$id ++;
$output = '<div class="cv-item" id="cv-item-' . $id . '">';
/* Uit-/inklap-knop */
if ( $status == 'open' ) {
$toggle_class = 'fa-arrow-up';
} else {
$toggle_class = 'fa-arrow-down';
}
if ( $content != null ) {
$output .= '<i class="fa ' . $toggle_class . ' cv-item-toggle" id="cv-item-' . $id . '"></i>';
}
/* Eerste regel, met naam, bedrijf en locatie */
$output .= '<span class="cv-item-title">';
$output .= '<strong>' . $naam . '</strong>';
if ( $organisatie != null ) {
$output .= ', <span class="organisation">' . $organisatie . '</span>';
if ( $locatie != null ) {
$output .= ' (' . $locatie . ')';
}
}
$output .= '</span>';
/* Tweede regel, met duur en aantal uren per week */
if ( $duur != null || $uren != null || $cijfer != null ) {
$output .= '<span class="cv-item-details">';
if ( $duur != null ) {
$output .= $duur;
}
if ( $uren != null | $cijfer != null ) {
if ( $duur != null ) {
$output .= ' | ';
}
if ( $uren != null ) {
$output .= $uren . __( ' uur per week', 'peerlings' );
}
if ( $cijfer != null ) {
$output .= $cijfer;
if ( $cijfer_datum != null ) {
$output .= ' (' . __( 'per', 'peerlings', 'Cijfer XX per YY' ) . ' ' . $cijfer_datum . ')';
}
}
}
$output .= '</span>';
}
/* Omschrijving */
if ( $content != null ) {
$output .= '<div class="cv-item-description ' . $status . '">' . $content . '</div>';
}
/* Closing */
$output .= '</div>';
return $output; }
As you can see, there are a bunch of conditionals in the function that processes the shortcode. Also, some separators (like the comma between item title and organisation) are added, as are a bunch of wrapper-span
s used for styling purposes (such as span.cv-item-title
and span.details
). I have trouble replicating this in Gutenberg.
New situation, problems and solution attempts
I've been working on adjusting this custom profile block to suit my needs. So far, that has worked reasonably well (full block.js
-code here), but I'm stuck on two problems:
- Replicating the conditionals
- Adding the separators and
span
-wrappers
Conditionals
In the original code, values from the block settings (in the right sidebar) are displayed conditionally, per the code below (extract from return-statement from the edit
- and save
-functions):
el('div', {className: 'organic-profile-social'},
attributes.nameURL && el('a', {
className: 'social-link',
href: attributes.nameURL,
target: '_blank'
},
el('i', {className: 'fa fa-facebook'})
),
attributesanisationURL && el('a', {
className: 'social-link',
href: attributesanisationURL,
target: '_blank'
},
el('i', {className: 'fa fa-twitter'})
)
)
The same logic, however, doesn't apply to regular fields, as shown below (extract from return-statement from the save
-function):
el('span', {},
attributes.grade && el('span', {
className: 'grade',
value: attributes.grade
}),
attributes.gradeDate && el('span', {
className: 'grade-date',
value: attributes.gradeDate
}
)
)
This outputs the grade
and grade-date
span
s regardless of their content. Also, the span
that is supposed to wrap the these two, turns up as an empty span
preceding them:
<span></span><span class="grade"></span><span class="grade-date"></span>
Of course, I can just move all conditional fields to the settings-panel in the sidebar, but that (to me) defeats the entire WYSIWYG-on-the-back-end-approach that Gutenberg is (insofar I understand correctly) all about.
Question: Is it even possible to achieve what I have in mind, and how can I do so?
Separators, wrappers and intermediate text
I thought I had this to work, by adding the separators using helper-functions that set a particular separator, or set it to null
if the surrounding values are empty, like so:
if (typeof props.attributes.grade !== 'undefined' && typeof props.attributes.gradeDate !== 'undefined') {
var separatorGradeGradeDatePre = ' (' + i18n.__('per')
var separatorGradeGradeDateSuf = ')'
} else {
var separatorGradeGradeDatePre = null
var separatorGradeGradeDateSuf = null
}
I then added these two the return-statement of the save
-function. That worked well one way, but not the way back. Put differently: if the edit
-function goes to read the post_content
-HTML, it comes across text that isn't specified in the edit
-function and throws an error.
Idea's I've thought of so far - as well as my reasons for not implementing (yet):
- Including the separators in the
edit
-function, too, and then hiding these using editor-specific CSS. Haven't implemented, because I think it's ugly (to say the least) to include content that I'm then (in all instances) going to hide using CSS. - Not including the separators in the
save
-function, but adding them using CSScontent
-properties. Even more ugly than the previous option, especially as it will require a nasty PHP-CSS-mashup to include the conditional statements.
Question: Are there any other ways to achieve this, or should I just go for one of my ideas posted above?
I'm working to translate a custom shortcode to a Gutenberg-block but am stuck on two issues. This post first treats the current situation, the next heading shows the problems I'm running into in greater detail.
Current situation
I'm using the shortcode (syntax and code below) to display resume-items on my website. It includes fields for a title, organisation, location, duration, number of hours per week and/or GPA and outputs as follows. There are links to some of the attributes.
Syntax of the shortcode (removed part of the description for brevity) is as follows:
[cv-item naam='<a href="https://www.tudelft.nl/en/education/programmes/masters/aerospace-engineering/msc-aerospace-engineering/" target="_blank" rel="noopener">Aerospace Engineering (MSc)</a>' organisatie='<a href="http://www.tudelft.nl/en/" target="_blank" rel="noopener">Delft University of Technology</a>' locatie='Delft, The Netherlands' duur='09/2014 - current' status='open' cijfer='7.7/10' cijfer_datum='11/2018'] Double-track specialisation (180 EC, three years) in the [...] on the other. [/cv-item]
Shortcode definition is as follows:
function cv_item( $atts, $content = null ) {
$atts_completed = shortcode_atts( array(
'naam' => null,
'organisatie' => null,
'locatie' => null,
'duur' => null,
'uren' => null,
'status' => 'closed',
'cijfer' => null,
'cijfer_datum' => null,
), $atts );
$naam = $atts_completed['naam'];
$organisatie = $atts_completed['organisatie'];
$locatie = $atts_completed['locatie'];
$duur = $atts_completed['duur'];
$uren = $atts_completed['uren'];
$status = $atts_completed['status'];
$cijfer = $atts_completed['cijfer'];
$cijfer_datum = $atts_completed['cijfer_datum'];
static $id = 1;
$id ++;
$output = '<div class="cv-item" id="cv-item-' . $id . '">';
/* Uit-/inklap-knop */
if ( $status == 'open' ) {
$toggle_class = 'fa-arrow-up';
} else {
$toggle_class = 'fa-arrow-down';
}
if ( $content != null ) {
$output .= '<i class="fa ' . $toggle_class . ' cv-item-toggle" id="cv-item-' . $id . '"></i>';
}
/* Eerste regel, met naam, bedrijf en locatie */
$output .= '<span class="cv-item-title">';
$output .= '<strong>' . $naam . '</strong>';
if ( $organisatie != null ) {
$output .= ', <span class="organisation">' . $organisatie . '</span>';
if ( $locatie != null ) {
$output .= ' (' . $locatie . ')';
}
}
$output .= '</span>';
/* Tweede regel, met duur en aantal uren per week */
if ( $duur != null || $uren != null || $cijfer != null ) {
$output .= '<span class="cv-item-details">';
if ( $duur != null ) {
$output .= $duur;
}
if ( $uren != null | $cijfer != null ) {
if ( $duur != null ) {
$output .= ' | ';
}
if ( $uren != null ) {
$output .= $uren . __( ' uur per week', 'peerlings' );
}
if ( $cijfer != null ) {
$output .= $cijfer;
if ( $cijfer_datum != null ) {
$output .= ' (' . __( 'per', 'peerlings', 'Cijfer XX per YY' ) . ' ' . $cijfer_datum . ')';
}
}
}
$output .= '</span>';
}
/* Omschrijving */
if ( $content != null ) {
$output .= '<div class="cv-item-description ' . $status . '">' . $content . '</div>';
}
/* Closing */
$output .= '</div>';
return $output; }
As you can see, there are a bunch of conditionals in the function that processes the shortcode. Also, some separators (like the comma between item title and organisation) are added, as are a bunch of wrapper-span
s used for styling purposes (such as span.cv-item-title
and span.details
). I have trouble replicating this in Gutenberg.
New situation, problems and solution attempts
I've been working on adjusting this custom profile block to suit my needs. So far, that has worked reasonably well (full block.js
-code here), but I'm stuck on two problems:
- Replicating the conditionals
- Adding the separators and
span
-wrappers
Conditionals
In the original code, values from the block settings (in the right sidebar) are displayed conditionally, per the code below (extract from return-statement from the edit
- and save
-functions):
el('div', {className: 'organic-profile-social'},
attributes.nameURL && el('a', {
className: 'social-link',
href: attributes.nameURL,
target: '_blank'
},
el('i', {className: 'fa fa-facebook'})
),
attributesanisationURL && el('a', {
className: 'social-link',
href: attributesanisationURL,
target: '_blank'
},
el('i', {className: 'fa fa-twitter'})
)
)
The same logic, however, doesn't apply to regular fields, as shown below (extract from return-statement from the save
-function):
el('span', {},
attributes.grade && el('span', {
className: 'grade',
value: attributes.grade
}),
attributes.gradeDate && el('span', {
className: 'grade-date',
value: attributes.gradeDate
}
)
)
This outputs the grade
and grade-date
span
s regardless of their content. Also, the span
that is supposed to wrap the these two, turns up as an empty span
preceding them:
<span></span><span class="grade"></span><span class="grade-date"></span>
Of course, I can just move all conditional fields to the settings-panel in the sidebar, but that (to me) defeats the entire WYSIWYG-on-the-back-end-approach that Gutenberg is (insofar I understand correctly) all about.
Question: Is it even possible to achieve what I have in mind, and how can I do so?
Separators, wrappers and intermediate text
I thought I had this to work, by adding the separators using helper-functions that set a particular separator, or set it to null
if the surrounding values are empty, like so:
if (typeof props.attributes.grade !== 'undefined' && typeof props.attributes.gradeDate !== 'undefined') {
var separatorGradeGradeDatePre = ' (' + i18n.__('per')
var separatorGradeGradeDateSuf = ')'
} else {
var separatorGradeGradeDatePre = null
var separatorGradeGradeDateSuf = null
}
I then added these two the return-statement of the save
-function. That worked well one way, but not the way back. Put differently: if the edit
-function goes to read the post_content
-HTML, it comes across text that isn't specified in the edit
-function and throws an error.
Idea's I've thought of so far - as well as my reasons for not implementing (yet):
- Including the separators in the
edit
-function, too, and then hiding these using editor-specific CSS. Haven't implemented, because I think it's ugly (to say the least) to include content that I'm then (in all instances) going to hide using CSS. - Not including the separators in the
save
-function, but adding them using CSScontent
-properties. Even more ugly than the previous option, especially as it will require a nasty PHP-CSS-mashup to include the conditional statements.
Question: Are there any other ways to achieve this, or should I just go for one of my ideas posted above?
Share Improve this question asked Jan 6, 2019 at 11:33 BramBram 2761 silver badge15 bronze badges1 Answer
Reset to default 1Although a little different than I had initially anticipated, I have found/created a solution to my two problems.
Conditionals
I have opted to move non-required input to the sidebar, using the InspectorControls
-element. Reason for this is that I realised that it would be impossible to hide a field (in the editor-part of the screen) that was left blank (required to show an accurate front-end representation), while still being able to edit that field.
For example, this is the definition of a textfield in the sidebar.
el(TextControl, {
type: 'text',
value: attr.location,
label: __('Location', 'peerlings-blocks'),
onChange: function (newLocation) {
props.setAttributes({location: newLocation})
}
}),
This is (in the edit
- and save
-parts) the code used for displaying it, if the field has been used
attr.location && el('span', {className: 'location'}, attr.location),
The reason why I originally didn't want to move these details to the sidebar was that I thought they then wouldn't show in the editor-preview, but using the code above, that was possible to achieve. Problem solved.
Separators and wrappers
This took some more fiddling to get to work, for which I had a few revelations:
- The
selector
(defined as part of anyattribute
) looks at the HTML extracted frompost_content
to find the value of a particular attribute. If this selection finds other HTML than expected, the block validation fails. - The
edit
-part is used to display the block-'preview' in the Wordpress Dashboard. Also, it reads the HTML frompost_content
, when a block is edited again. On the other hand, thesave
-part transforms the attribute values to a HTML that is sent to (and saved in)post_content
. Essentially,save
encodes the block to HTML,edit
decodes the HTML to attributes (found in the selectors discussed previously). This explains why minor differences between the two (in the parts found by the selectors) immediately invalidate the block.
Given the above, I placed the separators and wrappers outside of the elements that are selected to retrieve the attributes. That resulted in some extra wrapper elements, but does the trick. To illustrate, the shortcode had the following HTML output:
<span class="cv-item-details">08/2013 - current | 6 hours per week</span>
Now, that line looks like this:
<span class="resume-item-details">
<span class="duration">
08/2013 – current
</span>
|
<span class="hours">
<span class="data">
6
</span>
hours per week
</span>
</span>
The problems I had originally envisioned (separators in between block-level input fields) were essentially solved by moving a number of input fields to the sidebar (as outlined above). Separators between input fields that do remain are only included in the save
-part, and that works as intended - as shown in the code below.
edit
-part:
el('span', {className: 'resume-item-title'},
el(RichText, {
key: 'editable',
tagName: 'strong',
className: 'name',
placeholder: __('Name', 'peerlings-blocks'),
keepPlaceholderOnFocus: true,
value: attr.name,
onChange: function (newName) {
props.setAttributes({name: newName})
}
}),
el(RichText, {
key: 'editable',
className: 'organisation',
placeholder: __('Organisation', 'peerlings-blocks'),
keepPlaceholderOnFocus: true,
value: attranisation,
onChange: function (newOrganisation) {
props.setAttributes({organisation: newOrganisation.replace('<br>', '')})
}
})
),
save
-part (note the comma between the two RichText
-parts, conditional on attranisation
not being empty):
el('span',
{className: 'resume-item-title'},
el(RichText.Content, {
tagName: 'strong',
className: 'name',
value: attr.name
}),
attranisation && ', ',
el(RichText.Content, {
tagName: 'span',
className: 'organisation',
value: attranisation
})
),
You can find the full JS-file of the block (at least, the current version) here.