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);
