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( [ - '