{"id":6001,"date":"2020-08-05T15:17:43","date_gmt":"2020-08-05T13:17:43","guid":{"rendered":"https:\/\/florianbrinkmann.com\/en\/?p=6001"},"modified":"2020-08-05T15:18:06","modified_gmt":"2020-08-05T13:18:06","slug":"gutenberg-field-post-tags","status":"publish","type":"post","link":"https:\/\/florianbrinkmann.com\/en\/gutenberg-field-post-tags-6001\/","title":{"rendered":"Create Gutenberg field like the one for post tags"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">For a project I needed the feature to link posts of a custom post type with posts of another custom post type. I did not know which Gutenberg component would be the best match for that, but then the tags field for post tags seemed like a good fit: the user types something and gets suggestions which he can add to a selection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/github.com\/WordPress\/gutenberg\/blob\/master\/packages\/components\/src\/form-token-field\/README.md\"><code>FormTokenField<\/code> can<\/a> be used to create that behavior, all possible options can be found in the linked readme. With that, I had my component of choice, just with custom post type posts instead of tags.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p class=\"wp-block-paragraph\">That is how the component looks like as part of the project (code is reduced to a minimum):<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-code-table shcb-line-numbers\"><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ &#91;\u2026]<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ `posts` contains post objects from REST API call.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-comment\">\/\/ `selectedPosts` contains array of currently selected post IDs.<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">let<\/span> postNames = &#91;];\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">let<\/span> postFieldValue = &#91;];\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">if<\/span> ( posts !== <span class=\"hljs-literal\">null<\/span> ) {\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/ Create post names array.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\tpostNames = posts.map( <span class=\"hljs-function\">(<span class=\"hljs-params\"> post <\/span>) =&gt;<\/span> post.title.raw );\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span>\t<span class=\"hljs-comment\">\/\/ Create array of post titles that match the post IDs that are currently selected.<\/span>\n<\/span><\/span><span class='shcb-loc'><span>\tpostFieldValue = selectedPosts.map( <span class=\"hljs-function\">(<span class=\"hljs-params\"> postId <\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">let<\/span> wantedPost = posts.find( <span class=\"hljs-function\">(<span class=\"hljs-params\"> post <\/span>) =&gt;<\/span> {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t<span class=\"hljs-keyword\">return<\/span> post.id === postId;\n<\/span><\/span><span class='shcb-loc'><span>\t\t} );\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">if<\/span> ( wantedPost === <span class=\"hljs-literal\">undefined<\/span> || ! wantedPost ) {\n<\/span><\/span><span class='shcb-loc'><span>\t\t\t<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>;\n<\/span><\/span><span class='shcb-loc'><span>\t\t}\n<\/span><\/span><span class='shcb-loc'><span>\t\t<span class=\"hljs-keyword\">return<\/span> wantedPost.title.raw;\n<\/span><\/span><span class='shcb-loc'><span>\t} );\n<\/span><\/span><span class='shcb-loc'><span>}\n<\/span><\/span><span class='shcb-loc'><span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"hljs-keyword\">const<\/span> postsSelectComponent = <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">FormTokenField<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t<span class=\"hljs-attr\">label<\/span>=<span class=\"hljs-string\">'Posts'<\/span><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t<span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{<\/span> <span class=\"hljs-attr\">postFieldValue<\/span> }<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t<span class=\"hljs-attr\">suggestions<\/span>=<span class=\"hljs-string\">{<\/span> <span class=\"hljs-attr\">postNames<\/span> }<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t<span class=\"hljs-attr\">maxSuggestions<\/span>=<span class=\"hljs-string\">{<\/span> <span class=\"hljs-attr\">20<\/span> }<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t<span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{<\/span> ( <span class=\"hljs-attr\">selectedPosts<\/span> ) =&gt;<\/span> {<\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\/\/ Build array of selected posts.<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\tlet selectedPostsArray = &#91;];<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\tselectedPosts.map(<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t( postName ) =&gt; {<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t\tconst matchingPost = posts.find( ( post ) =&gt; {<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t\t\treturn post.title.raw === postName;<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t\t} );<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t\tif ( matchingPost !== undefined ) {<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t\t\tselectedPostsArray.push( matchingPost.id );<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t\t}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t\t}<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\t)<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\"><\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t\tsetAttributes( { selectedPosts: selectedPostsArray } );<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\t} }<\/span><\/span>\n<\/span><\/span><span class='shcb-loc'><span><span class=\"xml\"><span class=\"hljs-tag\">\/&gt;<\/span><\/span>\n<\/span><\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Because I do not want to show the post IDs as suggestions to the user, that are stored inside a meta value, but the title, it is a little more complex than the example in the readme. Starting at line 27, I need to convert the selected post title into the ID before saving it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">At the beginning of the code block instead, the IDs need to be converted to the titles for the suggestions list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I created a <a href=\"https:\/\/gist.github.com\/florianbrinkmann\/167939b3e0a8c33a5ae3f1c0dc561859\">Gist with a working code example<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>For a project I needed the feature to link posts of a custom post type with posts of another custom post type. I did not know which Gutenberg component would be the best match for that, but then the tags field for post tags seemed like a good fit: the user types something and gets [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wpf_show_in_dewp_planet_feed":false,"flobn_post_versions":"","webmentions_disabled_pings":false,"webmentions_disabled":false,"lazy_load_responsive_images_disabled":false,"footnotes":""},"categories":[115],"tags":[],"class_list":["post-6001","post","type-post","status-publish","format-standard","hentry","category-wordpress-snippets"],"wp-worthy-pixel":{"ignored":false,"public":"965b0c85f30f4c989d819cae3e51a352","server":"vg07.met.vgwort.de","url":"https:\/\/vg07.met.vgwort.de\/na\/965b0c85f30f4c989d819cae3e51a352"},"wp-worthy-type":"normal","_links":{"self":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/6001","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/comments?post=6001"}],"version-history":[{"count":4,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/6001\/revisions"}],"predecessor-version":[{"id":6005,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/posts\/6001\/revisions\/6005"}],"wp:attachment":[{"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/media?parent=6001"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/categories?post=6001"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/florianbrinkmann.com\/en\/wp-json\/wp\/v2\/tags?post=6001"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}