A discrepancy in rendering between Firefox vs Chrome, Edge, Safari - javascript

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

Related

How to apply drag and drop for a div spanning multiple cells

I've been working on a side project to recreate diablo / tarkov like inventory screens with pure html, css, and js. I'm attempting to use the draggable api's built into html for this but am hitting a blocker. Everything working fine for 1x1 wide/high cell's / items. The problem begins when trying to add an item that is longer in width and/or height. I can still drag around the item but I can't get it to do two things.
Cover all the cells correctly so there is no bleed through from the cells background.
Stop the item from being placed in a cell that would cover another item.
My ideal solution modifies either the linked codepen below or provides direction / a solution on a more appropriate approach whatever that may be. The only constraint is that it must use html, css, and javascript. The only exception is jQuery.
The Code
var draggedItem = null;
function Inventory(options) {
// Setup Rows and Items
this.init = function (options) {
let slotSizeW = options.size.w / options.slots.w;
let slotSizeH = options.size.h / options.slots.h;
this.html = { inventory: options.selector };
// Build grid
let grid = document.createElement("grid");
grid.style.width = options.size.w + "px";
grid.style.height = options.size.h + "px";
grid.style.gridTemplateColumns = "1fr ".repeat(options.slots.w);
grid.style.gridTemplateRows = "1fr".repeat(options.slots.h);
grid.cells = [];
for (var i = 0; i < options.slots.w * options.slots.h; i++) {
var cell = document.createElement("cell");
cell.style.width = slotSizeW + "px";
cell.style.height = slotSizeH + "px";
grid.appendChild(cell);
grid.cells.push(cell);
}
this.html.grid = grid;
this.html.inventory.appendChild(this.html.grid);
// Add all items
let items = [];
options.items.forEach(function (item, index, array) {
items.push(item);
let itemEl = document.createElement("item");
itemEl.setAttribute("draggable", true);
itemEl.style.background = item.color;
itemEl.style.width = 100 * item.w + "%";
itemEl.style.height = 100 * item.h + "%";
grid.cells[options.slots.w * item.x + item.y].appendChild(itemEl);
});
this.html.items = items;
let itemTags = document.querySelectorAll("item");
for (let i = 0; i < itemTags.length; i++) {
itemTags[i].addEventListener("dragstart", this.dragStart);
itemTags[i].addEventListener("dragend", this.dragEnd);
}
let cellTags = document.querySelectorAll("cell");
for (let i = 0; i < cellTags.length; i++) {
cellTags[i].addEventListener("dragover", this.dragOver);
cellTags[i].addEventListener("dragenter", this.dragEnter);
cellTags[i].addEventListener("dragleave", this.dragLeave);
cellTags[i].addEventListener("drop", this.dragDrop);
}
};
this.dragStart = function () {
this.classList.toggle("hold");
window.requestAnimationFrame(() => this.classList.toggle("invisible"));
draggedItem = this;
};
this.dragEnd = function () {
this.classList.toggle("hold");
this.classList.toggle("invisible");
};
this.dragOver = function (e) {
e.preventDefault();
};
this.dragEnter = function (e) {
e.preventDefault();
this.classList.toggle("hovered");
};
this.dragLeave = function (e) {
this.classList.remove("hovered");
};
this.dragDrop = function (e) {
this.classList.remove("hovered");
if (draggedItem != null) {
draggedItem.parentElement.removeChild(draggedItem);
this.appendChild(draggedItem);
}
};
this.init(options);
}
var inventory = new Inventory({
selector: document.querySelector("inventory"),
size: { w: 300, h: 300 },
slots: { w: 4, h: 4 },
items: [
{ x: 0, y: 0, w: 2, h: 2, content: "2x2", color: "#ffd54f" },
{ x: 2, y: 0, w: 1, h: 2, content: "1x2", color: "#66bb6a" },
{ x: 3, y: 3, w: 1, h: 1, content: "1x1", color: "#e53935" }
]
});
html,
body {
background: darksalmon;
}
grid {
position: relative;
display: grid;
}
cell {
position: relative;
border: 3px salmon solid;
background-color: white;
}
item {
display: block;
position: relative;
color: white;
cursor: pointer;
z-index: 2;
}
.hold {
border: solid #ccc 4px;
}
.hovered {
background: #f4f4f4;
border-style: dashed;
}
.invisible {
display: none;
}
<container>
<inventory>
</inventory>
</container>
To get each item's color to fill the underlying cells you have to take into account the extra width of each cell created by its border.
This snippet sets the width and height of an item using this formula:
itemEl.style.width = "calc((100% + 6px) * " + item.w + " - 4.9px)";
itemEl.style.height = "calc((100% + 6px) * " + item.h + " - 4.9px)";
This says add on an allowance for all 4 borders but remove the width of the outer borders. However, there can be a 'quirk' where the calculation the system has to do to map CSS pixels to screen pixels (several screen pixels may be used for one CSS pixel in modern displays) results in the odd screen pixel being left behind - in this case resulting in a thin white line around the item's color.
The item is made slightly wider (by just over 1 CSS pixel) and is placed half a pixel up and left which, at least in the tests I did with Edge/Chrome Windows10 removed this white 'border' at all zoom levels.
var draggedItem = null;
function Inventory(options) {
// Setup Rows and Items
this.init = function (options) {
let slotSizeW = options.size.w / options.slots.w;
let slotSizeH = options.size.h / options.slots.h;
this.html = { inventory: options.selector };
// Build grid
let grid = document.createElement("grid");
grid.style.width = options.size.w + "px";
grid.style.height = options.size.h + "px";
grid.style.gridTemplateColumns = "1fr ".repeat(options.slots.w);
grid.style.gridTemplateRows = "1fr".repeat(options.slots.h);
grid.cells = [];
for (var i = 0; i < options.slots.w * options.slots.h; i++) {
var cell = document.createElement("cell");
cell.style.width = slotSizeW + "px";
cell.style.height = slotSizeH + "px";
grid.appendChild(cell);
grid.cells.push(cell);
}
this.html.grid = grid;
this.html.inventory.appendChild(this.html.grid);
// Add all items
let items = [];
options.items.forEach(function (item, index, array) {
items.push(item);
let itemEl = document.createElement("item");
itemEl.setAttribute("draggable", true);
itemEl.style.background = item.color;
//itemEl.style.width = 100 * item.w + "%";
//itemEl.style.height = 100 * item.h + "%";
itemEl.style.width = "calc((100% + 6px) * " + item.w + " - 4.9px)";
itemEl.style.height = "calc((100% + 6px) * " + item.h + " - 4.9px)";
grid.cells[options.slots.w * item.x + item.y].appendChild(itemEl);
});
this.html.items = items;
let itemTags = document.querySelectorAll("item");
for (let i = 0; i < itemTags.length; i++) {
itemTags[i].addEventListener("dragstart", this.dragStart);
itemTags[i].addEventListener("dragend", this.dragEnd);
}
let cellTags = document.querySelectorAll("cell");
for (let i = 0; i < cellTags.length; i++) {
cellTags[i].addEventListener("dragover", this.dragOver);
cellTags[i].addEventListener("dragenter", this.dragEnter);
cellTags[i].addEventListener("dragleave", this.dragLeave);
cellTags[i].addEventListener("drop", this.dragDrop);
}
};
this.dragStart = function () {
this.classList.toggle("hold");
window.requestAnimationFrame(() => this.classList.toggle("invisible"));
draggedItem = this;
};
this.dragEnd = function () {
this.classList.toggle("hold");
this.classList.toggle("invisible");
};
this.dragOver = function (e) {
e.preventDefault();
};
this.dragEnter = function (e) {
e.preventDefault();
this.classList.toggle("hovered");
};
this.dragLeave = function (e) {
this.classList.remove("hovered");
};
this.dragDrop = function (e) {
this.classList.remove("hovered");
if (draggedItem != null) {
draggedItem.parentElement.removeChild(draggedItem);
this.appendChild(draggedItem);
}
};
this.init(options);
}
var inventory = new Inventory({
selector: document.querySelector("inventory"),
size: { w: 300, h: 300 },
slots: { w: 4, h: 4 },
items: [
{ x: 0, y: 0, w: 2, h: 2, content: "2x2", color: "#ffd54f" },
{ x: 2, y: 0, w: 1, h: 2, content: "1x2", color: "#66bb6a" },
{ x: 3, y: 3, w: 1, h: 1, content: "1x1", color: "#e53935" }
]
});
html,
body {
background: darksalmon;
}
grid {
position: relative;
display: grid;
}
cell {
position: relative;
border: 3px salmon solid;
background-color: white;
}
item {
display: block;
position: relative;
color: white;
cursor: pointer;
z-index: 2;
top: -0.5px;
left: -0.5px;
}
.hold {
border: solid #ccc 4px;
}
.hovered {
background: #f4f4f4;
border-style: dashed;
}
.invisible {
display: none;
}
<container>
<inventory>
</inventory>
</container>
For the second part of the question on a drop you will have to loop through the items to see if any overlaps with the dragged item and if it doesn't then you can remove the dragged item from its current cell and append it to its new one.

