Sunday 15 May 2022

Web Tutorial: Florida Man Headline Generator

The Florida Man meme is an internet phenomenon due to the frankly bizarre headlines that come up every time someone does an internet search for the term "florida man". Honesely, I'm not so sure what's special about this swampy state in the USA, but it is what it is.

Just to get in some practice with VueJS, today we will be making a Florida Man headline generator. It won't be much - it will generate eight random headlines every time the page refreshes.

I plan to use images - but on the off-chance you don't want to, or want to keep images at a minimum, just consider using this one.

floridaman.jpg


Let's get cracking!

The starting HTML is here. Notice that we have a remote link to Vue.
<!DOCTYPE html>
<html>
    <head>
        <title>Florida Man</title>

        <style>

        </style>
    </head>

    <body>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

        <script>

        </script>
    </body>
</html>


For this, in the HTML, we want a div in there with a class of floridamanApp. And in the JavaScript, we make a call to create a new Vue object.
<!DOCTYPE html>
<html>
    <head>
        <title>Florida Man</title>

        <style>

        </style>
    </head>

    <body>
        <div id="floridamanApp">

        </div>

 
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.18/vue.min.js"></script>

        <script>
            var app = new Vue
            (
      
            );

        </script>
    </body>
</html>


Within that div, we have eight divs, each styled using the CSS class headlineContainer. Each one has an inner div styled using the CSS class headline. And in turn, each div has a h1 tag.
<div id="floridamanApp">
    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>
 
    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

    <div class="headlineContainer">
        <div class="headline">
            <h1></h1>
        </div>
    </div>

</div>


We will fill up the first div with some sample content within the h1 tag. The curly bracer pairs are placeholders.
<div class="headlineContainer">
    <div class="headline">
        <h1>Florida Man {{ }} {{ }} {{ }} {{ }} for {{ }} {{ }}!</h1>
    </div>
</div>


Now, admittedly, this isn't much. This needs some styling.




For the headlineContainer CSS class, we provide a specific width and height, set the float property to left and give it a top and left margin of 5 pixels. That's for layout.
<style>
    .headlineContainer
    {
        width: 500px;
        height: 200px;
        float: left;
        margin: 5px 0 0 5px;
    }

</style>


We then give it round corners and a thick grey border, make sure the background-size property is set to cover (because there will be a background image later on) and ensure that it's set to a vintage coloring by applying grayscale at 100%. Lastly, we set the overflow property to hidden in order to ensure that the HTML contents are neatly handled.
<style>
    .headlineContainer
    {
        width: 500px;
        height: 200px;
        float: left;
        border-radius: 10px;
        border: 5px solid rgba(100, 100, 100, 1);

        margin: 5px 0 0 5px;
        filter: grayscale(100%);
        background-size: cover;
        overflow: hidden;

    }
</style>


For the headline CSS class, we set width and height at 100%, float it left and apply a translucent black background.
<style>
    .headlineContainer
    {
        width: 500px;
        height: 200px;
        float: left;
        border-radius: 10px;
        border: 5px solid rgba(100, 100, 100, 1);
        margin: 5px 0 0 5px;
        filter: grayscale(100%);
        background-size: cover;
        overflow: hidden;
    }

    .headline
    {
        width: 100%;
        height: 100%;
        float: left;
        background-color: rgba(0, 0, 0, 0.5);
    }

</style>


This looks OK-ish. For headlineContainer, the overflow property has been set to hidden, so you won't see any ugly seams at the edges. The layout appears to be working nicely.




For the h1 tag, we add some padding and specify the width. Center everything. Make the words captialized.
.headline
{
    width: 100%;
    height: 100%;
    float: left;
    background-color: rgba(0, 0, 0, 0.5);
}

.headline h1
{
    text-transform: capitalize;
    text-align: center;
    padding: 10px;
    width: 480px;
}


Then we make the text all white and use the text-shadow property to give it a black outline.
.headline h1
{
    text-transform: capitalize;
    text-align: center;
    color: rgba(255, 255, 255, 1);
    text-shadow: 1px -1px 0 rgba(0, 0, 0, 1), -1px 1px 0 rgba(0, 0, 0, 1), -1px -1px 0 rgba(0, 0, 0, 1), 1px 1px 0 rgba(0, 0, 0, 1);

    padding: 10px;
    width: 480px;
}


Yeah! Looks great!




That's out of the way. We are going to do some JavaScript next. For Vue, we have to specify data and methods. And before we forget, set the el property to interact with floridamanApp.
<script>
    var app = new Vue
    (
        {
            el: "#floridamanApp",
            data:
            {

            },
            methods:
            {
            
            }
        }

    );
</script>


For data, we have headlines and wordTypes, both arrays. We will deal with headlines in a bit, but let's first take a look at wordTypes. Each element is an array of words. You can fill this up with whatever words you wish. I've done a few right here.
data:
{
    headlines:
    [

    ],
    wordTypes:
    {    
        nouns:
        [
            "alligator",
            "businessman",
            "cop",
            "dragonfly",
            "ewe",
            "florist",
            "girl",
            "hamster",
            "iguana",
            "joker"
        ],
        nouns_plural:
        [
            "alligators",
            "businessmen",
            "cops",
            "dragonflies",
            "ewes",
            "florists",
            "girls",
            "hamsters",
            "iguanas",
            "jokers"
        ],
        numbers:
        [
            "three",
            "six",
            "thirteen",
            "nineteen",
            "twenty-three",
            "fifty-nine",
            "eighty-seven"
        ],
        units:
        [
            "bags",
            "crates",
            "pounds",
            "pairs",
            "consignments"
        ],
        units_time:
        [
            "days",
            "hours",
            "minutes",
            "weeks"
        ],
        actions:
        [
            "antangonize",
            "brutalize",
            "chase",
            "kill",
            "marry",
            "proposition",
            "question"
        ],
        actions_past:
        [
            "antangonized",
            "brutalized",
            "chased",
            "killed",
            "married",
            "propositioned",
            "questioned"
        ],
        actions_plural:
        [
            "antangonizes",
            "brutalizes",
            "chases",
            "kills",
            "marries",
            "propositions",
            "questions"
        ],
        actions_active:
        [
            "antangonizing",
            "brutalizing",
            "chasing",
            "killing",
            "marrying",
            "propositioning",
            "questioning"
        ],
        descriptors:
        [
            "anxious",
            "articulate",
            "blasphemous",
            "blind",
            "caustic",
            "cowardly",
            "dead",
            "dramatic",
            "enormous",
            "epic",
            "friendly",
            "furious",
            "generous",
            "gorgeous",
            "heavy",
            "huge",
            "impassive",
            "irate",
            "jovial",
            "jumpy",
            "kind",
            "knowledgeable",
            "lascivious",
            "livid",
            "monstrous",
            "mysterious"
        ]
    }

},


Now, for headlines. Each element in headlines is an object consisting of key-value pairs. These keys should have an equivalent to the keys in wordTypes, and the values are arrays of empty strings. Note that descriptors has two empty strings instead of one. You'll see why later on.
headlines:
[
    {
        actions_plural: [""],
        descriptors: ["", ""],
        nouns: [""],
        numbers: [""],
        units_time: [""]
    }

],


Let's create some methods! We begin by having a fillHeadlineArray() method. It has a parameter, headlineIndex. This is a pointer to the element in headlines that we want to change, or fill, as it were. There is only one element in headlines right now, but that is going to change later.
methods:
{
    fillHeadlineArrays: function(headlineIndex)
    {
                                                        
    }

}


We first get keys by taking all the keys of the wordTypes array. What we want to do is use a For-each loop to iterate through the keys.
methods:
{
    fillHeadlineArrays: function(headlineIndex)
    {
        var keys = Object.keys(this.wordTypes);

        keys.forEach(
            (wordType) =>
            {

            }
        );
                                                        
    }
}


And in it, search the current element of headlines for that particular key. If it exists...
methods:
{
    fillHeadlineArrays: function(headlineIndex)
    {
        var keys = Object.keys(this.wordTypes);

        keys.forEach(
            (wordType) =>
            {
                if (this.headlines[headlineIndex][wordType])
                {

                }

            }
        );                                                        
    }
}


