4
\$\begingroup\$

Here's the JSFiddle, the GitHub repo, and here it is out in the wild.

Note: the application uses Chrome's Speech Synthesis API and only works on Chrome or Safari for iOS7 (oddly enough).

It was my first voyage into jQuery-UI. I'd love some feedback on structure, coding style, and things that could break.

JS

var jsonData, interval;

var activities, intervals, workoutLength;

var ROUND_OVER = "Round over.",
    WORKOUT_CANCELLED = "Workout cancelled.",
    ACTIVITY_OVER = "Done";

$(function () {

    $("#round, #break, #intervals, #type").buttonset();

    $("button")
        .button()
        .click(function (event) {
        event.preventDefault();
    });

    $("#rate").slider({
        range: "max",
        min: 0,
        max: 2,
        value: 1.2,
        step: 0.01
    });

    $("#type :radio").change(function () {
        var acts = getSelectedActivities(jsonData);
        renameActivities(acts);
    });

    $("#start").click(function () {
        beginRound();
    });

    $('#aboutDialog').dialog({
        modal: true,
        autoOpen: false,
        width: 400,
        buttons: {
            Ok: function () {
                $(this).dialog("close");
            }
        }
    });

    $('#about').click(function () {
        $('#aboutDialog').dialog('open');
        return false;
    });

    $('#sorry').dialog({
        modal: true,
        autoOpen: false,
        width: 400,
        buttons: {
            Ok: function () {
                $(this).dialog("close");
            }
        }
    });

    $.ajax({
        type: "GET",
        url: "https://api.myjson.com/bins/21rrc",
        dataType: "json",
        success: function (d) {
            jsonData = d;
            renameActivities(getSelectedActivities(jsonData));
        }
    });


    if (window.SpeechSynthesisUtterance === undefined) {
        $('#sorry').dialog('open');
    }


    function beginRound() {

        // defined here so that the user can change them on the fly
        activities = getActivities();
        intervals = getIntervals();
        workoutLength = +getRoundLength() * 60;

        clearInterval(interval);

        var activityDuration = 5;
        var timeElapsed = 0;

        say("Starting in " + activityDuration + " seconds.");

        var loop = function () {
            clearInterval(interval);


            if (timeElapsed >= workoutLength || activities.length < 1) {
                beginBreak();
                return;
            }

            if (timeElapsed > 0) say(ACTIVITY_OVER);

            activityDuration = +getInterval();
            timeElapsed += activityDuration;

            say(getCommand(activityDuration));
            interval = setInterval(loop, activityDuration * 1000);
        };

        interval = setInterval(loop, activityDuration * 1000);

    }

    $("#end").click(function () {
        window.speechSynthesis.cancel();
        clearInterval(interval);
        say(WORKOUT_CANCELLED);
    });

    function say(text) {
        var msg = new SpeechSynthesisUtterance();
        var voices = window.speechSynthesis.getVoices();
        msg.voiceURI = 'native';
        msg.volume = 1;
        msg.rate = getSpeechRate();
        msg.text = text;
        msg.lang = 'en-US';

        console.log('"' + text + '"');
        speechSynthesis.speak(msg);
    }

    function getActivity() {
        return activities[Math.floor(Math.random() * activities.length)];
    }

    function getInterval() {
        return intervals[Math.floor(Math.random() * intervals.length)];
    }

    function getCommand(time) {
        var activity = getActivity();
        activities.remove(activity);
        return time + " seconds of " + activity;
    }

    function beginBreak() {
        window.speechSynthesis.cancel();
        say(ROUND_OVER + " " + getBreakLength().toString() + " second break.");
        window.setTimeout(beginRound, +getBreakLength() * 1000)
    }

    function getActivities() {
        return $("#activities").val()
            .split("\n")
        // remove empty/null/undefined components
        .filter(function (e) {
            return e === 0 || e
        });
    }

    function getIntervals() {
        return $('#intervals input:checkbox:checked').map(function () {
            return +this.value;
        }).get();
    }

    function getRoundLength() {
        return +$("#round input:radio:checked").val();
    }

    function getBreakLength() {
        return +$("#break input:radio:checked").val();
    }

    function getSpeechRate() {
        return +$("#rate").slider("values", 0);
    }

    Array.prototype.remove = function () {
        var what, a = arguments,
            L = a.length,
            ax;
        while (L && this.length) {
            what = a[--L];
            while ((ax = this.indexOf(what)) !== -1) {
                this.splice(ax, 1);
            }
        }
        return this;
    };


    function getSelectedActivities(activities) {
        var selected = $("#type :radio:checked").val();
        return activities[selected];
    }

    function renameActivities(activities) {

        $("#activities").text("");

        for (var i = 0; i < activities.length; i++) {
            $("#activities").append(activities[i] + "\n");
        }

        $("#activities").attr("rows", activities.length + 1);
    }

});

