javascript, jQuery, markdown, user-content

Javascript Quick & Dirty Dynamic Numeric Evaluations From Dynamic Content

I’m writing a help page for a lottery order form to confirm to specific gaming standards. The page has to show odds for a given set of games that are offered on the order form. I made the decision to pull the content from the database, so that it can be edited from the admin site rather than us going in and editing it. I also decided that a Markdown syntax would restrict how the content was displayed yet allow a degree of power in how it was formatted.

However, there definitely was a problem in displaying things like pay tables. I had to insert a couple of tables into this content to show odds and payout factors for the games that were displayed. Here’s the table in Markdown (GFM-flavor) format. For those who don’t know what Markdown is, it is a method of writing content that is still very readable in its un-rendered form.

# Payout Factors for Different Bet Types

Game Type | Combination | Example Bet | Payout
 ------------ | ------------------------------------------- | -------------------- | --------
 Pick 2 | Straight: Any 2 digit # from 00 to 99 | 12 (exact order) | 90:1
 Pick 2 | Boxed (2-way; 2 unique digits) | 12 (any order) | 45:1
 Pick 3 | Straight: Any 3 digit # from 000 to 999 | 123 (exact order) | 900:1
 Pick 3 | Boxed (6-way; 3 unique digits) | 123 (any order) | 150:1
 Pick 3 | Boxed (3-way, 2 unique digits) | 334 (any order) | 300:1
 Pick 4 | Straight: Any 4 digit # from 0000 to 9999 | 1234 (exact order) | 9000:1
 Pick 4 | 24-Way Box (4 digit # with unique digits) | 1234 (any order) | 375:1
 Pick 4 | 12-Way Box (4 digit #, 3 unique digits) | 1233 (any order) | 750:1
 Pick 4 | 6-Way Box (4 digit #, 2 sets of 2 digits) | 3344 or 4343 (any order) | 1500:1
 Pick 4 | 4-Way Box (4 digit, 3 digits the same) | 1112 (any order) | 2250:1

The above renders into an H1 tag for the title and an HTML table for the table portion.

That’s all fine, but see where it says 90:1 for a straight bet? I don’t want a human to edit that number. Additionally, I don’t want to use an example number, since that would force the users to mentally calculate the payout factors themselves, and could possibly violate the standards we are implementing. Therefore, the actual payout factor values should be pulled from the database. There’s a number of different ways to do this, one being using something like a replacement token, e.g. {2BallPF}:1. But then, I gotta do something like {2BallPF/2}:1 for the boxed bet, which I would have to figure out on the server…. sounds like a huge pain. I could just let the users edit it every time mass payout factors are changed…

 Pick 2 | Straight: Any 2 digit # from 00 to 99 | 12 (exact order) | 70:1
Pick 2 | Boxed (2-way; 2 unique digits) | 12 (any order) | 45:1
Pick 3 | Straight: Any 3 digit # from 000 to 999 | 123 (exact order) | 700:1
Pick 3 | Boxed (6-way; 3 unique digits) | 123 (any order) | 150:1
Pick 3 | Boxed (3-way, 2 unique digits) | 334 (any order) | 300:1

NO. I can’t entrust the operators of the system to make these changes any time a payout factor update is done. And I definitely don’t want to manage that crap. It always happens on a Friday night when I am relaxing, or any time on Saturday or Sunday. I really value my free time, even if it’s to do absolutely nothing.

Would a framework like angular.js, react.js, durandal, etc. work for what I’m trying to do? Possibly, but I wasn’t interested in the learning curve (at this moment, mind you, although learning at least one or two of these frameworks sounds like fun), or the weight. All I wanted to do was something like take a token, replace it with its value, and evaluate anything as an expression. I thought to myself “man, a ‘data-‘ style attribute would be really nice about now”. I then remembered that Markdown allows HTML to be inserted.

So this is what I did! I made some example output in jsbin:

<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
 <meta charset="utf-8">
 <title>JS Bin</title>
</head>
<body>
<input type="hidden" id="max2" value="90" />
 <input type="hidden" id="max3" value="900" />
 <input type="hidden" id="max4" value="9000" />
 <input type="hidden" id="mx" value="6" />
 
 This is a test to see how:<br /><br />
 
 <span data-fix="{max2}">x</span>
 / <span data-fix="{mx}">16</span> = <span data-fix="{max2} / {mx}">y</span>.<br><br>
 
 <span data-fix="{max3}">x</span>
 / <span data-fix="{mx}">16</span> = <span data-fix="{max3} / {mx}">y</span>.<br><br>
 
 <span data-fix="{max4}">x</span>
 / <span data-fix="{mx}">16</span> = <input type="text" data-fix="{max4} / {mx}" value="?" />.<br><br>
 
</body>
</html>

So for elements that I want to dynamically render a mathematical value, I enclose them in a span tag with an attribute of data-fix. This contains the formula, which is actually something that eval() can parse in Javascript. A bit dangerous, if someone were to get into the admin and change a value to something destructive, but then they could do a heck of a lot more than just add stuff for eval() statements to hit.

A little javascript magic goes a long way:

$(function(){
 var regex = /\{[\w\d\-_]+?\}/gim;
 $("[data-fix]").each(function(){
 var that = $(this);
 var formula = that.attr("data-fix");
 //console.log("formula = " + formula);
 var rendered = formula.replace(regex, function(match, capture) {
 var s = match.replace("{", "").replace("}", "");
 //console.log("-- s = " + s);
 var elem = document.getElementById(s);
 //console.log("-- tag = " + elem.tagName);
 if (elem == null || elem === undefined)
 return "null";
 var value = $(elem).val() | $(elem).html();
 return value;
 });
 //console.log("rendered = " + rendered);
 var final = eval(rendered);
 //console.log("final = " + final);
 if (this.tagName.toLowerCase() == "input" || this.tagName.toLowerCase() == "select")
 that.val(final);
 else
 that.html(final);
 });
});

What’s going on here is that I’m grabbing all instances of any element that has the data-fix attribute, grabbing the “template” formula, doing any replacing in it as needed, doing an eval() on the final formula, and then inserting the value back into the element, one time.

Check out the JS Bin for more details and to play with it!