...fill each empty string in that particular array, with a randomly selected element from that particular array in wordTypes. For this, we use a For loop and the getOne() method.
methods:
{
    fillHeadlineArrays: function(headlineIndex)
    {
        var keys = Object.keys(this.wordTypes);

        keys.forEach(
            (wordType) =>
            {
                if (this.headlines[headlineIndex][wordType])
                {
                    for (let i = 0; i < this.headlines[headlineIndex][wordType].length; i++)
                    {
                        var newWord = this.getOne(this.wordTypes[wordType]);

                        this.headlines[headlineIndex][wordType][i] = newWord;
                    }

                }
            }
        );                                                        
    }
}


This is the getOne() method. It accepts an array, arr. We just randomly pick one element from arr and return it.
methods:
{
    fillHeadlineArrays: function(headlineIndex)
    {
        var keys = Object.keys(this.wordTypes);

        keys.forEach(
            (wordType) =>
            {
                if (this.headlines[headlineIndex][wordType])
                {
                    for (let i = 0; i < this.headlines[headlineIndex][wordType].length; i++)
                    {
                        var newWord = this.getOne(this.wordTypes[wordType]);

                        this.headlines[headlineIndex][wordType][i] = newWord;
                    }
                }
            }
        );                                                        
    },
    getOne: function(arr)
    {
        return arr[Math.floor((Math.random() * arr.length))];
    }

}


Now in Vue, set created.
methods:
{
    fillHeadlineArrays: function(headlineIndex)
    {
        var keys = Object.keys(this.wordTypes);

        keys.forEach(
            (wordType) =>
            {
                if (this.headlines[headlineIndex][wordType])
                {
                    for (let i = 0; i < this.headlines[headlineIndex][wordType].length; i++)
                    {
                        var newWord = this.getOne(this.wordTypes[wordType]);

                        this.headlines[headlineIndex][wordType][i] = newWord;
                    }
                }
            }
        );                                                        
    },
    getOne: function(arr)
    {
        return arr[Math.floor((Math.random() * arr.length))];
    }
},
created: function()
{

}


Let's use a For loop to iterate through headlines, running the fillHeadlineArrays() method for each one. Remember, right now, and I know I risk sounding like a broken record at this point, there is only one element in headlines.
created: function()
{
    for (let i = 0; i < this.headlines.length; i++)
    {
        this.fillHeadlineArrays(i);
    }

}


In the HTML, we need to put in the references in the placeholder bracers. Note that "descriptors" appears twice. That's because the descriptors array in the first (and only) element of headlines has two elements. So the first one is referenced using index 0, and the second using index 1.
<div class="headlineContainer">
    <div class="headline">
        <h1>Florida Man {{ headlines[0]["actions_plural"][0] }} {{ headlines[0]["descriptors"][0] }} {{ headlines[0]["descriptors"][1] }} {{ headlines[0]["nouns"][0] }} for {{ headlines[0]["numbers"][0] }} {{ headlines[0]["units_time"][0] }}!</h1>
    </div>
</div>


Let's see what we get!




Let's make another headline. This time, the elements of headlines are indexed using 1, whereas in the previous one, they were indexed using 0.
<div class="headlineContainer">
    <div class="headline">
        <h1>Florida Man {{ headlines[0]["actions_plural"][0] }} {{ headlines[0]["descriptors"][0] }} {{ headlines[0]["descriptors"][1] }} {{ headlines[0]["nouns"][0] }} for {{ headlines[0]["numbers"][0] }} {{ headlines[0]["units_time"][0] }}!</h1>
    </div>
</div>

<div class="headlineContainer">
    <div class="headline">
        <h1>Florida Man {{ headlines[1]["actions_past"][0] }} by {{ headlines[1]["nouns"][0] }}  for {{ headlines[1]["actions_active"][0] }} {{ headlines[1]["nouns"][1] }}!</h1>
    </div>
</div>

<div class="headlineContainer">
    <div class="headline">
        <h1></h1>
    </div>
</div>


In headlines, we add another object. This one has a different set of word types.
headlines:
[
    {
        actions_plural: [""],
        descriptors: ["", ""],
        nouns: [""],
        numbers: [""],
        units_time: [""]
    },
    {
        actions_past: [""],
        nouns: ["", ""],
        actions_active: [""]
    }

];


Again, let's see what we get. Notice that "dragonfly" appears twice in the second headline. It's a little boring to have the same words being used, so let's take care of that.




In the fillHeadlineArrays() method, add this line. It's a While loop that ends only if the randomly selected word isn't already in that particular array. Do note that this will result in an infinite loop if you have more elements in that particular array than there are in the corresponding wordTypes array!
fillHeadlineArrays: function(headlineIndex)
{
    var keys = Object.keys(this.wordTypes);

    keys.forEach(
        (wordType) =>
        {
            if (this.headlines[headlineIndex][wordType])
            {
                for (let i = 0; i < this.headlines[headlineIndex][wordType].length; i++)
                {
                    var newWord = this.getOne(this.wordTypes[wordType]);

                    while (this.headlines[headlineIndex][wordType].indexOf(newWord) != -1)
                    {
                        newWord = this.getOne(this.wordTypes[wordType]);
                    }


                    this.headlines[headlineIndex][wordType][i] = newWord;
                }
            }
        }
    );                                                        
},


This should be fine now.




Adding images

What's a headline without an image? We will add a call to the getStyle() method in each div here, and pass in the current index of each div, as an argument.
<div class="headlineContainer" v-bind:style="getStyle(0)">
    <div class="headline">
        <h1>Florida Man {{ headlines[0]["actions_plural"][0] }} {{ headlines[0]["descriptors"][0] }} {{ headlines[0]["descriptors"][1] }} {{ headlines[0]["nouns"][0] }} for {{ headlines[0]["numbers"][0] }} {{ headlines[0]["units_time"][0] }}!</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(1)">
    <div class="headline">
        <h1>Florida Man {{ headlines[1]["actions_past"][0] }} by {{ headlines[1]["nouns"][0] }} for {{ headlines[1]["actions_active"][0] }} {{ headlines[1]["nouns"][1] }}!</h1>
    </div>
</div>


