Dancer Advent Calendar 2023 http://advent.perldancer.org/ Looking ahead to 2024 http://advent.perldancer.org/2023/24 perl http://advent.perldancer.org/2023/24 Sun, 24 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="looking_ahead_to_2024"></a>Looking ahead to 2024</h1> <p>Wow, what a year 2023 was! How could the Dancer Core Team top that one?! Well, here's a couple of things currently in the hopper:</p> <h2><a name="the_year_of_the_docs"></a>The Year of the Docs?</h2> <p>We've recently had some discussions with an author and technical writer on how to get our docs rewritten across the finish line. We have some solid ideas and are picking up some steam on this.</p> <p>One of the things we accomplished in the lead-up to Dancer2 1.0.0 was overhauling some of our supplemental documentation (how to contribute, etc.). We're committed to making those changes to the core documentation as well.</p> <h2><a name="shrinking_of_the_backlog"></a>Shrinking of the backlog</h2> <p>Sawyer has recently picked off a few of the oldest issues in the backlog, and I tend to work from the newest backward. Before long, we can meet in the middle. We have several open issues. Some are so close to completion that you can almost taste them. It would be great to carry those over the finish line.</p> <h2><a name="core_team_meetup_hackathon"></a>Core Team Meetup/Hackathon?</h2> <p>We've kicked around this idea for the last year or so, and tried to work out the logistics of an in-person hackathon. I'm not sure we're able to manage that yet, but given how much momentum we picked up at TPRC this summer, we're all excited to get together, if only virtually, to see what great things will come from all of us getting together again.</p> <p>Shameless plug: donations help make in-person meetups happen. If you're interested in helping sponsor this, please contact us!</p> <h2><a name="final_thoughts"></a>Final Thoughts</h2> <p>We have the best intentions in making these things happen, and looking at things today, I think they will. Please remember these are intentions, not promises.</p> <p>Our community support and excitement over this year's release has reaffirmed our reasons for building Dancer2; it's been great to see the positive buzz that's been generated this year and to hear all the great things our users are building with Dancer2.</p> <p>Thanks for being such a great community, and we look forward to engaging with you in 2024!</p> <h2><a name="author"></a>Author</h2> <p>This article has been written by Jason Crome (CromeDome) for the Twelve Days of Dancer.</p> <h2><a name="copyright"></a>Copyright</h2> <p>No copyright retained. Enjoy.</p> <p>Jason A. Crome / CromeDome</p> </div> Cookiecuttin' data with JSON:API http://advent.perldancer.org/2023/23 perl http://advent.perldancer.org/2023/23 Sat, 23 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="cookiecuttin__data_with_json_api"></a>Cookiecuttin' data with JSON:API</h1> <p>A web service providing access to a datastore, that's simple. Even moreso with Dancer2. Like, let's say you're on <a href="https://advent.perldancer.org/2023/17">Glugg's team</a> and maintain the North Pole Wishlist Database service. Then your endpoint might look something like, say,</p> <pre class="prettyprint">get '/child/:child_name' =&gt; sub { # database magic replaced by hardcoded # data for the example's sake to_json { name =&gt; 'cromedome', naughtoscale =&gt; 8, wishlist =&gt; [ 'Cessna Skyhawk', 'Verg articulated figure', ], } };</pre> <p>As advertised; simple.</p> <p>Except...</p> <p>Except those endpoints never stay simple for long, do they? Those gifts in the wishlist? They are probably objects of their own right in the datastore, and it'd be nice to be able to follow a thread to get them, somehow.</p> <p>Or that same gift list might get too long in the case of greedy kids, so in some cases -- for bandwidth's sake -- we might want to get the data minus the gifts.</p> <p>And if we had a list of children, maybe we would like to sort them my name, or by their naughtoscale score.</p> <p>And what about pagination?</p> <p>I'll stop here. But yeah. Those endpoints, they never stay simple for long.</p> <h2><a name="doing_the_same_thing_over_and_over_again__and_expecting_the_same_result"></a>Doing the same thing over and over again, and expecting the same result</h2> <p>The thing is, all those operations on the data listed in the previous section aren't weird feature requests. In fact, it's more surpring to have an API that <b>doesn't</b> get to implement them in one way or another. And yet we tend to reinvent the way they are implemented each time they pop up. And that's kind of silly.</p> <p>Enters <a href="https://jsonapi.org/">JSON:API</a>, which is a set of specs that prescribes a standard way to serialize database-like data, and how to interact with it via a REST-like interface.</p> <h2><a name="so_what_does_it_look_like"></a>So what does it look like?</h2> <p>An example will speak volume here. Let's take the data returned by our original endpoint.</p> <pre class="prettyprint">{ name =&gt; 'cromedome', naughtoscale =&gt; 8, wishlist =&gt; [ 'Cessna Skyhawk', 'Verg articulated figure', ], }</pre> <p>That same data, when covered in JSON:API sauce, could look like this:</p> <pre class="prettyprint">{ jsonapi =&gt; { version =&gt; '1.0 '}, links =&gt; { self =&gt; '/child/cromedome', }, data =&gt; { id =&gt; 'child-83626812-b', type =&gt; 'child', attributes =&gt; { naughtoscale =&gt; 8, name =&gt; 'cromedome', }, relationships=&gt; { gifts =&gt; { links =&gt; { self =&gt; '/child/cromedome/wishlist', }, data =&gt; [ { type =&gt; 'gift', id =&gt; 'g829137' }, { type =&gt; 'gift', id =&gt; 'g998383' }, ] } } }, included =&gt; [ { type =&gt; 'gift', id =&gt; 'g829137', attributes =&gt; { name =&gt; 'Cessna Skyhawk' }, }, { type =&gt; 'gift', id =&gt; 'g998383', attributes =&gt; { name =&gt; 'Verg articulated figure' }, } ] }</pre> <p>Now, I know what you are thinking: "HOLLY DECKS THE HALLS, that's verbose!". And yes, yes it is. JSON:API doesn't aspire to be terse or pretty, but rather to be consistent and straightforward to parse.</p> <p>I won't go into the nitty gritties, but the main things to know about a JSON:API serialized object is that you have a wee bit of meta information</p> <pre class="prettyprint">jsonapi =&gt; { version =&gt; '1.0 '}, links =&gt; { self =&gt; '/child/cromedome', },</pre> <p>and then you always have the object's type and id,</p> <pre class="prettyprint">data =&gt; { id =&gt; 'child-83626812-b', type =&gt; 'child',</pre> <p>you <b>can</b> have attributes of that object,</p> <pre class="prettyprint">attributes =&gt; { naughtoscale =&gt; 8, name =&gt; 'cromedome', },</pre> <p>and you <b>can</b> have a list of related objects.</p> <pre class="prettyprint">relationships=&gt; { gifts =&gt; { links =&gt; { self =&gt; '/child/cromedome/wishlist', }, data =&gt; [ { type =&gt; 'gift', id =&gt; 'g829137' }, { type =&gt; 'gift', id =&gt; 'g998383' }, ] } }</pre> <p>It's also possible to have those related objects fleshed out. In that case, the information in the <code>relationships</code> hash still stays the <code>type</code>/<code>id</code> pair, but the full object would be in an <code>included</code> field.</p> <pre class="prettyprint"> included =&gt; [ { type =&gt; 'gift', id =&gt; 'g829137', attributes =&gt; { name =&gt; 'Cessna Skyhawk' }, }, { type =&gt; 'gift', id =&gt; 'g998383', attributes =&gt; { name =&gt; 'Verg articulated figure' }, } ] }</pre> <p>As for all those sorting / filtering / including tweaks, the JSON:API specs prescribe how to convey them via the endpoint query path. Requesting a subset of the attributes is done via <code>?fields=name,naughtoscale</code>. Adding related objects to the payload is done via <code>?include=gifts</code>. Pagination is done via <code>?page[number]=X</code> or perhaps <code>?page[offset]=Y</code>. Is this way of doing thing revolutionary? Not at all. Chances are you are already using very keywords. But the point is that it's setting a very well-defined mold. No need to ever agonize about the details of any of those parameters; just follow the recipe.</p> <h2><a name="cue_in_dancer2__plugin__jsonapi"></a>Cue in Dancer2::Plugin::JsonApi</h2> <p>Now, how does that apply to the dance floor? Glad you asked, for there is a (very) new plugin in town, <a href="https://metacpan.org/module/Dancer2::Plugin::JsonApi">Dancer2::Plugin::JsonApi</a> to help.</p> <p>The first, and biggest thing that Dancer2::Plugin::JsonApi provides is a way to serialize and deserialize those cumbersome JSON:API representations. To do that you have to register all your objects in a JSON:API registry.</p> <pre class="prettyprint">use Dancer2::Plugin::JsonApi; jsonapi_registry-&gt;add_type( child =&gt; { relationships =&gt; { wishlist =&gt; { type =&gt; 'gift' } } } );</pre> <p>Yup, that's all. We didn't even have to declare the <code>gift</code> type at all as it is only using the defaults. In fact, if our data source was a <code>DBIx::Class</code> schema, we could even do some magic to auto-populate the registry off the <code>DBIx::Class::ResultSource</code> classes we have.</p> <p>In any case, now we can use the special keyword <code>jsonapi</code> on a route, and... it'll work.</p> <pre class="prettyprint">get '/child/:name' =&gt; jsonapi 'child' =&gt; sub { return +{ resultset('Child') -&gt;find({ name =&gt; route_parameters-&gt;get('name') }) -&gt;get_columns }; };</pre> <p>The endpoint returns the data structure properly serialized as JSON:API. It'll even automatically populate the <code>link.self</code> for you (you're welcome).</p> <h2><a name="dealing_with_query_parameters"></a>Dealing with query parameters</h2> <p>Right now, the plugin only provides the content of <code>vars</code> and the current <code>request</code> as part of the <code>$xtra</code> variable passed to various generating functions supported by <a href="https://metacpan.org/module/Dancer2::Plugin::JsonApi::Schema">Dancer2::Plugin::JsonApi::Schema</a>. The actual munging of the data (<code>sort</code>, <code>fields</code>, <code>include</code>, etc.) is left to the developer, as this will vary a lot depending on the type of backend, what kind of data is provided, etc.</p> <p>This might sound like there is still a lot of work left for the developer there, but all of those query parameter-controlled behaviors are optional, so they can always be implemented as needed. Not to mention that there is the potential for creating factories for a lot of those things, given our backend provides some method of introspection.</p> <p>For example, if our underlying data store is accessed via <a href="https://metacpan.org/module/DBIx::Class">DBIx::Class</a>, here's a naive (but working!) way to implement support for the <code>fields</code> and <code>include</code> parameters in a generic way:</p> <pre class="prettyprint">sub rs_to_data($rs,$xtra) { return $rs unless blessed $rs; # it's already a hash my %data = $rs-&gt;get_columns; if ( $xtra-&gt;{request}-&gt;query_parameters-&gt;{fields} ) { my %keep = map { $_ =&gt; 1 } 'id', $xtra-&gt;{request}-&gt;query_parameters-&gt;{fields}; %data = pairgrep { $keep{$a} } %data; } for ( split ",", $xtra-&gt;{request}-&gt;query_parameters-&gt;{include} ) { $data{$_} = [ map { +{ $_-&gt;get_columns } } $rs-&gt;$_-&gt;all ]; } return \%data; } jsonapi_registry-&gt;add_type( 'child', { before_serialize =&gt; rs_to_data relationships =&gt; { wishlist =&gt; { type =&gt; 'gift', links =&gt; { self =&gt; sub ( $data, $extra_data ) { return "/child/" . $data-&gt;{name} . "/wishlist"; } } }, } } ); get '/child/:name' =&gt; jsonapi 'child' =&gt; sub { return resultset('Child')-&gt;find( { name =&gt; route_parameters-&gt;get('name') } ); };</pre> <p>It's not a solution that works for all cases, but considering that it was thrown together within a handful of minutes, it's promising. It could even develop into a DBIx-specific set of <code>Dancer2::Plugin::JsonApi</code>. Who knows... if it ends up on the wishlist of a dev who was good all year round, maybe, just maybe it'll find its way under the Christmas tree? I guess we'll have to wait and see to find out.</p> <p>(but I'd leave a few cookies out on the 24th, just in case)</p> </div> Snazzy Mustaches: Handlebars templating http://advent.perldancer.org/2023/22 perl http://advent.perldancer.org/2023/22 Fri, 22 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="snazzy_mustaches__handlebars_templating"></a>Snazzy Mustaches: Handlebars templating</h1> <p>I had been playing around with <a href="https://github.com/janl/mustache.js/">Mustache</a>, and Yanick's fantastic plugin for Dancer2, <a href="https://metacpan.org/module/Dancer2::Plugin::Mustache">Dancer2::Plugin::Mustache</a>, when I bumped into its steroid-fed brother system, <a href="https://handlebarsjs.com/">Handlebars</a>, and its beautiful system for implementing additional custom helpers into the templates. Yanick had written a Mustache plugin for earlier Dancer, but there wasn't one for Dancer2. So I shamelessly plagarized, remixed those two modules, and released my very first module to CPAN.</p> <h2><a name="about_handlebars"></a>About Handlebars</h2> <p>Handlebars, like Mustache, is a minimalist templating system. Like Template Toolkit, it provides for variable substitution, including nested objects. Unlike Template Toolkit, though, neither Handlebars nor Mustache ships with a large suite of useful functions; creating those is left up to you, with both showing in their documentation that those can be easily created in JavaScript within the calling page.</p> <p>Where they differ, most critically to me, is that Handlebars includes a few logical helpers, where Mustache does not. Those are nice to have--<code>#if</code>, <code>#unless</code>, <code>#with</code>, and <code>#each</code> are so ubiquitously used, that it's very useful to have those shipped with the system. As Yanick worked out <a href="https://metacpan.org/pod/Dancer::Template::Handlebars">in his Dancer plugin for it</a>, it's relatively straightforward to write any helpers you need in Perl.</p> <h1><a name="so_how_do_i_use_it_in_my_application"></a>So how do I use it in my application?</h1> <p>Simply specify it as your template system in your <code>config.yml</code>:</p> <pre class="prettyprint">template: handlebars engines: handlebars: helpers: - MyApp::HandlebarsHelpers</pre> <p>...and name your templates using the suffix .hbs. Then, in your route code:</p> <pre class="prettyprint">get '/style/:style' =&gt; sub { template 'style' =&gt; { style =&gt; route_parameters-&gt;get('style'), }; };</pre> <p>Your template, named <code>style.hbs</code>, might look something like this:</p> <pre class="prettyprint">Why, that's a lovely {{style}} mustache you have!</pre> <p>It works a <b>lot</b> like Template Toolkit, as you can see here.</p> <h2><a name="loops"></a>Loops</h2> <p>Loops are equally easy in Handlebars. Suppose you had sent to your template an arrayref <code>people</code>, each of which had fields <code>name</code> and <code>hometown</code>. You could print that out as a bulleted list like so, in your template:</p> <pre class="prettyprint">&lt;ul&gt; {{#each people}} &lt;li&gt;{{this.name}} is from {{this.hometown}}.&lt;/li&gt; {{/each}} &lt;/ul&gt;</pre> <p>In the built-in helpers, <code>this</code> will represent the current context or object, and it's always available to you inside these loops. No more of this:</p> <pre class="prettyprint">[% FOREACH person IN people %]</pre> <p>...just use <code>this</code>!</p> <h2><a name="custom_helpers"></a>Custom Helpers</h2> <p>You can write a custom helper to take any data you like, and wrap some structure around it in the output HTML, and with this setup, you just specify where that module of helpers lives in your <code>config.yml</code>, as I did above. Then, write your code, like so:</p> <pre class="prettyprint">package MyApp::HandlebarsHelpers; use parent Dancer2::Template::Handlebars::Helpers; sub shout :Helper { my( $context, $text ) = @_; return '&lt;b&gt;' . uc $text . '&lt;/b&gt;'; } 1;</pre> <p>You always get the full context at the time of the call, so any page variable (including <code>this</code>, if you're inside a loop) can be used. You can specify additional parameters in the call, too, so this template call:</p> <pre class="prettyprint">{{ shout('Hey') }}, friend. Don't forget to wax your mustache!</pre> <p>would output:</p> <b>HEY</b>, friend. Don't forget to wax your mustache! <h2><a name="happy_dancing__from_the_plugins_princess_"></a>Happy Dancing, from the Plugins Princess.</h2> <p>I joined the Dancer core team this year, and it seems to be the case that I am emerging as the Princess of the Plugins. It's not like I've released a whole bunch of them--there are only four on <a href="https://metacpan.org/author/GEEKRUTH">CPAN</a>. But there are more coming down the road!</p> <p>Whatever holidays you and the people you love celebrate at this time of year, I hope they are happy and fulfilling. Happy Dancing, and see you in 2024!</p> <h2><a name="author"></a>Author</h2> <p>This article was written by <a href="https://metacpan.org/author/GEEKRUTH">D Ruth Holloway</a> for the Dancer Advent Calendar 2023.</p> </div> Tidings of Comfort and Joy: Dancer Core Team, TPRF Partnership http://advent.perldancer.org/2023/21 perl http://advent.perldancer.org/2023/21 Thu, 21 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="tidings_of_comfort_and_joy__dancer_core_team__tprf_partnership"></a>Tidings of Comfort and Joy: Dancer Core Team, TPRF Partnership</h1> <p>In the lead-up to Dancer2 1.0.0, the Dancer Core Team finalized an exciting collaboration with The Perl and Raku Foundation (TPRF) to have the foundation collect and manage funds on behalf of the Dancer project.</p> <h2><a name="background"></a>Background</h2> <p>Running an open-source project like Dancer2 is no small feat. Our team, though passionate and dedicated, is still relatively small. Dealing with administrative things (managing funds, paying bills, etc.) is not our strong suit. And every inefficient minute we spend on it, we're not spending on advancing Dancer and addressing community needs.</p> <p>Enter TPRF: a foundation with the resources and infrastructure to support Perl and Raku projects by handling the administration and monetary aspects, leaving us free to focus on what we do best - coding and enhancing Dancer.</p> <p>We're thrilled to pilot this partnership, which could serve as a model for other Perl and Raku projects in the future.</p> <h2><a name="why_this_matters"></a>Why This Matters</h2> <p>TPRF's involvement is a game-changer. They don't just help us with the paperwork; they open doors to easily receive new funding and allow us to secure the resources we need to fund the ongoing development of Dancer and its ecosystem.</p> <h2><a name="donations___the_gift_that_keeps_on_giving"></a>Donations - the gift that keeps on giving</h2> <p>All future donations will go into a fund that TPRF manages, and existing Dancer funds will be transferred to this fund. In doing this, TPRF can extend its 501c status to Dancer donations, meaning that <b>any donations made to the Dancer project are fully tax deductible!</b> So not only will your donations advance the development of Dancer, but you can also use these donations to reduce your tax burden. Everyone wins!</p> <p>Funds collected by TPRF on behalf of the Dancer project go 100% towards Dancer and the Dancer Core Team to be spent on the further development of Dancer and Dancer-related projects.</p> <h2><a name="details__details"></a>Details, details</h2> <p>In the spirit of community, the Dancer Core Team agreed to:</p> <ul> <li><a name="item_Follow_a_Code_of_Conduct"></a><b>Follow a Code of Conduct</b> <p>We believe in the spirit of a clear, inclusive Code of Conduct to create a safe space for the community. Our existing <a href="https://metacpan.org/dist/Dancer2/view/lib/Dancer2/Policy.pod">Code of Conduct</a> serves us in this purpose, and we hope more communities will adopt a similar policy.</p> </li> <li><a name="item_Responsible_spending_of_funds"></a><b>Responsible spending of funds</b> <p>We chose a process that works for our team's system of governance on how funds can be spent. TL;DR: I can't unilaterally take that vacation to Tahiti that I've always wanted (at least not on team funds!).</p> </li> </ul> <p>In addition to the agreed adherence rules set by TPRF, we intend to openly and transparently share with the community how funding will be spent.</p> <h2><a name="donating"></a>Donating</h2> <p>If you're looking to donate to the Dancer project, you can reach out to <a href="https://www.perlfoundation.org/donate.html">The Perl &amp; Raku Foundation</a> to donate and make it clear the donation should go to the Dancer project.</p> <p>Thank you for helping us maintain and develop Dancer. Please reach out to inform us of your interest in Dancer, and if you have any interests in particular aspects of Dancer you want to see improved or supported.</p> <h2><a name="acknowledgements"></a>Acknowledgements</h2> <p>We extend our thanks to TPRF for their willingness to work with us on piloting this program. We hope the community is as excited about this as we are!</p> <h2><a name="author"></a>Author</h2> <p>This article has been written by Jason Crome for the Twelve Days of Dancer.</p> <h2><a name="copyright"></a>Copyright</h2> <p>No copyright retained. Enjoy.</p> <p>Jason A. Crome / CromeDome</p> </div> Dancing with HTMX http://advent.perldancer.org/2023/20 perl http://advent.perldancer.org/2023/20 Wed, 20 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="dancing_with_htmx"></a>Dancing with HTMX</h1> <p>Santa's organisation is, of course, rather traditional. But some parts of it are slowly trying to drag themselves into the 21st century. Pixie the Elf (her parents had strange ideas about names) suggested that they should consider writing a web app that allowed them to maintain a list of the children they need to deliver to and which presents each child will be getting. Santa liked the idea and suggested that it should be written in Dancer because he liked the idea of using a technology that was named after one of his reindeer.</p> <p>Pixie grumbled to herself a bit about end-users dictating technology decisions but found it impossible to disguise her disappointment when he added, "And I think it should be a Single Page Application." How on Earth did Santa know terms like that? And that was going to make her life far harder than it needed to be.</p> <p>Pixie knew of two ways to write an app in Dancer. The older way was where each click on the page made a request to the server and, in response, the server created a new page of HTML that replaced the old page. That was the opposite of what Santa had asked for. A Single Page Application (or SPA) required the server to return data in a format like JSON which Javascript in the web page would parse and manipulate in order to update sections of the page to display the new data.</p> <p>The problem was that Pixie hadn't ever got round to learning Javascript in enough detail to write code complex enough to do that. And while she loved the idea of getting her skills up to date, she really didn't think she had enough time to learn all of that before the application was needed. Disheartened, she sat in the North Pole canteen reading r/elftech.</p> <p>A few pages in, she read a post talking about a technology called HTMX which sounded like it might be the solution to all of her problems. Effectively <a href="https://htmx.org/">HTMX</a> is a very clever Javascript library that wraps up all of the complex Javascript work and gives developers a way to write an SPA by just adding a few new attributes to their HTML tags. Pixie thought this sounded like the solution to her problem and she went back to her desk with a bounce in her step.</p> <p>HTMX consists of a single library that you need to load. So Pixie added the line</p> <pre class="prettyprint">&lt;script src="https://unpkg.com/htmx.org@1.9.6" integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni" crossorigin="anonymous"&gt;&lt;/script&gt;</pre> <p>to the top of her <code>index.tt</code> (being an SPA, this is the only page in the app).</p> <p>She then quickly created an SQLite database with two tables (one for children and one for their presents) and used <code>DBIx::Class::Schema::Loader</code> to generate classes to access those tables.</p> <p>The first thing to do was to display a list of the children. Her DBIC classes made that simple enough. She created an index route that looked like this:</p> <pre class="prettyprint">get '/' =&gt; sub { my $sch = HTMXmas::Schema-&gt;connect(...); return template 'index.tt', { rows =&gt; [ $sch-&gt;resultset('Child')-&gt;all ], }; };</pre> <p>And the important part of the <code>index.tt</code> template looked like this:</p> <pre class="prettyprint">&lt;div id="list-table"&gt; &lt;% INCLUDE list.tt -%&gt; &lt;/div&gt;</pre> <p>Two good tips for working with HTMX are 1) use DIVs with IDs for any piece of HTML that will be displaying data and 2) use lots of embedded templates. This will make it easier to generate HTML that replaces chunks of our page.</p> <p>The <code>list.tt</code> template looks like this:</p> <pre class="prettyprint">&lt;table class="table table-striped table-hover"&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;id&lt;/th&gt; &lt;th&gt;name&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody id="list-body"&gt; &lt;% INCLUDE list_body.tt %&gt; &lt;/tbody&gt; &lt;/table&gt;</pre> <p>And most of the real work is in <code>list_body.tt</code>:</p> <pre class="prettyprint">&lt;% FOREACH row IN rows -%&gt; &lt;tr&gt; &lt;td&gt;&lt;% row.id %&gt;&lt;/td&gt; &lt;td id="edit-&lt;% row.id %&gt;"&gt;&lt;% row.name %&gt;&lt;/td&gt; &lt;td&gt; &lt;i hx-get="/edit/&lt;% row.id %&gt;" hx-target="#edit-&lt;% row.id %&gt;" title="edit" class="bi-pencil-square text-primary" style="font-size: 1.25rem"&gt;&lt;/i&gt; &lt;i hx-get="/view/&lt;% row.id %&gt;" hx-target="#view" title="view" class="bi-gift-fill text-primary" style="font-size: 1.25rem"&gt;&lt;/i&gt; &lt;i hx-delete="/delete/&lt;% row.id %&gt;" hx-confirm="Are you sure you want to delete this record?" hx-target="#list-body" title="delete" class="bi-trash-fill text-danger" style="font-size: 1.25rem"&gt;&lt;/i&gt; &lt;/td&gt; &lt;/tr&gt; &lt;% END -%&gt;</pre> <p>There's quite a lot going on there. Some of the complexity comes from HTMX (the attributes that start <code>hx-*</code>) and some of it is from Bootstrap (the CSS framework that Pixie uses for all her web sites). But, basically, what this is doing is creating a row for every child in the database. Each row has three columns. The first two display the ID and name from the database record and the third displays some icons that allow the user to interact with the record in various ways. The first allows you to edit the name, the second will (when the code is implemented) display a list of the presents the child will receive and the third deletes the child from the database. Let's start by looking at the easiest option, the delete icon.</p> <p>There are three <code>hx-*</code> attributes on the delete icon:</p> <ul> <li><a name="item_hx_delete"></a><b>hx-delete</b> <p>This defines the action that clicking on this icon will take. There are <code>hx-VERB</code> attributes for all of the HTTP verbs - <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code> and <code>DELETE</code>. In this case we're using <code>hx-delete</code> because that's the correct verb to use to delete a resource. When the icon is clicked, HTMX will generate an HTTP <code>DELETE</code> request to <code>/delete/ID</code> (where ID is the ID of the database row). This means that we'll need to write a route in our Dancer app that matches <code>del '/delete/:id'</code>. We'll see the code for that in a minute.</p> </li> <li><a name="item_hx_confirm"></a><b>hx-confirm</b> <p>This is a nice feature that HTMX gives us. Adding an <code>hx-confirm</code> attribute to our HTML tells HTMX to intercept a click on the icon and display a pop-up message in the browser. The pop-up will have OK and Cancel buttons and the action will only proceed if the OK button is selected. This is a simple way to add a confirmation step to potentially destructive actions.</p> </li> <li><a name="item_hx_target"></a><b>hx-target</b> <p>When HTMX makes a request to the server, it expects to receive HTML back. By default, it will replace the contents of the element that triggered the request (by, for example, being clicked) with the HTML that was returned. In my experience, this is rarely what you want so you can change the element that is replaced by defining it using a CSS selector in an <code>hx-target</code> attribute. Usually, the simplest way to do this is to put an ID on the element you want to replace.</p> <p>In this case, having deleted a row from the database, we want to replace the element containing the list of rows (because it will be one item shorter). So we give it a target of <code>#list-body</code> and ensure that ID is defined on the correct HTML element (as you'll see in the <code>list.tt</code> template above).</p> </li> </ul> <p>So, we're in the situation where clicking the delete icon will take the following steps:</p> <ul> <li> <p>Present a confirmation dialogue.</p> </li> <li> <p>Make an HTTP <code>DELETE</code> request to <code>/delete/ID</code>.</p> </li> <li> <p>Expect to get an HTML fragment back from the server and replace the <code>#list_body</code> element with that HTML.</p> </li> </ul> <p>All we need to do now is to write the correct route in our Dancer app. The code looks like this:</p> <pre class="prettyprint">del '/delete/:id' =&gt; sub { my $id = route_parameters-&gt;get('id'); return 404 unless $id; return 404 if $id =~ /\D/; my $sch = HTMXmas::Schema-&gt;connect(...); my $rs = $sch-&gt;resultset('Child'); $rs-&gt;find($id)-&gt;delete; return template 'list_body.tt', { rows =&gt; [ $rs-&gt;all ], }; };</pre> <p>The code has two responsibilites. It needs to 1) delete the correct row and 2) return the correct HTML. Given the way we've set the system up, this proves to be rather easy.</p> <p>We extract the ID from the route. We check we've got an ID and that it looks like an integer (returning 404 if we fail either of those checks). We then connect to the database, find the row and delete it.</p> <p>We can then use our <code>list_body.tt</code> template (see, this is why we broke our views down into lots of nested templates) to produce the HTML that we need to replace the original table.</p> <p>That's a lot of detail about a rather simple operation. Let's now look at the edit action in a bit less detail. The HTML for the edit icon has the following <code>hx-*</code> attributes:</p> <ul> <li><a name="item_hx_get"></a><b>hx-get</b> <p>This is the HTML verb to use in the request. We make a <code>GET</code> request to <code>/edit/ID</code> and it returns HTML that replaces the <code>&lt;td&gt;</code> containing the name with a simple form for editing the name.</p> </li> <li><a name="item_hx_target"></a><b>hx-target</b> <p>Once again, the HTML element that is clicked (the edit icon) is not the one that needs to be replaced. We've given the correct <code>&lt;td&gt;</code> element the ID <code>edit-ID</code>, so we use that as the target.</p> </li> </ul> <p>We now need to implement the <code>get '/edit/:id'</code> route in our Dancer app. It needs to return the edit form, so it looks like this:</p> <pre class="prettyprint">get '/edit/:id' =&gt; sub { my $id = route_parameters-&gt;get('id'); my $sch = HTMXmas::Schema-&gt;connect(...); my $rs = $sch-&gt;resultset('Child'); my $row = $rs-&gt;find($id); return template 'edit_name.tt', { row =&gt; $row, }; };</pre> <p>And the <code>edit_name.tt</code> template looks like this:</p> <pre class="prettyprint">&lt;form&gt; &lt;input class="form-control" id="name" name="name" value="&lt;% row.name %&gt;"&gt; &lt;i hx-post="/update/&lt;% row.id %&gt;" hx-params="&lt;% col %&gt;" hx-ext="json-enc" hx-target="#edit-&lt;% row.id %&gt;" title="save" class="bi bi-check-square text-success" style="font-size: 1.25rem"&gt;&lt;/i&gt; &lt;i hx-get="/reset/&lt;% row.id %&gt;" hx-target="#edit-&lt;% row.id %&gt;" title="cancel" class="bi bi-x-square text-danger" style="font-size: 1.25rem"&gt;&lt;/i&gt; &lt;/form&gt;</pre> <p>So what we're doing here is replacing some HTML with other HTML that includes <code>hx-*</code> attributes. Editing the name is a two-stage process. The first stage displays the form for editing the name and the second stage saves any changes to the database.</p> <p>Notice that we've added two icons to the edit form. There's a check mark to save the changes to the database and an X that cancels any changes. After pressing either of those, the form should be replaced with the original <code>&lt;td&gt;</code> - so that's the HTML that the routes should return.</p> <p>We understand most of the <code>hx-*</code> attributes that are used here, so I won't go into them again. There's just one new one to explain.</p> <ul> <li><a name="item_hx_ext"></a><b>hx-ext</b> <p>There are a number of extensions to the basic HTMX features. By default, a <code>POST</code> request like we're using for the update here would use the standard <code>application/x-www-form-urlencoded</code> encoding. But in this case, Pixie decided to use an extension (called "json-enc") which, instead, encodes the data as JSON. She thinks that makes it a little easier to deal with the data at the in the Dancer app.</p> </li> </ul> <p>Which brings us neatly back to the Dancer app. We need to add two new routes to handle these icon - <code>post '/update/:id/'</code> will save the data to the database (and remove the form from the table) and <code>get '/reset/:id'</code> will undo any changes and remove the form. They look like this:</p> <pre class="prettyprint">post '/update/:id' =&gt; sub { my $id = route_parameters-&gt;get('id'); my $object = from_json(request-&gt;body); my $sch = HTMXmas::Schema-&gt;connect(...); my $rs = $sch-&gt;resultset('Child'); if ($id) { my $row = $rs-&gt;find($id); $row-&gt;update($object); $row-&gt;discard_changes; return sprintf '&lt;td id="edit-%s"&gt;%s&lt;/td&gt;', $row-&gt;id, $row-&gt;name; } return '404'; }; get '/reset/:id' =&gt; sub { my $id = route_parameters-&gt;get('id'); my $sch = HTMXmas::Schema-&gt;connect(...); my $rs = $sch-&gt;resultset('Child'); my $row = $rs-&gt;find($id); return sprintf '&lt;td id="edit-%s"&gt;%s&lt;/td&gt;', $row-&gt;id, $row-&gt;name; };</pre> <p>Pixie is a little worried about this code. There are a few checks missing and that hard-coded HTML in the <code>return</code> statements is embarrassing. But it's just a proof of concept and she'll clean it up before it goes into production... honest!</p> <p>There's still a bit more to do. Pixie hasn't started on displaying the children's list of toys. She also wants to include a way to add children (as well as their choice of toys). And it would be great to be able to search for particular children. But she's happy that HTMX has allowed her to write the start of her SPA very easily - and without having to learn any more Javascript.</p> <p>Pixie has given me access to the latest version of her code and I have put it <a href="https://github.com/davorg/htmxmas">on GitHub</a>. I'll keep it up to date as she makes improvements over the coming weeks.</p> <h2><a name="author"></a>Author</h2> <p>This article was written by Dave Cross (dave@perlhacks.com) for the Dancer Advent Calendar.</p> </div> Navigating the Dancefloor: The Elegance of Named Routes http://advent.perldancer.org/2023/19 perl http://advent.perldancer.org/2023/19 Tue, 19 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="navigating_the_dancefloor__the_elegance_of_named_routes"></a>Navigating the Dancefloor: The Elegance of Named Routes</h1> <p><b>TL;DR</b> Hey, you can name routes now! You can also create links to them! It also works in templates! Okay read on!</p> <h2><a name="paths__paths__paths"></a>Paths, Paths, Paths</h2> <p>When working with a large web application or service that includes numerous routes that serve multiple purposes, one significant need is to generate paths the user will use to navigate between the routes.</p> <p>In templates for pages, you need to create links and forms that point to other routes. Take this contrived example:</p> <pre class="prettyprint"># In your template &lt;!-- part of the menu --&gt; [% FOREACH user IN users %] &lt;a href="/view/user/[% user_id %]?track=[% track_code %]"&gt;User [% user.id %]&gt;/a&gt;&lt;br/&gt; [% END %]</pre> <p>Let's say your application is mounted (using <a href="https://metacpan.org/module/Plack::Builder">Plack::Builder</a> or using some web server configuration on top of <code>/admin</code>. Well, those paths won't work anymore. Now they need to be <code>/admin/view/user/...</code>, right?</p> <h2><a name="generating_paths"></a>Generating Paths</h2> <p>We can fix this using <code>uri_for</code>:</p> <pre class="prettyprint">&lt;a href="[% request.uri_for("/view/user/$user_id?track=$track_code") %]"&gt; User [% user.id %] &lt;/a&gt;</pre> <p>This will take into account the possible mounting. Also, it can take care of the query parameter for the tracking code.</p> <pre class="prettyprint">&lt;a href="[% request.uri_for("/view/users/$user_id", { track =&gt; $track_code }) %]"&gt; User [% user.id %] &lt;/a&gt;</pre> <p>While it's longer, it's definitely more readable and less error-prone.</p> <h2><a name="hardcode_me_not"></a>Hardcode Me Not</h2> <p>The two problems that remain are needing to remember the path for each route and to hardcode it in the template or any code that generates paths for the user.</p> <p>We can fix both of those with the new <code>uri_for_route()</code>.</p> <h2><a name="name_that_route"></a>Name that Route</h2> <p>Begin by providing a name for your routes:</p> <pre class="prettyprint">get 'view_user' =&gt; '/view/user/:id' =&gt; sub {...};</pre> <p>By prefixing the path with another string, we provide a name for this route which we can then use to generate a URI for its path in the template or in any code.</p> <h2><a name="enter__code_uri_for_route__code_"></a>Enter <code>uri_for_route</code></h2> <p>Now let's use the route's name with <code>uri_for_route</code>:</p> <pre class="prettyprint">&lt;a href="[% request.uri_for_route("view_user", { id =&gt; $user_id }) %]"&gt; User [% user.id %] &lt;/a&gt;</pre> <p>Oh, and we can also include the tracking code as a query parameter:</p> <pre class="prettyprint">&lt;a href="[% request.uri_for_route("view_user", { id =&gt; $user_id }, { track =&gt; $track_code }) %]"&gt; User [% user.id %] &lt;/a&gt;</pre> <h2><a name="naming_all_options"></a>Naming All Options</h2> <p><code>uri_for_route</code> has a lot of arguments. It's worthwhile exploring them:</p> <ol> <li><a name="item_Route_name"></a><b>Route name</b> <p>The name of the route as you have given it. You can provide a name to all routes except <code>HEAD</code>. This means <code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>PATCH</code>, and <code>DELETE</code>.</p> </li> <li><a name="item_Route_arguments"></a><b>Route arguments</b> <p>These will be the arguments to the route path. We support named arguments, typed named arguments, splat, and megasplat:</p> <pre class="prettyprint"># Named arguments post 'req_form' =&gt; '/upload/request/:id' =&gt; sub {...}; $path = uri_for_route( 'req_form', { 'id' =&gt; 4 } ); # $path = /upload/request/4 # Typed named arguments post 'req_form' =&gt; '/upload/request/:id[Num]' =&gt; sub {...}; $path = uri_for_route( 'req_form', { 'id' =&gt; 4 } ); # $path = /upload/request/4 # Splat and Megasplat post 'req_form' =&gt; '/upload/request/*/*/**' =&gt; sub {...}; $path = uri_for_route( 'req_form', [ 'foo', 'bar', [ 'baz', 'quux' ] ] ); # $path = /upload/request/foo/baz/baz/quux # And a mix of these post 'req_form' =&gt; '/upload/request/:id/*/*/**' =&gt; sub {...}; $path = uri_for_route( 'req_form', { 'id' =&gt; 4, 'splat' =&gt; [ 'foo', 'bar', [ 'baz', 'quux' ] ], }, ); # $path = /upload/request/4/foo/baz/baz/quux</pre> <p>(Notice that when you're mixing these, the splat-like arguments will be under the <code>splat</code> key which shouldn't be used in route arguments.)</p> </li> <li><a name="item_Query_parameters"></a><b>Query parameters</b> <pre class="prettyprint">get 'view_user' =&gt; '/view/user/:id' =&gt; sub {...}; $path = uri_for_route( 'view_user', # Route name { 'id' =&gt; 4 }, # Route arguments { 'ext' =&gt; 'str' }, # Query parameters ); # $path = /view/user/4?ext=str</pre> </li> <li><a name="item_URI_escape_control"></a><b>URI escape control</b> <p>Lastly, we escape all query parameters by default since a user is likely to be using them. You don't want accidental HTML and JS code to be there.</p> <p>However, the last argument allows you to disable this ability:</p> <pre class="prettyprint">get 'view_user' =&gt; '/view/user/:id' =&gt; sub {...}; $path = uri_for_route( 'view_user', # Route name { 'id' =&gt; 4 }, # Route arguments { 'ext' =&gt; '&lt;javascript&gt;...' }, # Query parameters 1, # Disable escaping ); # $path = /view/user/4?ext=&lt;javascript&gt;...</pre> <p>(We still suggest you leaving this as is, so escaping will occur.)</p> </li> </ol> <h2><a name="author"></a>Author</h2> <p>This article has been written by Sawyer X for the Perl Dancer Advent Calendar 2023.</p> </div> A new way to use DBIx::Class in your Dancer2 apps! http://advent.perldancer.org/2023/18 perl http://advent.perldancer.org/2023/18 Mon, 18 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="a_new_way_to_use_dbix__class_in_your_dancer2_apps_"></a>A new way to use DBIx::Class in your Dancer2 apps!</h1> <p>In software terms, <a href="https://metacpan.org/pod/Dancer2::Plugin::DBIC">Dancer2::Plugin::DBIC</a> is well into middle-age, with the latest release happening just over six years ago. It works just fine, and makes integrating your applications with your database of choice a pleasurable, easy thing to do. It has focused, well-designed syntactic sugar baked in to make your database access smooth and easy to read and maintain.</p> <p>But with some help, I'd written a new feature for DBIx::Class, <a href="https://metacpan.org/pod/DBIx::Class::Schema::ResultSetNames">DBIx::Class::Schema::ResultSetNames</a>, and it seemed like it would be a lovely idea to have those new resultset name tokens available directly in Dancer2. So here we go!</p> <h2><a name="forklift_replacement"></a>Forklift replacement</h2> <p>First of all, <a href="https://metacpan.org/pod/Dancer2::Plugin::DBIx::Class">Dancer2::Plugin::DBIx::Class</a> is designed to be a forklift upgrade for the older plugin--if you've got existing code using that plugin, just change your <code>use</code> statement and your Dancer2 <code>config.yml</code> or environment file, and it'll Just Work. For those existing subroutines, I did a little bit of streamlining, but they're basically Naveed's code, tidied up and more conformant with my own coding style.</p> <h2><a name="___and_now_for_the_magic_"></a>...and now for the magic.</h2> <p>...but if you have the ResultSetNames module installed and in your schema, that's when things get interesting. Dancer2::Plugin::DBIx::Class will import all of your resultset names as terms in Dancer2. So, instead of this:</p> <pre class="prettyprint">use Dancer2::Plugin::DBIC; my $set = resultset('Person')-&gt;search({...});</pre> <p>you can simply do this:</p> <pre class="prettyprint">use Dancer2::Plugin::DBIx::Class; my $set = persons-&gt;search({ ...});</pre> <h2><a name="how_about_smoothing_out__code_find__code_"></a>How about smoothing out <code>find</code>?</h2> <p><code>find</code> couldn't be easier. With this module, just specify the result name, and the primary key:</p> <pre class="prettyprint">my $person = person(23); # id on the table.</pre> <h2><a name="there_is_a_caveat__though_"></a>There is a caveat, though.</h2> <p>It is entirely possible in a complex web application that you might have a table whose resultset name would conflict with a Dancer2 keyword. If that's the case--and I'm looking at the table "session" in several of the apps I work on in particular--then the resultset name keyword will not be created, and a warning will be emitted at application start time.</p> <p>To get around this, there is a setting in the configuration for the plugin. Your config.yml or environment file will look something like this:</p> <pre class="prettyprint">plugins: DBIx::Class: default: dsn: dbi:SQLite:dbname=my.db # Just about any DBI-compatible DSN goes here schema_class: MyApp::Schema export_prefix: 'db'</pre> <p>With the export_prefix set, your keywords are all prefixed with <code>db_</code>, so a search becomes:</p> <pre class="prettyprint">my $set = db_persons-&gt;search({ ...});</pre> <p>You can use any prefix you want, and if you have multiple schema in your application, you'll probably want to use different ones on each, to prevent any possibility of collision.</p> <h2><a name="neat__huh"></a>Neat, huh?</h2> <p>I like it; I'm using it in production on a couple of things I am working on, and trying to convince my bosses to use it in our production code starting in the new year. I hope it is of use to you, too. Happy Dancing!</p> <h2><a name="author"></a>Author</h2> <p>This article was written by <a href="https://metacpan.org/author/GEEKRUTH">D Ruth Holloway</a> for the Dancer Advent Calendar 2023.</p> </div> Svelte as a Dancer http://advent.perldancer.org/2023/17 perl http://advent.perldancer.org/2023/17 Sun, 17 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="svelte_as_a_dancer"></a>Svelte as a Dancer</h1> <p>It has been said that Gluggag&#xe6;gir (Glugg to his friends) has been employed by the North Pole for a long, long time. And that itself was said a <a href="https://perladvent.org/2008/13/">long, long time ago</a>.</p> <p>Let's just say that if elves could grow beards, his would be very grey indeed. By now his technological stack is nothing short of geological. Still, he loves to keep himself abreast of what's new. After all, he knows developers are like sharks. Except that they don't live in water. And they have digits, because typing with fins would be helluvah awkward. And anchovies. Not a lot of developers really dig anchovies. Least of all on pizzas.</p> <p>Anyway, the point is: to thrive developers must keep moving, must continue to learn what's new, or they doom themselves into the sunless abyss of bygone technologies, where old, tired systems slowly sink, and become code legacy.</p> <h2><a name="svelte_a_claus"></a>Svelte a Claus</h2> <p>With that in mind, it's no big surprise that Glugg played a lot with JavaScript frameworks in the last few years. <a href="https://angular.io/">Angular</a>, <a href="https://react.dev/">React</a>, <a href="https://vuejs.org/">Vue</a>, and a sundry others, he tried them like so many delicacies in a Christmas buffet. These days, his darling is <a href="https://svelte.dev/">Svelte</a>. It does much while keeping close to the standards, and has a general "Can Do" vibe that reminds him a lot of his favorite Bactrian language.</p> <p>While those JavaScript frameworks were born in the primordial tag soup of the browser, most of them now offer ways to write both frontend and backend in the same environment. React has <a href="https://nextjs.org/">Next</a>, Vue has <a href="https://v2.nuxt.com/">Nuxt</a>, and Svelte has <a href="https://kit.svelte.dev/">SvelteKit</a>. Brings awesome possibilities for new projects. But, as Glugg could tell you, frontends come and go like so many toys of the year. But backends? Ah, backends are like traditions, encrusting themselves for generations, seldom modified, let alone replaced.</p> <h2><a name="the_time_of_the_year_where_the_old_touches_the_new"></a>The time of the year where the old touches the new</h2> <p>But that doesn't faze Glugg at all. Even though this year he's hacking together a quick web interface to the gift wishlist database so that elves can easily consult and edit wishlists wherever they are. The database and its API are top-secret North Pole special sauces, so we can't really discuss them here without an ironclad NDA, but let's just drop some names like 'DBIx::Class' and 'Dancer2', and leave it at that.</p> <p>So, how does Glugg would connect a Svelte frontend to a Dancer2 backend?</p> <p>As it turns out, in many ways. Let's, in Christmas' true tradition, visit three of them.</p> <h2><a name="the_ghost_of_html_forms_past"></a>The ghost of HTML forms past</h2> <p>The first way to embrace the future is to, paradoxically, ignore it. Svelte takes pride in degrading gracefully when invoked in a JavaScript-less context. Which means that one can write HTML forms like they always did, and It Will Just Work (tm).</p> <p>For example, let's assume the supra-complex North Pole API service is reduced to the following small Dancer2 mockup.</p> <pre class="prettyprint">package NorthPole::API; use Dancer2; my $frontend_root_url = "http://localhost:5173"; my $db = { cromedome =&gt; [ { name =&gt; 'Pony' }, { name =&gt; 'Cessna Skyhawk' }, { name =&gt; 'Hyperborean Huntsman Compendium' }, ] }; get '/api/child/:child_name/wishlist' =&gt; sub { my $name = route_parameters-&gt;get('child_name'); to_json $db-&gt;{$name}; }; post '/api/child/:child_name/add_gift' =&gt; sub { my $name = route_parameters-&gt;get('child_name'); push $db-&gt;{$name}-&gt;@*, +{ name =&gt; body_parameters-&gt;get('new_gift') }; redirect "$frontend_root_url/child/$name/wishlist"; }; post '/api/child/:child_name/remove_gift' =&gt; sub { my $child_name = route_parameters-&gt;get('child_name'); my $to_remove = body_parameters-&gt;get('gift'); $db-&gt;{$child_name} = [ grep { $_-&gt;{name} ne $to_remove } $db-&gt;{$child_name}-&gt;@* ]; redirect "$frontend_root_url/child/$child_name/wishlist"; }; true;</pre> <p>Then a perfectly cromulent Svelte frontend could look like the following.</p> <pre class="prettyprint">// in src/routes/child/[child_name]/wishlist/+page.js const api_root_url = "http://localhost:5000"; export async function load({ params: { child_name} }) { return { api_root_url, child_name, }; } // in src/routes/child/[child_name]/wishlist/+page.svelte &lt;script&gt; export let data; let new_gift; const gifts = fetch( `${data.api_root_url}/api/child/${data.child_name}/wishlist` ).then( doc =&gt; doc.json() ); &lt;/script&gt; &lt;article&gt; &lt;h1&gt;Wishlist of {data.child_name}&lt;/h1&gt; &lt;div class="gifts"&gt; {#await gifts} &lt;progress class="circle"&gt;&lt;/progress&gt; {:then gifts} {#each gifts as gift (gift.name)} &lt;form method="POST" action={ `${data.api_root_url}/api/child/${data.child_name}/remove_gift` } &gt; &lt;div class="row"&gt; &lt;input type="hidden" name="gift" value={gift.name} /&gt; &lt;div class="max"&gt;{gift.name}&lt;/div&gt; &lt;button class="circle"&gt;&lt;i&gt;delete&lt;/i&gt;&lt;/button&gt; &lt;/div&gt; &lt;/form&gt; {/each} {/await} &lt;/div&gt; &lt;form method="POST" action={`${data.api_root_url}/api/child/${data.child_name}/add_gift`}&gt; &lt;div class="new_gift row"&gt; &lt;div class="field border max"&gt; &lt;input type="text" name="new_gift" placeholder="new gift" bind:value={new_gift} /&gt; &lt;/div&gt; &lt;div&gt; &lt;button class="circle" disabled={!new_gift}&gt;&lt;i&gt;add&lt;/i&gt; &lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/form&gt; &lt;/article&gt; &lt;style&gt; h1 { margin-bottom: 0.5em; } .gifts { margin-left: 2em; margin-right: 3em; margin-bottom: 2em; } .gifts .row { margin-bottom: 0.5em; } .new_gift { margin-left: 2em; margin-right: 3em; } &lt;/style&gt;</pre> <p>And, as the younger generation would say, "boom, here it is".</p> <img style="width: 90%" src="/images/2023/dancer-and-svelte/svelte-dancer-1.png" /> <p>(Incidentally, Glugg uses <a href="https://www.beercss.com/">beercss</a> to spruce up the look of the app. After all, as a Christmas elf, if there is one thing he knows, is that neat, shiny wrappings are nothing to sneeze at.)</p> <h2><a name="the_ghost_of_enhancement_present"></a>The ghost of enhancement present</h2> <p>The good thing with that first frontend page is that it does everything we want in an old-fashioned way. The less good thing with it is that it does everything we want in an old-fashioned way. Sure, having the form submit triggering a reload the page is great for backward compatibility (and all the elves still using <a href="https://metacpan.org/module/WWW::Mechanize">WWW::Mechanize</a> to interact with the page will appreciate it), but younger elves are also expecting zippier interfaces. Updates of the page without those janky reloads.</p> <p>Well, what if Glugg told you you can have your fruitcake and eat it too?</p> <p>Svelte has a way to define form such that without JavaScript, it'll behave as a regular HTML form. And if JavaScript is around... well, then it can do something a little more special.</p> <p>For this version, to make things more convenient we alter the Dancer2 service to return the wishlist of the kid instead of a redirect.</p> <pre class="prettyprint">post '/child/:child_name/add_gift' =&gt; sub { my $name = route_parameters-&gt;get('child_name'); push $db-&gt;{$name}-&gt;@*, +{ name =&gt; body_parameters-&gt;get('new_gift') }; return to_json $db-&gt;{$name}; }; post '/child/:child_name/remove_gift' =&gt; sub { my $child_name = route_parameters-&gt;get('child_name'); my $to_remove = body_parameters-&gt;get('gift'); $db-&gt;{$child_name} = [ grep { $_-&gt;{name} ne $to_remove } $db-&gt;{$child_name}-&gt;@* ]; return to_json $db-&gt;{$child_name}; };</pre> <p>With that, we're going to change the <code>script</code> block of the Svelte page.</p> <pre class="prettyprint">&lt;script&gt; import { enhance } from '$app/forms'; import { fade } from 'svelte/transition'; export let data; let new_gift; let gifts = []; const loading = fetch( `${data.api_root_url}/api/child/${data.child_name}/wishlist` ) .then( doc =&gt; doc.json()) .then( g =&gt; gifts = g); const submit_gift = () =&gt; ({result}) =&gt; { gifts = result; }; &lt;/script&gt;</pre> <p>And we're going to add two things to our HTML: a <code>use:enhance={submit_gift}</code> to the <code>form</code> tags, and a <code>transition:fade</code> to the gift rows.</p> <pre class="prettyprint"> {#each gifts as gift (gift.name)} &lt;form method="POST" action={ `${data.api_root_url}/api/child/${data.child_name}/remove_gift` } use:enhance={submit_gift}&gt; &lt;div class="row" transition:fade&gt; &lt;input type="hidden" name="gift" value={gift.name} /&gt; &lt;div class="max"&gt;{gift.name}&lt;/div&gt; &lt;button class="circle"&gt;&lt;i&gt;delete&lt;/i&gt;&lt;/button&gt; &lt;/div&gt; &lt;/form&gt; {/each}</pre> <p>With that, we don't reload the page if we're in a JavaScript-capable browser. Instead, we update the list of gifts in-place. Using a little fade in or fade out effect too, because as Glugg's slightly lisping friend would put it: "itch noth weally quweezmaz withouth a dasch oth pizzazz".</p> <h2><a name="the_ghost_of_space_age_future"></a>The ghost of space age future</h2> <p>The mechanism that we just saw works well for most cases. But what if we need... bigger guns? What if we have a heavier API, which requires more beefy magic behind the scene (think authentication, session information, and other devilish details)? Not a problem. Svelte provides the form manipulation tools we saw, but don't restrict us to it. We can elect to go as wild as turtledoves on the second day of Christmas.</p> <p>For example, let's change our backend service look more RESTy, and let's equip it with an <a href="https://www.openapis.org/">OpenAPI</a> definition.</p> <pre class="prettyprint">get '/child/:child_name/wishlist' =&gt; sub { my $name = route_parameters-&gt;get('child_name'); to_json $db-&gt;{$name}; }; put '/child/:child_name/wishlist' =&gt; sub { my $name = route_parameters-&gt;get('child_name'); warn "adding new gift"; push $db-&gt;{$name}-&gt;@*, +{ name =&gt; body_parameters-&gt;get('gift') }; return to_json $db-&gt;{$name}; }; del '/child/:child_name/wishlist' =&gt; sub { my $child_name = route_parameters-&gt;get('child_name'); my $to_remove = body_parameters-&gt;get('gift'); warn "removing gift\n"; $db-&gt;{$child_name} = [ grep { $_-&gt;{name} ne $to_remove } $db-&gt;{$child_name}-&gt;@* ]; return to_json $db-&gt;{$child_name}; }; get '/openapi.json' =&gt; sub { to_json { openapi =&gt; '3.0.1', servers =&gt; [ { url =&gt; 'http://localhost:5000/api' } ], paths =&gt; { '/child/{child_name}/wishlist' =&gt; { parameters =&gt; [ { name =&gt; 'child_name', in =&gt; 'path' } ], get =&gt; { operationId =&gt; 'get_child_wishlist', }, put =&gt; { operationId =&gt; 'add_to_child_wishlist', parameters =&gt; [ { name =&gt; 'gift', in =&gt; 'body' } ], }, delete =&gt; { operationId =&gt; 'remove_from_child_wishlist', parameters =&gt; [ { name =&gt; 'gift', in =&gt; 'body' } ], } } } } };</pre> <p>(Note that for this we could have used <a href="https://metacpan.org/module/Dancer2::Plugin::OpenAPIRoutes">Dancer2::Plugin::OpenAPIRoutes</a>, or <a href="https://metacpan.org/module/Dancer2::Plugin::Swagger2">Dancer2::Plugin::Swagger2</a>, or even made puppy eyes at Yanick to port <a href="https://metacpan.org/module/Dancer::Plugin::Swagger">Dancer::Plugin::Swagger</a> to Dancer.)</p> <p>Then one of the many things we could do is to use the npm package <a href="https://www.npmjs.com/package/openapi-client-axios">openapi-client-axios</a> which reads that OpenAPI definition file right off our backend and create the API object straight out of it. And with that the Svelte page becomes the following.</p> <pre class="prettyprint">&lt;script&gt; import { fade } from 'svelte/transition'; import OpenAPIClientAxios from "openapi-client-axios"; const api = new OpenAPIClientAxios({ definition: "/api/openapi.json", }); api.init(); export let data; let new_gift; let gifts = []; const loading = api.getClient() .then( client =&gt; client.get_child_wishlist(data.child_name) ) .then( ({data}) =&gt; gifts = data ); const submit_gift = () =&gt; ({result}) =&gt; { gifts = result; }; async function add_gift() { const client = await api.getClient(); await client.add_to_child_wishlist(data.child_name,{gift: new_gift}); gifts = [ ...gifts, { name: new_gift }]; } const remove_gift = (gift) =&gt; async () =&gt; { const client = await api.getClient(); await client.remove_from_child_wishlist(data.child_name,{gift}); gifts = gifts.filter( ({name}) =&gt; name !== gift ); } &lt;/script&gt; &lt;article&gt; &lt;h1&gt;Wishlist of {data.child_name}&lt;/h1&gt; &lt;div class="gifts"&gt; {#await loading} &lt;progress class="circle"&gt;&lt;/progress&gt; {:then} {#each gifts as gift (gift.name)} &lt;div class="row" transition:fade&gt; &lt;input type="hidden" name="gift" value={gift.name} /&gt; &lt;div class="max"&gt;{gift.name}&lt;/div&gt; &lt;button class="circle" on:click={remove_gift(gift.name)}&gt; &lt;i&gt;delete&lt;/i&gt; &lt;/button&gt; &lt;/div&gt; {/each} {/await} &lt;/div&gt; &lt;div class="new_gift row"&gt; &lt;div class="field border max"&gt; &lt;input type="text" name="new_gift" placeholder="new gift" bind:value={new_gift} /&gt; &lt;/div&gt; &lt;div&gt; &lt;button class="circle" disabled={!new_gift} on:click={add_gift}&gt;&lt;i&gt;add&lt;/i&gt;&lt;/button&gt; &lt;/div&gt; &lt;/div&gt; &lt;/article&gt;</pre> <h2><a name="a_merry_framework_to_us_all__glugg_love_them__everyone_"></a>A merry framework to us all; Glugg love them, everyone.</h2> <p>So, what's the takeaway of today's article? Perhaps that old recipes bring comfort, but that there is nothing wrong with spicing it up with new flavors now and then.</p> <p>Merry Xmas!</p> </div> Building a Sleek Blog in a Snap with Dancer2 and LiteBlog http://advent.perldancer.org/2023/16 perl http://advent.perldancer.org/2023/16 Sat, 16 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="building_a_sleek_blog_in_a_snap_with_dancer2_and_liteblog"></a>Building a Sleek Blog in a Snap with Dancer2 and LiteBlog</h1> <h2><a name="a_word_of_introduction"></a>A Word of Introduction</h2> <p>Perl has a curious way of never truly releasing its grip on a programmer. Recently, I felt the need to revamp my website, but the idea of spawning another Wordpress docker image on my server felt really overkill for what I had in mind. I wanted to do something super simple, with a landing page, a bit of info about my social profiles and, maybe, some articles if I wish to write content here and there.</p> <p>It had been a decade since I last delved into Perl code, but then I saw the release of Dancer2 1.0.0, announced by Jason. That was all I needed to truly awaken from my hibernation.</p> <p>So I decided to revisit my first programming love and see how rusty my Perl skills were.</p> <p>As it turns out, not only did I really enjoy building my website from the ground up using Dancer2, but it also became clear that I had curated enough material to craft a reusable plugin.</p> <p>And here was LiteBlog, a plugin able to change a Dancer2 app into a sleek, responsive, and file-based blogging engine.</p> <p>The 2023 edition of this Dancer Advent Calendar is the perfect chance for me to share a bit about Liteblog, explain its features, and perhaps convince you to update your own personal site with it (why not!).</p> <h2><a name="the_concept_behind_liteblog"></a>The Concept Behind LiteBlog</h2> <p>Minimalism stands at the core of LiteBlog. The aim was to design something uncluttered and lightweight &#x2014; true to the spirit of Dancer itself.</p> <p>The system is designed to work without traditional databases. Instead, everything from articles to configurations is manageable via static files in YAML or Markdown formats.</p> <p>As I aimed for a modern and responsive design that follows the latest HTML/CSS best practices, I decided to use Chat-GPT as my virtual web designer. As you may know, it excels in this area, especially when given detailed prompts for each design element. Iterating with it step by step, I managed to get great HTML/CSS content that matched exactly what I had in mind for the expected render.</p> <p>Currently, <a href="https://metacpan.org/dist/Dancer2-Plugin-LiteBlog">Dancer2::Plugin::LiteBlog</a> has reached version 0.05. It is stable and ready to serve as the backbone for your blog or personal site should you decide to give it a try.</p> <p>In this article, I'll detail its standout features, show how to bootstrap a Liteblog site, and guide you through the content creation process.</p> <p>If you want to see LiteBlog in action right away, you can head over to my site: <a href="https://alexissukrieh.com">https://alexissukrieh.com</a>.</p> <h2><a name="scaffolding_a_dancer2_app_into_a_liteblog_site"></a>Scaffolding a Dancer2 app into a Liteblog site</h2> <p>In a nutshell, Liteblog is crafted for those who appreciate minimalism. Editing YAML and Markdown file is the only thing you need to craft content and fine-tune your Liteblog site. There is no database involved, on-purpose.</p> <p>Before LiteBlog can work its magic, you need a Dancer2 app. Generate it:</p> <pre class="prettyprint">$ dancer2 gen -a my_app</pre> <p>Then, run the LiteBlog scaffolder from within the application directory:</p> <pre class="prettyprint">$ cd my_app $ liteblog-scaffold .</pre> <p>This command populates your app directory with LiteBlog's assets, such as CSS files, views, and even starter content.</p> <p>To hook it all up, modify the main app module to enable the LiteBlog plugin:</p> <pre class="prettyprint">package My::App; use Dancer2; use Dancer2::Plugin::LiteBlog; liteblog_init; 1;</pre> <p>Now, your Dancer2 app is a Liteblog. A default '/' route is defined, and widgets (that can be defined in your liteblog's settings) can also define their own routes. The blog engine is actually a Liteblog Widget. But let's just see what happens right there, when Liteblog is invoked from a pure Dancer2 app.</p> <p>Just launch your app and open its URL with your browser. You'll be greeted with a splash screen:</p> <pre class="prettyprint">$ plackup bin/app.psgi</pre> <p>Follow the on-screen instructions to enable features and widgets by adding the provided config snippet to your 'config.yml', then restart.</p> <p>Now, you have <code>Activities</code> cards enabled on the home and a nice article that you can read thanks to the <code>Blog</code> widget.</p> <h2><a name="publishing_content"></a>Publishing content</h2> <p>LiteBlog makes content creation intuitive. Articles reside in a specified root directory as markdown files with coresponding 'meta.yml' for metadata. To publish, go in your <code>articles/</code> folder (it has been created by the scaffolder), and then follow theses steps:</p> <p>Create a directory for the category of your post (if not, the article will be considered a page). Note that the directory name is the post slug.</p> <p>Inside, you first need to create the metadata file: <code>meta.yml</code>. It is mandatory and should at least contain a title. Here is an example of a valid metadata file for an article:</p> <pre class="prettyprint">--- title: "Some Great LiteBlog Article" image: "featured.jpg" tags: - "perl" - "dancer"</pre> <p>Finally, you can now create the <code>content.md</code> file, which, as its name suggests, should be written in Markdown format and will be used as the actual content of the article.</p> <p>The easiest way to get started is to edit the scaffolded article provided once you bootstrap your Liteblog app.</p> <h2><a name="main_features_of_a_liteblog_site"></a>Main features of a Liteblog site</h2> <p>As of version 0.05, LiteBlog's toolbox includes:</p> <ul> <li><a name="item__b_Activities_cards__b___Display_your_profiles_and_activities_in_an_elegant_grid_"></a><b><b>Activities cards</b>: Display your profiles and activities in an elegant grid.</b> </li> <li><a name="item__b_Blog_engine__b___Effortlessly_manage_and_publish_articles_"></a><b><b>Blog engine</b>: Effortlessly manage and publish articles.</b> </li> <li><a name="item__b_Caching_system__b___Speed_up_page_rendering_without_breaking_a_sweat_"></a><b><b>Caching system</b>: Speed up page rendering without breaking a sweat.</b> </li> <li><a name="item__b_Responsive_design__b___Your_blog_will_look_great_on_any_device_"></a><b><b>Responsive design</b>: Your blog will look great on any device.</b> </li> <li><a name="item__b_Highlight_js_support__b___Syntax_highlighting_for_articles_featuring_code_"></a><b><b>Highlight.js support</b>: Syntax highlighting for articles featuring code.</b> </li> </ul> <h2><a name="deploying_as_a_docker_image"></a>Deploying as a Docker image</h2> <p>It's quite classic these days to go with a Docker image for deployment. In case you want to build a Docker image for your Liteblog site, I have everything you need.</p> <p>Here is a Dockerfile to build your LiteBlog image:</p> <pre class="prettyprint"># Use a recent Debian release as base FROM debian:buster-slim # Install required packages RUN apt-get update &amp;&amp; \ apt-get install -y perl cpanminus libdbi-perl build-essential libssl-dev libexpat1-dev &amp;&amp; \ apt-get clean &amp;&amp; \ rm -rf /var/lib/apt/lists/* # Install Dancer2, Starman, and other necessary CPAN modules RUN cpanm -n Dancer2 Starman # Install LiteBlog and its dependencies RUN set -e; \ cpanm --installdeps -n Dancer2::Plugin::LiteBlog || (cat /root/.cpanm/work/*/build.log &amp;&amp; false) RUN set -e; \ cpanm Dancer2::Plugin::LiteBlog || (cat /root/.cpanm/work/*/build.log &amp;&amp; false) # Set the environment for Dancer2 ENV DANCER_ENVIRONMENT production # Copy your Dancer2 application to the container COPY . /app # Set working directory WORKDIR /app # Expose the port Starman will run on EXPOSE 5000 # Start the Dancer2 application with Starman CMD ["starman", "--workers=2", "bin/app.psgi", "-p", "5000"]</pre> <h2><a name="more_info__amp__contributing"></a>More info &amp; Contributing</h2> <p>To see LiteBlog in action, visit: <a href="https://alexissukrieh.com">https://alexissukrieh.com</a></p> <p>You can also read about <a href="https://alexissukrieh.com/dev/liteblog-a-minimalist-file-based-blog-engine-for-perl/">the story behind Liteblog</a> on my site.</p> <p>To contribute or report issues, head over to GitHub: <a href="https://github.com/sukria/Dancer2-Plugin-LiteBlog">https://github.com/sukria/Dancer2-Plugin-LiteBlog</a></p> <p>And don't forget to show your support on CPAN: <a href="https://metacpan.org/dist/Dancer2-Plugin-LiteBlog">https://metacpan.org/dist/Dancer2-Plugin-LiteBlog</a></p> <h2><a name="author"></a>Author</h2> <p>This article was written by Alexis Sukrieh for the Dancer Advent Calendar 2023.</p> </div> Dancer2::Plugin::Mason - What's old is new again! http://advent.perldancer.org/2023/15 perl http://advent.perldancer.org/2023/15 Fri, 15 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="dancer2__plugin__mason___what_s_old_is_new_again_"></a>Dancer2::Plugin::Mason - What's old is new again!</h1> <p>This year, Retro Santa paid us a visit and gave us a new gift of something old - an <a href="https://metacpan.org/module/HTML::Mason">HTML::Mason</a> plugin for Dancer2!</p> <h2><a name="but_santa__why_why"></a>But Santa, why? WHY?</h2> <p>Before we start, one thing to clarify. Not unlike Dancer, the Mason framework underwent a total rewrite at some point. And, like Dancer, the two versions of the framework adopted different namespaces (<a href="https://metacpan.org/module/HTML::Mason">HTML::Mason</a> and <a href="https://metacpan.org/module/Mason">Mason</a>, for v1 and v2 respectively) such that the breaking changes of the new version wouldn't break old applications. In this article we are specifically talking about using the templating engine of the original Mason, <a href="https://metacpan.org/module/HTML::Mason">HTML::Mason</a>.</p> <p>Mason as a framework is a templating engine and controller logic that is tightly coupled to the Apache web server that was meant to power it. Something that, at the time, made all the sense in the world. But since then... well, the world moved on a little bit. But while the controller layer of Mason got superceded by other mechanisms, its templating engine remains a strong and solid one. So it's not surprising that a lot of projects (such as <a href="https://bestpractical.com/request-tracker">Request Tracker (RT)</a>) kept using it, pairing it with their own controller layer. Likewise, most Perl web frameworks provide a way to use it.</p> <p>Catalyst? <a href="https://metacpan.org/module/Catalyst::View::HTML::Mason">Check</a>.</p> <p>Mojolicious? <a href="https://metacpan.org/module/Mojolicious::Plugin::Mason1Renderer">Check</a>.</p> <p>Dancer1? <a href="https://metacpan.org/module/Dancer::Template::Mason">Check</a>.</p> <p>Dancer2? ... uh oh.</p> <p>Well, uh oh no more!</p> <p><a href="https://metacpan.org/module/Dancer2::Template::Mason">Dancer2::Template::Mason</a> is a template adapter for HTML::Mason. If HTML::Mason is your favorite template system, you can now use it in your favorite web framework. If you have legacy Mason applications that would benefit from having a Dancer-powered controller, or a general refresh with some a more modern design, Dancer2 has your back.</p> <h2><a name="still_talking_about_mason_in_2023_does_this_mean_i_m_on_the_naughty_list"></a>Still talking about Mason in 2023? Does this mean I'm on the Naughty List?</h2> <p>Not at all! It's actually a pretty good gift, combining two powerful tools that are already in the toolbox of a lot of Perl developers. Go ahead, you can thank Retro Santa now. I'll wait.</p> <h2><a name="how_do_i_use_it"></a>How do I use it?</h2> <p>First, create your layout (<i>views/layouts/main.m</i>):</p> <pre class="prettyprint">&lt;html lang="en"&gt; &lt;head&gt; &lt;title&gt;&lt;% $title %&gt;&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;% $content %&gt; &lt;/body&gt; &lt;/html&gt; &lt;%args&gt; $title =&gt; "It's HTML::Mason... in Dancer2!" $content $deferred $perl_version $dancer_version $settings $request $params $vars $session &lt;/%args&gt;</pre> <p>That pile of variables in <code>%args</code>? As with its other template adapters, Dancer2 gives you those automatically. HTML::Mason, unlike some other template engines (<a href="https://metacpan.org/module/Template::Toolkit">Template::Toolkit</a> for example) prefers you to explicitly declare variables before using them. But, and don't tell anybody I told you this, if you don't wanna declare them, you can reference them all via the <code>%ARGS</code> hash too.</p> <p>Next, create your page template (<i>views/index.m</i>):</p> <pre class="prettyprint">&lt;p&gt;Hello, &lt;% $ARGS{ name } %&gt;!&lt;/p&gt;</pre> <p>And finally, add a route that uses your Mason template:</p> <pre class="prettyprint">get '/:name?' =&gt; sub { my $name = body_paramaters-&gt;get( 'name' ) // 'Mystery Visitor'; template 'index', { name =&gt; $name }; };</pre> <p>and Bob's your uncle! Seriously! Now, when you go to <code>http://localhost:5000/Uncle%20Bob</code> you'll see:</p> <pre class="prettyprint">Hello, Uncle Bob!</pre> <p>And if you go to <code>http://localhost/:5000</code> you get:</p> <pre class="prettyprint">Hello, Mystery Visitor!</pre> <h2><a name="care_and_feeding_of_your_mason_templates"></a>Care and feeding of your Mason templates</h2> <p>Off the shelf, the engine will re-compile the templates each time they are accessed, which is what one wants when developing. When the time comes for production and a little more ooomph is desired, the templates can be easily cached by adding the following to <i>environments/production.yml</i>:</p> <pre class="prettyprint">template: "mason" engines: template: mason: extension: m data_dir: "/path/to/your/app/var/" use_object_files: 1 static_source: 1</pre> <p>You'll need to clear the cache when templates change, however:</p> <pre class="prettyprint">rm -rf /path/to/your/app/var/obj</pre> <h2><a name="ok__where_did_this_gift_really_come_from_retro_santa_my_______"></a>Ok, where did this gift really come from? Retro Santa my %^$!!!</h2> <p>There are actually two mischievous elves hiding behind the beard and Santa suit. Yanick reworked his original Dancer1 Mason plugin, thanks to CromeDome's relentless poking, prodding, agitating, encouraging, testing, troubleshooting, and patch submitting.</p> <h2><a name="acknowledgements"></a>Acknowledgements</h2> <p>I'd like to thank Jonathan Swartz for the shout-out in the Mason documentation, and for contributing a Mason (v2) template adapter for Dancer(1), and Yanick for putting up with my endless shenanigans.</p> <h2><a name="author"></a>Author</h2> <p>This article has been written by Jason Crome for the Twelve Days of Dancer.</p> <h2><a name="copyright"></a>Copyright</h2> <p>No copyright retained. Enjoy.</p> <p>Jason A. Crome / CromeDome</p> </div> A new plugin for host-specific routing http://advent.perldancer.org/2023/14 perl http://advent.perldancer.org/2023/14 Thu, 14 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="a_new_plugin_for_host_specific_routing"></a>A new plugin for host-specific routing</h1> <p>The <a href="https://perldancer.org">Dancer2</a> team decided this year to turn on <a href="https://github.com/PerlDancer/Dancer2/discussions">GitHub Discussions</a>, and the first new-feature idea that came over the transom was a way to have routes respond only for specific request hosts.</p> <p>It is, after all, trivial to have <a href="https://nginx.org">nginx</a> or most any other web server that you might be using as a proxy to your Dancer2 application only be sent information from a specific host:</p> <pre class="prettyprint">upstream dancer_url { server unix:/srv/dancer_app/DEV/server.sock; } server { server_name my_dancer_app.org; server_name admin.my_dancer_app.org; location / { try_files $uri @proxy; } location @proxy { proxy_pass http://dancer_url; } }</pre> <p>...but how to make specific routes respond only to a specific host? My little testbed application, the <a href="https://jokeybot.com">JokeyBot</a>, does this in a <code>before</code> hook:</p> <pre class="prettyprint">hook before =&gt; sub { var dirty =&gt; 0; if ( request-&gt;base =~ /dirty\.jokeybot/ ) { var dirty =&gt; 1; } };</pre> <p>Thus, if you would like to see jokes that are, shall we say, less acceptable in polite company, you just go to <a href="https://dirty.jokeybot.com">https://dirty.jokeybot.com</a>. The <code>var</code> in the hook is used in all the routes needed to inject search parameters for jokes that have been marked as "dirty." <i>(As an aside, I must in all fairness warn you that I alone curate jokes into the JokeyBot, so the tags and sensibilities of a joke are all mine, and don't represent those of any professional comedian, my employer, or anyone else.)</i></p> <h2><a name="okay__so_why_a_new_plugin"></a>Okay, so why a new plugin?</h2> <p>It may be that you want certain routes to <b>only</b> respond to a specific URL hostname, or that you may have a wildly different function for that hostname, that uses the same database. An example of this might be if you had some sort of administrative interface, at <i>https://admin.my-dancer-app.org</i>, and you wanted those routes to behave completely differently if you point at them from the main address.</p> <p>Also, let's face it, TIMTOWTDI. A plugin that gave a route predicate for host-specific routing would be another way to do what I did on the JokeyBot. The idea had me curious, so I tinkered around for a few hours, and <a href="https://metacpan.org/pod/Dancer2::Plugin::HostSpecificRoute">Dancer2::Plugin::HostSpecificRoute</a> was born.</p> <h2><a name="the__code_host__code__predicate"></a>The <code>host</code> Predicate</h2> <p>Dancer2::Plugin::HostSpecificRoute introduced only one new keyword, <code>host</code>, which you use as a "predicate" in the introduction to a route, like so:</p> <pre class="prettyprint">get '/' =&gt; host 'special.my-dancer-app.com' =&gt; sub { # special code here }; get '/' =&gt; sub { # default code here };</pre> <p>Using it couldn't be easier! You could, of course, extend this to as many routes as you need for as many "special" variants as your web server will respond to. There are a couple of tips I should give you, though:</p> <ul> <li><a name="item_Always_put_your_default_route__if_any__last_"></a><b>Always put your default route (if any) last.</b> <p>You don't have to have a default fall-back route; if all of the routes with <code>host</code> predicates fail to match, the Dancer2 application will simply return a 404 error, as no route matches the request. If you choose to have a default behavior, put it last! For this reason, it's useful to keep them all in the same file, if you're splitting your routes across multiple files, just so include order doesn't trip you up.</p> </li> <li><a name="item_You_can_use_a_regex_for_the_host"></a><b>You can use a regex for the host</b> <p>Simply state your predicate with a regex, like so: <code>host qr/\.funkyhost.example$/</code>, and then any request that otherwise matches the route that is adressed to <code>*.funkyhost.example</code> will match.</p> </li> <li><a name="item_You_don_t_have_to_use_this_for_every_route"></a><b>You don't have to use this for every route</b> <p>You probably want some routes to behave the same way for all hosts that might go there--that's fine, just don't include a <code>host</code> predicate on those; they'll behave as normal Dancer2 routes.</p> </li> </ul> <h2><a name="conclusion"></a>Conclusion</h2> <p>This little plugin was a simple one to write, and it made me very happy to be able to answer a request from a Dancer2 user for this feature. If it's something you can use in your own applications, I hope it brings you as much joy as I got out of building it. Happy Dancing!</p> <h2><a name="author"></a>Author</h2> <p>This article was written by <a href="https://metacpan.org/author/GEEKRUTH">D Ruth Holloway</a> for the Dancer Advent Calendar 2023.</p> </div> The Twelve Days of Dancer, 2023 Edition http://advent.perldancer.org/2023/13 perl http://advent.perldancer.org/2023/13 Wed, 13 Dec 2023 00:00:00 -0000 <div class="pod-document"><h1><a name="the_twelve_days_of_dancer__2023_edition"></a>The Twelve Days of Dancer, 2023 Edition</h1> <p>Well, we made it another year! 2023 is about to ride off into the sunset, and 2024 is just around the corner. It's been a while since we've done one of these, and the Dancer Core Team thought that with the exciting year we've had, how better to wrap up the year with pretty paper and a bow than to celebrate with the 2023 installment of the Twelve Days of Dancer!</p> <h2><a name="state_of_the_dancer"></a>State of the Dancer</h2> <p>This has been an exciting year for Dancer2, and another lap around the sun for Dancer(1).</p> <h3><a name="dancer2_1_0_0"></a>Dancer2 1.0.0</h3> <p>The big news of 2023 is the release of Dancer2 1.0.0. This is perhaps the most exciting release of Dancer2 since it first found its way into the Perl community; possibly ever!</p> <p>With this release, we're very publicly planting the flag that Dancer2 is stable and ready for the big time. But anyone who's used Dancer2 in the last 6 or 7 years already knew that, didn't you? :-)</p> <p>So, why now? What happened? Let's take a peek behind the curtain and see how this all happened.</p> <h3><a name="july_in_toronto"></a>July in Toronto</h3> <p>On a warm, sunny July day in Toronto, three core team members (Sawyer, Yanick, and Jason), found themselves in close proximity for the first time in a long while. Seizing this rare opportunity, they decided to commandeer a table near the conference's registration area. Their mission? To chart the future course of Dancer2's journey towards greater heights in the world of web development. Or something like that.</p> <p>GeekRuthie, a long time vocal supporter and contributor to Dancer and its community, also happened to be at the conference, was able to join the meeting to help shape what the future world of Dancer2 dominance would look like.</p> <p>We cogitated and brainstormed and schemed and marshalled our resources, and laid out what the next release of Dancer2 should look like, lined up our troops, and sprang into action.</p> <h3><a name="but_what_to_call_it"></a>But what to call it?</h3> <p>As we hammered out details of the next release, the question arose of what version number to use. Should it be 0.500000? 0.450000? 123456? The more we talked, the further away that answer became.</p> <p>It was then Yanick came up with the most amazing idea: <i>the next version should be <b>1.0.0</b>.</i></p> <p>Dancer2 had been stable for years at that point. We were spending too much time on determining a version number, when really we could just jump to 1.0.0, go with a simpler versioning scheme that everyone understood (including us!), and giving us a few extra clock cycles to spend on more important things (like dinner).</p> <p>We ironed out a few more details, and once the conference wrapped up, the hard work began. It took longer than we had originally anticipated, but finally, Dancer2 1.0.0 was unleashed upon an unsuspecting world.</p> <p>The community feedback on this has been amazing, and exceeded our wildest expectations. Thank you so much for your support and glowing reception of this release!</p> <h3><a name="new_core_member_"></a>New core member!</h3> <p>The meeting in Toronto directly led to the GeekRuthie joining the Dancer Core Team. We're excited to have her as part of the team; Ruth's software development and social skills, plus her great ideas, are a wonderful, welcomed addition to the team.</p> <h3><a name="dancer_1_"></a>Dancer(1)</h3> <p>David Precious (bigpresh) continues to oversee Dancer(1). While it requires minimal attention, allowing more focus on Dancer2, we remain committed to its maintenance. We'll keep the community updated on any significant changes to Dancer(1).</p> <h2><a name="questions_for_the_community"></a>Questions for the Community</h2> <p>We have a few questions and invitations for our community:</p> <ul> <li><a name="item_Porting_of_Dancer_1__Plugins"></a><b>Porting of Dancer(1) Plugins</b> <p>We have a wishlist of Dancer plugins to be ported to Dancer2 on our <a href="https://github.com/PerlDancer/Dancer2/wiki/Wishlist">wiki</a>.</p> <p>Are you interested in tackling any of these? Let us know and we'll offer whatever assistance and moral support that we can!</p> <p>If there's more you'd like to see ported, we encourage you to add to the wiki.</p> </li> <li><a name="item_GitHub_Discussions"></a><b>GitHub Discussions</b> <p>With the launch of Dancer2 1.0.0, we also enabled GitHub Discussions for Dancer2. Do you find this to be valuable? Would you prefer to stick to IRC and the mailing list? Are there other avenues of communication (Slack, Discord, etc.) you'd like us to pursue?</p> </li> <li><a name="item_Future_Direction"></a><b>Future Direction</b> <p>Is there something you'd like to see in Dancer2 that we haven't addressed? We'd love to hear about it!</p> <p>Open an issue on GitHub, post to our mailing list, or start a discussion on GitHub. Our goal is to best serve the needs of our community, and we rely on your feedback and ideas to make that happen.</p> </li> </ul> <h2><a name="let_s_dance_"></a>Let's Dance!</h2> <p>Grab that framework and bust a move on the dancefloor! Enjoy our carefully curated selection of articles, and grow your Dancer toolbox just a bit more. We hope these inspire you to further engage and participate in this wonderful community.</p> <h2><a name="author"></a>Author</h2> <p>This article has been written by Jason Crome (CromeDome) for the Twelve Days of Dancer.</p> <h2><a name="copyright"></a>Copyright</h2> <p>No copyright retained. Enjoy.</p> <p>Jason A. Crome / CromeDome</p> </div>