diff --git a/js/sr2ini.js b/js/sr2ini.js index 3ae1635..dca8718 100644 --- a/js/sr2ini.js +++ b/js/sr2ini.js @@ -1,44 +1,46 @@ -/* Notes -- different behavior sr2/3; but I'll only implement 2e now -- Initiative could also offer to enter Ini dice (1-5) and Reaction (0-20); that way the app itself could roll their ini -- highlight current combatant -> es ist sowieso immer der ganz oben dran (Ausnahme: mehrere mit der gleichen ini) -- how do I handle several combatants with the same ini? -- style mit bootstrap -*/ +/* + * helper functions + */ - - -// keydown handler for editable ini elements; gets called when for every key press while editing an ini value -function handleIniKeydown (e) { - var keycode = e.which; - - // not among the allowed keys: ignore the keystroke - if ( ! - ( keycode == 8 //backspace - || keycode == 9 //tab - || keycode == 46 //del - || keycode == 27 //escape - || keycode >= 35 && keycode <= 40 //arrows, pos1, end - || keycode >= 48 && keycode <= 57 && $(this).text().length <= 1 ) ) //number key, and text length is less than 2 - { - e.preventDefault(); - }; - - // enter key: trigger blur - // note: keycode is 13 except on safari and iphone where it's 10 - if ( keycode == 13 || keycode == 10 ) { - $(this).trigger("blur"); +// roll for initiative with the given reaction and number of ini dice +function rollForInitiative(dice, rea) { + let ini = 0; + for ( let i = 0; i < parseInt(dice); i++ ) { + roll = Math.floor(Math.random() * 6) + 1; + console.log(roll); + ini += roll; } + return ini + parseInt(rea); } -// keydown handler for editable name elements -function handleNameKeydown (e) { - // enter key: don't add newline, just trigger blur - // note: keycode is 13 except on safari and iphone where it's 10 - if ( e.which == 13 || e.keycode == 10 ) { - e.preventDefault(); - $(this).trigger("blur"); +/* + * Event handler functions + */ + +// blur handler for input elements (blur is triggered when an element loses focus) that checks if validation is required +function handleBlur (e) { + let $tr = $(e.target).parents("tr"); + + // trigger validation check only if the blurred input element is not in #newCombRow and focus is not going to another input in the same row + if ( ( ! $tr.is("#newCombRow") ) && ( ! ( $(e.relatedTarget).is("input") && $(e.relatedTarget).parents("tr") == $tr ) ) ) { + + // validation check + if ( ! validateCombRowValues($tr) ) { + e.preventDefault(); + return false; + } + + // did value stay the same? do nothing then + if ( e.target.value == $(e.target).parents("tr").attr("data-ini") ) { + return true; + } + + // value changed => reposition row + $(e.target).parents("tr").attr("data-ini", e.target.value); + let $trc = $tr.clone(true); + $tr.remove(); + insertCombRow($trc); } } @@ -47,18 +49,17 @@ function handleNameKeydown (e) { function handleActButtonClick (e) { // find current table row let $tr = $(e.target).parents("tr.combRow").clone(true); + let input = $tr.find(".combIni")[0]; // reduce ini by 10 but not lower than 0 - let ini = parseInt($tr.find(".ini-editable").text()) - 10; - if ( ini <= 0 ) { - ini = 0; - $tr.addClass("ini-zero"); - } - $tr.find(".ini-editable").text(ini); + input.value = Math.max(parseInt(input.value) - 10, 0); + $(input).parents("tr").attr("data-ini", input.value); // remove original current table row and insert clone at newly calculated position $(e.target).parents("tr.combRow").remove(); insertCombRow($tr); + + return; } @@ -68,107 +69,224 @@ function handleRemoveButtonClick (e) { $(e.target).parents("tr.combRow").remove(); } -// Blur handler for ini fields. Sets CSS classes and repositions the combatant row -function handleIniBlur (e) { - // find current table row - let $tr = $(e.target).parents("tr.combRow").clone(true); - // add/remove class ini-zero - if ( $tr.find(".ini-editable").text() == "0" ) { - $tr.addClass("ini-zero"); - } else { - $tr.removeClass("ini-zero"); +/* + * Validation functions + */ + +// validate a combatant row form by checking for all conditions, including regular HTML5 validation +function validateCombRowValues($tr) { + + let inputElements = $($tr).find("input[class*='comb']").toArray(); + + // do standard HTML5 form validation first (validates that name is not empty and that all other values are numbers within their individual ranges) + let valid = true; + for (let input of inputElements) { + if ( ! input.reportValidity() ) { + valid = false; + break + } + } + if ( ! valid ) { + return false; } - //reposition - $(e.target).parents("tr.combRow").remove(); - insertCombRow($tr); + // now some custom validation + // first get values + let ini = inputElements[1].value.trim(); + let dice = inputElements[2].value.trim(); + let rea = inputElements[3].value.trim(); +console.log("I/D/R: " + ini + "/" + dice + "/" + rea); + + // invalidate if ini, dice and rea are all empty + if ( ini == "" && ( dice == "" || rea == "" ) ) { + inputElements[1].setCustomValidity("Requiring values for initiative, ini dice and reaction, or all three"); + inputElements[1].reportValidity(); + inputElements[1].setCustomValidity(""); + return false; + } + + // invalidate if dice or rea is empty but not both + if ( ( dice == "" ) != ( rea == "" ) ) { + inputElements[2].setCustomValidity("Values required for both dice and reaction, or none (if ini is given)"); + inputElements[2].reportValidity(); + inputElements[2].setCustomValidity(""); + return false; + } + + // invalidate if value for ini doesn't align with values for dice+rea + if ( ini != "" && dice != "" && rea != "" ) { + let d = parseInt(dice); + let r = parseInt(rea); + if ( ini < d+r || ini > d*6+r ) { + inputElements[1].setCustomValidity("Impossible to reach initiative " + ini + " with " + dice + "D6+" + rea + ", please adjust ini or leave empty"); + inputElements[1].reportValidity(); + inputElements[1].setCustomValidity(""); + return false; + } + } + + // ok fine + return true; } + +/* + * Main functions + */ + // inserts a combatant table row (in form of a jQuery object) at the correct position in the table function insertCombRow($tr) { - let ini = parseInt($tr.find(".ini-editable").text()); - let $combRows = $("tr.combRow"); + + let ini = parseInt($tr.find(".combIni").val()); + let $combRows = $("tr.combRow:not(#newCombRow)"); // find correct position for new row - // no combatants in table? put the new row below the form row + // no combatants in table => put new row below the form row if ( $combRows.length == 0 ) { - $("tr#formRow").after($tr); + $("tr#newCombRow").after($tr); } - // ini is less than the lowest in table? put the new row at the end - else if ( ini <= parseInt($combRows.last().find(".ini-editable").text() ) ) { - $("tbody#sortable").append($tr); + // ini is less than the lowest in table => put new row at the end + else if ( ini < parseInt($combRows.last().find(".combIni").val() ) ) { + $combRows.last().after($tr); } - - // compare ini with the ones in table from the top down) and put the new row in the first spot where its ini is larger + + // ok, compare ini with the ones in table (top to bottom) else { - for ( var i = 0; i < $combRows.length; i++ ) { - if ( ini > parseInt($combRows.eq(i).find(".ini-editable").text()) ) { - $("tr.combRow:eq(" + i + ")").before($tr); - break; + for ( let i = 0; i < $combRows.length; i++ ) { + let currentRowIni = parseInt($combRows.eq(i).find(".combIni").val()); + + // ini larger than the one already in table? insert before that row + if ( ini > currentRowIni ) { + $combRows.eq(i).before($tr); + break; + } + + // ini equal to the one already in table => compare rea + else if ( ini == currentRowIni ) { + let rea = parseInt($tr.find(".combRea").val()); + let currentRowRea = parseInt($combRows.eq(i).find(".combRea").val()); +console.log("rea is " + rea + " and currentRowRea is " + currentRowRea); + + // one or two values for rea missing, or new row rea larger than current row rea => insert before + if ( rea != NaN && currentRowRea != NaN && rea > currentRowRea ) { + $combRows.eq(i).before($tr); + break; + } + + // we're already at the last row => insert after + if ( i + 1 == $combRows.length ) { + $combRows.eq(i).after($tr); + } } } } - - return; } + // add new combatant function addCombatant (e) { - // form not validating? return - if ( ! ( $("#combForm")[0].checkValidity() ) ) { -console.log("form's not validating :-("); - // do some javascript magic to trigger a form submit which will show validation messages while not actually going through - $('').hide().appendTo("#combForm").click().remove(); + if ( ! validateCombRowValues($("#newCombRow")) ) { return; } - // strip ini value of leading zero - let ini = $("#combIni").val(); - if ( ini.length == 2 && ini[0] == "0" ) { - ini = ini[1]; - } -console.log("Ini is: " + ini); - + // get values + let ini = $("#newCombRow .combIni").val().trim(); + let dice = $("#newCombRow .combDice").val().trim(); + let rea = $("#newCombRow .combRea").val().trim(); + + // roll for initiative if ini is empty + ini = (ini != "") ? ini : rollForInitiative(dice, rea); + // construct jQuery object for table row let $tr = $($.parseHTML( [ - '\n', - '', ini , '', - '', $("#combName").val(), '', - '', - '
', - '', - ' ', - '', - '
', - '', - ''].join("") + '\n', + '
\n', + '\n', + '\n', + '\n', + '\n', + '\n', + '\n', + '\n', + '
\n', + '\n', + 'D+\n', + '\n', + '
\n', + '\n', + '\n', + '
\n', + ' \n', + '\n', + '
\n', + '\n', + '
\n', + ''].join("") )); // add handlers to table row object - $tr.find(".ini-editable").on("keydown", handleIniKeydown).on("blur", handleIniBlur); - $tr.find(".name-editable").on("keydown", handleNameKeydown); - $tr.find(".act-button").on("click", handleActButtonClick); - $tr.find(".remove-button").on("click", handleRemoveButtonClick); + $tr.find("input[class*='comb']").on("blur", handleBlur); + $tr.find("button.act-button").on("click", handleActButtonClick); + $tr.find("button.remove-button").on("click", handleRemoveButtonClick); // insert combatant row insertCombRow($tr); //reset form values - $("#combIni, #combName").val(""); + $("#newCombRow input[class*='comb']").val(""); return; } +// start a new combat round +function newRound() { + + // are there rows at all? + let $oldRows = $("tr.combRow:not('#newCombRow')"); + if ( $oldRows.length == 0 ) { + return; + } + + // clone existing rows + let $newRows = $oldRows.clone(true); + + // reset ini values in cloned rows + $newRows.each( function() { + let $input = $(this).find(".combIni"); + let dice = $(this).find(".combDice").val(); + if ( dice == "" ) { + $input.val(0); + } else { + $input.val(rollForInitiative(dice, $(this).find(".combRea").val())); + } + $(this).attr("data-ini", $input.val()); + }); + + // remove old rows + $oldRows.remove(); + + // insert cloned rows + $newRows.each( function() { + insertCombRow($(this)); + }); +} + + +/* + * Initialize document + */ + $(document).ready(function(){ - // add handlers for using the Add button or enter key in the new combatant form - $("#combAddButton").on("click", addCombatant); - $("#combIni, #combName").on("keydown", function (e) { + // add event handlers to initial buttons and input elements + $("#newCombAddButton").on("click", addCombatant); + $("#newRoundOkButton").on("click", newRound); + $("#newCombRow input[class*='comb']").on("blur", handleBlur).on("keydown", function (e) { if ( e.which == 13 || e.which == 10 ) { - $("#combAddButton").trigger("click"); + addCombatant(); } }); });