Created
June 4, 2015 09:24
-
-
Save marek-saji/94abb49be59f2db2019d to your computer and use it in GitHub Desktop.
Fakturomir: dead–simple, single element polish invoicong
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<!-- | |
TODO | |
[ ] ładność | |
[ ] tabela | |
[ ] kolor? | |
[ ] suma kontrolna numeru konta | |
http://wipos.p.lodz.pl/zylla/ut/banki.html | |
[ ] suma kontrolna NIP | |
http://pl.wikipedia.org/wiki/NIP#Znaczenie_numeru | |
--> | |
<html> | |
<head> | |
<title>Faktura</title> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<style media=all> | |
html | |
{ | |
font-family: serif; | |
line-height: 1.5em; | |
} | |
body | |
{ | |
/* A4 measuring 210 × 297 millimetres (8.3 in × 11.7 in) */ | |
max-width: 21.0cm; | |
margin: 0 auto; | |
padding: 1em; | |
} | |
h1 | |
{ | |
float: left; | |
font-size: 6em; | |
line-height: 1em; | |
text-align: left; | |
width: 55%; | |
margin: 0; | |
padding: 0; | |
} | |
h1 + h2 | |
{ | |
font-size: 1em; | |
text-align: right; | |
vertical-align: top; | |
margin: 0 0 1em 0; | |
padding: 0; | |
} | |
h1 + h2, | |
header .dates | |
{ | |
float: right; | |
clear: none; | |
text-align: center; | |
width: 45%; | |
overflow: hidden; | |
position: relative; | |
} | |
header > dl > dt | |
{ | |
font-style: italic; | |
} | |
header > dl > dd:last-of-type | |
{ | |
margin-bottom: 2em; | |
} | |
header > dl > dt, | |
header > dl > dd | |
{ | |
display: inline-block; | |
width: 49%; | |
} | |
header > dl > dt:first-of-type, | |
header > dl > dd:first-of-type | |
{ | |
width: 51%; | |
float: left; | |
} | |
header dt::after | |
{ | |
content: ''; | |
} | |
header > dl | |
{ | |
clear: both; | |
text-align: center; | |
margin: 0; | |
padding: 0; | |
} | |
.parties > dt | |
{ | |
margin-bottom: 1em; | |
} | |
.parties > dd:nth-of-type(1)::before, | |
.parties > dd:nth-of-type(2)::before | |
{ | |
content: '→'; | |
font-size: 4em; | |
position: absolute; | |
width: 2em; | |
left: 50%; margin-left: -0.9em; | |
text-align: center; | |
color: #ccc; | |
} | |
.parties > dd:nth-of-type(2)::before | |
{ | |
content: '←'; | |
margin-top: 0.76em; | |
} | |
.parties > dt, | |
.parties > dd | |
{ | |
text-align: left; | |
box-sizing: border-box; | |
padding: 0 3em; | |
} | |
.parties > dt:first-of-type, | |
.parties > dd:first-of-type | |
{ | |
text-align: right; | |
} | |
.parties > dt > dl | |
{ | |
display: inline-block; | |
text-align: left; | |
} | |
h2, | |
h3 | |
{ | |
font-size: 1em; | |
margin: 0; | |
padding: 0; | |
} | |
dl | |
{ | |
margin: 0; | |
padding: 0; | |
} | |
dd, | |
dt | |
{ | |
display: inline; | |
margin: 0; | |
padding: 0; | |
} | |
dt::after | |
{ | |
content: ': '; | |
} | |
dt::before | |
{ | |
content: ' '; | |
display: block; | |
font-size: 0; | |
height: 0; | |
} | |
.payment | |
{ | |
clear: both; | |
margin-bottom: 2em; | |
} | |
table .col-id, | |
table .col-short-word | |
{ | |
width: 5%; | |
} | |
table .col-money | |
{ | |
width: 12.5%; | |
} | |
table .col-short-money, | |
table .col-num | |
{ | |
width: 10%; | |
} | |
.products | |
{ | |
counter-reset: products; | |
} | |
.products .product-id::before | |
{ | |
counter-increment: products; | |
content: counter(products); | |
} | |
.products | |
{ | |
width: 100%; | |
border-collapse: collapse; | |
} | |
.products th, | |
.products td | |
{ | |
border: #aaa solid thin; | |
padding: 0.25em; | |
} | |
.products td[colspan] | |
{ | |
border: none; | |
} | |
.products th | |
{ | |
background-color: #eee; | |
} | |
.products td, | |
.products tfoot th | |
{ | |
text-align: right; | |
line-height: 2em; | |
} | |
.products tbody td:nth-child(1), | |
.products tbody td:nth-child(4) | |
{ | |
text-align: center; | |
} | |
.products tbody td:nth-child(2) | |
{ | |
text-align: left; | |
} | |
.products tfoot | |
{ | |
font-weight: bold; | |
} | |
footer | |
{ | |
margin-top: 2em; | |
text-align: right; | |
} | |
footer .sum, | |
footer .sum + dd | |
{ | |
font-weight: bold; | |
} | |
footer .sum + dd | |
{ | |
font-size: 2em; | |
line-height: 1em; | |
} | |
footer .sum + dd > dl | |
{ | |
font-size: 0.5em; | |
font-weight: normal; | |
} | |
.signatures, | |
.signatures li | |
{ | |
list-style: none; | |
margin: 0; padding: 0; | |
text-align: center; | |
line-height: 1.2em; | |
} | |
.signatures > li | |
{ | |
width: 39%; | |
font-size: 0.75em; | |
display: inline-block; | |
vertical-align: top; | |
margin: 5rem 5% 0 5%; | |
padding-top: 1em; | |
border-top: #aaa dashed thin; | |
} | |
.signatures > li p | |
{ | |
margin: 0; | |
} | |
</style> | |
<style media=screen> | |
[contenteditable] | |
{ | |
text-align: center; | |
display: inline-block; | |
min-width: 4em; | |
position: relative; | |
padding: 0 .4em; | |
outline: none; | |
} | |
[contenteditable].error | |
{ | |
border: red solid thin; | |
} | |
[contenteditable][id*=date], | |
[contenteditable][id*=deadline], | |
[contenteditable][id$="-no"], | |
[contenteditable][for*=date], | |
td:not(:nth-of-type(2)) > [contenteditable], | |
h1 [contenteditable] | |
{ | |
min-width: 1em; | |
} | |
[contenteditable]::before | |
{ | |
content: ''; | |
border: #b44 solid thin; | |
border-width: 0 thin thin; | |
position: absolute; | |
left: 0.1em; right: 0.1em; bottom: 0.1em; | |
height: 0.2em; max-height: 0.5rem; | |
} | |
[placeholder]:empty:not(:focus)::after | |
{ | |
content: attr(placeholder); | |
color: #b44; | |
} | |
[for]:not([for*=","]):not([contenteditable]) | |
{ | |
cursor: pointer; | |
} | |
body | |
{ | |
margin-bottom: 4em; | |
} | |
#tools | |
{ | |
position: fixed; | |
max-width: 21.0cm; | |
bottom: 0; | |
width: 100%; | |
height: 3em; | |
background-color: #eee; | |
opacity: 0.7; | |
margin-left: -1em; | |
padding-right: 1em; | |
} | |
#tools > ul, | |
#tools > ul > li | |
{ | |
list-style: none; | |
margin: 0; | |
padding: 0; | |
} | |
#tools > ul | |
{ | |
display: flex; | |
} | |
#tools > ul > li | |
{ | |
flex: 1 1 auto; | |
text-align: center; | |
line-height: 1em; | |
padding: 1em 0; | |
} | |
</style> | |
<style media=print> | |
[contenteditable]:empty, | |
output:empty | |
{ | |
outline: #b44 solid thick; | |
text-align: center; | |
display: inline-block; | |
min-width: 2em; min-height: 1em; | |
position: relative; | |
padding: 0 .4em; | |
} | |
[contenteditable]:empty::after | |
{ | |
content: attr(placeholder); | |
color: #b44; | |
} | |
#tools | |
{ | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<header> | |
<h1> | |
<span contenteditable id=org placeholder=Firma></span> | |
</h1> | |
<h2> | |
Faktura VAT № | |
<span id=invoice-no> | |
<output class=concat-of for=invoice-date-y placeholder="YYYY"></output> | |
/ | |
<output class=concat-of for=invoice-date-m contenteditable placeholder="MM"></output> | |
/ | |
<span id=invoice-no-no contenteditable placeholder="№"></span> | |
</span> | |
</h2> | |
<dl class=dates> | |
<dt>data wystawienia</dt> | |
<dd id=invoice-date> | |
<span id=invoice-date-d contenteditable placeholder="DD"></span> | |
- | |
<span id=invoice-date-m contenteditable placeholder="MM"></span> | |
- | |
<span id=invoice-date-y contenteditable placeholder="YYYY"></span> | |
</dd> | |
<dt>data sprzedaży</dt> | |
<dd id=sell-date> | |
<span id=sell-date-d class=concat-of for=invoice-date-d contenteditable placeholder="DD"></span> | |
- | |
<span id=sell-date-m class=concat-of for=invoice-date-m contenteditable placeholder="MM"></span> | |
- | |
<span id=sell-date-y class=concat-of for=invoice-date-y contenteditable placeholder="YYYY"></span> | |
</dd> | |
</dl> | |
<dl class=parties> | |
<dt>Sprzedawca</dt> | |
<dd> | |
<h3> | |
<em class=concat-of for=org placeholder="Nazwa firmy"></em> | |
<span contenteditable id=seller-n placeholder="Imię i nazwisko"></span> | |
</h3> | |
<dl> | |
<dd> | |
<span contenteditable id=seller-street-address placeholder="Ulica"></span> | |
<br> | |
<span contenteditable id=seller-postal-code placeholder="Kod pocztowy"></span> | |
<span contenteditable id=seller-locality placeholder="Miejscowość"></span> | |
</dd> | |
<dt>NIP</dt> | |
<dd> | |
<span contenteditable id=seller-NIP placeholder="xxxxxxxxxx" type=nip></span> | |
</dd> | |
</dl> | |
</dd> | |
<dt>Nabywca</dt> | |
<dd> | |
<h3> | |
<em contenteditable id=purchaser-org placeholder="Nazwa firmy"></em> | |
<span contenteditable id=purchaser-n placeholder="Imię i nazwisko"></span> | |
</h3> | |
<dl> | |
<dd> | |
<span contenteditable id=purchaser-street-address placeholder="Ulica"></span> | |
<br> | |
<span contenteditable id=purchaser-postal-code placeholder="Kod pocztowy"></span> | |
<span contenteditable id=purchaser-locality placeholder="Miejscowość"></span> | |
</dd> | |
<dt>NIP</dt> | |
<dd> | |
<span contenteditable id=purchaser-NIP placeholder="xxxxxxxxxx" type=nip></span> | |
</dd> | |
</dl> | |
</dd> | |
</dl> | |
</header> | |
<dl class=payment> | |
<dt>Termin płatności</dt> | |
<dd id=payment-deadline> | |
<span contenteditable id=payment-deadline-d placeholder="DD"></span> | |
- | |
<span contenteditable id=payment-deadline-m placeholder="MM"></span> | |
- | |
<span contenteditable id=payment-deadline-y placeholder="YYYY"></span> | |
</dd> | |
<dt>Sposób płatności</dt> | |
<dd contenteditable id=payment-type>Przelew</dd> | |
<dt>Bank</dt> | |
<dd contenteditable id=payment-bank>mBank</dd> | |
<dt>Numer konta</dt> | |
<dd contenteditable id=payment-account placeholder="PL xx xxxx xxxx xxxx" type=iban></dd> | |
</dl> | |
<table class=products> | |
<thead> | |
<tr> | |
<th class=col-id>L.p.</th> | |
<th>Nazwa</th> | |
<th class=col-num>Ilość</th> | |
<th class=col-short-word>J.m.</th> | |
<th class=col-money>Cena netto</th> | |
<th class=col-money>Wartość netto</th> | |
<th class=col-num>Stawka VAT</th> | |
<th class=col-short-money>Kwota VAT</th> | |
<th class=col-money>Wartość brutto</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr class=product> | |
<td class=product-id></td> | |
<td><span contenteditable id=product-1-name>Usługi</span></td> | |
<td><span contenteditable class=product-count | |
id=product-1-count>1</span></td> | |
<td><span contenteditable class=product-unit | |
id=product-1-unit>szt.</span></td> | |
<td><span contenteditable class=product-unit-price | |
type=money id=product-1-unit-price></span></td> | |
<td><output class=product-of for=product-1-count,product-1-unit-price | |
id=product-1-price type=money></output></td> | |
<td><span contenteditable class=product-tax-rate | |
id=product-1-tax-rate>23</span>%</td> | |
<td><output class=product-of for=product-1-price,product-1-tax-rate,0.01 | |
id=product-1-tax type=money></output></td> | |
<td><output class=sum-of for=product-1-price,product-1-tax | |
id=product-1-value type=money></output></td> | |
</tr> | |
</tbody> | |
<tfoot> | |
<tr class> | |
<td colspan=4> </td> | |
<th>Razem</th> | |
<td><output type=money class=sum-of for=product-1-price></output></td> | |
<td>–</td> | |
<td><output type=money class=sum-of for=product-1-tax></output></td> | |
<td><output type=money class=sum-of for=product-1-value id=sum></output></td> | |
</tr> | |
</tfoot> | |
<tfoot> | |
<tr> | |
<td colspan=4> </td> | |
<th>W tym</th> | |
<td><output type=money class=sum-of for=product-1-price></output></td> | |
<td><output class=sum-of for=product-1-tax-rate>23</output>%</td> | |
<td><output type=money class=sum-of for=product-1-tax></output></td> | |
<td><output type=money class=sum-of for=product-1-value></output></td> | |
</tr> | |
</tfoot> | |
</table> | |
<footer> | |
<dl> | |
<dt>Zapłacono</dt> | |
<dd> | |
<span contenteditable id=paid type=money>0,00</span> zł | |
<dl> | |
<dt>Słownie</dt> | |
<dd> | |
<span contenteditable id=paid-verbally-zł | |
class=verbalization-of for=paid>zero</span> zł, | |
<span contenteditable id=paid-verbally-gr | |
class=fraction-verbalization-of for=paid>zero</span> gr | |
</dd> | |
</dl> | |
</dd> | |
<dt class=sum>Razem do zapłaty</dt> | |
<dd> | |
<span class=sum-of for=sum type=money placeholder=Suma></span> zł | |
<dl> | |
<dt>Słownie</dt> | |
<dd> | |
<span contenteditable id=sum-verbally-zł | |
class=verbalization-of for=sum></span> zł, | |
<span contenteditable id=sum-verbally-gr | |
class=fraction-verbalization-of for=sum></span> gr | |
</dd> | |
</dl> | |
</dd> | |
</dl> | |
<ol class=signatures> | |
<li class=seller> | |
<dl> | |
<dt>Sprzedawca</dt> | |
<dd> | |
<p> | |
<output class=concat-of for=org></output> | |
<output class=concat-of for=seller-n></output> | |
</p> | |
</dd> | |
</dl> | |
</li> | |
<li class=purchaser> | |
<dl> | |
<dt>Nabywca</dt> | |
<dd> | |
<p> | |
<output class=concat-of for=purchaser-org></output> | |
<output class=concat-of for=purchaser-n></output> | |
</p> | |
</dd> | |
</dl> | |
</li> | |
</ol> | |
</footer> | |
<nav id=tools> | |
<ul> | |
<li> | |
<a id=download download="faktura.html" href="#html"> | |
pobierz html | |
</a> | |
</li> | |
<li> | |
<a id=print href="#"> | |
drukuj | |
</a> | |
</li> | |
<li> | |
<a id=clear href="."> | |
wyczyść | |
</a> | |
</li> | |
</ul> | |
</nav> | |
<script> | |
const APP_ID = 'Fakturomir'; | |
/** | |
* Convert formatted money string to a float | |
*/ | |
function unformatMoney (value) | |
{ | |
if (value === undefined || "" == value) | |
{ | |
return ''; | |
} | |
else | |
{ | |
return parseFloat( (value+"").replace(/[,.]/g, '.').replace(/\s/g, '') ); | |
} | |
} | |
/** | |
* Convert float or string to a polish currency format | |
*/ | |
function formatMoney (value) | |
{ | |
value = unformatMoney(value); | |
if (value === undefined || isNaN(value) || "" == ""+value) | |
{ | |
return ""; | |
} | |
else | |
{ | |
return formatMoney.formatter.format(value).replace(/\s*PLN\s*/, ''); | |
} | |
} | |
formatMoney.formatter = new Intl.NumberFormat('pl', { | |
style: 'currency', | |
currency: 'PLN', | |
currencyDisplay: 'code', | |
minimumFractionDigits: 2, | |
maximumFractionDigits: 2 | |
}); | |
function verbalizeMoney (value) | |
{ | |
var idx, | |
verbally = ''; | |
value = parseInt(value); | |
if (!isNaN(value) && verbalizeMoney.MIN <= value && value <= verbalizeMoney.MAX) | |
{ | |
while (value > 9) | |
{ | |
for (idx in verbalizeMoney.WORDS) | |
{ | |
if (value >= verbalizeMoney.WORDS[idx].amount) | |
{ | |
value -= verbalizeMoney.WORDS[idx].amount; | |
verbally += verbalizeMoney.WORDS[idx].word + ' '; | |
break; | |
} | |
} | |
} | |
if (verbalizeMoney.DIGITS.hasOwnProperty(value)) | |
{ | |
verbally += verbalizeMoney.DIGITS[value]; | |
} | |
else if ('' === verbally) | |
{ | |
verbally = verbalizeMoney.ZERO; | |
} | |
} | |
return verbally; | |
} | |
verbalizeMoney.MIN = 0; | |
verbalizeMoney.MAX = 9999; | |
verbalizeMoney.ZERO = 'zero'; | |
verbalizeMoney.DIGITS = { | |
9: 'dziewięć', | |
8: 'osiem', | |
7: 'siedem', | |
6: 'sześć', | |
5: 'pięć', | |
4: 'cztery', | |
3: 'trzy', | |
2: 'dwa', | |
1: 'jeden' | |
}; | |
verbalizeMoney.WORDS =[ | |
{amount:1000, word:'tysiąc'}, | |
{amount:900, word: 'dziewięćset'}, | |
{amount:800, word: 'osiemset'}, | |
{amount:700, word: 'siedemset'}, | |
{amount:600, word: 'sześćset'}, | |
{amount:500, word: 'pięćset'}, | |
{amount:400, word: 'czterysta'}, | |
{amount:300, word: 'trzysta'}, | |
{amount:200, word: 'dwieście'}, | |
{amount:100, word: 'sto'}, | |
{amount:90, word: 'dziewięćdziesiąt'}, | |
{amount:80, word: 'osiemdziesiąt'}, | |
{amount:70, word: 'siedemdziesiąt'}, | |
{amount:60, word: 'sześćdziesiąt'}, | |
{amount:50, word: 'pięćdziesiąt'}, | |
{amount:40, word: 'czterdzieści'}, | |
{amount:30, word: 'trzydzieści'}, | |
{amount:20, word: 'dwadzieścia'}, | |
{amount:19, word: 'dziewiętnaście'}, | |
{amount:18, word: 'osiemnaście'}, | |
{amount:17, word: 'siedemnaście'}, | |
{amount:16, word: 'szesnaście'}, | |
{amount:15, word: 'piętnaście'}, | |
{amount:14, word: 'czternaście'}, | |
{amount:13, word: 'trzynaście'}, | |
{amount:12, word: 'dwanaście'}, | |
{amount:11, word: 'jedenaście'}, | |
{amount:10, word: 'dziesięć'} | |
]; | |
verbalizeMoney.WORDS = [] | |
.concat( | |
[10,9,8,7,6,5].map(function (digit) { | |
return { | |
amount: digit * 1000, | |
word: verbalizeMoney(digit) + ' tysięcy' | |
}; | |
}) | |
) | |
.concat( | |
[4,3,2].map(function (digit) { | |
return { | |
amount: digit * 1000, | |
word: verbalizeMoney(digit) + ' tysiące' | |
}; | |
}) | |
) | |
.concat(verbalizeMoney.WORDS); | |
/** | |
* Get id used for storing data | |
*/ | |
function getStorageId (id) | |
{ | |
return APP_ID + ':values:' + id; | |
} | |
/** | |
* Set element's value | |
* | |
* - Format elemnts with [type=money] | |
* - Dispatch 'input' event after setting value | |
*/ | |
function updateValue (value, element) | |
{ | |
var event; | |
if (element.getAttribute('type') === 'money') | |
{ | |
value = formatMoney(value); | |
} | |
if (element.textContent !== value) | |
{ | |
element.textContent = value; | |
event = document.createEvent("HTMLEvents"); | |
event.initEvent('input', true, true); | |
// dispatching events seems to be working only after DOM is ready | |
if (document.readyState !== 'complete') | |
{ | |
setTimeout(element.dispatchEvent.bind(element, event), 0); | |
} | |
else | |
{ | |
element.dispatchEvent(event); | |
} | |
} | |
} | |
/** | |
* Store element's value on input | |
*/ | |
function eventInput (event) | |
{ | |
var element = event.target, | |
storageId; | |
if (element.id) | |
{ | |
storageId = getStorageId(element.id); | |
if (element.textContent) | |
{ | |
localStorage.setItem(storageId, element.textContent); | |
} | |
else | |
{ | |
localStorage.removeItem(storageId); | |
} | |
} | |
} | |
/** | |
* Format currency, when bluring [type=money] | |
*/ | |
function eventMoneyBlur (event) | |
{ | |
var element = event.target; | |
updateValue( element.textContent, element ); | |
} | |
/** | |
* Bind input and blur events, restore stored values | |
*/ | |
Array.prototype.forEach.call( | |
document.querySelectorAll('[contenteditable][id], output'), | |
function (element) { | |
var storedContent = localStorage.getItem( getStorageId(element.id) ); | |
element.addEventListener('input', eventInput, false); | |
if (element.getAttribute('type') === 'money') | |
{ | |
element.addEventListener('blur', eventMoneyBlur, false); | |
} | |
if (null !== storedContent) | |
{ | |
updateValue(storedContent, element); | |
} | |
} | |
); | |
/** | |
* Update filename and page title, when invoice number changes | |
*/ | |
function eventInputInvoiceNo (event) | |
{ | |
var invoiceNo = this.textContent.replace(/\s*/g, '').replace(/[\/_]/g, '-').trim(); | |
document.title = 'Faktura ' + invoiceNo; | |
document.getElementById('download').download = 'faktura_' | |
+ invoiceNo.replace(/[^a-zA-Z0-9_-]+/g, '-') | |
+ '.html'; | |
} | |
document.getElementById('invoice-no').addEventListener( | |
'input', eventInputInvoiceNo, false | |
); | |
/** | |
* Upate payment deadline, when invoice date changes | |
*/ | |
function eventInputInvoiceDate (event) | |
{ | |
var date; | |
try | |
{ | |
date = new Date( | |
document.getElementById(this.id + '-y').textContent, | |
document.getElementById(this.id + '-m').textContent, | |
document.getElementById(this.id + '-d').textContent | |
); | |
if (isNaN(date.getDate())) | |
{ | |
throw date; | |
} | |
date.setDate( date.getDate() + 7 ); | |
updateValue( | |
( date.getDate() < 10 ? '0' : '' ) + date.getDate(), | |
document.getElementById('payment-deadline-d') | |
); | |
updateValue( | |
( date.getMonth() < 10 ? '0' : '' ) + date.getMonth(), | |
document.getElementById('payment-deadline-m') | |
); | |
updateValue( | |
date.getFullYear(), | |
document.getElementById('payment-deadline-y') | |
); | |
} | |
catch (e) | |
{} | |
} | |
document.getElementById('invoice-date').addEventListener( | |
'input', eventInputInvoiceDate, false | |
); | |
/** | |
* Check account number (IBAN) checksum | |
*/ | |
/* | |
function validateIban (iban) | |
{ | |
iban = iban.toLowerCase().replace(/[^a-z0-9]/g, ''); | |
if ('' === iban) | |
{ | |
return true; | |
} | |
else if (!iban.match(/^[a-z]{2}[0-9]{26}$/)) | |
{ | |
return false; | |
} | |
else | |
{ | |
iban = iban.split('').map(function (char) { | |
var charCode = char.charCodeAt(0); | |
if ('a'.charCodeAt(0) <= charCode && charCode <= 'z'.charCodeAt(0)) | |
{ | |
return charCode - 'a'.charCodeAt(0) + 10; | |
} | |
else | |
{ | |
return +char; | |
} | |
}); | |
iban = iban.concat( iban.splice(0,4) ); | |
return 1 === (+iban.join('')) % 97; | |
} | |
} | |
function eventBlurIban (event) | |
{ | |
var element = event.target; | |
element.classList.toggle('error', !validateIban(element.textContent)); | |
} | |
Array.prototype.forEach.call( | |
document.querySelectorAll('[type=iban]'), | |
function (element) { | |
element.addEventListener('blur', eventBlurIban, false); | |
} | |
); | |
*/ | |
/** | |
* Synchronize .concat-of, .sum-of and .product-of with their [for]. | |
*/ | |
function eventInputOperationParticipant (event) | |
{ | |
var element = event.target; | |
Array.prototype.forEach.call( | |
document.querySelectorAll('[class$="-of"][for]'), | |
function (dep) { | |
var parts = dep.getAttribute('for').split(','), | |
values, newValue; | |
if (-1 !== parts.indexOf(element.id)) | |
{ | |
values = parts.map(function (part) { | |
var element = document.getElementById(part); | |
if (!element) | |
{ | |
return part; | |
} | |
else if (element.getAttribute('type') === 'money') | |
{ | |
return unformatMoney(element.textContent); | |
} | |
else | |
{ | |
return element.textContent; | |
} | |
}); | |
if (dep.classList.contains('concat-of')) | |
{ | |
newValue = values.reduce( | |
function (prevValue, value) { | |
return prevValue + value; | |
}, | |
'' | |
); | |
} | |
else if (dep.classList.contains('verbalization-of')) | |
{ | |
newValue = values.map(verbalizeMoney).join(', '); | |
} | |
else if (dep.classList.contains('fraction-verbalization-of')) | |
{ | |
newValue = values.map(function (value) { | |
return (value.toString().split('.')[1] + '00').substr(0,2); | |
}).map(verbalizeMoney).join(', '); | |
} | |
else | |
{ | |
if (dep.classList.contains('sum-of')) | |
{ | |
newValue = values.reduce( | |
function (prevValue, value) { | |
return prevValue + parseFloat(value); | |
}, | |
0 | |
); | |
} | |
else if (dep.classList.contains('product-of')) | |
{ | |
newValue = values.reduce( | |
function (prevValue, value) { | |
return prevValue * value; | |
}, | |
1 | |
); | |
} | |
if (isNaN(newValue) || !isFinite(newValue)) | |
{ | |
value = ''; | |
} | |
} | |
updateValue(newValue, dep); | |
} | |
} | |
); | |
} | |
Array.prototype.forEach.call( | |
document.querySelectorAll('[contenteditable][id], output'), | |
function (element) { | |
element.addEventListener('input', eventInputOperationParticipant, false); | |
} | |
); | |
/** | |
* When clicking a [for], focus what it's for | |
* | |
* Browsers support this only for form elements and we use it | |
* with *[contenteditable]. | |
*/ | |
function eventFocusFor (event) | |
{ | |
var targetFor = document.getElementById( event.target.getAttribute('for') ); | |
if (targetFor) | |
{ | |
targetFor.focus(); | |
} | |
} | |
Array.prototype.forEach.call( | |
document.querySelectorAll('[for]:not([contenteditable])'), | |
function (element) { | |
element.addEventListener('click', eventFocusFor, false); | |
} | |
); | |
/** | |
* Download HTML with scripts etc stripped out and data filled in. | |
*/ | |
function eventDownload (event) | |
{ | |
var doc = document.implementation.createHTMLDocument(); | |
doc.documentElement.innerHTML = document.documentElement.innerHTML; | |
Array.prototype.forEach.call( | |
doc.querySelectorAll('[contenteditable]'), | |
function (element) { | |
element.removeAttribute('contenteditable'); | |
if ('' === element.textContent.trim()) | |
{ | |
element = document.getElementById( element.id ); | |
element.scrollIntoViewIfNeeded(); | |
element.focus(); | |
event.preventDefault(); | |
} | |
} | |
); | |
Array.prototype.forEach.call( | |
doc.querySelectorAll('[for], [placeholder]'), | |
function (element) { | |
element.removeAttribute('for'); | |
element.removeAttribute('placeholder'); | |
} | |
); | |
Array.prototype.forEach.call( | |
doc.querySelectorAll('script, #tools, style[media=screen]'), | |
function (element) { | |
console.log(element); | |
element.remove(); | |
} | |
); | |
event.target.href = 'data:text/html,' | |
+ encodeURIComponent(doc.documentElement.outerHTML); | |
} | |
document.getElementById('download').addEventListener( | |
'click', eventDownload, false | |
); | |
/** | |
* Clear ctored data | |
*/ | |
function eventClean (event) | |
{ | |
localStorage.clear(); | |
event.preventDefault(); | |
window.location.reload(); | |
} | |
document.getElementById('clear').addEventListener( | |
'click', eventClean, false | |
); | |
/** | |
*/ | |
document.getElementById('print').addEventListener( | |
'click', window.print.bind(null), false | |
); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment