Some days ago, I posted a problem to SO. Admittedly, the initial post was composed in a bit of a hurry, and somewhat incomplete. This was voted as off-topic. Even though the final edited version of the question doesn't seem to me to be off-topic, the fact remains that the question was closed.
In order to try and comply with the inherent (and in my opinion, not always perfectly fair, as I believe a chance should be given to rephrase the problem if necessary, and some assistance could be given in the way of suggestions) limitations of SO, I'm attempting to rephrase the problem. It goes as follows.
A few years back, I wrote a little tool (app) to overlay a grid on an image to generate printable images for Cross-Stitch work. At the time, I specifically wrote this for Firefox, but I generally expected it would work in any modern browser. However, now I find that it only seems to work in Firefox! (Not even in Firefox Focus).
The problem statement, this time around, is: This page renders more or less as expected in Firefox Desktop Browser. Is Firefox handling the page (app) in a standards compliant manner or not? If not, can anyone possibly explain a bit what's going on? In either case, is there a simple workaround to get this page to work as expected in major modern browsers other than Firefox? (i.e. Chrome, Edge, Opera, Safari, and mobile versions of similar browsers, including Firefox Focus). In other words, if this code is standards compliant, how do I deal with non-compliant browsers, or else is it possible to make the code standards compliant without having to change it too much?
The expected behavior of the page (app): Click on Browse, and pick an image, then click Generate. The image should be displayed below with a grid overlaid on top of it (or alternatively, you could look at it as the image being divided into squares or rectangles). There are additional details to how exactly the image ought to be rendered, stating which would lengthen this post unnecessarily, but you'd probably be able to figure these out by taking a look at the available customizable fields of the app and possibly by experimenting with them. These parameters generally are there to change the grid cell size (height, width), page margins, options to fill the page/image with squares etc.
This is old code, and I don't want to rewrite it significantly...
IIRC, I was attempting to get the img to take the full size of the containing div, hence position:absolute, and wdth:100%, height:100%. But at the same time, I was trying to size the containing div to fit the content, i.e. the grid. It's the grid that defines the size. The image is supposed to scale into that space...
To me, it appears that browsers aren't honoring the sizes specified for the grid, probably because it's empty, but my attempts to fix that failed...
EDIT 1:
As per suggestions, I finally got around to reducing the code to the key parts of the HTML and CSS. I was hoping for an answer that applies broadly to the complete code, but anyone wanting to get to the core problem can take a look at the following snippet. Note: I haven't yet tested this code in browsers other than FF Desktop, but I believe this should demonstrate the issue...
table {
border-collapse: collapse;
}
col {
width: 30px;
}
tr {
height: 30px;
}
table, td {
border: 1px solid red;
}
#div {
display: inline-block;
position: relative;
}
#img {
position: absolute;
width: 100%;
height: 100%;
z-index: -1;
}
<!DOCTYPE html>
<html>
<body>
<div id="div">
<img id="img" src="https://www.w3schools.com/js/landscape.jpg">
<table>
<thead>
<col>
<col>
<col>
</thead>
<tbody>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
</div>
</body>
</html>
Complete Code:
/*********************************************\
** Copyright © 2017-2019 Thejaka Maldeniya **
\*********************************************/
'use strict';
(function (a) {
a.i()
}(
{
z: 0, // image file data URL
p: '3', // previous value of units (default: Millimeters)
f: { // functions
_: function (l, e, h) { l.addEventListener(e, h, 0) },
l: function (a, f) { // load files
if (f.length) {
f = f[0]
var l = a.u.l, r = new FileReader()
l.name.innerHTML = f.name
a.f._(r, 'loadend', function (e) {
l.preview.src = a.z = e.target.result
})
r.readAsDataURL(f)
}
},
r: function (x) { // round to 3 decimal places
return Math.round(x * 1000) / 1000
},
f: function (a, b) { // get conversion factor
var f = 0
if (a == b) f = 1
else {
// if either a or b is not known, factor will be 0
// first, convert to Millimeters
switch (a) { // previous units value
case '1': // Inches
f = 25.4
break
case '2': // Centimeters
f = 10
break
case '3': // Millimeters
f = 1
}
// second, convert from Millimeters
switch (b) { // new units value
case '1': // Inches
f /= 25.4
break
case '2': // Centimeters
f /= 10
break
case '3': // Millimeters
// no change
}
}
return f
},
c: function () { // units or size value changed
var t = this, l = a.u.l
, r = t.r // round function
, width = l.width.value, height = l.height.value // A4
, mt = l.top.value, mb = l.bottom.value, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
, s = 'A4' // paper size
, f = t.f(a.p, a.p = l.units.value) // unit conversion factor
, g = t.f('3', l.units.value) // unit conversion factor for default values (currently in Millimeters)
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
switch (l.size.value) {
case '0': // Custom
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
g = f
break
case '1': // Letter
width = 216
height = 279
l.rotate.checked = 0
s = 'Letter'
break
case '3': // Legal
width = 216
height = 356
l.rotate.checked = 0
s = 'Legal'
break
case '6': // A3
width = 297
height = 420
l.rotate.checked = 0
s = 'A3'
break
case '7': // A4
width = 210
height = 297
l.rotate.checked = 0
s = 'A4'
break
case '8': // A5
width = 148
height = 210
l.rotate.checked = 0
s = 'A5'
}
l.width.value = r(width * g)
l.height.value = r(height * g)
l.top.value = r(mt * f)
l.bottom.value = r(mb * f)
l.left.value = r(ml * f)
l.right.value = r(mr * f)
l.w.value = r(w * f)
l.h.value = r(h * f)
l.m.value = m
l.n.value = n
}
},
u: {
l: {
style: 0,
ui: 0,
form: 0,
units: 0,
size: 0,
width: 0,
height: 0,
rotate: 0,
top: 0,
bottom: 0,
left: 0,
right: 0,
w: 0,
h: 0,
square: 0,
fill1: 0,
m: 0,
n: 0,
fill2: 0,
color: 0,
white: 0,
black: 0,
file: 0,
browse: 0,
name: 0,
preview: 0,
reset: 0,
print: 0,
page: 0,
content: 0
},
f: {
b: function (f, c) {
return function () {
var a = arguments, n = a.length, b = Array(n), i = 0
for (; i < n; ++i)
b[i] = a[i]
f.apply(c, b)
}
}
},
e: {
document: {
DOMContentLoaded: function () {
}
},
window: {
load: function () {
var a = this, l = a.u.l
a.p = l.units.value
if (l.size.value == '0') {
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
}
if (!l.square.checked)
l.h.removeAttribute('readonly')
}
},
ui: {
dragover: function (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
drop: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.dataTransfer.files)
}
},
rotate: {
change: function () {
var l = this.u.l, v = l.width.value
l.width.value = l.height.value
l.height.value = v
v = l.top.value
if (l.rotate.checked) {
l.top.value = l.left.value
l.left.value = l.bottom.value
l.bottom.value = l.right.value
l.right.value = v
} else {
l.top.value = l.right.value
l.right.value = l.bottom.value
l.bottom.value = l.left.value
l.left.value = v
}
v = l.w.value
l.w.value = l.h.value
l.h.value = v
v = l.m.value
l.m.value = l.n.value
l.n.value = v
}
},
w: {
change: function (e) {
var l = this.u.l
if (l.square.checked)
l.h.value = e.target.value
}
},
square: {
change: function (e) {
var l = this.u.l
if (e.target.checked) {
l.h.setAttribute('readonly', '')
l.h.value = l.w.value
} else l.h.removeAttribute('readonly')
}
},
file: {
change: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.target.files)
}
},
units: {
change: function () {
this.f.c(this)
}
},
size: {
change: function () {
this.f.c(this)
}
},
fill1: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
l.m.value = (l.width.value - l.left.value - l.right.value) / l.w.value | 0
l.n.value = (l.height.value - l.top.value - l.bottom.value) / l.h.value | 0
}
}
},
fill2: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
var w = (l.width.value - l.left.value - l.right.value) / l.m.value
, h = (l.height.value - l.top.value - l.bottom.value) / l.n.value
if (l.square.checked)
if (w < h) h = w
else w = h
l.w.value = w
l.h.value = h
}
}
},
white: {
click: function () {
this.u.l.color.value = '#ffffff'
}
},
black: {
click: function () {
this.u.l.color.value = '#000000'
}
},
browse: {
click: function () {
this.u.l.file.click()
}
},
reset: {
click: function () {
var l = this.u.l
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
l.content.innerHTML = l.style.innerHTML = ''
}
},
print: {
click: function () {
window.print()
}
},
form: {
submit: function (e) {
e.stopPropagation()
e.preventDefault()
var a = this, l = a.u.l, u = l.units.value
, width = l.width.value, height = l.height.value // page size
, mt = l.top.value, mb = l.bottom.value // margins
, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
switch (u) {
case '1': // Inches
u = 'in'
break
case '2': // Centimeters
u = 'cm'
break
case '3': // Millimeters
default: // use Millimeters
u = 'mm'
}
l.style.innerHTML = '#page{size:' + width + u + ' ' + height + u
+ (l.rotate.checked ? ';landscape' : '')
+ '}#page{margin:' + mt + u + ' ' + mr + u + ' ' + mb + u + ' ' + ml + u
+ '}#media screen{#page{width:' + width + u + ';height:' + height + u
+ '}}#content{width:' + (width - ml - mr) + u + ';height:' + (height - mt - mb) + u
+ '}#grid>colgroup>col{min-width:' + w + u
+ '}#grid>tbody>tr{height:' + h + u
+ '}#grid,#grid>colgroup>col,#grid>tbody>tr{border-color:' + l.color.value
+ '}'
l.content.innerHTML = '<div id="box"><img alt="" id="image" src="' + a.z
+ '"><table id="grid"><colgroup>'
+ '<col>'.repeat(m) + '</colgroup>'
+ '<tr></tr>'.repeat(n)
+ '</table></div>'
}
}
},
i: function (p) {
var t = this, l = t.l, b = t.f.b, e = t.e, i, j, k
for (i in l)
l[i] = document.getElementById(l[i] || i)
l.window = window
l.document = document
for (i in e) {
k = e[i]
for (j in k)
l[i].addEventListener(j, b(k[j], p), 0)
}
}
},
i: function () {
var t = this
t.u.i(t)
}
}
))
/*********************************************\
** Copyright © 2017-2019 Thejaka Maldeniya **
\*********************************************/
hr {
border: 1px solid #797;
}
table {
border-collapse: collapse;
}
h1 {
margin-top: 0;
font-size: x-large;
}
h3 {
margin-bottom: 0;
font-size: medium;
}
input, select, button {
margin: 1px;
padding: 1px 4px 2px;
}
input:not([type=checkbox]), select, button {
vertical-align: middle;
}
input[type=checkbox] {
margin-right: 5px;
}
label {
vertical-align: 1px;
}
input, select {
border: 1px solid #898;
background: #efe;
color: #353;
}
select {
padding: 0 0 1px;
}
input[readonly] {
background-color: #cdc;
}
input[type=file] {
display: none;
}
button {
border: 1px solid #898;
border-radius: 6px;
background: #bcb;
padding-right: 6px;
padding-left: 6px;
color: #353;
cursor: pointer;
}
button:hover {
border-color: #899;
background-color: #cdd;
color: #465;
}
.tt {
margin-left: 5px;
}
.tt td {
padding: 2px;
}
#ui {
border: 2px solid #8a8;
border-radius: 10px;
background: #cdc;
padding: 10px;
color: #575;
}
#name, #preview {
border: 1px solid #898;
padding: 5px;
}
#preview {
display: inline-block;
width: 256px;
}
#page, #content {
display: flex;
align-items: center;
justify-content: center;
}
#content {
overflow: hidden;
}
#box {
position: relative;
}
#image {
z-index: -1;
position: absolute;
width: 100%;
height: 100%;
object-position: center;
object-fit: contain;
}
#grid {
table-layout: fixed;
margin: auto;
border: 2px solid;
}
#grid > colgroup > col {
border-right: 1px solid;
}
#grid > colgroup > col:nth-child(10n) {
border-right-width: 2px;
}
#grid > tbody > tr {
border-bottom: 1px solid;
}
#grid > tbody > tr:nth-child(10n) {
border-bottom-width: 2px;
}
#footer {
margin-top: 20px;
border-top: 1px solid #797;
padding-top: 6px;
}
#media screen {
#page {
border: 1px solid #898;
}
}
#media print {
#ui {
display: none;
}
}
<!DOCTYPE html>
<html>
<head>
<title>Cross-Stitch Image Generation Tool</title>
<meta charset="utf-8">
<style id="style"></style>
</head>
<body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-142604605-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-142604605-1');
</script>
<div id="ui">
<h1>Cross-Stitch Image Generation Tool</h1>
<h3>For the most basic usage, Browse an image, then click Generate, then scroll down to view the image. Possibly only works in Firefox...</h3>
<br>
<form id="form">
<table class="tt">
<tr>
<td><label for="units">Units:</label></td>
<td>
<select id="units">
<option value="1">Inches</option>
<option value="2">Centimeters</option>
<option value="3" selected>Millimeters</option>
</select>
</td>
<td colspan="5"></td>
</tr>
<tr>
<td><label for="size">Page Size:</label></td>
<td>
<select id="size">
<option value="0">Custom</option>
<option value="1">Letter</option>
<option value="3">Legal</option>
<option value="6">A3</option>
<option value="7" selected>A4</option>
<option value="8">A5</option>
</select>
</td>
<td><label for="width">Width:</label></td>
<td><input id="width" type="text" required readonly pattern="\d+(.\d+)?" value="210"></td>
<td><label for="height">Height:</label></td>
<td><input id="height" type="text" required readonly pattern="\d+(.\d+)?" value="297"></td>
<td><input id="rotate" type="checkbox"><label for="rotate">Rotate</label></td>
</tr>
</table>
<h3>Margins</h3>
<table class="tt">
<tr>
<td><label for="top">Top:</label></td>
<td><input id="top" type="text" required pattern="\d+(.\d+)?" value="10"></td>
<td><label for="bottom">Bottom:</label></td>
<td><input id="bottom" type="text" required pattern="\d+(.\d+)?" value="10"></td>
</tr>
<tr>
<td><label for="left">Left:</label></td>
<td><input id="left" type="text" required pattern="\d+(.\d+)?" value="10"></td>
<td><label for="right">Right:</label></td>
<td><input id="right" type="text" required pattern="\d+(.\d+)?" value="10"></td>
</tr>
</table>
<h3>Grid</h3>
<table class="tt">
<tr>
<td><label for="w">Cell Width:</label></td>
<td><input id="w" type="text" required pattern="\d+(.\d+)?" value="2"></td>
<td><label for="h">Cell Height:</label></td>
<td><input id="h" type="text" required readonly pattern="\d+(.\d+)?" value="2"></td>
<td><input id="square" type="checkbox" checked><label for="rotate">Same (Square)</label></td>
<td><button id="fill1" type="button">Fill Page</button></td>
</tr>
<tr>
<td><label for="m">Grid Width (cells):</label></td>
<td><input id="m" type="text" required pattern="\d+" value="90"></td>
<td><label for="n">Grid Height (cells):</label></td>
<td><input id="n" type="text" required pattern="\d+" value="130"></td>
<td></td>
<td><button id="fill2" type="button">Fill Page</button></td>
</tr>
<tr>
<td><label for="color">Grid Color:</label></td>
<td>
<input id="color" type="color" value="#ffffff">
<button id="white" type="button">White</button>
<button id="black" type="button">Black</button>
</td>
<td colspan="4"></td>
</tr>
</table>
<h3>Image</h3>
<input id="file" type="file">
<table class="tt">
<tr>
<td><button id="browse" type="button">Browse</button></td>
<td><div id="name">(Select an image)</div></td>
</tr>
<tr>
<td></td>
<td><img id="preview" alt="(No Preview)" src="#"></td>
</tr>
</table>
<br>
<button id="generate">Generate</button>
<button id="reset" type="reset">Reset</button>
<button id="print" type="button">Print</button>
</form>
<div id="footer">
© 2017-2019 Thejaka Maldeniya. All rights reserved.
</div>
</div>
<div id="page">
<div id="content"></div>
</div>
</body>
</html>
Reducing the code pointed me in the direction of how to solve the core problem. Turns out I had omitted the tds because FF Desktop didn't require them, to reduce the size of the generated code. At the time I wrote this, I was optimizing just for FF Desktop as per requirements. Adding empty tds seems to solve the core issue. (Only tested in Edge, so far...)
/*********************************************\
** Copyright © 2017-2019 Thejaka Maldeniya **
\*********************************************/
'use strict';
(function (a) {
a.i()
}(
{
z: 0, // image file data URL
p: '3', // previous value of units (default: Millimeters)
f: { // functions
_: function (l, e, h) { l.addEventListener(e, h, 0) },
l: function (a, f) { // load files
if (f.length) {
f = f[0]
var l = a.u.l, r = new FileReader()
l.name.innerHTML = f.name
a.f._(r, 'loadend', function (e) {
l.preview.src = a.z = e.target.result
})
r.readAsDataURL(f)
}
},
r: function (x) { // round to 3 decimal places
return Math.round(x * 1000) / 1000
},
f: function (a, b) { // get conversion factor
var f = 0
if (a == b) f = 1
else {
// if either a or b is not known, factor will be 0
// first, convert to Millimeters
switch (a) { // previous units value
case '1': // Inches
f = 25.4
break
case '2': // Centimeters
f = 10
break
case '3': // Millimeters
f = 1
}
// second, convert from Millimeters
switch (b) { // new units value
case '1': // Inches
f /= 25.4
break
case '2': // Centimeters
f /= 10
break
case '3': // Millimeters
// no change
}
}
return f
},
c: function () { // units or size value changed
var t = this, l = a.u.l
, r = t.r // round function
, width = l.width.value, height = l.height.value // A4
, mt = l.top.value, mb = l.bottom.value, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
, s = 'A4' // paper size
, f = t.f(a.p, a.p = l.units.value) // unit conversion factor
, g = t.f('3', l.units.value) // unit conversion factor for default values (currently in Millimeters)
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
switch (l.size.value) {
case '0': // Custom
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
g = f
break
case '1': // Letter
width = 216
height = 279
l.rotate.checked = 0
s = 'Letter'
break
case '3': // Legal
width = 216
height = 356
l.rotate.checked = 0
s = 'Legal'
break
case '6': // A3
width = 297
height = 420
l.rotate.checked = 0
s = 'A3'
break
case '7': // A4
width = 210
height = 297
l.rotate.checked = 0
s = 'A4'
break
case '8': // A5
width = 148
height = 210
l.rotate.checked = 0
s = 'A5'
}
l.width.value = r(width * g)
l.height.value = r(height * g)
l.top.value = r(mt * f)
l.bottom.value = r(mb * f)
l.left.value = r(ml * f)
l.right.value = r(mr * f)
l.w.value = r(w * f)
l.h.value = r(h * f)
l.m.value = m
l.n.value = n
}
},
u: {
l: {
style: 0,
ui: 0,
form: 0,
units: 0,
size: 0,
width: 0,
height: 0,
rotate: 0,
top: 0,
bottom: 0,
left: 0,
right: 0,
w: 0,
h: 0,
square: 0,
fill1: 0,
m: 0,
n: 0,
fill2: 0,
color: 0,
white: 0,
black: 0,
file: 0,
browse: 0,
name: 0,
preview: 0,
reset: 0,
print: 0,
page: 0,
content: 0
},
f: {
b: function (f, c) {
return function () {
var a = arguments, n = a.length, b = Array(n), i = 0
for (; i < n; ++i)
b[i] = a[i]
f.apply(c, b)
}
}
},
e: {
document: {
DOMContentLoaded: function () {
}
},
window: {
load: function () {
var a = this, l = a.u.l
a.p = l.units.value
if (l.size.value == '0') {
l.width.removeAttribute('readonly')
l.height.removeAttribute('readonly')
}
if (!l.square.checked)
l.h.removeAttribute('readonly')
}
},
ui: {
dragover: function (e) {
e.stopPropagation()
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
drop: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.dataTransfer.files)
}
},
rotate: {
change: function () {
var l = this.u.l, v = l.width.value
l.width.value = l.height.value
l.height.value = v
v = l.top.value
if (l.rotate.checked) {
l.top.value = l.left.value
l.left.value = l.bottom.value
l.bottom.value = l.right.value
l.right.value = v
} else {
l.top.value = l.right.value
l.right.value = l.bottom.value
l.bottom.value = l.left.value
l.left.value = v
}
v = l.w.value
l.w.value = l.h.value
l.h.value = v
v = l.m.value
l.m.value = l.n.value
l.n.value = v
}
},
w: {
change: function (e) {
var l = this.u.l
if (l.square.checked)
l.h.value = e.target.value
}
},
square: {
change: function (e) {
var l = this.u.l
if (e.target.checked) {
l.h.setAttribute('readonly', '')
l.h.value = l.w.value
} else l.h.removeAttribute('readonly')
}
},
file: {
change: function (e) {
e.stopPropagation()
e.preventDefault()
this.f.l(this, e.target.files)
}
},
units: {
change: function () {
this.f.c(this)
}
},
size: {
change: function () {
this.f.c(this)
}
},
fill1: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
l.m.value = (l.width.value - l.left.value - l.right.value) / l.w.value | 0
l.n.value = (l.height.value - l.top.value - l.bottom.value) / l.h.value | 0
}
}
},
fill2: {
click: function () {
var l = this.u.l
if (l.form.reportValidity()) {
var w = (l.width.value - l.left.value - l.right.value) / l.m.value
, h = (l.height.value - l.top.value - l.bottom.value) / l.n.value
if (l.square.checked)
if (w < h) h = w
else w = h
l.w.value = w
l.h.value = h
}
}
},
white: {
click: function () {
this.u.l.color.value = '#ffffff'
}
},
black: {
click: function () {
this.u.l.color.value = '#000000'
}
},
browse: {
click: function () {
this.u.l.file.click()
}
},
reset: {
click: function () {
var l = this.u.l
l.width.setAttribute('readonly', '')
l.height.setAttribute('readonly', '')
l.content.innerHTML = l.style.innerHTML = ''
}
},
print: {
click: function () {
window.print()
}
},
form: {
submit: function (e) {
e.stopPropagation()
e.preventDefault()
var a = this, l = a.u.l, u = l.units.value
, width = l.width.value, height = l.height.value // page size
, mt = l.top.value, mb = l.bottom.value // margins
, ml = l.left.value, mr = l.right.value // margins
, w = l.w.value, h = l.h.value // width, height of cell
, m = l.m.value, n = l.n.value // width, height of grid
switch (u) {
case '1': // Inches
u = 'in'
break
case '2': // Centimeters
u = 'cm'
break
case '3': // Millimeters
default: // use Millimeters
u = 'mm'
}
l.style.innerHTML = '#page{size:' + width + u + ' ' + height + u
+ (l.rotate.checked ? ';landscape' : '')
+ '}#page{margin:' + mt + u + ' ' + mr + u + ' ' + mb + u + ' ' + ml + u
+ '}#media screen{#page{width:' + width + u + ';height:' + height + u
+ '}}#content{width:' + (width - ml - mr) + u + ';height:' + (height - mt - mb) + u
+ '}#grid>colgroup>col{min-width:' + w + u
+ '}#grid>tbody>tr{height:' + h + u
+ '}#grid,#grid>colgroup>col,#grid>tbody>tr{border-color:' + l.color.value
+ '}'
l.content.innerHTML = '<div id="box"><img alt="" id="image" src="' + a.z
+ '"><table id="grid"><colgroup>'
+ '<col>'.repeat(m) + '</colgroup>'
+ ('<tr>' + '<td></td>'.repeat(m) + '</tr>').repeat(n)
+ '</table></div>'
}
}
},
i: function (p) {
var t = this, l = t.l, b = t.f.b, e = t.e, i, j, k
for (i in l)
l[i] = document.getElementById(l[i] || i)
l.window = window
l.document = document
for (i in e) {
k = e[i]
for (j in k)
l[i].addEventListener(j, b(k[j], p), 0)
}
}
},
i: function () {
var t = this
t.u.i(t)
}
}
))
I have an array of students, that should be random paired by clicking the button "button-newPairs". These pairs should be shown in 8 divs "pair one", "pair two", ... that contains two spans "studentOne" and "studentTwo".
I get the pairs in console but not by clicking the button "button-newPairs" and I don´t know how to change or insert the text content in my spans. Can someone help me, please? Thank you in advance.
var students = ['Al', 'Ma', 'Pu', 'Mi', 'Ma', 'Me', 'Ca', 'Na', 'Ja', 'Go', 'Ha', 'Fa', 'Ti', 'Fi' ];
var studentOne = document.querySelector('#student1');
var studentTwo = document.querySelector('#student2');
if (students.length % 2 != 0) {
alert("You must have an even number of students. You currently have " + students.length + " students.");
} else {
var arr1 = students.slice(),
arr2 = students.slice();
arr1.sort(function () { return 0.5 - Math.random(); });
arr2.sort(function () { return 0.5 - Math.random(); });
while (arr1.length) {
var student1 = arr1.pop(),
student2 = arr2[0] == student1 ? arr2.pop() : arr2.shift();
$(".button-newPairs").click(function () {
studentOne.textContent = student1;
studentTwo.textContent = student2;
});
console.log(student1 + ' works with ' + student2);
}
}
.container-pairs {
display: grid;
grid-template-columns: 150px 150px;
grid-gap: 20px;
justify-content: space-around;
align-content: center;
margin-bottom:20px;
}
.one {
grid-column: 1 / 2;
grid-row: 1;
}
.two {
grid-column: 2 / 2;
grid-row: 1;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="container-pairs">
<div class="pair one">
<span id="studentOne">NEW </span> <br>
<span id="studentTwo"> PAIRS</span>
</div>
<div class="pair two">
<span id="studentOne">NEW </span><br>
<span id="studentTwo"> PAIRS</span>
</div>
<div id="container-footer">
<div class="button-newPairs">
<span>NEW PAIRS</span>
</div>
</div>
</body>
I've commented inline. Look for the lines with a //
// Let's wrap this in a function so that we can call it with our button.
function makePairs() {
// We will clear our lists before each run of the function.
$('#studentOne').html('<h1>Student 1</h1>');
$('#studentTwo').html('<h1>Student 2</h1>');
var students = ['Al', 'Ma', 'Pu', 'Mi', 'Ma', 'Me', 'Ca', 'Na', 'Ja', 'Go', 'Ha', 'Fa', 'Ti', 'Fi'];
// By capturing these nodes in variables, we can reference them as our targets for insertion, below.
var studentOne = document.querySelector('#studentOne');
var studentTwo = document.querySelector('#studentTwo');
if (students.length % 2 != 0) {
alert("You must have an even number of students. You currently have " + students.length + " students.");
} else {
var arr1 = students.slice(),
arr2 = students.slice();
arr1.sort(function() {
return 0.5 - Math.random();
});
arr2.sort(function() {
return 0.5 - Math.random();
});
// Here we make a function that will insert a DOM fragment inside a target node
const insertFragment = (output, target) => {
let el;
let fragment = document.createDocumentFragment();
el = document.createElement('p');
el.innerText = output
fragment.appendChild(el);
target.appendChild(fragment);
}
while (arr1.length) {
var student1 = arr1.pop(),
student2 = arr2[0] == student1 ? arr2.pop() : arr2.shift();
// Here we use the function, sending student1 (the student) to studentOne (the target DOM node specified at the very top, #studentOne)
insertFragment(student1, studentOne)
insertFragment(student2, studentTwo)
console.log(student1 + ' works with ' + student2);
}
}
}
// Run the function on load
makePairs();
.container-pairs {
display: grid;
grid-template-columns: 150px 150px;
grid-gap: 20px;
justify-content: space-around;
align-content: center;
margin-bottom: 20px;
}
.one {
grid-column: 1 / 2;
grid-row: 1;
}
.two {
grid-column: 2 / 2;
grid-row: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div class="container-pairs">
<div id="studentOne">
<h1>Student 1</h1>
</div>
<div id="studentTwo">
<h1>Student 2</h1>
</div>
</div>
<div id="container-footer">
<button class="button-newPairs" onclick="makePairs()">
NEW PAIRS
</button>
</div>
</body>
The button tag, just above this line, has registered an event handler for clicks that will run the makePairs() function again.
I would use splice to remove the user from the array and loop through each user like so:
let students = ['Al', 'Ma', 'Pu', 'Mi', 'Ma', 'Me', 'Ca', 'Na', 'Ja', 'Go', 'Ha', 'Fa', 'Ti', 'Fi'];
while (students.length) {
let student1 = students.splice((Math.random() * 1000) % students.length, 1)
let student2 = students.splice((Math.random() * 1000) % students.length, 1)
console.log(`${student1} works with ${student2}`)
}
I would like to create dynamically changing graph with timeline in javascript, That would look something like this.
Edit: I would like to decide by myself which node should be in which time slice.
I wonder, is there a library that I can use to do this, or I need to create it by myself ( by simply drawing on canvas )? I tried to find one, however it seems that there are many implementations of timelines and of graphs but the combination of those two is hard to find. The most suitable solution was using gojs. However I can't create a node with two parents in it because it is implemented as a tree data structure.
You may have to play around with the maths, but I hope this will be useful as a starting point:
DEMO: JSFiddle
HTML
<div id='board'>
<div id='titles'></div>
</div>
CSS
#board {
position: relative;
width: 500px;
height: 600px;
background-color:#f83213;
}
#titles {
color: #ffffff;
width: 100%;
height: 18px;
font-size: 12px;
}
#titles div {
display:inline-block;
margin: 10px;
}
.event{
border: 0px;
background-color: #3a2356;
color: #ffffff;
width: 18px;
height: 18px;
position: absolute;
padding: 4px;
font-size: 18px;
z-index: 2;
}
.line{
height: 1px;
width: 60px;
background-color: #3a2356;
position: absolute;
z-index: 1;
}
** JavaScript**
var margin = 20;
var events = {
"A": {
day: 0,
indexInDay: 0,
lineTos: ["D"]
},
"B": {
day: 0,
indexInDay: 1,
lineTos: ["D"]
},
"D": {
day: 1,
indexInDay: 0,
lineTos: ["E","F"]
},
"E": {
day: 2,
indexInDay: 0,
lineTos: null
},
"C": {
day: 0,
indexInDay: 2,
lineTos: ["F"]
},
"F": {
day: 2,
indexInDay: 2,
lineTos: null
},
};
drawAll(events);
function drawAll(events) {
drawTitles(events);
drawEvents(events);
drawLines(events);
}
function drawTitles(events) {
var titles = document.getElementById('titles');
var max = 0;
for (var name in events) {
if (events[name].day > max)
max = events[name].day;
}
for (var i = 0 ; i <= max ; i++)
titles.innerHTML += '<div>' + 'Day' + i + '</div>';
}
function drawEvents(events) {
var board = document.getElementById('board');
for (var name in events) {
var ev = events[name];
var eventDiv = document.createElement('DIV');
board.appendChild(eventDiv);
eventDiv.className = 'event';
setTopLeftEvent(ev, eventDiv);
eventDiv.innerText = name;
}
}
function drawLines(events) {
var board = document.getElementById('board');
for (var name in events) {
var from = events[name];
var tos = from.lineTos;
if (!tos) continue;
for (var j = 0 ; j < tos.length ; j++) {
var to = events[tos[j]];
var lineDiv = document.createElement('DIV');
board.appendChild(lineDiv);
lineDiv.className = 'line';
setTopLeftLine(from, lineDiv);
lineDiv.style.width = margin + 1 * margin * distance(to.indexInDay,from.indexInDay,to.day, from.day) + 'px';
var tan = (to.indexInDay - from.indexInDay) / (to.day - from.day);
lineDiv.style.top = lineDiv.offsetTop + (tan * margin) +'px';
var angle = Math.atan(tan) * 180/Math.PI;
// Code for Safari
lineDiv.style.WebkitTransform = "rotate(" + angle + "deg)";
// Code for IE9
lineDiv.style.msTransform = "rotate(" + angle + "deg)";
// Standard syntax
lineDiv.style.transform = "rotate(" + angle + "deg)";
}
}
}
function distance(x1, y1, x2, y2){
var res = Math.sqrt((y2-y1)*(y2-y1) + (x2-x1)*(x2-x1));
return res;
}
function setTopLeftEvent(event, eventDiv) {
eventDiv.style.left = (margin + event.day * (margin * 2)) + 'px';
eventDiv.style.top = (margin * 2 + event.indexInDay * (margin * 2)) + 'px';
}
function setTopLeftLine(event, lineDiv) {
lineDiv.style.left = (margin + event.day * (margin * 2)) + 'px';
lineDiv.style.top = (margin * 2.5 + event.indexInDay * (margin * 2)) + 'px';
}
As that GoJS sample mentions in the text, it is easy to replace the TreeLayout with a LayeredDigraphLayout and the TreeModel with a GraphLinksModel. Here's what I just did to modify the sample.
Replace go.TreeLayout with go.LayeredDigraphLayout, so that the custom layout no longer inherits from TreeLayout. Change the constructor not to bother setting TreeLayout specific properties. Change the diagram's layout to use LayeredDigraphLayout specific properties:
layout: $(LayeredTreeLayout, // custom layout is defined above
{
angle: HORIZONTAL ? 0 : 90,
columnSpacing: 5,
layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource
}),
Replace that sample's model with a GraphLinksModel holding the data that you want:
// define the node data
var nodearray = [
{ // this is the information needed for the headers of the bands
key: "_BANDS",
category: "Bands",
itemArray: [
{ text: "Day 0" },
{ text: "Day 1" },
{ text: "Day 2" },
{ text: "Day 3" },
{ text: "Day 4" },
{ text: "Day 5" }
]
}
];
var linkarray = [
{ from: "A", to: "D" },
{ from: "B", to: "D" },
{ from: "D", to: "E" },
{ from: "D", to: "F" },
{ from: "C", to: "F" }
];
myDiagram.model = $(go.GraphLinksModel,
{ // automatically create node data objects for each "from" or "to" reference
// (set this property before setting the linkDataArray)
archetypeNodeData: {},
nodeDataArray: nodearray,
linkDataArray: linkarray
});
Without having changed any of the templates or the styling, the result is:
Just to make sure it works, I also tried setting HORIZONTAL = false: