{"id":903,"date":"2025-01-22T22:53:31","date_gmt":"2025-01-22T22:53:31","guid":{"rendered":"https:\/\/bendauphinee.com\/writing\/?p=903"},"modified":"2025-01-22T22:53:31","modified_gmt":"2025-01-22T22:53:31","slug":"an-app-from-scratch-part-10-reflections-and-template-editing","status":"publish","type":"post","link":"https:\/\/bendauphinee.com\/writing\/2025\/01\/22\/an-app-from-scratch-part-10-reflections-and-template-editing\/","title":{"rendered":"An App From Scratch: Part 10 \u2013 Reflections And Template Editing"},"content":{"rendered":"\n<p class=\"has-small-font-size\">11 minute reading time; ~2120 words<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<p>Welcome and hello!<\/p>\n\n\n\n<p>In this post, we&#8217;re going to touch on the overall lessons from the series so far, and then dive into delivering the ability to actually edit a template.<\/p>\n\n\n\n<p class=\"has-accent-5-background-color has-background\">If you haven&#8217;t read the previous posts, I&#8217;d suggest you start at <a href=\"https:\/\/bendauphinee.com\/writing\/2024\/12\/20\/designing-an-app-from-scratch-part-1-what-to-build\/\" data-type=\"link\" data-id=\"https:\/\/bendauphinee.com\/writing\/2024\/12\/20\/designing-an-app-from-scratch-part-1-what-to-build\/\">An App From Scratch: Part 1 \u2013 What To Build<\/a>. You can also find all the related documents and code here: <a href=\"https:\/\/bendauphinee.com\/writing\/building-tailgunner\/\">Building Tailgunner<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Some Housekeeping<\/h2>\n\n\n\n<p>In between the last post and this one, I made a few small changes to address some housekeeping notes from the work I&#8217;ve done.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/github.com\/bendauphinee\/tailgunner\/pull\/11\">Add GA to tailgunner<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/bendauphinee\/tailgunner\/pull\/12\">Commit new .gitignore<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Reflections \/ Lessons<\/h2>\n\n\n\n<p>So far in this series we&#8217;ve explored what it takes to get from an overall idea down to an actual functional user feature. Along the way, we talked about how to explore the idea, and break it down into smaller and smaller pieces, until we reached a point where an engineer can actually execute and deliver the functional product, incrementally building in a way that always left us with something we could use.<\/p>\n\n\n\n<p>One of the biggest reflections I have is how much work is involved in both making a change and also breaking down the change and documenting every piece of it. It&#8217;s something I&#8217;d forgotten, because I&#8217;ve spent so much time over the last two years reasoning about the documentation part, freeing up the developers on my team to focus on building it.<\/p>\n\n\n\n<p>Hopefully through this series so far, you&#8217;ve gained a new appreciation for the parts of the process that you didn&#8217;t know well, be it on the product design and planning side, or the building side of the fence. For every hour of code I&#8217;ve written, I&#8217;ve spent at least twice that time talking about it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Expanding The Functionality<\/h2>\n\n\n\n<p>Now that we&#8217;ve completed the ability to add templates, we&#8217;re going to zoom out and tackle more of the workflows we previously identified for <a href=\"https:\/\/bendauphinee.com\/writing\/project-plan-v2\/#us1\">US1<\/a>.<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-contrast-background-color has-text-color has-background has-link-color wp-elements-d5edc563d01ba25838af7ccd636ff270 has-global-padding is-layout-constrained wp-container-core-group-is-layout-9597dc02 wp-block-group-is-layout-constrained\" style=\"padding-top:var(--wp--preset--spacing--10);padding-right:var(--wp--preset--spacing--10);padding-bottom:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--10)\">\n<p><strong>US1: Workflows<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><mark style=\"background-color:#86dd61\" class=\"has-inline-color\"><strong>Creating a template<\/strong><br>Defining the metadata of a new template, including title.<\/mark><\/li>\n\n\n\n<li><mark style=\"background-color:#97d8ff\" class=\"has-inline-color\"><strong>Editing the template metadata<\/strong><br>Changing the metadata for a template.<\/mark><\/li>\n\n\n\n<li><mark style=\"background-color:#97d8ff\" class=\"has-inline-color\"><strong>Editing the form fields in a template<\/strong><br>Adding, editing, re-ordering, and deleting the fields in the form.<\/mark><\/li>\n\n\n\n<li><mark style=\"background-color:#97d8ff\" class=\"has-inline-color\"><strong>Editing the options of a form dropdown<\/strong><br>Adding, editing, re-ordering, and deleting the options in a dropdown.<\/mark><\/li>\n\n\n\n<li><mark style=\"background-color:#97d8ff\" class=\"has-inline-color\"><strong>Saving changes to the template<\/strong><br>Persisting all changes to a template.<\/mark><\/li>\n\n\n\n<li><strong>Cloning a template<\/strong><br>Duplicating a template, with a new name.<\/li>\n\n\n\n<li><strong>Deleting a template<\/strong><br>Permanently removing a form template from the system.<\/li>\n<\/ul>\n<\/div>\n\n\n\n<p>This is a much larger piece of work in a single post, so I&#8217;m going to only dive into specific pieces of the work I&#8217;ve done.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Interesting Changes<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Font Awesome Icons<\/h3>\n\n\n\n<p>I wanted to add some iconography to the site, so I installed FontAwesome, using <a href=\"https:\/\/docs.fontawesome.com\/web\/use-with\/vue\">their guide<\/a>. <\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">Install Commands<\/span><span role=\"button\" tabindex=\"0\" data-code=\"npm i --save @fortawesome\/fontawesome-svg-core\nnpm i --save @fortawesome\/free-regular-svg-icons\nnpm i --save @fortawesome\/vue-fontawesome@latest-3\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #DCDCAA\">npm<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">i<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #569CD6\">--save<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">@fortawesome\/fontawesome-svg-core<\/span><\/span>\n<span class=\"line\"><span style=\"color: #DCDCAA\">npm<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">i<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #569CD6\">--save<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">@fortawesome\/free-regular-svg-icons<\/span><\/span>\n<span class=\"line\"><span style=\"color: #DCDCAA\">npm<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">i<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #569CD6\">--save<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">@fortawesome\/vue-fontawesome@latest-3<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Since I&#8217;m not using a Kit (a pre-built collection of icons), I need to specify each icon that I want. We also need to add the library into our Inertia app as a component (line 20).<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .75rem);--cbp-line-highlight-color:rgba(234, 191, 191, 0.2);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">JavaScript<\/span><span role=\"button\" tabindex=\"0\" data-code=\"\/* Set up Font Awesome *\/\nimport { library } from '@fortawesome\/fontawesome-svg-core'\nimport { FontAwesomeIcon } from '@fortawesome\/vue-fontawesome'\n\n\/* Import some regular style icons *\/\nimport { faTrashCan, faClone } from '@fortawesome\/free-regular-svg-icons'\nlibrary.add(faTrashCan, faClone)\n\n\/* Import some solid style icons *\/\nimport { faSort, faPlus, faEye, faGripVertical, faTimes, faTriangleExclamation, faCircleCheck, faSpinner } from '@fortawesome\/free-solid-svg-icons'\nlibrary.add(faSort, faPlus, faEye, faGripVertical, faTimes, faTriangleExclamation, faCircleCheck, faSpinner)\n\ncreateInertiaApp({\n    title: (title) =&gt; `${title} - ${appName}`,\n    resolve: (name) =&gt; resolvePageComponent(`.\/Pages\/${name}.vue`, import.meta.glob('.\/Pages\/**\/*.vue')),\n    setup({ el, App, props, plugin }) {\n        return createApp({ render: () =&gt; h(App, props) })\n            .use(plugin)\n            .use(ZiggyVue)\n            .component('font-awesome-icon', FontAwesomeIcon)\n            .mount(el);\n    },\n    progress: {\n        color: '#4B5563',\n    },\n});\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #6A9955\">\/* Set up Font Awesome *\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">import<\/span><span style=\"color: #D4D4D4\"> { <\/span><span style=\"color: #9CDCFE\">library<\/span><span style=\"color: #D4D4D4\"> } <\/span><span style=\"color: #C586C0\">from<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;@fortawesome\/fontawesome-svg-core&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">import<\/span><span style=\"color: #D4D4D4\"> { <\/span><span style=\"color: #9CDCFE\">FontAwesomeIcon<\/span><span style=\"color: #D4D4D4\"> } <\/span><span style=\"color: #C586C0\">from<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;@fortawesome\/vue-fontawesome&#39;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\">\/* Import some regular style icons *\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">import<\/span><span style=\"color: #D4D4D4\"> { <\/span><span style=\"color: #9CDCFE\">faTrashCan<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faClone<\/span><span style=\"color: #D4D4D4\"> } <\/span><span style=\"color: #C586C0\">from<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;@fortawesome\/free-regular-svg-icons&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">library<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #DCDCAA\">add<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">faTrashCan<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faClone<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\">\/* Import some solid style icons *\/<\/span><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">import<\/span><span style=\"color: #D4D4D4\"> { <\/span><span style=\"color: #9CDCFE\">faSort<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faPlus<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faEye<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faGripVertical<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faTimes<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faTriangleExclamation<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faCircleCheck<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faSpinner<\/span><span style=\"color: #D4D4D4\"> } <\/span><span style=\"color: #C586C0\">from<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;@fortawesome\/free-solid-svg-icons&#39;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #9CDCFE\">library<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #DCDCAA\">add<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">faSort<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faPlus<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faEye<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faGripVertical<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faTimes<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faTriangleExclamation<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faCircleCheck<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">faSpinner<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #DCDCAA\">createInertiaApp<\/span><span style=\"color: #D4D4D4\">({<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #DCDCAA\">title<\/span><span style=\"color: #9CDCFE\">:<\/span><span style=\"color: #D4D4D4\"> (<\/span><span style=\"color: #9CDCFE\">title<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">`<\/span><span style=\"color: #569CD6\">${<\/span><span style=\"color: #9CDCFE\">title<\/span><span style=\"color: #569CD6\">}<\/span><span style=\"color: #CE9178\"> - <\/span><span style=\"color: #569CD6\">${<\/span><span style=\"color: #9CDCFE\">appName<\/span><span style=\"color: #569CD6\">}<\/span><span style=\"color: #CE9178\">`<\/span><span style=\"color: #D4D4D4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #DCDCAA\">resolve<\/span><span style=\"color: #9CDCFE\">:<\/span><span style=\"color: #D4D4D4\"> (<\/span><span style=\"color: #9CDCFE\">name<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">resolvePageComponent<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #CE9178\">`.\/Pages\/<\/span><span style=\"color: #569CD6\">${<\/span><span style=\"color: #9CDCFE\">name<\/span><span style=\"color: #569CD6\">}<\/span><span style=\"color: #CE9178\">.vue`<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #C586C0\">import<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">meta<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #DCDCAA\">glob<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #CE9178\">&#39;.\/Pages\/**\/*.vue&#39;<\/span><span style=\"color: #D4D4D4\">)),<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #DCDCAA\">setup<\/span><span style=\"color: #D4D4D4\">({ <\/span><span style=\"color: #9CDCFE\">el<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">App<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">props<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">plugin<\/span><span style=\"color: #D4D4D4\"> }) {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #C586C0\">return<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">createApp<\/span><span style=\"color: #D4D4D4\">({ <\/span><span style=\"color: #DCDCAA\">render<\/span><span style=\"color: #9CDCFE\">:<\/span><span style=\"color: #D4D4D4\"> () <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">h<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">App<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">props<\/span><span style=\"color: #D4D4D4\">) })<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">            .<\/span><span style=\"color: #DCDCAA\">use<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">plugin<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">            .<\/span><span style=\"color: #DCDCAA\">use<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">ZiggyVue<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line cbp-line-highlight\"><span style=\"color: #D4D4D4\">            .<\/span><span style=\"color: #DCDCAA\">component<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #CE9178\">&#39;font-awesome-icon&#39;<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">FontAwesomeIcon<\/span><span style=\"color: #D4D4D4\">)<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">            .<\/span><span style=\"color: #DCDCAA\">mount<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">el<\/span><span style=\"color: #D4D4D4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    },<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #9CDCFE\">progress:<\/span><span style=\"color: #D4D4D4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #9CDCFE\">color:<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;#4B5563&#39;<\/span><span style=\"color: #D4D4D4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    },<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">});<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Create Template Edit Page<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Drag To Organize<\/h4>\n\n\n\n<p>There are two things we need to sort: the fields in the template, and also the options in our dropdown \/ checkbox fields. Drag to organize is great, because the feedback is immediate and it&#8217;s easy to work with.<\/p>\n\n\n\n<p>Building it was a bit of a challenge, because we&#8217;re sorting nested elements when we act on dropdown options, but here&#8217;s the final version.<\/p>\n\n\n\n<p>First, we added a handle to grab, so we can attach the code to it.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">Vue<\/span><span role=\"button\" tabindex=\"0\" data-code=\"&lt;div :class=&quot;dragClasses.handle&quot; draggable=&quot;true&quot;\n    @dragstart=&quot;fieldsDraggable.handleDragStart(index, $event)&quot;\n    @dragover.prevent=&quot;(e) =&gt; props.template.fields = fieldsDraggable.handleDragOver(index, props.template.fields, e)&quot;\n    @dragend=&quot;fieldsDraggable.handleDragEnd&quot;&gt;\n    &lt;font-awesome-icon :icon=&quot;['fas', 'grip-vertical']&quot; \/&gt;\n&lt;\/div&gt;\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #569CD6\">div<\/span><span style=\"color: #D4D4D4\"> :<\/span><span style=\"color: #9CDCFE\">class<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><span style=\"color: #9CDCFE\">dragClasses<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">handle<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">draggable<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;true&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    @<\/span><span style=\"color: #9CDCFE\">dragstart<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><span style=\"color: #9CDCFE\">fieldsDraggable<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #DCDCAA\">handleDragStart<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">index<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">$event<\/span><span style=\"color: #D4D4D4\">)<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    @<\/span><span style=\"color: #9CDCFE\">dragover<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">prevent<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">e<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">props<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">template<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">fields<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #9CDCFE\">fieldsDraggable<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #DCDCAA\">handleDragOver<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">index<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">props<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">template<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">fields<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">e<\/span><span style=\"color: #D4D4D4\">)<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    @<\/span><span style=\"color: #9CDCFE\">dragend<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><span style=\"color: #9CDCFE\">fieldsDraggable<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">handleDragEnd<\/span><span style=\"color: #D4D4D4\">&quot;<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    &lt;font-awesome-icon :icon=&quot;[&#39;fas&#39;, &#39;grip-vertical&#39;]&quot; \/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #808080\">&lt;\/<\/span><span style=\"color: #569CD6\">div<\/span><span style=\"color: #808080\">&gt;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>We&#8217;ve also got some code to handle the various actions related to dragging:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Drag start (when we click and grab the handle)<\/li>\n\n\n\n<li>Drag over (when we move the handle over a different row)<\/li>\n\n\n\n<li>Drag end (when we let go of the handle)<\/li>\n<\/ul>\n\n\n\n<p>There&#8217;s also a state function called <code>isDragging<\/code> that tells us if a specific field is being dragged, so we can add some CSS to partially fade the row, and highlight it, so we can see where we&#8217;re dragging it to.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"583\" height=\"244\" src=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-20.png\" alt=\"\" class=\"wp-image-916\" srcset=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-20.png 583w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-20-300x126.png 300w\" sizes=\"auto, (max-width: 583px) 100vw, 583px\" \/><\/figure>\n\n\n\n<p>Finally, all this code was extracted to a <a href=\"https:\/\/vuejs.org\/guide\/reusability\/composables\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">composable<\/a>, which is a small piece of code that is intended to be reusable in multiple places, and tracks state (for example, the position of a mouse on a page). This composable also pulls in the CSS for the draggable elements, so it&#8217;s all in one place. This approach sets us up to be able to add draggable elements anywhere we want in Tailgunner!<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">JavaScript<\/span><span role=\"button\" tabindex=\"0\" data-code=\"import '.\/useDraggable.css';\n\nexport function useDraggable(listId) {\n\n    const handleDragStart = (index, event) =&gt; {};\n\n    const handleDragOver = (index, items, event) =&gt; {};\n\n    const handleDragEnd = () =&gt; {};\n\n    const isDragging = (index) =&gt; {};\n\n    \/\/ The css classes for the draggable\n    const dragClasses = {};\n\n    return {\n        isDragging,\n        handleDragStart,\n        handleDragOver,\n        handleDragEnd,\n        dragClasses\n    };\n}\n\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #C586C0\">import<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;.\/useDraggable.css&#39;<\/span><span style=\"color: #D4D4D4\">;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #C586C0\">export<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #569CD6\">function<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">useDraggable<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">listId<\/span><span style=\"color: #D4D4D4\">) {<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">handleDragStart<\/span><span style=\"color: #D4D4D4\"> = (<\/span><span style=\"color: #9CDCFE\">index<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">event<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> {};<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">handleDragOver<\/span><span style=\"color: #D4D4D4\"> = (<\/span><span style=\"color: #9CDCFE\">index<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">items<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">event<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> {};<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">handleDragEnd<\/span><span style=\"color: #D4D4D4\"> = () <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> {};<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">isDragging<\/span><span style=\"color: #D4D4D4\"> = (<\/span><span style=\"color: #9CDCFE\">index<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> {};<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #6A9955\">\/\/ The css classes for the draggable<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #4FC1FF\">dragClasses<\/span><span style=\"color: #D4D4D4\"> = {};<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #C586C0\">return<\/span><span style=\"color: #D4D4D4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #9CDCFE\">isDragging<\/span><span style=\"color: #D4D4D4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #9CDCFE\">handleDragStart<\/span><span style=\"color: #D4D4D4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #9CDCFE\">handleDragOver<\/span><span style=\"color: #D4D4D4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #9CDCFE\">handleDragEnd<\/span><span style=\"color: #D4D4D4\">,<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #9CDCFE\">dragClasses<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    };<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">}<\/span><\/span>\n<span class=\"line\"><\/span><\/code><\/pre><\/div>\n\n\n\n<h4 class=\"wp-block-heading\">Making Sure We Save<\/h4>\n\n\n\n<p>The save button is built to serve as a good indicator of the template state. We&#8217;ve done this in a few ways:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The text of the button tells you if there are no changes, if we&#8217;re saving, or to save.<\/li>\n\n\n\n<li>There&#8217;s also an icon to indicate the status.<\/li>\n\n\n\n<li>The button is disabled if there are no changes or we&#8217;re currently saving.<\/li>\n<\/ul>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(2 * 0.6 * .75rem);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">Vue<\/span><span role=\"button\" tabindex=\"0\" data-code=\"&lt;script&gt; &lt;!-- Ignore this, just for the formatter --&gt;\n\n&lt;button\n    class=&quot;save_button&quot;\n    type=&quot;submit&quot;\n    @click=&quot;saveTemplate&quot;\n    :disabled=&quot;!isModified || isSaving&quot;\n&gt;\n    &lt;span v-if=&quot;!isModified&quot;&gt;\n        &lt;font-awesome-icon :icon=&quot;['fas', 'circle-check']&quot; class=&quot;text-green-400&quot; \/&gt;\n        No Changes To Save\n    &lt;\/span&gt;\n    &lt;span v-else-if=&quot;isSaving&quot;&gt;\n        &lt;font-awesome-icon :icon=&quot;['fas', 'spinner']&quot; class=&quot;animate-spin&quot; \/&gt;\n        Saving...\n    &lt;\/span&gt;\n    &lt;span v-else&gt;\n        &lt;font-awesome-icon :icon=&quot;['fas', 'triangle-exclamation']&quot; class=&quot;text-yellow-400&quot; \/&gt;\n        Save Template Changes\n    &lt;\/span&gt;\n&lt;\/button&gt;\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #569CD6\">script<\/span><span style=\"color: #808080\">&gt;<\/span><span style=\"color: #D4D4D4\"> &lt;!-- <\/span><span style=\"color: #9CDCFE\">Ignore<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #569CD6\">this<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #9CDCFE\">just<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">for<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">the<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">formatter<\/span><span style=\"color: #D4D4D4\"> --&gt;<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #569CD6\">button<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #9CDCFE\">class<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;save_button&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #9CDCFE\">type<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;submit&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #F44747\">@click=&quot;saveTemplate&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #F44747\">:disabled=&quot;!isModified<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #F44747\">||<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #F44747\">isSaving&quot;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #569CD6\">span<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">v-if<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;!isModified&quot;<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #4EC9B0\">font-awesome-icon<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #F44747\">:icon=&quot;[&#39;fas&#39;,<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;circle-check&#39;<\/span><span style=\"color: #F44747\">]&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">class<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;text-green-400&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #808080\">\/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        No Changes To Save<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #808080\">&lt;\/<\/span><span style=\"color: #569CD6\">span<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #569CD6\">span<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">v-else-if<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;isSaving&quot;<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #4EC9B0\">font-awesome-icon<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #F44747\">:icon=&quot;[&#39;fas&#39;,<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;spinner&#39;<\/span><span style=\"color: #F44747\">]&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">class<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;animate-spin&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #808080\">\/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        Saving...<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #808080\">&lt;\/<\/span><span style=\"color: #569CD6\">span<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #569CD6\">span<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">v-else<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        <\/span><span style=\"color: #808080\">&lt;<\/span><span style=\"color: #4EC9B0\">font-awesome-icon<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #F44747\">:icon=&quot;[&#39;fas&#39;,<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #CE9178\">&#39;triangle-exclamation&#39;<\/span><span style=\"color: #F44747\">]&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">class<\/span><span style=\"color: #D4D4D4\">=<\/span><span style=\"color: #CE9178\">&quot;text-yellow-400&quot;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #808080\">\/&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">        Save Template Changes<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #808080\">&lt;\/<\/span><span style=\"color: #569CD6\">span<\/span><span style=\"color: #808080\">&gt;<\/span><\/span>\n<span class=\"line\"><span style=\"color: #808080\">&lt;\/<\/span><span style=\"color: #569CD6\">button<\/span><span style=\"color: #808080\">&gt;<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>Along with that, if there have been changes and the user clicks the &#8220;Cancel Changes \/ Return To List&#8221; button, they&#8217;re prompted to confirm they want to discard the changes.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Flash Messages<\/h4>\n\n\n\n<p>One useful UI element is something called a flash message. Intended for a short message that doesn&#8217;t need to be acknowledged, flash messages allow us to unobtrusively pop up a notification that something did or did not go wrong for the user. (If you were paying attention, we did a different style of flash message <a href=\"https:\/\/bendauphinee.com\/writing\/2025\/01\/14\/an-app-from-scratch-part-8-creating-new-templates-tool-us1-c3\/#flashmsg\" data-type=\"post\" data-id=\"747\" target=\"_blank\" rel=\"noreferrer noopener\">previously<\/a>).<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"152\" src=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-23.png\" alt=\"\" class=\"wp-image-921\" srcset=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-23.png 705w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-23-300x65.png 300w\" sizes=\"auto, (max-width: 705px) 100vw, 705px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"583\" height=\"152\" src=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-22.png\" alt=\"\" class=\"wp-image-920\" srcset=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-22.png 583w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-22-300x78.png 300w\" sizes=\"auto, (max-width: 583px) 100vw, 583px\" \/><\/figure>\n\n\n\n<p>To solve this, I&#8217;ve built another composable, and a small template element for these messages. The template contains the styles as well as the HTML required to show the message. This template was registered in the AppLayout, so it&#8217;s available across the whole application.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Debounce<\/h4>\n\n\n\n<p>Debouncing is an optimization to reduce load on a system caused by rapid changes. <\/p>\n\n\n\n<p>Here&#8217;s an example of what that actually means:<\/p>\n\n\n\n<p>Imagine I ask you to list all the lights in your house and whether they&#8217;re on or off. Simple, right? Now, imagine I want you to update that list every time I flip a switch. That\u2019s still manageable if I only flip one switch every few seconds. However, if I start flipping switches multiple times per second, it quickly becomes overwhelming to keep up.<\/p>\n\n\n\n<p>Debounce is like saying, &#8220;It&#8217;s okay if the list isn&#8217;t updated instantly, as long as it&#8217;s accurate when I need it.&#8221; If I only check the list once a minute, you just need to note that a switch was flipped and update the list before I look again, which reduces your workload significantly.<\/p>\n\n\n\n<p>In the page, there&#8217;s a validation that checks to make sure that two fields don&#8217;t have the same name.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"397\" height=\"349\" src=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-19.png\" alt=\"\" class=\"wp-image-913\" srcset=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-19.png 397w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-19-300x264.png 300w\" sizes=\"auto, (max-width: 397px) 100vw, 397px\" \/><\/figure>\n\n\n\n<p>This check watches the field names, and since they update every time someone types a character, the check would run very frequently. I&#8217;ve applied a debounce to this code, so that it will only check at most once every 100ms.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .75rem);--cbp-line-highlight-color:rgba(234, 191, 191, 0.2);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">JavaScript<\/span><span role=\"button\" tabindex=\"0\" data-code=\"const debouncedValidateFieldNames = debounce(validateFieldNames, 100);\n\n\/\/ Watch for field name changes\nwatch(() =&gt; props.template.fields, () =&gt; {\n    debouncedValidateFieldNames();\n}, { deep: true });\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line cbp-line-highlight\"><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #4FC1FF\">debouncedValidateFieldNames<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #DCDCAA\">debounce<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #9CDCFE\">validateFieldNames<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #B5CEA8\">100<\/span><span style=\"color: #D4D4D4\">);<\/span><\/span>\n<span class=\"line\"><\/span>\n<span class=\"line\"><span style=\"color: #6A9955\">\/\/ Watch for field name changes<\/span><\/span>\n<span class=\"line\"><span style=\"color: #DCDCAA\">watch<\/span><span style=\"color: #D4D4D4\">(() <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #9CDCFE\">props<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">template<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">fields<\/span><span style=\"color: #D4D4D4\">, () <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #DCDCAA\">debouncedValidateFieldNames<\/span><span style=\"color: #D4D4D4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">}, { <\/span><span style=\"color: #9CDCFE\">deep:<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #569CD6\">true<\/span><span style=\"color: #D4D4D4\"> });<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<p>This means that the page still feels responsive, because it&#8217;s not a lot of time in between checks, but also doesn&#8217;t lag our browser if we type fast. The more fields we have in our template, the bigger the impact of this debounce is, because it would take longer and longer to check.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">No Spaces In Field Names<\/h4>\n\n\n\n<p>Since field names are the programmatic name we want to attach to pieces of data, they need to make sense, and one of the requirements is names should not contain spaces. We could have handled this by displaying an error on the field, or when saving, to tell a user to remove the spaces, but I had a better idea.<\/p>\n\n\n\n<p>As the user types, we are running debounced validation to check if we&#8217;re duplicating the name. The other thing I&#8217;ve added is an automatic replacement of spaces with underscores, so as a user types in a field name, when they hit space, it&#8217;s immediately replaced.<\/p>\n\n\n\n<div class=\"wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers\" data-code-block-pro-font-family=\"Code-Pro-JetBrains-Mono\" style=\"font-size:.75rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#D4D4D4;--cbp-line-number-width:calc(1 * 0.6 * .75rem);--cbp-line-highlight-color:rgba(234, 191, 191, 0.2);line-height:1rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)\"><span style=\"display:flex;align-items:center;padding:10px 0px 10px 16px;margin-bottom:-2px;width:100%;text-align:left;background-color:#2b2b2b;color:#c7c7c7\">JavaScript<\/span><span role=\"button\" tabindex=\"0\" data-code=\"const handleFieldNameInput = (field) =&gt; {\n    \/\/ Replace spaces with underscores\n    field.name = field.name.replace(\/\\s+\/g, '_');\n    debouncedValidateFieldNames();\n};\" style=\"color:#D4D4D4;display:none\" aria-label=\"Copy\" class=\"code-block-pro-copy-button\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"width:24px;height:24px\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2\"><path class=\"with-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4\"><\/path><path class=\"without-check\" stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2\"><\/path><\/svg><\/span><pre class=\"shiki dark-plus\" style=\"background-color: #1E1E1E\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color: #569CD6\">const<\/span><span style=\"color: #D4D4D4\"> <\/span><span style=\"color: #DCDCAA\">handleFieldNameInput<\/span><span style=\"color: #D4D4D4\"> = (<\/span><span style=\"color: #9CDCFE\">field<\/span><span style=\"color: #D4D4D4\">) <\/span><span style=\"color: #569CD6\">=&gt;<\/span><span style=\"color: #D4D4D4\"> {<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #6A9955\">\/\/ Replace spaces with underscores<\/span><\/span>\n<span class=\"line cbp-line-highlight\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #9CDCFE\">field<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">name<\/span><span style=\"color: #D4D4D4\"> = <\/span><span style=\"color: #9CDCFE\">field<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #9CDCFE\">name<\/span><span style=\"color: #D4D4D4\">.<\/span><span style=\"color: #DCDCAA\">replace<\/span><span style=\"color: #D4D4D4\">(<\/span><span style=\"color: #D16969\">\/\\s<\/span><span style=\"color: #D7BA7D\">+<\/span><span style=\"color: #D16969\">\/<\/span><span style=\"color: #569CD6\">g<\/span><span style=\"color: #D4D4D4\">, <\/span><span style=\"color: #CE9178\">&#39;_&#39;<\/span><span style=\"color: #D4D4D4\">);<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">    <\/span><span style=\"color: #DCDCAA\">debouncedValidateFieldNames<\/span><span style=\"color: #D4D4D4\">();<\/span><\/span>\n<span class=\"line\"><span style=\"color: #D4D4D4\">};<\/span><\/span><\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Manual Testing<\/h2>\n\n\n\n<p>I&#8217;ve not wired up an automated testing suite for the UI actions, but the first step is always knowing what in the UI I want to test. As part of either planning, or during development, a list of test cases will start to emerge. They may be written down, or may just be in a developer&#8217;s head (and really should be written down if it&#8217;s more than just you \ud83d\ude06), but they always exist.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"759\" src=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-24-1024x759.png\" alt=\"\" class=\"wp-image-922\" srcset=\"https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-24-1024x759.png 1024w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-24-300x222.png 300w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-24-768x569.png 768w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-24-1536x1138.png 1536w, https:\/\/bendauphinee.com\/writing\/wp-content\/uploads\/2025\/01\/image-24.png 1548w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Here&#8217;s a list of all the things that we can and should test on this one page (22 that I could think of). The goal of testing is to make sure every button does something, the whole interface works, and we check both the <a href=\"https:\/\/www.google.ca\/search?q=happy+path+testing\" target=\"_blank\" rel=\"noreferrer noopener nofollow\">happy path<\/a>, and at least some of the unhappy path (errors, validation issues).<\/p>\n\n\n\n<p><strong>Template Level Actions<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Can the user create a new template<\/li>\n\n\n\n<li>Can the user change the template title<\/li>\n\n\n\n<li>Can the user change the template description<\/li>\n\n\n\n<li>Can the user save the template changes\n<ul class=\"wp-block-list\">\n<li>Do the changes persist when saving, and reloading the page<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Can the user cancel any pending changes<\/li>\n\n\n\n<li>The user can&#8217;t save a template when there are no changes<\/li>\n\n\n\n<li>The user can&#8217;t save a template with a duplicated field name<\/li>\n<\/ol>\n\n\n\n<p><strong>Template Field Actions<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Can the user add a new field\n<ul class=\"wp-block-list\">\n<li>Can the user change the field label<\/li>\n\n\n\n<li>Can the user change the field name<\/li>\n\n\n\n<li>Can the user change the type of field<\/li>\n\n\n\n<li>Can the user add more than one field<\/li>\n\n\n\n<li>Can the fields order be changed<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Can the user remove a field<\/li>\n<\/ol>\n\n\n\n<p><strong>Template Dropdown \/ Checkbox Field<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Can the user create a dropdown \/ checkbox field<\/li>\n\n\n\n<li>Can the user add an option<\/li>\n\n\n\n<li>Can the user remove an option<\/li>\n\n\n\n<li>Can the options order be changed<\/li>\n<\/ol>\n\n\n\n<p><strong>Misc Actions<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Can the user navigate to add a record with this template<\/li>\n\n\n\n<li>Can the user navigate to view the records for this template<\/li>\n<\/ol>\n\n\n\n<p>These tests are critical to the process, because they ensure we don&#8217;t deliver a tool that is broken in some obvious or less than obvious way.<\/p>\n\n\n\n<p>Can you think of any tests I missed? Comment below!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Pull Requests<\/h2>\n\n\n\n<p>I broke the work up into a few distinct pieces:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>This <a href=\"https:\/\/github.com\/bendauphinee\/tailgunner\/pull\/14\">pull request<\/a> adds Font Awesome, and cleans up some style things.<\/li>\n\n\n\n<li>This <a href=\"https:\/\/github.com\/bendauphinee\/tailgunner\/pull\/15\">pull request<\/a> creates the edit template page, with basic alerts connected to buttons.<\/li>\n\n\n\n<li>And this <a href=\"https:\/\/github.com\/bendauphinee\/tailgunner\/pull\/16\">pull request<\/a> adds in all the actual functionality for the tool.<\/li>\n<\/ul>\n\n\n\n<p>Each PR offers a distinct chunk of working functionality, allowing us to show progress on the tool in stages.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>While the last few posts in the series have been about zoomed in pieces of work, this one stepped back and allowed us to deliver a larger chunk. At the end of this, we&#8217;ve now solved 4 more workflows from our user story, leaving us over 70% complete!<\/p>\n\n\n\n<div class=\"wp-block-group has-base-color has-contrast-background-color has-text-color has-background has-link-color wp-elements-d31dc603ca0622a2bb8c129b0a66f203 has-global-padding is-layout-constrained wp-container-core-group-is-layout-9597dc02 wp-block-group-is-layout-constrained\" style=\"padding-top:var(--wp--preset--spacing--10);padding-right:var(--wp--preset--spacing--10);padding-bottom:var(--wp--preset--spacing--10);padding-left:var(--wp--preset--spacing--10)\">\n<p><strong>US1: Workflows<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><mark style=\"background-color:#86dd61\" class=\"has-inline-color\"><strong>Creating a template<\/strong><\/mark><\/li>\n\n\n\n<li><mark style=\"background-color:#86dd61\" class=\"has-inline-color\"><strong>Editing the template metadata<\/strong><\/mark><\/li>\n\n\n\n<li><strong><mark style=\"background-color:#86dd61\" class=\"has-inline-color\">Editing the form fields in a template<\/mark><\/strong><\/li>\n\n\n\n<li><mark style=\"background-color:#86dd61\" class=\"has-inline-color\"><strong>Editing the options of a form dropdown<\/strong><\/mark><\/li>\n\n\n\n<li><strong><mark style=\"background-color:#86dd61\" class=\"has-inline-color\">Saving changes to the template<\/mark><\/strong><\/li>\n\n\n\n<li><strong>Cloning a template<\/strong><\/li>\n\n\n\n<li><strong>Deleting a template<\/strong><\/li>\n<\/ul>\n<\/div>\n\n\n\n<p>The template editing tool is now in a useful state, and from here, we can either finish this story or prioritize creating tools to add and manage the data entered with a template.<\/p>\n\n\n\n<p>As always, I hope you learned something interesting through this post, and stay tuned for the next!<\/p>\n\n\n\n<p>Leave me a comment if you found this helpful, or you found something I could do better!<\/p>\n\n\n\n<p>Cheers!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>11 minute reading time; ~2120 words Welcome and hello! In this post, we&#8217;re going to touch on the overall lessons from the series so far, and then dive into delivering the ability to actually edit a template. If you haven&#8217;t read the previous posts, I&#8217;d suggest you start at An App From Scratch: Part 1 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[46,16,40,25,45,26,34,19],"class_list":["post-903","post","type-post","status-publish","format-standard","hentry","category-software-engineering","tag-debounce","tag-design","tag-github","tag-laravel","tag-optimization","tag-php","tag-software","tag-tailgunner"],"_links":{"self":[{"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/posts\/903","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/comments?post=903"}],"version-history":[{"count":27,"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/posts\/903\/revisions"}],"predecessor-version":[{"id":939,"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/posts\/903\/revisions\/939"}],"wp:attachment":[{"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/media?parent=903"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/categories?post=903"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bendauphinee.com\/writing\/wp-json\/wp\/v2\/tags?post=903"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}