711 lines
17 KiB
HTML
711 lines
17 KiB
HTML
|
<html>
|
||
|
|
||
|
<head>
|
||
|
</head>
|
||
|
|
||
|
<body>
|
||
|
|
||
|
<div id="app" class="app"></div>
|
||
|
<pre>
|
||
|
TODO:
|
||
|
- if orange fields, no other selections should be possible than deselect.
|
||
|
</pre>
|
||
|
<template id="template_sudoku_container">
|
||
|
<div style="width:640px;height:640px;font-size:1.2em;" class="container-content"></div>
|
||
|
</template>
|
||
|
<template id="template_sudoku_parser">
|
||
|
<div style="width:100%;height:100%" class="parser-content"></div>
|
||
|
</template>
|
||
|
<template id="template_sudoku_cell">
|
||
|
<div class="cell-content"></div>
|
||
|
</template>
|
||
|
<style type="text/css">
|
||
|
.app {
|
||
|
width: 400px;
|
||
|
height: 400px;
|
||
|
}
|
||
|
|
||
|
.cell-content {
|
||
|
|
||
|
font-family: 'Courier New', Courier, monospace;
|
||
|
user-select: none;
|
||
|
-webkit-user-select: none;
|
||
|
-moz-user-select: none;
|
||
|
-ms-user-select: none;
|
||
|
border: 1px solid #CCCCCC;
|
||
|
text-align: center;
|
||
|
height: 10%;
|
||
|
width: 10%;
|
||
|
float: left;
|
||
|
display: flex;
|
||
|
justify-content: center;
|
||
|
align-items: center;
|
||
|
font-size: calc(5px + 1vw);
|
||
|
}
|
||
|
|
||
|
.sudoku-cell-selected {
|
||
|
background-color: lightgreen;
|
||
|
}
|
||
|
|
||
|
.sudoku-cell-invalid {
|
||
|
color: red;
|
||
|
font-weight: bold;
|
||
|
}
|
||
|
</style>
|
||
|
<script type="text/javascript">
|
||
|
|
||
|
const game_size = 9;
|
||
|
const container_template = document.getElementById('template_sudoku_container');
|
||
|
const cell_template = document.getElementById("template_sudoku_cell");
|
||
|
const sudokuParserTemplate = document.getElementById('template_sudoku_parser');
|
||
|
const container = document.getElementById('app'); //.importNode(container_template.content, true);
|
||
|
const _hashChars = "abcdefghijklmnopqrstuvwxyz\"':[]0123456789";
|
||
|
function hashString(str) {
|
||
|
let result = 0;
|
||
|
for (let i = 0; i < str.length; i++) {
|
||
|
result += (_hashChars.indexOf(str[i]) + 1) * (i * 40);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
class State {
|
||
|
number = 0
|
||
|
cells = []
|
||
|
selection = []
|
||
|
selectionCount = 0
|
||
|
_string = null
|
||
|
_hash = null
|
||
|
_json = null;
|
||
|
constructor(arg) {
|
||
|
if (isNaN(arg)) {
|
||
|
this.number = arg.number;
|
||
|
this.cells = arg.cells;
|
||
|
this.selectCount = arg.selectionCount;
|
||
|
this._hash = arg._hash;
|
||
|
this._json = arg;
|
||
|
} else {
|
||
|
this.number = arg;
|
||
|
}
|
||
|
}
|
||
|
toJson() {
|
||
|
if (this._json == null) {
|
||
|
const json = {
|
||
|
number: this.number,
|
||
|
cells: this.cells,
|
||
|
selectionCount: this.selectionCount,
|
||
|
_hash: this.getHash()
|
||
|
};
|
||
|
this._json = json;
|
||
|
}
|
||
|
return this._json;
|
||
|
}
|
||
|
|
||
|
getHash() {
|
||
|
if (this._hash == null) {
|
||
|
let started = false;
|
||
|
let count = 0;
|
||
|
let hash = this.cells.filter((cell) => {
|
||
|
if (!started && cell.state[0] == 0 && cell.state[1] == 1)
|
||
|
return false;
|
||
|
started = true;;
|
||
|
count++;
|
||
|
return true;
|
||
|
}).map(cell => {
|
||
|
return cell.state;
|
||
|
}).join('');
|
||
|
this._hash = `${count}${hash}`;
|
||
|
}
|
||
|
return this._hash
|
||
|
}
|
||
|
equals(otherState) {
|
||
|
if (otherState.selectionCount != this.selectionCount)
|
||
|
return false;
|
||
|
return otherState.getHash() == this.getHash();
|
||
|
}
|
||
|
toString() {
|
||
|
if (this._string == null) {
|
||
|
this._string = JSON.stringify({
|
||
|
number: this.number,
|
||
|
selection: this.selection.map(cell => cell.toString()),
|
||
|
cells: this.cells
|
||
|
});
|
||
|
}
|
||
|
return this._string
|
||
|
}
|
||
|
};
|
||
|
class Cell {
|
||
|
row = 0;
|
||
|
col = 0;
|
||
|
initial = false;
|
||
|
letter = null
|
||
|
name = null
|
||
|
options = [];
|
||
|
value = 0;
|
||
|
values = []
|
||
|
element = null;
|
||
|
app = null;
|
||
|
selected = false;
|
||
|
container = null;
|
||
|
async solve(){
|
||
|
this.app.pushState();
|
||
|
let originalValues = this.values;
|
||
|
let valid = false;
|
||
|
this.selected = false;
|
||
|
for(let i = 1; i < 10; i++){
|
||
|
this.addNumber(i);
|
||
|
if(this.validate())
|
||
|
if(await this.app.solve())
|
||
|
return true;
|
||
|
this.value = 0;
|
||
|
this.values = originalValues;
|
||
|
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
getState() {
|
||
|
return `${this.selected ? 1 : 0}${this.valid ? 1 : 0}`;
|
||
|
}
|
||
|
toggleSelect() {
|
||
|
this.selected = !this.selected;
|
||
|
|
||
|
if (this.selected) {
|
||
|
this.select();
|
||
|
//this.element.classList.add('sudoku-cell-selected');
|
||
|
} else {
|
||
|
this.deSelect();
|
||
|
//this.element.classList.remove('sudoku-cell-selected');
|
||
|
}
|
||
|
this.update();
|
||
|
return this.selected;
|
||
|
|
||
|
|
||
|
}
|
||
|
async addNumber(value) {
|
||
|
this.values.pop(value);
|
||
|
this.values.push(value);
|
||
|
this.value = Number(value);
|
||
|
const _this = this;
|
||
|
window.requestAnimationFrame(() => {
|
||
|
this.element.textContent = this.value == 0 ? "" : String(this.value);
|
||
|
|
||
|
})
|
||
|
this.validate();
|
||
|
//this.update();
|
||
|
|
||
|
|
||
|
}
|
||
|
onClick() {
|
||
|
//this.
|
||
|
if (!this.initial)
|
||
|
this.toggleSelect();
|
||
|
//this.app.onCellClick(this)
|
||
|
}
|
||
|
deSelect() {
|
||
|
this.selected = false;
|
||
|
this.element.classList.remove('sudoku-cell-selected');
|
||
|
}
|
||
|
select() {
|
||
|
|
||
|
this.selected = true;
|
||
|
this.element.classList.add('sudoku-cell-selected');
|
||
|
|
||
|
}
|
||
|
validateBox(){
|
||
|
let startRow = this.row - this.row % (9 / 3);
|
||
|
let startCol = this.col - this.col % (9 / 3);
|
||
|
for (let i = startRow; i < 9 / 3; i++) {
|
||
|
for (let j = startCol; j < 9 / 3; j++) {
|
||
|
let fieldIndex = (i * 9) + j;
|
||
|
console.info(fieldIndex);
|
||
|
if (this.app.cells[fieldIndex].value == this.value) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
isValid() {
|
||
|
const _this = this;
|
||
|
this.valid = !this.value && (!this.app.cells.filter(cell => {
|
||
|
return cell.value != 0 &&
|
||
|
cell != _this &&
|
||
|
(cell.row == _this.row || cell.col == _this.col) &&
|
||
|
cell.value == _this.value;
|
||
|
}).length && this.validateBox()); //&& !this.app.getBoxValues(this.name).indexOf(this.value) == -1);
|
||
|
return this.valid;
|
||
|
}
|
||
|
update() {
|
||
|
if (this.selected)
|
||
|
this.select()
|
||
|
else
|
||
|
this.deSelect()
|
||
|
this.app.cells.forEach(cell => {
|
||
|
cell.validate()
|
||
|
})
|
||
|
this.element.textContent = this.value ? String(this.value) : "";
|
||
|
|
||
|
}
|
||
|
validate() {
|
||
|
if (this.isValid() || !this.value) {
|
||
|
this.element.classList.remove('sudoku-cell-invalid');
|
||
|
} else {
|
||
|
this.element.classList.add('sudoku-cell-invalid');
|
||
|
}
|
||
|
return this.valid;
|
||
|
}
|
||
|
destructor() {
|
||
|
//this.container.delete();
|
||
|
}
|
||
|
constructor(app, row, col) {
|
||
|
this.app = app;
|
||
|
this.container = document.importNode(cell_template.content, true);
|
||
|
this.row = row;
|
||
|
this.col = col;
|
||
|
this.selected = false;
|
||
|
this.letter = "abcdefghi"[row];
|
||
|
this.name = `${this.letter}${this.col}`
|
||
|
this.value = 0;
|
||
|
this.values = [];
|
||
|
this.element = this.container.querySelector('.cell-content');
|
||
|
this.valid = true;
|
||
|
const _this = this;
|
||
|
this.element.addEventListener('click', (e) => {
|
||
|
_this.onClick();
|
||
|
});
|
||
|
|
||
|
this.element.addEventListener('mousemove', (e) => {
|
||
|
|
||
|
|
||
|
|
||
|
if (!_this.initial && e.buttons == 1)
|
||
|
_this.select();
|
||
|
else if (!_this.initial && e.buttons == 2)
|
||
|
_this.deSelect();
|
||
|
else
|
||
|
_this.app.pushState()
|
||
|
|
||
|
});
|
||
|
|
||
|
this.element.addEventListener('mouseexit', (e) => {
|
||
|
if (!e.buttons) {
|
||
|
// _this.app.pushState();
|
||
|
}
|
||
|
});
|
||
|
this.element.addEventListener('contextmenu', (e) => {
|
||
|
e.preventDefault();
|
||
|
|
||
|
if (!_this.initial && _this.selected) {
|
||
|
_this.deSelect();
|
||
|
} else {
|
||
|
_this.values.pop(_this.value);
|
||
|
_this.addNumber(0);
|
||
|
_this.deSelect();
|
||
|
_this.update();
|
||
|
//_this.app.deSelectAll();
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
redraw() {
|
||
|
|
||
|
}
|
||
|
render(container) {
|
||
|
container.appendChild(this.container);
|
||
|
}
|
||
|
toString() {
|
||
|
return `${this.row}:${this.col}`
|
||
|
}
|
||
|
}
|
||
|
class SudokuParser {
|
||
|
app = null
|
||
|
element = null
|
||
|
container = null
|
||
|
blank = true
|
||
|
content = ''
|
||
|
size = 9
|
||
|
constructor(app, container) {
|
||
|
//this.container = container;
|
||
|
this.app = app;
|
||
|
this.container = document.importNode(sudokuParserTemplate.content, true);
|
||
|
this.element = document.createElement('div');
|
||
|
this.element.style.width = '90%';
|
||
|
this.element.style.height = '50%';
|
||
|
this.element.border = '1px solid #CCCCCC';
|
||
|
this.content = '';
|
||
|
this.cells = []
|
||
|
this.element.contentEditable = true;
|
||
|
this.element.textContent = 'Paste puzzle here';//this.container.querySelector('.parser-content');
|
||
|
this.toggle();
|
||
|
this.blank = true;
|
||
|
const _this = this;
|
||
|
this.element.addEventListener('click', (e) => {
|
||
|
if (_this.blank)
|
||
|
_this.element.textContent = '';
|
||
|
_this.blank = false;
|
||
|
|
||
|
});
|
||
|
this.element.addEventListener('contextmenu', (e) => {
|
||
|
if (_this.blank)
|
||
|
_this.element.textContent = '';
|
||
|
this.blank = false;
|
||
|
|
||
|
|
||
|
});
|
||
|
this.element.addEventListener('input', (e) => {
|
||
|
_this.element.innerHTML = this.element.textContent;
|
||
|
_this.parseAndApply();
|
||
|
});
|
||
|
this.element.addEventListener('keyup', (e) => {
|
||
|
//_this.parseAndApply();
|
||
|
});
|
||
|
container.appendChild(this.element);
|
||
|
}
|
||
|
parseAndApply() {
|
||
|
this.parse();
|
||
|
this.apply();
|
||
|
}
|
||
|
parse() {
|
||
|
const content = this.element.textContent;
|
||
|
const regex = /\d/g;
|
||
|
const matches = [...content.matchAll(regex)];
|
||
|
let row = 0;
|
||
|
let col = 0;
|
||
|
const cells = []
|
||
|
const max = this.size * this.size;
|
||
|
|
||
|
matches.forEach(match => {
|
||
|
if (row * col == max) {
|
||
|
return;
|
||
|
}
|
||
|
if (col == 9) {
|
||
|
row++;
|
||
|
col = 0;
|
||
|
}
|
||
|
if (row == 9) {
|
||
|
row = 0;
|
||
|
col = 0;
|
||
|
}
|
||
|
const digit = match[0];
|
||
|
const cell = new Cell(this.app, row, col);
|
||
|
cell.addNumber(digit);
|
||
|
cell.initial = true;
|
||
|
cells.push(cell);
|
||
|
col++;
|
||
|
});
|
||
|
this.cells = cells;
|
||
|
}
|
||
|
apply() {
|
||
|
this.app.cells.forEach(cell => {
|
||
|
cell.initial = false;
|
||
|
cell.number = 0;
|
||
|
cell.numbers = [];
|
||
|
cell.addNumber(0);
|
||
|
});
|
||
|
this.cells.forEach(cell => {
|
||
|
const appCell = this.app.getCellByName(cell.name);
|
||
|
appCell.initial = cell.value != 0;
|
||
|
appCell.addNumber(cell.value);
|
||
|
|
||
|
});
|
||
|
this.toggle();
|
||
|
}
|
||
|
toggle() {
|
||
|
if (this.element.style.display == 'none') {
|
||
|
this.element.innerHTML = 'Paste here your puzzle';
|
||
|
}
|
||
|
this.element.style.display = this.element.style.display != 'none' ? 'none' : 'block';
|
||
|
|
||
|
}
|
||
|
}
|
||
|
class Sudoku {
|
||
|
cells = [];
|
||
|
game_size = 0;
|
||
|
cell_count = 0;
|
||
|
selectedCells = []
|
||
|
container = null
|
||
|
element = null
|
||
|
states = []
|
||
|
previousSelection = []
|
||
|
state_number = 1
|
||
|
parser = null;
|
||
|
status = null;
|
||
|
reset(){
|
||
|
this.cells.forEach(cell=>{
|
||
|
cell.values = []
|
||
|
cell.selected = false;
|
||
|
cell.addNumber(0);
|
||
|
})
|
||
|
}
|
||
|
loadSession() {
|
||
|
const session = this.getSession();
|
||
|
if (!session)
|
||
|
return null;
|
||
|
this.state_number = session.state_number;
|
||
|
this.states = session.states.map(state => {
|
||
|
return new State(state);
|
||
|
});
|
||
|
this.refreshState();
|
||
|
|
||
|
}
|
||
|
|
||
|
async getEmptyCell(){
|
||
|
return this.cells.filter(cell=>{
|
||
|
if(cell.value == 0)
|
||
|
return true
|
||
|
return false
|
||
|
})[0]
|
||
|
}
|
||
|
async solve() {
|
||
|
const cell = await this.getEmptyCell();
|
||
|
if(!cell)
|
||
|
return this.isValid();
|
||
|
return await cell.solve();
|
||
|
}
|
||
|
deleteSession(){
|
||
|
localStorage.removeItem('session');
|
||
|
}
|
||
|
getSession() {
|
||
|
const session = localStorage.getItem('session');
|
||
|
if (!session) {
|
||
|
return null
|
||
|
}
|
||
|
return JSON.parse(session);
|
||
|
}
|
||
|
saveSession() {
|
||
|
this.pushState();
|
||
|
const states = this.states.map(state => {
|
||
|
return state.toJson()
|
||
|
});
|
||
|
const session = {
|
||
|
state_number: this.state_number,
|
||
|
states: states
|
||
|
}
|
||
|
localStorage.setItem('session', JSON.stringify(session));
|
||
|
//console.info('session saved');
|
||
|
}
|
||
|
getBoxValues(cell_name) {
|
||
|
let values = this.cells.filter(cell => {
|
||
|
return cell.name != cell_name && cell.name[0] == cell_name[0]
|
||
|
}).map(cell => {
|
||
|
return cell.value
|
||
|
});
|
||
|
return values;
|
||
|
}
|
||
|
toggle() {
|
||
|
this.container.style.display = this.container.style.display != 'none' ? 'none' : 'block';
|
||
|
}
|
||
|
toggleParser() {
|
||
|
|
||
|
//this.parser.toggle();
|
||
|
this.deSelectAll();
|
||
|
this.parser.toggle()
|
||
|
|
||
|
}
|
||
|
constructor(container, game_size) {
|
||
|
const _this = this;
|
||
|
this.container = container
|
||
|
this.element = container
|
||
|
|
||
|
this.parser = new SudokuParser(this, this.container);
|
||
|
this.game_size = game_size;
|
||
|
this.cell_count = game_size * game_size;
|
||
|
for (let row = 0; row < this.game_size; row++) {
|
||
|
for (let col = 0; col < this.game_size; col++) {
|
||
|
this.cells.push(new Cell(this, row, col));
|
||
|
}
|
||
|
}
|
||
|
console.info("Loading session");
|
||
|
setTimeout(()=>{
|
||
|
if(_this.status == "applying state"){
|
||
|
_this.deleteSession();
|
||
|
window.location.reload();
|
||
|
}else{
|
||
|
console.info("Finished session validation");
|
||
|
}
|
||
|
},10000);
|
||
|
this.loadSession();
|
||
|
document.addEventListener('keypress', (e) => {
|
||
|
|
||
|
if (!isNaN(e.key) || e.key == 'd') {
|
||
|
let number = e.key == 'd' ? 0 : Number(e.key);
|
||
|
_this.addNumberToSelection(number);
|
||
|
//console.info({set:Number(e.key)});
|
||
|
}
|
||
|
if (e.key == 'p') {
|
||
|
_this.toggleParser();
|
||
|
}
|
||
|
if (e.key == 'u') {
|
||
|
_this.popState();
|
||
|
}
|
||
|
if (e.key == 'r') {
|
||
|
if (this.selection().length) {
|
||
|
this.pushState();
|
||
|
_this.deSelectAll();
|
||
|
} else {
|
||
|
let state = this.getLastState();
|
||
|
if (state) {
|
||
|
state.cells.filter(cell => cell.selected).forEach(cell => {
|
||
|
|
||
|
this.getCellByName(cell.name).select();
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
this.element.addEventListener('mousemove', (e) => {
|
||
|
//this.pushState();
|
||
|
})
|
||
|
document.addEventListener('dblclick', (e) => {
|
||
|
_this.previousSelection = _this.selection();
|
||
|
_this.cells.forEach(cell => {
|
||
|
cell.deSelect();
|
||
|
});
|
||
|
});
|
||
|
document.addEventListener('contextmenu', (e) => {
|
||
|
|
||
|
});
|
||
|
this.element.addEventListener('mouseexit', (e) => {
|
||
|
// Edge case while holding mouse button while dragging out. Should save state
|
||
|
_this.pushState();
|
||
|
});
|
||
|
this.element.addEventListener('mouseup', (e) => {
|
||
|
_this.pushState();
|
||
|
});
|
||
|
this.pushState()
|
||
|
|
||
|
}
|
||
|
isValid() {
|
||
|
return this.cells.filter(cell => !cell.isValid()).length == 0
|
||
|
}
|
||
|
createState() {
|
||
|
const state = new State(this.state_number)
|
||
|
let selectedCount = 0;
|
||
|
state.cells = this.cells.map(cell => {
|
||
|
if (cell.selected) {
|
||
|
selectedCount++;
|
||
|
}
|
||
|
return { name: cell.name, values: cell.values, value: cell.value, selected: cell.selected, state: cell.getState() }
|
||
|
|
||
|
});
|
||
|
state.selectedCount = selectedCount;
|
||
|
return state;
|
||
|
}
|
||
|
pushState() {
|
||
|
const state = this.createState();
|
||
|
const previousState = this.getLastState();
|
||
|
if (!previousState || !previousState.equals(state)) {
|
||
|
this.states.push(state);
|
||
|
this.state_number++;
|
||
|
this.saveSession();
|
||
|
//console.info({ pushState: state.getHash(), length: state.getHash().length, number: state.number });
|
||
|
}
|
||
|
}
|
||
|
refreshState() {
|
||
|
const state = this.getLastState();
|
||
|
if (!state)
|
||
|
return null;
|
||
|
this.applyState(state)
|
||
|
return state;
|
||
|
}
|
||
|
getLastState() {
|
||
|
return this.states.length ? this.states.at(this.states.length - 1) : null;
|
||
|
}
|
||
|
applyState(state) {
|
||
|
this.status = "applying state";
|
||
|
state.cells.forEach(stateCell => {
|
||
|
const cell = this.getCellByName(stateCell.name);
|
||
|
cell.selected = stateCell.selected;
|
||
|
cell.values = stateCell.values;
|
||
|
cell.value = stateCell.value;
|
||
|
cell.update();
|
||
|
})
|
||
|
this.status = "applied state"
|
||
|
|
||
|
}
|
||
|
popState() {
|
||
|
let state = this.states.pop();
|
||
|
|
||
|
if (!state)
|
||
|
return;
|
||
|
if (state.equals(this.createState())) {
|
||
|
return this.popState();
|
||
|
}
|
||
|
|
||
|
this.applyState(state);
|
||
|
this.saveSession();
|
||
|
//console.info({ popState: state.getHash(), length: state.getHash().length, number: state.number });
|
||
|
}
|
||
|
getCellByName(name) {
|
||
|
return this.cells.filter(cell => {
|
||
|
return cell.name == name
|
||
|
})[0]
|
||
|
}
|
||
|
deSelectAll() {
|
||
|
this.cells.forEach(cell => {
|
||
|
cell.deSelect();
|
||
|
});
|
||
|
}
|
||
|
selection() {
|
||
|
return this.cells.filter(cell => {
|
||
|
return cell.selected
|
||
|
});
|
||
|
}
|
||
|
onSelectionToggle() {
|
||
|
|
||
|
}
|
||
|
addNumberToSelection(number) {
|
||
|
const _this = this;
|
||
|
this.pushState();
|
||
|
this.selection().forEach((cell) => {
|
||
|
|
||
|
|
||
|
|
||
|
cell.addNumber(number)
|
||
|
cell.update();
|
||
|
|
||
|
})
|
||
|
_this.pushState();
|
||
|
if (this.isValid()) {
|
||
|
this.deSelectAll();
|
||
|
}
|
||
|
_this.pushState();
|
||
|
}
|
||
|
onCellDblClick(cell) {
|
||
|
this.previousSelection = this.selection();
|
||
|
this.popState()
|
||
|
let originalSelected = this.selctedCells
|
||
|
if (cell.selected) {
|
||
|
this.selectedCells.push(cell);
|
||
|
} else {
|
||
|
this.selectedCells.pop(cell);
|
||
|
}
|
||
|
if (!this.originalSelected != this.selectedCells) {
|
||
|
this.popState();
|
||
|
}
|
||
|
//console.info({selected:this.selectedCells});
|
||
|
}
|
||
|
render() {
|
||
|
|
||
|
|
||
|
this.cells.forEach(cell => {
|
||
|
cell.render(this.element);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
const sudoku = new Sudoku(container, 9);
|
||
|
|
||
|
sudoku.render();
|
||
|
const app = sudoku;
|
||
|
/*
|
||
|
document.addEventListener('contextmenu',(e)=>{
|
||
|
e.preventDefault();
|
||
|
});*/
|
||
|
/*
|
||
|
for(let i = 0; i < game_size*game_size; i++){
|
||
|
const cell = document.importNode(cell_template.content,true);
|
||
|
|
||
|
app.appendChild(cell);
|
||
|
}*/
|
||
|
//document.body.appendChild(app);
|
||
|
</script>
|
||
|
</body>
|