Thursday, January 4, 2018

Character Viewer: How To Save User Input - part 2 - From Form to Table

    And I'm back to the Character Viewer tutorial, sorry about the delay (unless you're reading this years later, in which case no time has passed thanks to the magic of the internet).  We started by writing data we had hand-coded to a table, nice but useless.  Last post we added a form to collect the character data from the user, also nice but useless.  Now we're going to make this actually useful and take the data from the form and add it to our table.  Well, as useful as it is to have a very limited character summery - hmm... we'll have to add some more to this project.  Later.  We've got enough work for today :)


Measure Twice and Cut Once
    That's old carpenter's advise, but it applies to most everything.  A good plan will keep things from going off the rails, and looking ahead lets you avoid problems instead of fixing them.  So let's look at what we need to do before we start coding.  I'm going to need a new function that takes the data from the form, formats it, and then calls the existing function to write it to the table.  So I'm going to add that function and fill it with some comments about the things I need it to do...


Now, I might have missed some steps, we'll see as we go along, but this is enough of an outline to get me pointed in the right direction.
    So let's set the foundation.  First, I'm going to use the same function from the first post to iterate over the character object's properties and populate the table - so I need a blank object with the same properties...


I'm going to pass that object to the function to write it, so let me add that too...


And at the moment I can't actually run the function because it isn't tied to anything.  So let me add an onClick to the button I put on the form.  I can do this in two ways.  I can create an event listener in JavaScript or I can add it to the element in HTML.  Under the Model-View-Controller paradigm I should keep it in JavaScript, where the "controller" stuff happens that makes the program run.  But I actually like adding it to the HTML of the button itself, so when I look at the button I know what it's supposed to do.  Neither is really wrong as far as the browser's concerned, it's a matter of style.  Since the button is the only event handler I need, I'm going HTML.  If I was using something like jQuery UI where I had to initialize a ton of elements I'd do it in JavaScript to keep things together.  Anyways, here's the HTML attribute to fire the function when the button is clicked...


Okay, that sets up the framework, one blank object ready to be filled, a call to the function to display it, and a handler for the button to launch it.  Now we just need to start getting some data.


Do You Want To Do This The Easy Way Or The Hard Way?
    Let's start with the easy way.  Some of our form data is going to need to be processed, so let's grab something that we can directly display - the HP total.  The user is going to put in a number and we're going to directly copy that to the table, so it's a easy place to start.  Or is it?
    In order to get the data from the form we have to tell JavaScript where that data is.  Which turns out to be more complicated that one might expect.  There are actually a couple of ways to do this simple little thing.  Let's go from most complicated to least (the latter being the one we want to actually use).
    Like all things computer-related JavaScript has grown and developed over several versions.  The last big version, or ECMAScript was ES5.  In that version there are two main ways to select an HTML element, by Id or Class.  There are two different functions for each.  In this way to select an element we'd type:

document.getElementById("id")
or
document.getElementByClassName("class")

    ES6, the latest version of JavaScript consolidated those into just one function:

document.querySelector("#id") or document.querySelector(".class")

     The ES6 method isn't bad, but it also isn't great.  It's kind of long, 26 characters without the selector.  And we have about 14 form elements we need to look at.  The easiest way is to create an alias, to define a function that's shorter to type and make it the same as the longer function.  That's what something like jQuery does.  In a simplified version (very, very simplified) jQuery does this:

function $ (selector) {
    document.querySelector (selector);
};


which means you can type $("#id") instead of document.querySelector("#id"), which is 25 characters less typing.  25 characters may not seem like a lot, but when you make a few hundred selections that comes out to a short story's worth of saved code.  And we could do the same thing jQuery does.  But we won't.  Honestly, if I'm going to do something jQuery-like then I might as well just use jQuery instead of reinventing the wheel (and a poor version at that).  So for this tutorial I'm going to stick with the ES6 syntax and just copy-and-paste the code I need.  14 elements isn't a big deal, and if I decide to bulk up this app and add a bunch of features I'll just use jQuery and we'll have a tutorial about that :)  So, now that we know how to select something let's do it.
    Because our target is a textbox we need to select it, and then get it's "value" property.  Then we're going to save that to a variable and write it to our object.  Here's the code...


    And here's what it looks like when we run it...

 Woo Hoo!!!  We just took user-input and wrote it to our form, high-fives!  Okay, I'm way too happy about something so trivial :)  Now that we got the easy part done, it's time to get more complicated.


