Categories
Programming Uncategorized

Making a HTML Form with tags using Hyperscript

I’ve recently been playing with the Hyperscript (https://hyperscript.org/) tool, and I’ve been really liking it. I set myself the challenge of making a HTML form, and adding the ability to add tags to the form.

So how might we achieve this? Lets start with a simple HTML form:

<form>
  <label for="item">Item: </label><input name="item">
</form>

Now how can we send the tags? Lets add a hidden input to send the tags:

<form>
  <label for="item">Item: </label><input name="item">
  <input type="hidden" name="tags" id="tags-input">
</form>

Now we need somewhere to enter the tags that we want to add to the form. To do this I’ve added a second form underneath the first:

<form id="tagform">
  <label for="tags">Tags: </label><input name="tags"> (Hit enter to add a tag)
</form>

Now, we have a couple of issues:
1. Hitting <enter> in the second form submits the whole page
2. We need to get the input from the tagform into the tags input of the initial form

Now we start to add some hyperscript. First, we can use “on submit” event to trigger our hyperscript code. Secondly, we can use “halt the event” to stop the form from triggering a whole page load. Lets add that to the tagform:

<form id="tagform"
 _="on submit halt the event">
  <label for="tags">Tags: </label><input name="tags"> (Hit enter to add a tag)
</form>

Now lets get the contents of the input, and send it as an event to the element with ID tags-input (our hidden input from the first form). To find the element we can use “first <input/> in me”, which will search the form (me) for input elements. Then we can get the value of that element, and set a variable to store the value. A one-liner of this is:
set taginput to (first in me).value

Next we want to send and event – this is easy in hyperscript. We just say send <eventname>(<dataname>: <data>) to <element>. To make this more real, we will call our event “newtag”. To select the target, we can use “#tags-input” to target the element. Our data is already saved in the variable “taginput”. So our line is:
send newtag(tag: taginput) to #tags-input

<form id="tagform"
 _="on submit
      halt the event
      set taginput to (first <input/> in me).value
      send newtag(tag: taginput) to #tags-input">
  <label for="tags">Tags: </label><input name="tags"> (Hit enter to add a tag)
</form>

Now to test that this works, lets log to the console. On our hidden input, lets receive the sent event. We can use “on <event>” to receive events:
on newtag log 'new tag arrived!'
Here is the hidden input:

  <input type="hidden" name="tags" id="tags-input"
  _="on newtag log 'new tag arrived!'">

Now when we put in a tag, we can see the log message in the console. However, lets instead log the actual tag. To do that, we can save the input of the “newtag” event to a variable. I’m using the backticks to make a formatted string:
on newtag(tag) log `new tag: $tag arrived!`

But we want to actually save the tag. Lets append a comma and the new tag to the value of our hidden input. This isn’t perfect, as our backend will have to filter the first comma character, but it should be workable. We can use the “@attribute” syntax to access it like this:
on newtag(tag) append ,$tag to @value

We should actually show the tags that are set to users, so lets add an unordered-list to our form, and put list-items in it to show them.

Let’s add a “ul” and give it an ID:
<ul id="shown-tags"></ul>

To create a HTML element we can use the “make” keyword like this:
make a <li/>

To put some content into the “li” like this:
make a <li/> then put tag into it

And then lets put it into the ul. We can use “put <thing> at the start of <target>” to put it inside the “ul” without removing the current contents:
put it at the start of #shown-tags

This is our form so far:

<form>
  <label for="item">Item: </label><input name="item">
  <input type="hidden" name="tags" id="tags-input"
  _="
  on newtag(tag)
    append `,$tag` to @value
    make a <li/> then put tag into it
    put it at the start of #shown-tags">
  <ul id="shown-tags"></ul>
</form>

We now have a working tag system! Here’s what we have so far:

<form>
  <label for="item">Item: </label><input name="item">
  <input type="hidden" name="tags" id="tags-input"
  _="on newtag(tag)
       append `,$tag` to @value
       make a <li/> then put tag into it
       put it at the start of #shown-tags">
  <ul id="shown-tags"></ul>
</form>
<form id="tagform"
 _="on submit
      halt the event
      set taginput to (first <input/> in me).value
      send newtag(tag: taginput) to #tags-input">
  <label for="tags">Tags: </label><input name="tags"> (Hit enter to add a tag)
</form>

However, we could make it a bit nicer. First of all, lets clear the tag input (in the second form) when adding a new tag, which we can do by changing the value of it:
set value of first <input/> in me to ''

There’s also no way to remove tags currently. We could implement that by allowing users to click on tags in the list, and then create an event out of that to delete the tag. First, lets begin to implement that on the unordered-list:
on click log 'want to remove a tag'

We can now see the log in the console. Now to get the actual tag name to remove, we can use the “target” which is set to the the target element of the “on click” event. This will be our “li” element that was clicked. Lets take the innerHTML of it, and set it to a variable:
on click set taginput to innerHTML of target then log taginput

Ok, so now lets instead turn that into an event and send it to the hidden input. Our final code looks like this:

  <ul id="shown-tags"
  _="on click send deltag(tag: innerHTML of target) to #tags-input"></ul>

Now we need to receive that event, and do something with it on the hidden input. To do so, we need to put an “end” on the current event handler block, and add a new “on deltag(tag)”. This is what we can start with:

  <input type="hidden" name="tags" id="tags-input"
  _="on newtag(tag)
       append `,$tag` to @value
       make a <li/> then put tag into it
       put it at the start of #shown-tags
     end
     
     on deltag(tag)
       log `want to delete $tag`
     end">

So first, lets remove the entry from our @value attribute. The best way I’ve found to do this is to read the attribute, call split(‘,’) on it to turn it into an array, turn it into a set, remove the item from the set, then turn it back into an array and re-join it with ‘,’. There might be a better way to do this, but this is what I’ve got so far. Let’s break it down:

The split function is a JS call, so we can use the “call” keyword:
call (@value of me).split(',')

The “Set” type is also a JS type, so we can call it’s constructor using the “make <type> from <arguments>” syntax. The output of the previous JS call is stored as the implicit “it”/”result” variable, which we can use as our input. Finally, lets save the created set as a variable. Note that “it” changes what it refers to mid-way through the code – first it refers to the Array created from “split()” then to the Set created from “make”:
call (@value of me).split(',')
make a Set from it then set s to it

Now lets remove the item from the set. Also, because we have a “,” at the beginning of the @value attribute we end up with an empty item in our set, so we can remove that:
call s.delete(tag) then call s.delete('')

Now we can turn the set back into an array, and re-join it:
call Array.from(s) then set my @value to it.join(',')

We now remove the tag from the input, but not from the page. We could remove it either in the event handler on the unordered-list, or we could remove it when processing the deltag event on the hidden input. I’ve decided to do it on the hidden input, to allow me to send deltag events from other places, and still remove the list-items correctly. To do this, we can use a for loop to loop over the children of the #shown-tags element:

for el in #shown-tags.children
  if innerHTML of el == tag
    remove el
  end
end

Our final code:

  • has a place to type tags
  • saves the tags (comma seperated) to a hidden input
  • shows the saved tags in list
  • lets you click the shown tags to remove the from the list/input

Here it is:

<form>
  <label for="item">Item: </label><input name="item">
  <input type="hidden" name="tags" id="tags-input"
  _="on newtag(tag)
       append `,$tag` to @value
       make a <li/> then put tag into it
       put it at the start of #shown-tags
     end
     
     on deltag(tag)
       call (@value of me).split(',')
       make a Set from it then set s to it
       call s.delete(tag) then call s.delete('')
       call Array.from(s) then set my @value to it.join(',')
       for el in #shown-tags.children
         if innerHTML of el == tag
           remove el
         end
       end
     end">
  <ul id="shown-tags"
  _="on click send deltag(tag: innerHTML of target) to #tags-input"></ul>
</form>
<form id="tagform"
 _="on submit
      halt the event
      set taginput to (first <input/> in me).value
      send newtag(tag: taginput) to #tags-input
      set value of first <input/> in me to ''">
  <label for="tags">Tags: </label><input name="tags"> (Hit enter to add a tag)
</form>

I’ve also put this into a JSFiddle here, so you can experiment with it: https://jsfiddle.net/jay_tuckey/05g9m4fc/70/

I think this is a pretty simple and readable example of what you can do with hyperscript. I’m liking it so far, and find it useful for simple page manipulation like this. Do you have any comments or suggestions on how I could improve it? Feel free to leave a comment or contact me: https://jaytuckey.name/about/

Leave a Reply

Your email address will not be published.