Stop the audio player

I have an audio script with play/pause, I want to make the audio stop when I click on the square button at the left, and to add the timing to the right
here is the code: https://codepen.io/Amirafik/pen/YzxQZQw
HTML
<div class="title">
Dina Mohamed
</div>
<div class="stop"></div>
<div id="audioButton">
<div id="playerContainer">
<div class="listen">LISTEN</div>
</div>
</div>
<div class="duration">
00:12
</div>
<br>_____________________________________________________</br>
<div class="note">
<p>All items were more than perfect, we loved the experience and will order ONE OAK services again in the future for sure. <!--<span style="color:#F04E36"><br> Dina sent us an amazing feedback about her experience, tap the play button to listen to her cheerful words.</span></p>-->
</div
CSS
#import url('https://fonts.googleapis.com/css?family=montserrat:100,200,300,400,500,600,700,800,900');
body {
background: #ffffff;
color: #000000;
font-size: 16px ;
font-family: "montserrat" ;
font-weight: 500;
-webkit-tap-highlight-color: transparent;
}
a {
color: #F04E36;
}
.title {
max-width: 700px;
margin: 0 auto;
color: #000000;
font-weight:700;
display:inline-block;
}
.note {
max-width: 380px;
margin: 0 auto;
color: #000000;
display:inline-block;
}
.circle-audio-player {
cursor: pointer;
width:25px;
padding:0px;
margin-top:-67%;
margin-bottom:-50%;
margin-left:-7px;
background-color:#EDEBE7;
border-radius:50%;
vertical-align:middle;
}
#playerContainer {
padding: 0px;
vertical-align:middle;
}
#audioButton {
border-radius: 50px;
border: 2px solid #000000;
padding: 10px;
max-width: 85px;
height: 10px;
display: inline-block;
vertical-align:middle;
margin-left:2px;
}
.listen {
margin-left: 5px;
color: #000000;
font-weight:700;
display:inline-block;
float:right;
vertical-align:middle;
margin-top:-5%;
font-size: 14px ;
}
.stop {
max-width: 500px;
margin-left:10px;
height: 10px;
width: 10px;
background-color: #000000;
font-weight:500;
font-size: 14px ;
display:inline-block;
vertical-align:middle;
}
.duration {
max-width: 500px;
margin-left: 2px;
color: #000000;
font-weight:500;
font-size: 14px ;
display:inline-block;
}
JS
// settings
var DEFAULTS = {
borderColor: "#EDEBE7",
playedColor: "#F04E36",
backgroundColor: "#d3cdc2",
iconColor: "#000000",
borderWidth: 2,
size: 48,
className: 'circle-audio-player'
};
// reused values
var pi = Math.PI;
var doublePi = pi * 2;
var arcOffset = -pi / 2;
var animTime = 200;
var loaderTime = 1800;
var CircleAudioPlayer = function (options) {
options = options || {};
for (var property in DEFAULTS) {
this[property] = options[property] || DEFAULTS[property];
}
// create some things we need
this._canvas = document.createElement('canvas');
this._canvas.setAttribute('class', this.className + ' is-loading');
this._canvas.addEventListener('mousedown', (function () {
if (this.playing) {
this.pause();
}
else {
this.play();
}
}).bind(this));
this._ctx = this._canvas.getContext('2d');
// set up initial stuff
this.setAudio(options.audio);
this.setSize(this.size);
// redraw loop
(function cAPAnimationLoop (now) {
// check if we need to update anything
if (this.animating) {
this._updateAnimations(now);
}
if (this._forceDraw || this.playing || this.animating || this.loading) {
this._draw();
this._forceDraw = false;
}
requestAnimationFrame(cAPAnimationLoop.bind(this));
}).call(this, new Date().getTime());
};
CircleAudioPlayer.prototype = {
// private methods
_animateIcon: function (to, from) {
// define a few things the first time
this._animationProps = {
animStart: null,
from: from,
to: to
};
if (from) {
this.animating = true;
}
else {
this._animationProps.current = this._icons[to].slice();
this.draw();
}
},
_updateAnimations: function (now) {
this._animationProps.animStart = this._animationProps.animStart || now;
var deltaTime = now - this._animationProps.animStart;
var perc = (1 - Math.cos(deltaTime / animTime * pi / 2));
if (deltaTime >= animTime) {
this.animating = false;
perc = 1;
this._animationProps.current = this._icons[this._animationProps.to].slice();
this.draw();
}
else {
var from = this._icons[this._animationProps.from];
var current = [];
for (var i = 0; i < from.length; i++) {
current.push([]);
for (var j = 0; j < from[i].length; j++) {
current[i].push([]);
var to = this._icons[this._animationProps.to][i][j];
current[i][j][0] = from[i][j][0] + (to[0] - from[i][j][0]) * perc;
current[i][j][1] = from[i][j][1] + (to[1] - from[i][j][1]) * perc;
}
}
this._animationProps.current = current;
}
},
_draw: function (progress) {
// common settings
if (isNaN(progress)) {
progress = this.audio.currentTime / this.audio.duration || 0;
}
// clear existing
this._ctx.clearRect(0, 0, this.size, this.size);
// draw bg
this._ctx.beginPath();
this._ctx.arc(this._halfSize, this._halfSize, this._halfSize - (this.borderWidth / 2), 0, doublePi);
this._ctx.closePath();
this._ctx.fillStyle = this.backgroundColor;
this._ctx.fill();
// draw border
// our active path is already the full circle, so just stroke it
this._ctx.lineWidth = this.borderWidth;
this._ctx.strokeStyle = this.borderColor;
this._ctx.stroke();
// play progress
if (progress > 0) {
this._ctx.beginPath();
this._ctx.arc(this._halfSize, this._halfSize, this._halfSize - (this.borderWidth / 2), arcOffset, arcOffset + doublePi * progress);
this._ctx.strokeStyle = this.playedColor;
this._ctx.stroke();
}
// icons
this._ctx.fillStyle = this.iconColor;
if (this.loading) {
var loaderOffset = -Math.cos((new Date().getTime() % (loaderTime)) / (loaderTime) * pi) * doublePi - (pi / 3) - (pi / 2);
this._ctx.beginPath();
this._ctx.arc(this._halfSize, this._halfSize, this._halfSize / 3, loaderOffset, loaderOffset + pi / 3 * 2);
this._ctx.strokeStyle = this.iconColor;
this._ctx.stroke();
}
else {
this._ctx.beginPath();
var icon = (this._animationProps && this._animationProps.current) || this._icons.play;
for (var i = 0; i < icon.length; i++) {
this._ctx.moveTo(icon[i][0][0], icon[i][0][1]);
for (var j = 1; j < icon[i].length; j++) {
this._ctx.lineTo(icon[i][j][0], icon[i][j][1]);
}
}
// this._ctx.closePath();
this._ctx.fill();
// stroke to fill in for retina
this._ctx.strokeStyle = this.iconColor;
this._ctx.lineWidth = 2;
this._ctx.lineJoin = 'miter';
this._ctx.stroke();
}
},
_setState: function (state) {
this.playing = false;
this.loading = false;
if (state === 'playing') {
this.playing = true;
this._animateIcon('pause', 'play');
}
else if (state === 'loading') {
this.loading = true;
}
else if (this.state !== 'loading') {
this._animateIcon('play', 'pause');
}
else {
this._animateIcon('play', null);
}
this.state = state;
this._canvas.setAttribute('class', this.className + ' is-' + state);
this.draw();
},
// public methods
draw: function () {
this._forceDraw = true;
},
setSize: function (size) {
this.size = size;
this._halfSize = size / 2; // we do this a lot. it's not heavy, but why repeat?
this._canvas.width = size;
this._canvas.height = size;
// set icon paths
var iconSize = this.size / 2;
var pauseGap = iconSize / 10;
var playLeft = Math.cos(pi / 3 * 2) * (iconSize / 2) + this._halfSize;
var playRight = iconSize / 2 + this._halfSize;
var playHalf = (playRight - playLeft) / 2 + playLeft;
var top = this._halfSize - Math.sin(pi / 3 * 2) * (iconSize / 2);
var bottom = this.size - top;
var pauseLeft = this._halfSize - iconSize / 3;
var pauseRight = this.size - pauseLeft;
this._icons = {
play: [
[
[playLeft, top],
[playHalf, (this._halfSize - top) / 2 + top],
[playHalf, (this._halfSize - top) / 2 + this._halfSize],
[playLeft, bottom]
],
[
[playHalf, (this._halfSize - top) / 2 + top],
[playRight, this._halfSize],
[playRight, this._halfSize],
[playHalf, (this._halfSize - top) / 2 + this._halfSize]
]
],
pause: [
[
[pauseLeft, top + pauseGap],
[this._halfSize - pauseGap, top + pauseGap],
[this._halfSize - pauseGap, bottom - pauseGap],
[pauseLeft, bottom - pauseGap]
],
[
[this._halfSize + pauseGap, top + pauseGap],
[pauseRight, top + pauseGap],
[pauseRight, bottom - pauseGap],
[this._halfSize + pauseGap, bottom - pauseGap]
]
]
};
if (this._animationProps && this._animationProps.current) {
this._animateIcon(this._animationProps.to);
}
if (!this.playing) {
this.draw();
}
},
setAudio: function (audioUrl) {
this.audio = new Audio(audioUrl);
this._setState('loading');
this.audio.addEventListener('canplaythrough', (function () {
this._setState('paused');
}).bind(this));
this.audio.addEventListener('play', (function () {
this._setState('playing');
}).bind(this));
this.audio.addEventListener('pause', (function () {
// reset when finished
if (this.audio.currentTime === this.audio.duration) {
this.audio.currentTime = 0;
}
this._setState('paused');
}).bind(this));
},
appendTo: function (element) {
element.appendChild(this._canvas);
},
play: function () {
this.audio.play();
},
pause: function () {
this.audio.pause();
}
};
// now init one as an example
var cap = new CircleAudioPlayer({
audio: 'https://www.siriusxm.com/content/dam/sxm-com/audio/test/audio-previews/audio_test03.mp3.png',
size: 120,
borderWidth: 8
});
cap.appendTo(playerContainer);
You can simply use AudioElement.pause() to pause an running element, and the next AudioElement.play() will start from where you left off.
You can essentially set the currentTime property of the audio element to start from the beginning
A simple demonstration of how it works
const pause = document.getElementById('pause');
const stop = document.getElementById('stop');
const play = document.getElementById('play');
const container = document.getElementById('player');
const duration = document.getElementById('duration');
const audio = new Audio('https://www.siriusxm.com/content/dam/sxm-com/audio/test/audio-previews/audio_test03.mp3.png');
let played = 0;
let playing = true;
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
run = async function () {
while(playing) {
if (played == Math.floor(audio.duration)) break;
duration.innerText = `${played} / ${Math.floor(audio.duration)}`;
played++;
await wait(1000);
}
}
container.appendChild(audio);
stop.addEventListener('click', () => {
duration.innerText = `0 / ${Math.floor(audio.duration)}`
audio.pause();
audio.currentTime = 0;
played = 0;
playing = false;
});
pause.addEventListener('click', () => {
audio.pause();
playing = false;
});
play.addEventListener('click', () => {
playing = true
audio.play();
run();
});
<div id="player"></div>
<div id="duration">0.0</div>
<button id="play">play</button>
<button id="stop">stop</button>
<button id="pause">pause</button>

