crossword-8.x-1.x-dev/js/crossword.js
js/crossword.js
(function ($, Drupal, once, drupalSettings) { Drupal.behaviors.crossword = { attach: function (context, settings) { var selector = drupalSettings.crossword.selector; once('crossword-init', selector).forEach(function(crossword){ var $crossword = $(crossword); var data = drupalSettings.crossword.data; // Handle persisting revealed status. data.revealed = Drupal.behaviors.crossword.isRevealed(data.id); if (data.revealed) { $crossword.addClass('crossword-revealed'); } var answers = Drupal.behaviors.crossword.loadAnswers(data); var Crossword = new Drupal.Crossword.Crossword(data, answers); Crossword.$crossword = $crossword; Crossword.$activeCluesText = $('.active-clues-text', $crossword); $crossword.data("Crossword", Crossword); Drupal.behaviors.crossword.addCrosswordEventHandlers($crossword, data); Drupal.behaviors.crossword.connectClues($crossword, data); Drupal.behaviors.crossword.connectSquares($crossword, data); Drupal.behaviors.crossword.addInputHandlers($crossword, data); Drupal.behaviors.crossword.addKeydownHandlers($crossword, data); Drupal.behaviors.crossword.addClickHandlers($crossword, data); // Trick the display into updating now that everything is connected. Crossword.setActiveClue(Crossword.activeClue); // Handle checkboxes for settings. // Show-errors setting. if ($('#show-errors', $crossword).prop('checked')) { $crossword.addClass('show-errors'); } once('crossword-show-errors-change', '#show-errors', crossword).forEach(function(element) { $(element).on('change', function () { $crossword.toggleClass('show-errors'); localStorage.setItem('#show-errors', $(this).prop('checked')); }); }); // Show-references setting. if ($('#show-references', $crossword).prop('checked')) { $crossword.addClass('show-references'); } once('crossword-show-references-change', '#show-references', crossword).forEach(function(element){ $(element).on('change', function(){ $crossword.toggleClass('show-references'); localStorage.setItem('#show-references', $(this).prop('checked')); }); }); // Show-clue setting. if ($('#show-clues', $crossword).prop('checked') === false) { $crossword.addClass('hide-clues'); } once('crossword-show-clues-change', '#show-clues', crossword).forEach(function(element){ $(element).on('change', function(){ $crossword.toggleClass('hide-clues'); localStorage.setItem('#show-clues', $(this).prop('checked')); }); }); // Load settings from storage to override defaults. Drupal.behaviors.crossword.loadSettings($crossword) // Instructions button is an odd duck. once('crossword-button-instructions', '.button-instructions', crossword).forEach(function(element){ $(element).click(function(e){ e.preventDefault(); $('.crossword-instructions-container', $crossword).toggleClass('active'); }); }); }); }, loadSettings: function($crossword) { var settings = [ '#show-errors', '#show-references', '#show-clues', ]; settings.forEach(function(setting) { if (localStorage.getItem(setting) !== null) { if (String($(setting, $crossword).prop('checked')) !== localStorage.getItem(setting)) { $(setting, $crossword).trigger('click'); } } }); }, loadAnswers: function (data) { var key = 'crossword:' + data.id; var storage; if (localStorage.getItem(key) !== null) { storage = JSON.parse(localStorage.getItem(key)); if (storage['answers']) { return storage['answers']; } } // If we haven't returned, no answers are saved yet. var emptyAnswers = Drupal.behaviors.crossword.emptyAnswers(data); if (!storage) { storage = {}; } storage['answers'] = emptyAnswers; localStorage.setItem(key, JSON.stringify(storage)); return emptyAnswers; }, saveAnswers: function (id, answers) { var key = 'crossword:' + id; var storage; if (localStorage.getItem(key) !== null) { storage = JSON.parse(localStorage.getItem(key)); } if (!storage) { storage = {}; } storage['answers'] = answers; localStorage.setItem(key, JSON.stringify(storage)); }, saveRevealed: function (id, revealed) { var key = 'crossword:' + id; var storage; if (localStorage.getItem(key) !== null) { storage = JSON.parse(localStorage.getItem(key)); } if (!storage) { storage = {}; } storage['revealed'] = revealed; localStorage.setItem(key, JSON.stringify(storage)); }, isRevealed: function (id) { var key = 'crossword:' + id; if (localStorage.getItem(key) !== null) { var storage = JSON.parse(localStorage.getItem(key)); if (storage['revealed']) { return storage['revealed']; } } return false; }, emptyAnswers: function (data) { var grid = data.puzzle.grid; var answers = []; for (var row_index = 0; row_index < grid.length; row_index++) { answers.push([]); for (var col_index = 0; col_index < grid[row_index].length; col_index++) { if (data.puzzle.grid[row_index][col_index].hint) { // A hint is always visible, even when loading the first time. answers[row_index].push(data.puzzle.grid[row_index][col_index].fill); } else { answers[row_index].push(null); } } } return answers; }, connectSquares: function ($crossword, data) { $('.crossword-square', $crossword).each(function(){ var row = Number($(this).data('row')); var col = Number($(this).data('col')); $(this).data("Square", $crossword.data("Crossword").grid[row][col]); $(this).data("Square").connect($(this)); }); }, connectClues: function ($crossword, data) { $('.crossword-clue', $crossword).each(function(){ if ($(this).data('clue-index-across') !== undefined) { var index = Number($(this).data('clue-index-across')); $(this).data("Clue", $crossword.data("Crossword").clues.across[index]); } else { var index = Number($(this).data('clue-index-down')); $(this).data("Clue", $crossword.data("Crossword").clues.down[index]); } $(this).data("Clue").connect($(this)); }); }, addInputHandlers: function($crossword, data) { var Crossword = $crossword.data("Crossword"); $('.crossword-square input', $crossword).on('input', function(e){ if (e.target.value) { Crossword.setAnswer(e.target.value, Drupal.behaviors.crossword.rebusEntryActive(e.target.value)).focus(); $(this).val(""); } }); // Make sure non-crossword inputs, like a searchbar, still work. $('input:not(".crossword-input")').on('focus', function() { Crossword.escape(); }); }, addKeydownHandlers: function($crossword, data) { var Crossword = $crossword.data("Crossword"); $(document).on("keydown", function(event) { if (Crossword.activeClue) { //for arrows, spacebar, escape, and tab switch(event.keyCode) { case 27: //escape // Opt out of key hijacking! event.preventDefault(); Crossword.escape(); Drupal.behaviors.crossword.turnOffRebus(); break; case 38: //up event.preventDefault(); Crossword.moveActiveSquare('up').focus(); Drupal.behaviors.crossword.turnOffRebus(); break; case 37: //left event.preventDefault(); Drupal.behaviors.crossword.turnOffRebus(); if (event.shiftKey) { Crossword.retreatToPreviousUnsolvedClue().focus(); } else { Crossword.moveActiveSquare('left').focus(); } break; case 39: //right event.preventDefault(); Drupal.behaviors.crossword.turnOffRebus(); if (event.shiftKey) { Crossword.advanceToNextUnsolvedClue().focus(); } else { Crossword.moveActiveSquare('right').focus(); } break; case 40: //down event.preventDefault(); Crossword.moveActiveSquare('down').focus(); Drupal.behaviors.crossword.turnOffRebus(); break; case 32: //spacebar event.preventDefault(); // Escape from rebus or toggle direction. if (Drupal.behaviors.crossword.rebusEntryActive("spacebar")) { Crossword.advanceActiveSquare().focus(); Drupal.behaviors.crossword.turnOffRebus(); } else { Crossword.changeDir().focus(); } break; case 13: //return event.preventDefault(); Drupal.behaviors.crossword.turnOffRebus(); Crossword.advanceActiveSquare().focus(); break; case 9: //tab event.preventDefault(); Drupal.behaviors.crossword.turnOffRebus(); if (event.shiftKey) { Crossword.retreatActiveClue().focus(); } else { Crossword.advanceActiveClue().focus(); } break; case 46: case 8: //backspace Crossword.setAnswer("", Drupal.behaviors.crossword.rebusEntryActive("backspace")).focus(); break; case 82: // r + CTRL toggles rebus. if (event.ctrlKey) { event.preventDefault(); Drupal.behaviors.crossword.toggleRebus(); } break; case 67: // c + CTRL cheats. if (event.ctrlKey) { event.preventDefault(); $('.button-cheat', $crossword).trigger('click'); Drupal.behaviors.crossword.turnOffRebus(); Crossword.focus(); } break; case 73: // i + CTRL toggles instructions. if (event.ctrlKey) { event.preventDefault(); $('.crossword-instructions-container', $crossword).toggleClass('active').focus(); } break; case 69: // e + CTRL toggles errors. if (event.ctrlKey) { event.preventDefault(); $('#show-errors', $crossword).trigger('click'). Crossword.focus(); } break; default: // In any other case add focus so letter can always be entered. // Helpful if user is clicking buttons and using keyboard. Crossword.focus(); break; } } }); }, addClickHandlers: function ($crossword, data) { var Crossword = $crossword.data("Crossword"); once('crossword-square-click', '.crossword-square').forEach(function(element){ $(element).click(function(){ if ($(this).data("Square") == Crossword.activeSquare && $(this).hasClass('focus')) { Crossword.changeDir(); } else { Crossword.setActiveSquare($(this).data("Square")); Drupal.behaviors.crossword.turnOffRebus(); } Crossword.focus(); }); }); once('crossword-clue-click', '.crossword-clue').forEach(function(element){ $(element).click(function(){ Crossword.setActiveClue($(this).data("Clue")).focus(); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-clue-change-click', '.crossword-clue-change').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); var dir = $(this).data('dir'); var change = Number($(this).data('clue-change')); Crossword.changeActiveClue(dir, change); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-express-click', '.next-clue-express').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); Crossword.advanceToNextUnsolvedClue(); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-express-click', '.prev-clue-express').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); Crossword.retreatToPreviousUnsolvedClue(); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-dir-change-click', '.crossword-dir-change').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); var dir = $(this).data('dir'); if (dir != Crossword.dir) { Crossword.changeDir(); } }); }); once('crossword-cheat-click', '.button-cheat').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); if ($(this).data('confirm')) { var message = $(this).data('confirm'); if (!confirm(message)) { return; } } Crossword.cheat(); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-undo-click', '.button-undo').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); if ($(this).data('confirm')) { var message = $(this).data('confirm'); if (!confirm(message)) { return; } } Crossword.undo().focus(); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-redo-click', '.button-redo').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); if ($(this).data('confirm')) { var message = $(this).data('confirm'); if (!confirm(message)) { return; } } Crossword.redo().focus(); Drupal.behaviors.crossword.turnOffRebus(); }); }); once('crossword-solution-click', '.button-solution').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); if ($(this).data('confirm')) { var message = $(this).data('confirm'); if (!confirm(message)) { return; } } Crossword.reveal(); }); }); once('crossword-clear-click', '.button-clear').forEach(function(element){ $(element).click(function(e){ e.preventDefault(); if ($(this).data('confirm')) { var message = $(this).data('confirm'); if (!confirm(message)) { return; } } Crossword.clear(); }); }); // When activating rebus entry, re-focus onto crossword. once('crossword-rebus-click', '.rebus-entry').forEach(function(element){ $(element).click(function(e){ Crossword.focus(); }); }); }, addCrosswordEventHandlers: function ($crossword, data) { $('.crossword-clue, .crossword-square', $crossword) .on('crossword-active', function(){ $(this).addClass('active'); }) .on('crossword-highlight', function(){ $(this).addClass('highlight'); }) .on('crossword-reference', function(){ $(this).addClass('reference'); }) .on('crossword-error', function(){ $(this).addClass('error'); }) .on('crossword-ok', function(){ $(this).removeClass('error'); }) .on('crossword-off', function(){ $(this) .removeClass('active') .removeClass('highlight') .removeClass('reference') .removeClass('focus') .find('input').blur(); }) .on('crossword-cheat', function(){ $(this).addClass('cheat'); }); $('.crossword-square', $crossword) .on('crossword-answer', function(e, answer){ $(this).find('.square-fill').text(answer.toUpperCase()); var Crossword = $crossword.data("Crossword"); Drupal.behaviors.crossword.saveAnswers(Crossword.id, Crossword.getAnswers()) }) .on('crossword-rebus', function(){ $(this).addClass('rebus'); }) .on('crossword-not-rebus', function(){ $(this).removeClass('rebus'); }) .on('crossword-focus', function(){ $(this).addClass('focus'); $(this).find('input').focus(); }) .on('crossword-active', function(){ // Manage aria-label. var Crossword = $crossword.data("Crossword"); var dir = Crossword.dir; var Square = $(this).data("Square"); if (Square && Square.isFirstLetter(dir)) { var Clue = Square[dir]; var solvedString = Crossword.solved ? "The puzzle has been solved. Well done! " : ""; var revealedString = Crossword.revealed ? "The puzzle has been revealed. " : ""; var errorString = Crossword.showingErrors() && Clue.hasError() ? " Contains error." : ""; $(this).find('input').attr("aria-label", solvedString + revealedString + Clue.getAriaClueText() + ". " + Clue.getAriaCurrentString() + errorString); } else { $(this).find('input').attr("aria-label",""); } }); $('.crossword-clue', $crossword) .on('crossword-clue-complete', function(){ $(this).addClass('complete'); }) .on('crossword-clue-not-complete', function(){ $(this).removeClass('complete'); }); $('.active-clues-text', $crossword) .on('crossword-active', function(e, Clue){ // Try to copy clue html from dom. // If no $clue, build html ourselves. if (Clue['$clue']) { var $clue_copy = $('<div class="active ' + Clue.dir + '">' + Clue['$clue'].html() + '</div>'); $clue_copy.data("real-clue", Clue['$clue']); $clue_copy.click(function(){ $(this).data("real-clue").trigger("click"); }); $(this).html($clue_copy); } else { var $clue = $('<div class="active ' + Clue.dir + '"><span class="numeral">' + Clue.numeral + '</span><span class="text"></span></div>'); $clue.find('.text').html(Clue.text); $(this).html($clue); } if (Clue.references) { for (var i = 0; i < Clue.references.length; i++) { if (Clue.references[i]['$clue']) { var $reference_copy = $('<div class="reference ' + Clue.references[i].dir + '">' + Clue.references[i]['$clue'].html() + '</div>'); $reference_copy.data("real-clue", Clue.references[i]['$clue']); $reference_copy.click(function () { $(this).data("real-clue").trigger("click"); }); $(this).append($reference_copy); } else { var $reference = $('<div class="reference ' + Clue.references[i].dir + '"><span class="numeral">' + Clue.references[i].numeral + '</span><span class="text"></span></div>'); $reference.find('.text').html(Clue.references[i].text); $(this).append($reference); } } } }) .on('crossword-off', function() { $(this).html(null); }); $crossword .on('crossword-solved', function() { $(this).addClass('crossword-solved'); console.log('The crossword puzzle has been solved.'); }) .on('crossword-revealed', function() { $(this).addClass('crossword-revealed'); Drupal.behaviors.crossword.saveRevealed(data.id , 'revealed'); }) .on('crossword-clear', function() { $(this).removeClass('crossword-solved').removeClass('crossword-revealed'); Drupal.behaviors.crossword.saveRevealed(data.id , ''); }); }, rebusEntryActive: function(letter) { return $('.rebus-entry').prop('checked'); }, turnOffRebus: function() { $('.rebus-entry').prop('checked', false); }, toggleRebus: function() { $('.rebus-entry').prop('checked', !$('.rebus-entry').prop('checked')); }, } })(jQuery, Drupal, once, drupalSettings);