Formatting The Attributes
    This part is going to be ugly.  The next hardest, in a very general sense, are the attributes.  The attribute scores themselves are easy, we're going to take them from the form and input them directly to the table.  It's the modifiers that are tricky.  We decided (well, I decided and you got dragged along for the ride) to have the user only input the score and let JavaScript calculate the modifier.  This is good because it prevents the player from messing up the modifier, people are human and and make mistakes, so things like this - just looking up data on a table - are good candidates for leaving to the computer.  It was also a kind of dumb decision because it means adding more work for us with questionable reward.  But this is a tutorial, so it gives us something to learn (and if I do develop this further it's likely something I'll change).
    Our task then is three-fold, we need get the value and then use that to get the modifier, both of which get saved to the character object.  Getting the value we've got, like with HP, we're going to look up the value and save it to a variable.  To calculate the modifier we're going to write a new function that will apply the correct formula and return the modifier.  Let's write that function now.  So what is the formula for an attribute modifier in Pathfinder?  It's the ability score minus 10, divided by 2 and rounded down.  We save that and return that variable, which makes our helper function look like this...


 Cool, we can calculate the modifier.  Now comes the really tricky part.  Our function to write the character object is really easy, it just iterates through the object and writes each property.  Which means we have 6 properties for the attributes all of which are going to do the exact same thing, take a score, calculate the modifier, then make a string with both.  So let's expand out helper function to do all of that, it'll cut down on the duplicate code.  Or final function is going to look like this...


We have to call it 6 times, once for each attribute, because the way the form is set up we can't easily iterate through it.  Okay, let me correct that.  I know some ways to iterate through it, and how I could change it, but I don't know the best way.  I'll look at changing this section after I've done some more research.  For now, while this is not the ideal method, it isn't that terrible, so we'll go this way...


Which now looks like this when we create a character...


Woo Hoo again!  That's a good chunk of our character done.  Three more table cells to go.


Name, Social Security Number and Date of Birth, Please
    Okay, let's get the name section done.  There are 4 things we need: the name textbox, the race drop-down box, the classes (which are checkboxes and there can be more than one checked), and the character's level number box.  Three of those things are easy :)
    Let's grab the easy variables, with this code...


    Which leaves the trickier part of checking the checkboxes.  And I discovered something, when I wrote the HTML code for the checkboxes I didn't give them any Id's to be able to target them later.  Ooops... so here's the fix to that...


And then I just need to query each checkbox and see if it's checked.  If it is I'll add it to the string I'm going to display, which makes the function this...


With the final app looking like this...



En garde
    That leaves just two more cells, the ones for the weapons and armor.  Let's handle the weapons first.  So we have three weapons, and they are on radio buttons so the player can only choose one.  We need to get which one the player selects first.  The weapon will give us the name and damage.  Then we need to check the Base Attack Bonus field and add the appropriate attribute modifier to get the final part, the to-hit bonus.  Those three strings get concatenated, or added together, to give us the table cell.
    Or at least, that's how it should work.
    There's just one little problem, our character object isn't formatted that way.  When I first wrote this app I was looking for something simple, an easy way to write the data to the page.  So each attribute is just a string, with both the score and the modifier together.  And now that's a problem since I want to deal with those values separately.  I could just take the score and calculate the modifier again, but the function I used to do that is the one that also returns the complete string.  I did not structure these functions properly to fill in these last cells.
    Now, I try with these tutorials to show things that work.  I've written a few versions of the code so far and they didn't turn out right, so I omitted them.  I want these to be learning experiences, not full of things you shouldn't do.  I'm leaving this mistake in though because it does go back to the beginning of this post, that part where I was outlining what I needed to do.  During that step I really should have looked closer at how to structure my data, what different pieces I was going to use.  I didn't and now I'm in a bit of a fix.  It's important to have a good plan, to be able to see what you're doing in your head.  Even still you'll make mistakes, that's just how it works, but a good plan means fewer mistakes.
    So how do I fix this?
    I'll give you a minute, if this was your program what would you do?..........
    .....
    'K, time's up.  I can see two options.  The good one is to re-write the character object, so that I could use the modifiers in other ways down the road.  That, however, would mean re-writing the function to put that data on the page.  That's a lot of re-writing.  So option two is easier, and what I'm going to do.  I'm going to take the function I called to make the attributes and split it.  I'll leave the part that makes the final string, I'm just moving the part that calculates the modifier into it's own function.  That makes the least changes to the attributes code.  Then, for the weapons and armor I can just call the function to make the modifier and use that.  It'll basically double those calculations - but it's not like that'll matter in such a small program.  It's not an elegant solution, but it's the least re-coding.  Honestly this whole thing was a mistake from the beginning.  My function to write the object directly to the table is simple, but it's also way, way too simple for something as complicated as a Pathfinder character.  If this was a real app, meant to be useful and not a learning tool, I should have used a totally different design.  But I didn't, so we'll go with what we've got and get this done.
     That means that this is the revised code for the attributes function and the new modifier function:


     Moving on from that glitch, now we need to get the selected weapon.  And again there's another problem, just like with the checkboxes I didn't give each button an Id to reference it by.  Ohmygosh, and the armor radio buttons are the same way of course.  So let me fix all that...