Is there a way to combine multiple divs into one

I have a dynamic group of n by m divs to form a grid. The items in this grid can be selected. The end result which I am hoping to achieve is to be able to combine and split the selected divs.
I have managed to get the divs to show correctly and select them, storing their id's in a list. Is there a way I could combine the selected divs, keeping the ones around it in their place?
#(Html.Kendo().Window()
.Name("window") //The name of the window is mandatory. It specifies the "id" attribute of the widget.
.Title("Dashboard Setup") //set the title of the window
.Content(#<text>
<div id="divSetup">
<div class="dvheader">
<b>Dashboard Setup</b>
</div>
<div>
<p>Enter the number of columns and rows of dashboard elements. This will create an empty dashboard with a set number of items which can be filled with KPI charts.</p>
<br />
<div class="dvform">
<table>
<tr style="margin-bottom: 15px;">
<td>
#Html.Label("Number of Columns: ")
</td>
<td>
<input type="number" name="NumColumns" id="NoColumns" min="1" max="20" />
</td>
</tr>
<tr style="margin-bottom: 15px;">
<td>
#Html.Label("Number of Rows: ")
</td>
<td>
<input type="number" name="NumRows" id="NoRows" min="1" max="20" />
</td>
</tr>
</table>
</div>
</div>
<div style="float: right">
<button id="btnSave" class="k-primary">Save</button>
<button id="btnClose">Close</button>
</div>
</div>
</text>)
.Draggable() //Enable dragging of the window
.Resizable() //Enable resizing of the window
.Width(600) //Set width of the window
.Modal(true)
.Visible(false)
)
<div id="dashboard">
</div>
<button id="combine" title="Combine">Combine</button>
<script>
$(document).ready(function () {
debugger;
$("#window").data("kendoWindow").open();
$("#btnClose").kendoButton({
click: close
});
$("#btnSave").kendoButton({
click: Save
});
$("#combine").kendoButton();
});
var array = [];
function clicked(e)
{
debugger;
var selectedDiv = "";
var x = document.getElementsByClassName('column')
for (var i = 0; i < x.length; i++)
{
if (x[i].id == e.id)
{
x[i].classList.add("selected");
array.push(x[i]);
}
}
for (var x = 0; x < array.length - 1; x++) {
array[x].classList.add("selected");
}
}
function close() {
$("#window").hide();
}
function Save()
{
debugger;
var col = document.getElementById("NoColumns").value;
var row = document.getElementById("NoRows").value;
for (var x = 1; x <= row; x++)
{
debugger;
document.getElementById("dashboard").innerHTML += '<div class="row">';
debugger;
for (var i = 1; i <= col; i++)
{
document.getElementById("dashboard").innerHTML += '<div onclick="clicked(this)" id="Row ' + x + ' Col ' + i + '" class="column">' + i + '</div>';
}
document.getElementById("dashboard").innerHTML += '</div>';
}
}
<style>
.selected {
background-color: #226fa3;
transition: background-color 0.4s ease-in, border-color 0.4s ease-in;
color: #ffffff;
}
#dashboard {
width: 80%;
margin: auto;
background-color: grey;
padding: 20px;
}
* {
box-sizing: border-box;
}
/* Create three equal columns that floats next to each other */
.column {
float: left;
padding: 20px;
border: 1px black solid;
}
/* Clear floats after the columns */
.row:after {
content: "";
display: table;
clear: both;
}
Below is an image of the selected blocks I would like to combine into one, while keeping it's place
If you were using a table it would be much easier, for div's, I can think of a solution if the style position is absolute, maybe it would help you start at least.
<div id="container"></div>
<button id="combine" title="Combine" disabled="disabled">Combine</button>
<div id="output"></div>
the script:
<script>
var cc;
function group() {
var xx = $(".selected").map(function () { return $(this).attr("data-x"); }).get();
var yy = $(".selected").map(function () { return $(this).attr("data-y"); }).get();
this.minX = Math.min.apply(Math, xx);
this.minY = Math.min.apply(Math, yy);
this.maxX = Math.max.apply(Math, xx);
this.maxY = Math.max.apply(Math, yy);
this.selectedCount = $(".selected").length;
this.CorrectGroup = function () {
var s = this.selectedCount;
return s == this.cellsCount() && s > 1;
}
this.width = function () {
return this.maxX - this.minX + 1;
}
this.height = function () {
return this.maxY - this.minY + 1;
}
this.cellsCount = function () {
return this.width() * this.height();
}
}
function cell(x, y, g) {
this.x = x;
this.y = y;
this.g = g;
this.spanX = 1;
this.spanY = 1;
this.visible = true;
var cellWidth = 80;
var cellHeight = 50;
this.div = function () {
var output = jQuery('<div/>');
output.attr('id', 'y' + y + 'x' + x);
output.attr('data-x', x);
output.attr('data-y', y);
output.attr('style', this.left() + this.top() + this.width() + this.height());
output.addClass('clickable');
output.html('(y=' + y + ' x=' + x + ')')
return output;
}
this.left = function () {
return 'left:' + (x * cellWidth) + 'px;';
}
this.top = function () {
return 'top:' + (100 + y * cellHeight) + 'px;';
}
this.width = function () {
return 'width:' + (this.spanX * cellWidth) + 'px;';
}
this.height = function () {
return 'height:' + (this.spanY * cellHeight) + 'px;';
}
}
function cells(xx, yy) {
this.CellWidth = xx;
this.CellHeight = yy;
this.CellList = [];
for (var y = 0; y < yy; y++)
for (var x = 0; x < xx; x++) {
this.CellList.push(new cell(x, y, 1));
}
this.findCell = function (xx, yy) {
return this.CellList.find(function (element) {
return (element.x == xx && element.y == yy);
});
}
this.displayCells = function (container) {
container.html('');
for (var y = 0; y < yy; y++)
for (var x = 0; x < xx; x++) {
var cell = this.findCell(x, y);
if (cell.visible)
cell.div().appendTo(container);
}
}
}
$(document).ready(function () {
$('#combine').click(function () {
$(".selected").each(function () {
var x = $(this).data('x');
var y = $(this).data('y');
var cell = cc.findCell(x, y);
cell.visible = false;
cell.g = 'y';
});
var first = $(".selected").first();
var xx = $(first).data('x');
var yy = $(first).data('y');
var cell = cc.findCell(xx, yy);
var g = new group();
cell.visible = true;
cell.g = xx + '_' + yy;
cell.spanX = g.width();
cell.spanY = g.height();
cc.displayCells($('#container'));
});
//make divs clickable
$('#container').on('click', 'div', function () {
$(this).toggleClass('selected');
if (CheckIfSelectedAreGroupable())
$('#combine').removeAttr("disabled");
else
$('#combine').attr("disabled", "disabled");
});
cc = new cells(12, 10);
cc.displayCells($('#container'));
});
function CheckIfSelectedAreGroupable() {
var g = new group();
return g.CorrectGroup();
}
</script>
Style:
<style>
.selected {
background-color: #226fa3 !important;
transition: background-color 0.4s ease-in, border-color 0.4s ease-in;
color: #ffffff;
}
.clickable {
border: 1px solid lightgray;
margin: 0px;
padding: 0px;
background-color: lightyellow;
position: absolute;
}
</style>
Im starting the divs by the following line, you can hock your form to trigger something similar.
cc = new cells(12, 10);
the combine button will not activate if you dont select a correct group, a rectangle shape selection.
the split will not be hard too but I did not have time to put it together, if this solution help, I can update it for the split.
Note: I wrote this quickly so its not optimized.
to try the solution use :
jsfiddle