Create the getStyle() method. What we want is to grab images. Since right now we only use floridaman.jpg, we can put that in an array, use the getOne() method to pick it (it will pick this image because that's the only one) and return the style string!
getOne: function(arr)
{
    return arr[Math.floor((Math.random() * arr.length))];
},
getStyle: function(headlineIndex)
{
    var arr = ["floridaman"];

    if (arr.length == 0)
    {
        return "";
    }
    else
    {
        return "background: url(" + this.getOne(arr) + ".jpg)";
    }
}


There you go.




But what if you want more? Then we'll have to add more. Here, I'm creating the images array and populating it with the names of all the images sans extension. The images can be found in this repository.
getStyle: function(headlineIndex)
{
    var images = ["alligator", "businessman", "cop", "dragonfly", "ewe", "florist", "girl", "hamster", "iguana", "joker", "alligators", "cops", "girls", "hamsters"];

    var arr = ["floridaman"];

    if (arr.length == 0)
    {
        return "";
    }
    else
    {
        return "background: url(" + this.getOne(arr) + ".jpg)";
    }
}


Next, we define keys the same way we did in fillHeadlineArrays(). And then I do a For-each loop, going through the entire wordTypes array.
getStyle: function(headlineIndex)
{
    var keys = Object.keys(this.wordTypes);
    var images = ["alligator", "businessman", "cop", "dragonfly", "ewe", "florist", "girl", "hamster", "iguana", "joker", "alligators", "cops", "girls", "hamsters"];

    var arr = ["floridaman"];

    keys.forEach(
        (wordType) =>
        {

        }
    );


    if (arr.length == 0)
    {
        return "";
    }
    else
    {
        return "background: url(" + this.getOne(arr) + ".jpg)";
    }
}


We do a search for wordType in the current element of headlines. If it's found, we do an inner For-each loop to go through images.
keys.forEach(
    (wordType) =>
    {
        if (this.headlines[headlineIndex][wordType])
        {
            images.forEach(
                (img) =>
                {

                }
            )
        }

    }
);


... and then push img into the array arr if it is found in the list of words! Now the getOne() method should return more than one possible value if we pass in arr as an argument.
keys.forEach(
    (wordType) =>
    {
        if (this.headlines[headlineIndex][wordType])
        {
            images.forEach(
                (img) =>
                {
                    if (this.headlines[headlineIndex][wordType].indexOf(img) != -1) arr.push(img);
                }
            )
        }
    }
);


Great! In the first headline, the script chooses between floridaman.jpg and cop.jpg. In the second headline, the script chooses between floridaman.jpg, dragonfly.jpg and ewe.jpg.




We can add more content now in the HTML.
<div class="headlineContainer" v-bind:style="getStyle(0)">
    <div class="headline">
        <h1>Florida Man {{ headlines[0]["actions_plural"][0] }} {{ headlines[0]["descriptors"][0] }} {{ headlines[0]["descriptors"][1] }} {{ headlines[0]["nouns"][0] }} for {{ headlines[0]["numbers"][0] }} {{ headlines[0]["units_time"][0] }}!</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(1)">
    <div class="headline">
        <h1>Florida Man {{ headlines[1]["actions_past"][0] }} by {{ headlines[1]["nouns"][0] }} for {{ headlines[1]["actions_active"][0] }} {{ headlines[1]["nouns"][1] }}!</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(2)">
    <div class="headline">
        <h1>Florida Man set to {{ headlines[2]["actions"][0] }} {{ headlines[2]["descriptors"][0] }} {{ headlines[2]["descriptors"][1] }} {{ headlines[2]["nouns"][0] }} in {{ headlines[2]["numbers"][0] }} {{ headlines[2]["units_time"][0] }}.</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(3)">
    <div class="headline">
        <h1>Florida Man {{ headlines[3]["actions_plural"][0] }} {{ headlines[3]["numbers"][0] }} {{ headlines[3]["units"][0] }} of {{ headlines[3]["nouns_plural"][0] }} for {{ headlines[3]["descriptors"][0] }} {{ headlines[3]["descriptors"][1] }} {{ headlines[3]["nouns"][0] }}.</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(4)">
    <div class="headline">
        <h1>Florida Man is {{ headlines[4]["descriptors"][0] }} at {{ headlines[4]["descriptors"][1] }} {{ headlines[4]["descriptors"][2] }} situation involving {{ headlines[4]["descriptors"][3] }} {{ headlines[4]["nouns_plural"][0] }} and {{ headlines[4]["descriptors"][4] }} {{ headlines[4]["nouns_plural"][1] }}.</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(5)">
    <div class="headline">
        <h1>Florida Man unable to {{ headlines[5]["actions"][0] }} {{ headlines[5]["nouns"][0] }} due to {{ headlines[5]["descriptors"][0] }} {{ headlines[5]["nouns_plural"][0] }}.</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(6)">
    <div class="headline">
        <h1>Florida Man jailed for {{ headlines[6]["actions_active"][0] }} {{ headlines[6]["nouns_plural"][0] }} and {{ headlines[6]["actions_active"][1] }} {{ headlines[6]["descriptors"][0] }} {{ headlines[6]["nouns"][0] }}.</h1>
    </div>
</div>

<div class="headlineContainer" v-bind:style="getStyle(7)">
    <div class="headline">
        <h1>Florida Man wins {{ headlines[7]["descriptors"][0] }} contest against {{ headlines[7]["numbers"][0] }} {{ headlines[7]["units"][0] }} of {{ headlines[7]["descriptors"][1] }} {{ headlines[7]["nouns_plural"][0] }}.</h1>
    </div>
</div>


And in the headlines array.
headlines:
[
    {
        actions_plural: [""],
        descriptors: ["", ""],
        nouns: [""],
        numbers: [""],
        units_time: [""]
    },
    {
        actions_past: [""],
        nouns: ["", ""],
        actions_active: [""]
    },
    {
        actions: [""],
        descriptors: ["", ""],
        nouns: [""],
        numbers: [""],
        units_time: [""]
    },
    {
        actions_plural: [""],
        numbers: [""],
        units: [""],
        nouns_plural: [""],
        descriptors: ["", ""],
        nouns: [""]
    },
    {
        descriptors: ["", "", "", "", ""],
        nouns_plural: ["", ""]
    },
    {
        actions: [""],
        nouns: [""],
        descriptors: [""],
        nouns_plural: [""]
    },
    {
        actions_active: ["", ""],
        nouns: [""],
        descriptors: [""],
        nouns_plural: [""]
    },
    {
        numbers: [""],
        units: [""],
        nouns_plural: [""],
        descriptors: ["", ""]
    }

],


Our final product!




Thanks for reading!
This was totally awesome. And yes, really frivolous. Probably not really something you'd want to use VueJS for.

Florida Man writes boisterous web tutorial that confounds the livid majestic webosphere!
T___T

No comments:

Post a Comment