Just like with the checkboxes we're basically going to do a bunch of if/then statements on each button.  There is a better way to iterate through the form, again though this is a small and limited app so I'm not going to worry about that right now.  This gets kind of complicated, so let me break it down step by step.
    First, I'm going to use the parseInt() function and get the Base Attack Bonus.  Even though this is listed as a number field in HTML I want to make sure JavaScript treats it as a number.
    I'm then going to check with some if/then blocks what radio button is checked.
    Another parseInt() on the function to find the correct attribute modifier gives us the other half of the to-hit modifier, and the damage modifier (except for the bow, which doesn't add any attribute for damage, only to-hit).
    Then we check if the To-hit modifier is 0 or higher, if it is I'm adding a "+" in front of the number; if it's negative there is no "+" of course.
    Lastly the same 0 or higher check for the damage modifier, for formatting purposes, and then the string is assigned to the character object.
    It's a lot of code for something so simple, here's the unarmed code block (for brevity's sake):


I missed the screenshot of the table, oh well, we're almost done so you can see the final version soon :)


Hide Behind The Pile Of Dead Bards!
     Ohmygoodness... we're almost done.  Yeah, this is a way too complicated "tutorial" for a fairly simple concept.  I know I haven't been the greatest teacher so far, and I'm sorry about that.  I'm learning myself.  But this is it, one last section and we'll be done.
    So armor is a lot like weapons.  First we need to figure out which one is selected.  Then, instead of getting a BAB score, each type of armor is going to have its own armor class modifier.  Then we just add different modifiers to the base armor class to get the "touch" and "flat-footed" armor classes.  So, like weapons this is going to take a fair amount of code to work out.
    Let's examine the individual components before we start madly coding.  There are three things that make a character's AC.  First there is the base AC that everybody gets, a 10.  This is the universal "naked man" AC.  Then there is the armor's modifier.  This is easy, each type has it's own mod (with "none" or no armor being a 0) - but, the catch is that you add your armor mod to your "full" or default AC and to your flat-footed AC, but not to your touch AC.  Last is the character's Dex modifier, again easy, but while you add it to the full AC and touch AC, it is capped depending on the type of armor, and it isn't added to the flat-footed AC.
    So first I'm going to determine what type of armor the character is wearing.  That will let me get the Armor AC modifier, the name of the armor for the final string, and then I can get the Dex mod and do an if/then block to make sure it isn't above the max dex for that type of armor.  Here's the code:


Then I can finish by adding the right things together, forming them into the string and writing the final string to the character object.  Here's that code:


And here are what a few finished characters look like:



   Great goodness, that took a very long time.  Well, for me at least - you're lucky you can just read the end results.  So at last we have looked at how to get data from a form and write it to an HTML table.  Is this app very useful?  Well, I guess it might be in a limited way.  If you changed everything to textboxes you could easily let people write their own stuff, and that would cut out a lot of the programming headaches, and it might actually be a somewhat useful app.  I will leave that to the reader if you want to try it :)  Here is the link to my Google Drive with the code.
    This little project spiraled out of control into something of a trainwreck, but hopefully it was a learning experience - it was for me :)  Who knows, maybe it would be worth it to play with this some more and turn it into some kind of character creation utility... hmmm.... we'll see.  For now I am going to sign off and go get that breakfast I'm 8 hours overdue for.  Until next time!


No comments:

Post a Comment