Trouble with Cloud-Zoom. Zoom box doesnt follow cursor and doesnt span the image

Here is a link to the website so that you can visualize the error im expressing in my title:
https://glassesled.com/aubrey
If you hover over the picture the zoom box doesn't follow the cursor and when you go the lower portions of the image the zoom box reaches a wall. Here are the relevant files (the ones I think are relevant)
The CloudZoom.min.js file
//////////////////////////////////////////////////////////////////////////////////
// Cloud Zoom V1.0.2
// (c) 2010 by R Cecco. <http://www.professorcloud.com>
// MIT License
//
// Please retain this copyright header in all versions of the software
//////////////////////////////////////////////////////////////////////////////////
! function($) {
function format(t) {
for (var o = 1; o < arguments.length; o++) t = t.replace("%" + (o - 1), arguments[o]);
return t
}
function CloudZoom(t, o) {
var e, i, s, a, n, r, l, d, u = $("img", t),
p = null,
c = null,
h = null,
m = null,
f = null,
g = null,
v = 0,
x = 0,
b = 0,
y = 0,
z = 0,
w = 0,
O = this;
setTimeout(function() {
if (null === c) {
var o = t.width();
t.parent().append(format('<div style="width:%0px;position:absolute;top:75%;left:%1px;text-align:center" class="cloud-zoom-loading" >Loading...</div>', o / 3, o / 2 - o / 6)).find(":last").css("opacity", .5)
}
}, 200);
var k = function() {
null !== g && (g.remove(), g = null)
};
this.removeBits = function() {
h && (h.remove(), h = null), m && (m.remove(), m = null), f && (f.remove(), f = null), k(), $(".cloud-zoom-loading", t.parent()).remove()
}, this.destroy = function() {
t.data("zoom", null), c && (c.unbind(), c.remove(), c = null), p && (p.remove(), p = null), this.removeBits()
}, this.fadedOut = function() {
p && (p.remove(), p = null), this.removeBits()
}, this.controlLoop = function() {
if (h) {
var t = r - u.offset().left - .5 * a >> 0,
e = l - u.offset().top - .5 * n >> 0;
0 > t ? t = 0 : t > u.outerWidth() - a && (t = u.outerWidth() - a), 0 > e ? e = 0 : e > u.outerHeight() - n && (e = u.outerHeight() - n), h.css({
left: t,
top: e
}), h.css("background-position", -t + "px " + -e + "px"), x = t / u.outerWidth() * s.width >> 0, b = e / u.outerHeight() * s.height >> 0, z += (x - z) / o.smoothMove, y += (b - y) / o.smoothMove, p.css("background-position", -(z >> 0) + "px " + (-(y >> 0) + "px"))
}
v = setTimeout(function() {
O.controlLoop()
}, 30)
}, this.init2 = function(t, o) {
w++, 1 === o && (s = t), 2 === w && this.init()
}, this.init = function() {
$(".cloud-zoom-loading", t.parent()).remove();
var e = $(".mousetrap");
e && e.remove(), c = $.browser.msie ? t.parent().append(format("<div class='mousetrap' style='background-image:url(\"/Plugins/SevenSpikes.Nop.Plugins.CloudZoom/Scripts/cloud-zoom.1.0.2/DummyPageForIE.htm\");z-index:999;position:absolute;width:%0px;height:%1px;left:%2px;top:%3px;'></div>", u.outerWidth(), u.outerHeight(), 0, 0)).find(":last") : t.parent().append(format("<div class='mousetrap' style='z-index:999;position:absolute;width:%0px;height:%1px;left:%2px;top:%3px;'></div>", u.outerWidth(), u.outerHeight(), 0, 0)).find(":last"), c.bind("mousemove", this, function(t) {
r = t.pageX, l = t.pageY
}), c.bind("mouseleave", this, function(t) {
return clearTimeout(v), h && h.fadeOut(299), m && m.fadeOut(299), f && f.fadeOut(299), p.fadeOut(300, function() {
O.fadedOut()
}), !1
}), c.bind("mouseenter", this, function(e) {
r = e.pageX, l = e.pageY, d = e.data, p && (p.stop(!0, !1), p.remove());
var i = o.adjustX,
v = o.adjustY,
x = u.outerWidth(),
b = u.outerHeight(),
y = o.zoomWidth,
z = o.zoomHeight;
"auto" == o.zoomWidth && (y = x), "auto" == o.zoomHeight && (z = b);
var w = t.parent();
switch (o.position) {
case "top":
v -= z;
break;
case "right":
i += x;
break;
case "bottom":
v += b;
break;
case "left":
i -= y;
break;
case "inside":
y = x, z = b;
break;
default:
w = $("#" + o.position), w.length ? (y = o.zoomWidth, z = o.zoomHeight) : (w = t, i += x, v += b)
}
p = w.append(format('<div id="cloud-zoom-big" class="cloud-zoom-big" style="display:none;position:absolute;left:%0px;top:%1px;width:%2px;height:%3px;background-image:url(\'%4\');z-index:99;"></div>', i, v, y, z, s.src)).find(":last"), u.attr("title") && o.showTitle && p.append(format('<div class="cloud-zoom-title">%0</div>', u.attr("title"))).find(":last").css("opacity", o.titleOpacity), $.browser.msie && $.browser.version < 7 && (g = $('<iframe frameborder="0" src="#"></iframe>').css({
position: "absolute",
left: i,
top: v,
zIndex: 99,
width: y,
height: z
}).insertBefore(p)), p.fadeIn(500), h && (h.remove(), h = null), a = u.outerWidth() / s.width * p.width(), n = u.outerHeight() / s.height * p.height(), h = t.append(format("<div class = 'cloud-zoom-lens' style='display:none;z-index:98;position:absolute;width:%0px;height:%1px;'></div>", a, n)).find(":last"), c.css("cursor", h.css("cursor"));
var O = !1;
o.tint && (h.css("background", 'url("' + u.attr("src") + '")'), m = t.append(format('<div class="cloud-zoom-tint" style="display:none;position:absolute; left:0px; top:0px; width:%0px; height:%1px; background-color:%2;" />', u.outerWidth(), u.outerHeight(), o.tint)).find(":last"), m.css("opacity", o.tintOpacity), O = !0, m.fadeIn(500)), o.softFocus && (h.css("background", 'url("' + u.attr("src") + '")'), f = t.append(format('<div class="cloud-zoom-softfocus" style="position:absolute;display:none;top:2px; left:2px; width:%0px; height:%1px;" />', u.outerWidth() - 2, u.outerHeight() - 2, o.tint)).find(":last"), f.css("background", 'url("' + u.attr("src") + '")'), f.css("opacity", .5), O = !0, f.fadeIn(500)), O || h.css("opacity", o.lensOpacity), "inside" !== o.position && h.fadeIn(500), d.controlLoop()
})
}, e = new Image, $(e).load(function() {
O.init2(this, 0)
}), e.src = u.attr("src"), i = new Image, $(i).load(function() {
O.init2(this, 1)
}), i.src = t.attr("href")
}
$(document).ready(function() {
$(".cloud-zoom, .cloud-zoom-gallery").CloudZoom()
}), $.fn.CloudZoom = function(options) {
try {
document.execCommand("BackgroundImageCache", !1, !0)
} catch (e) {}
return this.each(function() {
var relOpts, opts;
eval("var a = {" + $(this).attr("rel") + "}"), relOpts = a, $(this).is(".cloud-zoom") ? ($(this).css({
position: "relative",
display: "block"
}), $("img", $(this)).css({
display: "block"
}), "wrap" != $(this).parent().attr("id") && $(this).wrap('<div id="wrap" style="top:0px;position:relative;"></div>'), opts = $.extend({}, $.fn.CloudZoom.defaults, options), opts = $.extend({}, opts, relOpts), $(this).data("zoom", new CloudZoom($(this), opts))) : $(this).is(".cloud-zoom-gallery") && (opts = $.extend({}, relOpts, options), $(this).data("relOpts", opts), $(this).bind("click", $(this), function(t) {
var o = t.data.data("relOpts");
return $("#" + o.useZoom).data("zoom").destroy(), $("#" + o.useZoom).attr("href", t.data.attr("href")), $("#" + o.useZoom).attr("rel", t.data.attr("rel")), $("#" + o.useZoom + " img").attr("title", t.data.attr("title")), $("#" + o.useZoom + " img").attr("src", t.data.data("relOpts").smallImage), $("#" + t.data.data("relOpts").useZoom).CloudZoom(), !1
}))
}), this
}, $.fn.CloudZoom.defaults = {
zoomWidth: "auto",
zoomHeight: "auto",
position: "right",
tint: !1,
tintOpacity: .5,
lensOpacity: .5,
softFocus: !1,
smoothMove: 3,
showTitle: !0,
titleOpacity: .5,
adjustX: 0,
adjustY: 0
}
}(jQuery);
CSS file used in cshtml
/*
* Copyright 2014 Seven Spikes Ltd. All rights reserved. (http://www.nop-templates.com)
* http://www.nop-templates.com/t/licensinginfo
*/
#media all and (max-width: 1000px) {
#sevenspikes-cloud-zoom:before {
display: none;
}
#sevenspikes-cloud-zoom img {
position: static;
}
}
#media all and (min-width: 1001px) {
/* theme overwritting styles */
.gallery {
font-size: 0;
}
.gallery .picture-wrapper {
/*** !!! set line-height to the appropriate height !!! ***/
line-height: 320px;
}
.gallery .picture-wrapper .picture:before {
display: none;
}
/* main picture styles */
#sevenspikes-cloud-zoom {
margin: 0;
overflow: visible;
text-align: center;
font-size: 0;
}
#sevenspikes-cloud-zoom:before {
display: none;
}
#wrap {
display: block;
max-width: 100%;
vertical-align: middle;
line-height: 0;
}
#wrap a {
position: relative;
max-width: 100%;
vertical-align: middle;
line-height: 0;
overflow: hidden;
}
#wrap a:before {
content: "";
display: block;
padding-top: 125%;
}
#wrap img {
}
/* This is the overlay element. */
#wrap > .mousetrap {
right: 0;
bottom: 0;
margin: auto;
}
.cloud-zoom-lens {
margin: 0;
border: none;
background-color: #fff;
cursor: crosshair;
}
/* This is the zoom window. */
#cloudZoomWindowElement {
left: 0;
top: 0;
z-index: 1;
}
#cloud-zoom-big {
border: none;
overflow: hidden;
bottom: 0;
margin: auto;
}
.overview #cloud-zoom-big {
position: static !important; /* fix for the zoom window so that its wrapper takes the dimensions */
}
/* This is for the title text. */
.cloud-zoom-title {
background-color: #000;
padding: 5px;
text-align: center;
font-size: 11px;
line-height: normal;
font-weight: bold;
color: #fff;
}
/* This is the loading message. */
.cloud-zoom-loading {
width: 100% !important;
height: 100% !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
font-size: 0;
background: rgba(255,255,255,.5);
opacity: 1 !important;
}
#keyframes spinner {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.cloud-zoom-loading:after,
.cloud-zoom-loading:before {
content: '';
position: absolute;
border: 2px solid #454545;
width: 30px;
height: 30px;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
outline: 1px solid transparent; /*firefox fix*/
}
.cloud-zoom-loading:after {
animation: spinner 2.5s linear infinite;
}
.cloud-zoom-loading:before {
width: 44px;
height: 44px;
animation: spinner 2.5s linear infinite;
animation-direction: reverse;
}
/* with product ribbons enabled */
.gallery .ribbon-wrapper {
/*never display as inline or inline-block*/
vertical-align: middle;
line-height: 0;
}
.gallery .ribbon-wrapper:hover .ribbon-position {
opacity: 0;
}
}
CSHTML file
#** Copyright 2016 Seven Spikes Ltd. All rights reserved. (http://www.nop-templates.com)
* http://www.nop-templates.com/t/licensinginfo
*#
#using Nop.Core
#using Nop.Core.Infrastructure
#using SevenSpikes.Nop.Plugins.CloudZoom.Models
#model CloudZoomModel
#if (!string.IsNullOrEmpty(Model.DefaultPicture.FullSizeImageUrl))
{
Html.AddCssFileParts("~/Plugins/SevenSpikes.Nop.Plugins.CloudZoom/Themes/" + Model.Theme + "/Content/cloud-zoom.1.0.2/CloudZoom.css");
var supportRtl = EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Rtl;
if (supportRtl)
{
Html.AddCssFileParts("~/Plugins/SevenSpikes.Nop.Plugins.CloudZoom/Styles/CloudZoom.common.rtl.css");
}
Html.AddScriptParts("~/Plugins/SevenSpikes.Core/Scripts/SevenSpikesExtensions.min.js");
Html.AddScriptParts("~/Plugins/SevenSpikes.Nop.Plugins.CloudZoom/Scripts/cloud-zoom.1.0.2/cloud-zoom.1.0.2.min.js");
if (Model.IsIntegratedByWidget)
{
Html.AddScriptParts("~/Plugins/SevenSpikes.Nop.Plugins.CloudZoom/Scripts/CloudZoom.min.js");
}
if (Model.EnableClickToZoom)
{
Html.AddCssFileParts("~/Content/magnific-popup/magnific-popup.css");
Html.AddScriptParts("~/Scripts/jquery.magnific-popup.min.js");
<script type="text/javascript">
$(document).ready(function () {
$(".picture").on("click", ".mousetrap", function () {
var mainPictureHref = $('.picture a.cloud-zoom').attr('href');
var cloudZoomThumbs = $('.picture-thumbs a.cloud-zoom-gallery');
var imgSources = new Array();
var imgItem = function(source) {
this.src = source;
};
cloudZoomThumbs.each(function(){
imgSources.push(new imgItem($(this).attr('href')));
});
if(imgSources.length === 0){
imgSources.push(new imgItem(mainPictureHref));
}
$.magnificPopup.open({
items: imgSources,
type: 'image',
removalDelay: 300,
gallery: {
enabled: true
}
}, cloudZoomThumbs.filter('.active').index());
});
});
</script>
}
<script type="text/javascript">
#{
string pictureAdjustmentTableName = string.Format("productAttributeValueAdjustmentTable_{0}", Model.ProductId);
string pictureAdjustmentFuncName = string.Format("adjustProductAttributeValuePicture_CloudZoom_{0}", Model.ProductId);
string pictureFullSizePrefix = "fullsize";
}
function #(pictureAdjustmentFuncName)(controlId) {
var ctrl = $('#' + controlId);
var pictureFullSizeUrl = '';
if((ctrl.is(':radio') && ctrl.is(':checked')) || (ctrl.is(':checkbox') && ctrl.is(':checked'))) {
pictureFullSizeUrl = #(pictureAdjustmentTableName)[controlId + '_#(pictureFullSizePrefix)'];
} else if(ctrl.is('select')) {
var idx = $('#' + controlId + " option").index($('#' + controlId + " option:selected"));
if(idx !== -1) {
pictureFullSizeUrl = #(pictureAdjustmentTableName)[controlId + '_#(pictureFullSizePrefix)'][idx];
}
}
if (typeof pictureFullSizeUrl == 'string' && pictureFullSizeUrl !== '') {
var zoomGallerySelector = ".cloud-zoom-gallery[href='" + pictureFullSizeUrl + "']";
$(zoomGallerySelector).click();
$.event.trigger({
type: 'nopMainProductImageChanged',
target: ctrl,
pictureDefaultSizeUrl: pictureFullSizeUrl,
pictureFullSizeUrl: pictureFullSizeUrl
});
}
}
$(document).ready(function () {
$("[id^='product_attribute_']").on('change', function() {
#(pictureAdjustmentFuncName)($(this).attr('id'));
});
});
</script>
<div class="gallery sevenspikes-cloudzoom-gallery">
<div class="picture-wrapper">
<div class="picture" id="sevenspikes-cloud-zoom" data-zoomwindowelementid="#Model.ElementId"
data-selectoroftheparentelementofthecloudzoomwindow="#Model.SettingsModel.SelectorOfTheParentElementOfTheCloudZoomWindow"
data-defaultimagecontainerselector="#Model.SettingsModel.DefaultImageContainerSelector">
<a href="#Model.DefaultPicture.FullSizeImageUrl" class="cloud-zoom" id="zoom1" rel="#Model.DefaultPicture.DefaultRelation">
<img src="#Model.DefaultPicture.SmallImageUrl" alt="#Model.DefaultPicture.AlternateText" title="#Model.DefaultPicture.Title" id="cloudZoomImage" itemprop="image" />
</a>
#if (Model.Pictures.Count > 1)
{
<div class="picture-thumbs-navigation-arrow picture-thumbs-prev-arrow">
<span>#T("SevenSpikes.Themes.Uptown.Product.ImageThumbs.Prev")</span>
<img src="#Model.DefaultPicture.TinyImageUrl" data-fullSizeImageUrl="#Model.DefaultPicture.FullSizeImageUrl" alt="Previous" />
</div>
<div class="picture-thumbs-navigation-arrow picture-thumbs-next-arrow">
<span>#T("SevenSpikes.Themes.Uptown.Product.ImageThumbs.Next")</span>
<img src="#Model.DefaultPicture.TinyImageUrl" data-fullSizeImageUrl="#Model.DefaultPicture.FullSizeImageUrl" alt="Next" />
</div>
<div class="picture-thumbs">
#foreach (var picture in Model.Pictures)
{
<a class="cloud-zoom-gallery" href="#picture.FullSizeImageUrl" title="#picture.Title" rel="#picture.GalleryRelation">
<img class="cloud-zoom-gallery-img" src="#picture.TinyImageUrl" alt="#picture.AlternateText" title="#picture.Title" />
</a>
}
</div>
}
</div>
</div>
</div>
}
Any ideas? More files needed to resolve this issue? LMK!