HTML

 <h2>DRILLS</h2>

<div id="column">
    <div style="float:left">
        <!-- round length -->
        <label>Round Length (min)</label>
        <div id="round">
            <input type="radio" id="r1" name="round" value="1">
            <label for="r1" role="button">1</label>
            <input type="radio" id="r2" name="round" checked="checked" value="2">
            <label for="r2" role="button">2</label>
            <input type="radio" id="r3" name="round" value="3">
            <label for="r3" role="button">3</label>
        </div>
    </div>
    <div style="text-align:right">
        <!-- break length -->
        <label>Break Length (sec)</label>
        <div id="break">
            <input type="radio" id="b1" name="break" value="30">
            <label for="b1" role="button">30</label>
            <input type="radio" id="b2" name="break" checked="checked" value="60">
            <label for="b2" role="button">60</label>
            <input type="radio" id="b3" name="break" value="90">
            <label for="b3" role="button">90</label>
        </div>
    </div>
    <!-- intervals -->
    <label>Intervals (sec)</label>
    <div class="row">
        <div id="intervals">
            <input type="checkbox" id="check1" value="10">
            <label for="check1" role="button">10</label>
            <input type="checkbox" id="check2" checked="checked" value="20">
            <label for="check2" role="button">20</label>
            <input type="checkbox" id="check3" checked="checked" value="30">
            <label for="check3" role="button">30</label>
            <input type="checkbox" id="check4" checked="checked" value="40">
            <label for="check4" role="button">40</label>
            <input type="checkbox" id="check5" value="50">
            <label for="check5" role="button">50</label>
            <input type="checkbox" id="check6" value="60">
            <label for="check6" role="button">60</label>
        </div>
    </div>
    <!-- activities -->
    <label>Activities (you can edit or add to these!)</label>
    <div class="row">
        <div id="type">
            <input type="radio" id="t1" name="type" value="Calisthenics">
            <label for="t1" role="button">Calisthenics</label>
            <input type="radio" id="t2" name="type" checked="checked" value="Combos">
            <label for="t2" role="button">Combos</label>
            <input type="radio" id="t3" name="type" value="Cardio">
            <label for="t3" role="button">Cardio</label>
        </div>
    </div>
    <textarea id="activities" rows="11"></textarea>
    <br/>
    <br/>
    <label>Speech Rate</label>
    <div id="rate"></div>
    <br/>
    <div>
        <button id="start" role="button">start</button>
        <button id="end" role="button">end</button>
        <button id="about" role="button">about</button>
    </div>
    <p> <a href="https://github.com/crclayton/drills">Fork me on GitHub</a>

    </p>
    <div id="aboutDialog" title="About Drills"> <span class="ui-icon ui-icon-info" style="float:left; margin-top: 14px; margin-right:10px"></span>

        <p> <b>This program creates and speaks workout drills.</b>

        </p>
        <p>It will construct the drill randomly from the listed activities, read the activities out-loud until the round length is up, then start again after the break.</p>
        <p>There are some pre-defined activity categories, but you can edit these and create your own routine that the program will read.</p>
        <p>You can also modify the activity interval lengths, the round length, and the break length. Also, don't forget to hydrate.</p>
    </div>
    <div id="sorry" title="Not Supported"> <span class="ui-icon ui-icon-info" style="float:left; margin-top: 14px; margin-right:10px"></span>

        <p style="font-weight:bold">Sorry, your browser doesn't support text-to-speech.</p>
        <p>This program uses Google's Speech Synthesis API which your browser doesn't support. Try Chrome or Safari for iOS7.</p>
    </div>
</div>
</div>
\$\endgroup\$
0
\$\begingroup\$

Looks good in general, but I can suggest a little improvements.

  1. You put the whole code on the onReady of the dom, and is not necessarily bad, but consider this is the event who trigger when the web page is finish to load and the document is ready to being used. Are you sure you need the whole functionality after this event?

  2. If this code/functionality start to grow, the code will be hard to maintain. I suggest to start to split this in something like more complex model where the responsibility is spliced. You can make objects as "view", with a small logic, and others can be used as data. And later you can go near of a view-controller-model than interact with this dom manipulation. Theres a lot of frameworks to do this, a good start could be backbone.js

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.