Find what is causing a memory leak in this Windows Gadget

SCENARIO
I'm using the windows gadget platform with this gadget:
http://win7gadgets.com/pc-system/sushis_driveinfo.html
PROBLEM
The gadget has a memory leak, If I keep running this gadget +24h. it can increase the RAM consumption up to 1 GB, while other similar gadgets does not procude this, so I discarded that this is a sidebar.exe memory management, not, is an script bug.
When more time is running the gadget, more unresponsiveness becomes the gadget (on click).
My knowledges about JavaScript are NULL, but anyways I can understand the syntax and try to understand what the developer is doing in this code, I think that the problem is when managging the image objects, but at my point of view those objets seems to be properly disposed after each operation.
QUESTION
This is the gadget source.
Someone could help me to discover and fix what is causing the memory leak in this gadget?
sushi_driveinfo.html (main window):
<html>
<head>
<title>Drive Info</title>
<style>
body { margin: 0; padding: 0; width: 156px; height: 200px; background-image: url(images\canvas.png); color: #ffffff; font-family: 'Segoe UI'; }
#targets { position: absolute; top: 0; left: 0; }
.target { position: absolute; width: 156px; height: 48; left: 0; cursor: hand; }
</style>
<script type="text/javascript">
var lst = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var timeout = null;
var drives = new Array(26);
var drvchk = new Array(26);
var drvspc = new Array(26);
var vizchg = false;
var current_y = 0;
var background,theme,remove,local,network,media,show_pc,show_net;
var item_height=48;
var icon_offset=20;
var text_offset=72;
var meter_offset=24;
function convertBytes(b)
{
var i = 0, u = Array(' MB', ' GB', ' TB');
while (b >= 1024 && (b /= 1024) >= 1) i++;
return (Math.round(b * 100) / 100) + u[i];
}
function openDrive()
{
var d = window.event.srcElement.getAttribute('drive');
System.Shell.execute(d + ':\\');
return;
}
function openNetwork()
{
System.Shell.execute("Explorer", "/N,::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}");
return;
}
function openComputer()
{
System.Shell.execute("Explorer", "/N,::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
return;
}
function recheckDrives() {
for(var i = 0; i < 26; i++)
{
if (!drives[i]) {
drives[i] = System.Shell.drive(lst.charAt(i));
if (drives[i]) { vizchg = true; drvchk[i] = true; }
} else {
if (drives[i].isReady != drvchk[i]) { drvchk[i] = !drvchk[i]; vizchg = true; }
if (drives[i].isReady && drives[i].freeSpace != drvspc[i]) { drvspc[i] = drives[i].freeSpace; vizchg = true; }
}
}
}
function calcHeight(h) {
var y=0;
if(show_pc==2) y+=h;
if(show_net==2) y+=h;
for(var i=0;i<26;i++)
if(isDriveVisible(i)) y+=h;
return y;
}
function isDriveVisible(i) {
if(drvchk[i]) {
if (drives[i].driveType == 2 && remove == 1) ;
else if (drives[i].driveType == 3 && local == 1) ;
else if (drives[i].driveType == 4 && network == 1) ;
else if (drives[i].driveType == 5 && media == 1) ;
else if (drives[i].driveType == 1 || drives[i].driveType == 6) ;
else
return true;
}
return false;
}
function paintPC() {
if (show_pc == 2) {
canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);
var di=canvas.addImageObject('images/drives/pc'+ theme +'.png', icon_offset, current_y);
di.width*=0.8;
di.height*=0.8;
canvas.addTextObject('Computer', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
var b = document.createElement('DIV');
b.className = 'target';
b.style.posTop = current_y;
b.onclick = openComputer;
targets.appendChild(b);
current_y+=item_height;
}
return;
}
function paintNET() {
if (show_net == 2) {
canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);
var di=canvas.addImageObject('images/drives/net'+ theme +'.png', icon_offset, current_y);
di.width*=0.8;
di.height*=0.8;
canvas.addTextObject('Network', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
var b = document.createElement('DIV');
b.className = 'target';
b.style.posTop = current_y;
b.onclick = openNetwork;
targets.appendChild(b);
current_y+=item_height;
}
return;
}
function paintGadget()
{
try {
recheckDrives();
if (!vizchg) return;
var total_height=calcHeight(item_height);
System.Gadget.beginTransition();
document.body.style.height=total_height;
canvas.style.height=total_height;
canvas.removeObjects();
targets.innerHtml = '';
current_y = 0;
paintPC();
paintNET();
for(i = 0; i < 26; i++)
{
if(isDriveVisible(i)) {
if (drives[i].freeSpace != 0) {
canvas.addImageObject('images/backgrounds/background' + background + '.png', 0, current_y);
var f = Math.round(drives[i].freeSpace / drives[i].totalSize * 100);
var u = (100 - f);
canvas.addTextObject(convertBytes(drives[i].freeSpace) + ' / ' + f + '%', 'Segoe UI', 10, 'white', text_offset, current_y + 17);
var m = canvas.addImageObject('images/meter' + (u < 90 ? 'blue': (u < 98 ? 'orange': 'red')) + '.png', meter_offset, current_y + 34);
m.width = Math.floor((u * 128 / 100));
m.left = 24 - Math.floor(((128 - m.width) / 2));
} else {
canvas.addImageObject('images/backgrounds/background' + background + 's.png', 0, current_y);
canvas.addTextObject(convertBytes(drives[i].totalSize), 'Segoe UI', 10, 'white', text_offset, current_y + 17);
}
var di=canvas.addImageObject('images/drives/drive' + drives[i].driveType + theme + '.png', icon_offset, current_y-5);
di.width*=0.8;
di.height*=0.8;
canvas.addTextObject(drives[i].volumeLabel + ' (' + drives[i].driveLetter + ':)', 'Segoe UI', 11, 'white', text_offset, current_y + 5);
var o = document.createElement('DIV');
o.className = 'target';
o.style.posTop = current_y;
o.setAttribute('drive', drives[i].driveLetter);
o.onclick = openDrive;
targets.appendChild(o);
current_y += item_height;
}
System.Gadget.endTransition(System.Gadget.TransitionType.morph,0.1);
window.setTimeout(fixCanvasBackground, 600);
}
} finally {
vizchg = false;
return;
}
}
function fixCanvasBackground() {
canvas.src = canvas.src;
}
function initDrives()
{
for(var i = 0; i < 26; i++) {
drives[i] = System.Shell.drive(lst.charAt(i));
if (drives[i] && drives[i].isReady)
{ drvchk[i] = true ; drvspc[i] = drives[i].freeSpace; }
else { drvchk[i] = false; }
}
return;
}
function onShowSettings() {
window.clearInterval(timeout);
System.Gadget.beginTransition();
window.setTimeout(endTransitionFast, 400);
}
function onSettingsClosed() {
readSettings();
timeout=window.setInterval(paintGadget, 2500);
vizchg=true;
paintGadget();
}
function endTransitionFast() {
System.Gadget.endTransition(System.Gadget.TransitionType.morph, 0.1);
fixCanvasBackground();
}
function readSettings() {
background=System.Gadget.Settings.read("background");
if(background==0) { background=2; System.Gadget.Settings.write("background",2); }
theme=System.Gadget.Settings.read("theme");
if(theme==0) { theme=1; System.Gadget.Settings.write("theme",1); }
show_pc=System.Gadget.Settings.read("showpc");
if(show_pc==0) { show_pc=1; System.Gadget.Settings.write("showpc",1); }
show_net=System.Gadget.Settings.read("shownet");
if(show_net==0) { show_net=1; System.Gadget.Settings.write("shownet",1); }
local=System.Gadget.Settings.read("local");
if(local==0) { local=2; System.Gadget.Settings.write("local",2); }
media=System.Gadget.Settings.read("media");
if(media==0) { media=2; System.Gadget.Settings.write("media",2); }
network=System.Gadget.Settings.read("network");
if(network==0) { network=2; System.Gadget.Settings.write("network",2); }
remove=System.Gadget.Settings.read("remove");
if(remove==0) { remove=2; System.Gadget.Settings.write("remove",2); }
}
function onLoad()
{
System.Gadget.settingsUI = "settings.html";
System.Gadget.onSettingsClosed = onSettingsClosed;
System.Gadget.onShowSettings = onShowSettings;
readSettings();
initDrives();
timeout = window.setInterval(paintGadget, 2500);
vizchg = true;
paintGadget();
return;
}
</script>
</head>
<body onload="onLoad()">
<div id="targets"></div>
<g:background id="canvas" src="images/canvas.png" style="position: absolute; top: 0; left: 0; width: 156; height: 200; z-index: -999;" opacity="0" />
</body>
</html>
settings.html (settings window):
<html>
<head>
<style>
body { width: 250px; height: 800px; padding: 0px; margin: 0px; font-family: Tahoma; }
body,p,div,span,td { font-size: 9pt; }
label { font-weight: bold; }
input,select { font: Arial; font-size: 9pt; }
table { width: 100%; }
</style>
<script>
var background, maxBackgrounds = 3, theme = 1, maxThemes = 7;
function updateBackground()
{
var x = 84, y = 47, m;
canvas.removeObjects();
canvas.addImageObject('images/backgrounds/background' + background + '.png', x, y);
m = canvas.addImageObject('images/meterblue.png', x + 24, y + 34);
m.width = (0.25 * 128);
m.left = x + 24 - ((128 - m.width) / 2);
canvas.addImageObject('images/drives/drive3' + theme + '.png', x, y);
canvas.addTextObject('Vista (C:)', 'Segoe UI', 11, 'white', x + 58, y + 5);
canvas.addTextObject('40GB / 75%', 'Segoe UI', 10, 'white', x + 58, y + 17);
//y -= 20;
//canvas.addImageObject('images/backgrounds/background' + background + '.png', x, y);
//m = canvas.addImageObject('images/meterorange.png', x + 24, y + 34);
//m.width = (0.937 * 128);
//m.left = x + 24 - ((128 - m.width) / 2);
//canvas.addImageObject('images/drives/drive3.png', x, y);
//canvas.addTextObject('Apps (D:)', 'Segoe UI', 11, 'white', x + 58, y + 5);
//canvas.addTextObject('10GB / 6.3%', 'Segoe UI', 10, 'white', x + 58, y + 17);
canvas.addImageObject('images/drives/drive3' + theme + '.png', x-85, y+130);
canvas.addImageObject('images/drives/drive2' + theme + '.png', x-85, y+172);
canvas.addImageObject('images/drives/drive4' + theme + '.png', x-85, y+215);
canvas.addImageObject('images/drives/drive5' + theme + '.png', x-85, y+258);
}
function onBackground()
{
var e = window.event, o = e.srcElement, b = o.getAttribute('base');
o.src = 'images/settings/' + b + (e.type == 'mouseover' || e.type == 'mouseup' ? 'hover': (e.type == 'mousedown' ? 'pressed': '')) + '.png';
if (e.type == 'mouseup')
{
if (b == 'next') background++; else background--;
if (background < 1) background = maxBackgrounds;
if (background > maxBackgrounds) background = 1;
updateBackground();
}
}
function onTheme()
{
var e = window.event, o = e.srcElement, b = o.getAttribute('base');
o.src = 'images/settings/' + b + (e.type == 'mouseover' || e.type == 'mouseup' ? 'hover': (e.type == 'mousedown' ? 'pressed': '')) + '.png';
if (e.type == 'mouseup')
{
if (b == 'next') theme++; else theme--;
if (theme < 1) theme = maxThemes;
if (theme > maxThemes) theme = 1;
updateBackground();
}
}
function onClose(event)
{
if (event.closeAction == event.Action.commit)
{
System.Gadget.Settings.write("background", background);
System.Gadget.Settings.write("theme", theme);
System.Gadget.Settings.write("showpc", document.boxes.mypc.checked ? 2 : 1);
System.Gadget.Settings.write("shownet", document.boxes.netw.checked ? 2 : 1);
System.Gadget.Settings.write("remove", document.boxes.remove.checked ? 2 : 1);
System.Gadget.Settings.write("local", document.boxes.local.checked ? 2 : 1);
System.Gadget.Settings.write("network", document.boxes.network.checked ? 2 : 1);
System.Gadget.Settings.write("media", document.boxes.media.checked ? 2 : 1);
}
event.cancel = false;
// System.Gadget.beginTransition();
// window.setTimeout(endtransit, 400);
}
/* function endtransit() {
System.Gadget.endTransition(System.Gadget.TransitionType.morph, 0.1);
}*/
function onLoad()
{
var box;
System.Gadget.onSettingsClosing = onClose;
background = System.Gadget.Settings.read("background");
if (background == 0) background = 2;
theme = System.Gadget.Settings.read("theme");
if (theme == 0) theme = 1;
System.Gadget.Settings.read("remove") == 2 ? document.boxes.remove.checked = true : false;
System.Gadget.Settings.read("local") == 2 ? document.boxes.local.checked = true : false;
System.Gadget.Settings.read("network") == 2 ? document.boxes.network.checked = true : false;
System.Gadget.Settings.read("media") == 2 ? document.boxes.media.checked = true : false;
System.Gadget.Settings.read("showpc") == 2 ? document.boxes.mypc.checked = true : false;
System.Gadget.Settings.read("shownet") == 2 ? document.boxes.netw.checked = true : false;
updateBackground();
}
</script>
</head>
<body onload="onLoad()">
<g:background id="canvas" src="images/settings/desktop.png" style="position: absolute; left: 1; top: 1; z-index: -999;" />
<div style="position: absolute; left: 0; top: 147px;">
<table cellspacing="0" cellpadding="0">
<tr>
<td style="width: 33%; padding-right: 10px;" align="right"><img src="images/settings/previous.png" base="previous" style="cursor: hand;" onmouseover="onBackground();" onmouseout="onBackground();" onmousedown="onBackground();" onmouseup="onBackground();" /></td>
<td style="width: 33%;" align="center"><label>Backgrounds</label></td>
<td style="width: 33%; padding-left: 10px;" align="left"><img src="images/settings/next.png" base="next" style="cursor: hand;" onmouseover="onBackground();" onmouseout="onBackground();" onmousedown="onBackground();" onmouseup="onBackground();" /></td>
</tr>
<tr>
<td style="width: 33%; padding-right: 10px;" align="right"><img src="images/settings/previous.png" base="previous" style="cursor: hand;" onmouseover="onTheme();" onmouseout="onTheme();" onmousedown="onTheme();" onmouseup="onTheme();" /></td>
<td style="width: 33%;" align="center"><label>Icon Theme</label></td>
<td style="width: 33%; padding-left: 10px;" align="left"><img src="images/settings/next.png" base="next" style="cursor: hand;" onmouseover="onTheme();" onmouseout="onTheme();" onmousedown="onTheme();" onmouseup="onTheme();" /></td>
</tr>
</table>
<table cellspacing="0" cellpadding="0" style="margin-top: 15px;margin-left:60px;">
<tr><td>
<form name="boxes">
<input type="checkbox" name="local">
<font style="font-size: 8pt;">Local Drives</font><p>
<input type="checkbox" name="remove">
<font style="font-size: 8pt;">Removable Drives</font><p>
<input type="checkbox" name="network">
<font style="font-size: 8pt;">Network Drives</font><p>
<input type="checkbox" name="media">
<font style="font-size: 8pt;">Media Drives</font><p>
<input type="checkbox" name="mypc">
<font style="font-size: 8pt;">My Computer link</font><br>
<input type="checkbox" name="netw">
<font style="font-size: 8pt;">Network Link</font>
</form>
</td></tr>
</table>
</div>
</body>
</html>
UPDATE:
Here is the full gadget source if it helps:
https://www.mediafire.com/?c8h1271714sp6tz
There is always a chance that their is a leak in one of the drivers installed on your system that causes this. However, when looking at that javascript code there is a pattern that caused issues in the past and has a resolution now.
The main loop of the Gadget looks like this:
function paintGadget() {
// repaint/rebuild all UI elelments
// remove all elements
targets.innerHtml = '';
// buildup
var o = document.createElement('DIV');
o.onclick = openDrive;
targets.appendChild(o);
}
function openDrive() {
}
window.setInterval(paintGadget, 2500);
which basically means: call paintGadget every 2.5 seconds, for ever
This should be fine if the javascript engine and its resources are garbage collected when they are no longer in any scope. And this where things might go wromg due to sloppy programming.
Based on the answer from user dsg we learn that eventlisteners are a root cause for garbage collection to fail.
To overcome this problem we have to replace the line targets.innerHtml = ''; in the function paintGadget with an implementation that removes the eventhandlers on every element before removing the element it self, like so:
while(targets.firstChild) {
var ch = targets.firstChild;
ch.onclick = null;
targets.removeChild(ch);
}
As said in the introduction, paintGadget does more in particular with the canvas where it follows a similar pattern, remove everything and recreate. If there is a leak in there a reimplementation of that is needed as well.

Categories

Resources