Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: galvao/JHRW
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1.0.0
Choose a base ref
...
head repository: galvao/JHRW
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 6 commits
  • 17 files changed
  • 1 contributor

Commits on Jan 15, 2019

  1. Merge pull request #3 from galvao/1.0.0

    Version 1.0.0
    galvao authored Jan 15, 2019

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6a30d34 View commit details

Commits on Jan 23, 2020

  1. mode changes on local wc

    galvao committed Jan 23, 2020
    Copy the full SHA
    2f5dbea View commit details
  2. Merge pull request #4 from galvao/1.0.1

    mode changes on local wc
    galvao authored Jan 23, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    40d6071 View commit details

Commits on Apr 15, 2020

  1. Copy the full SHA
    ad9d6e4 View commit details
  2. Copy the full SHA
    51f54ba View commit details
  3. Merge pull request #5 from galvao/2.0.0

    Version 2.0.0
    galvao authored Apr 15, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    7493d15 View commit details
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.swp
*.~*
*.backup
27 changes: 27 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# CHANGELOG

## [2.0.0] - 2020-04-15

### Added
- A default timeout handler.
- The postTimeout configuration, meaning a callback to be executed after the default timout handler.
- The bypassCache parameter to the constructor.
- Object's properties list at the beginning of the class.
- The @version tag in code documentation
- This CHANGELOG.

### Changed
- There must be a function named 'JHRWHandler' to handle JHRW. The changing in name (previously 'defaultHandler') was done to prevent naming conflicts.
- The example handler (see above) now resides on the testing script, as it should.
- Timeouts are now explicitly handled by JHRW and therefore can't be configured anymore. See postTimeout above.
- The request is now created by the init method.
- The example tester is now more sophisticated, allowing for better testing.
- availableHandlers is now a static property.
- The project is now licensed under MIT (no big deal, I'm just standardizing my projects under the same license).
- Overall revamped documentation (README.md).
- JHRW now has a 'base' parameter and a 'urlPath' parameter, instead of the unified - and wrong - 'destination' parameter.
- All "timed" configurations (e.g.: ''timeout') are now handled in seconds.

### Fixed
- JHRW now correctly detects and retries timed out requests.
- The configure method documentation now states that it's parameter should be an Object (not JSON, since JSON doesn\'t allow functions).
Empty file modified LICENSE
100644 → 100755
Empty file.
107 changes: 73 additions & 34 deletions README.md
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,64 +1,96 @@
<p align="center">
<img src="media/logo.png" width="200">
</p>

# JHRW - JavaScript HTTP Request Wrapper
A wrapper for so-called "AJAX" Requests

## Goals
I've made JHRW to:

* Advance my JavaScript skills;
* Improve/Simplify the usage of the XMLHttpRequest object by:
* Adding default values to what's undefined;
* Adding additional Error checking and clarification;
* Improve/Simplify the usage of the [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object by:
* Adding default values to what's undefined;
* Adding additional Error checking and clarification;
* Adding interesting, simplified, feature, such as timeouts and retries.

## Documentation
```JavaScript
Object JHRW(String destination [, Boolean lazyExecution = false]);
Object JHRW(String base, String urlPath [, Boolean lazyExecution = false [, Boolean bypassCache = false]]);
```

### Parameters
* `String destination` - The request's target
* `Boolean lazyExecution`(optional) - If the request should be initialized and sent right after instantiation
* `String base` - The request's base URL
* `String urlPath` - The request's endpoint
* `Boolean lazyExecution`(optional) - If the request should be initialized and sent right after instantiation. Default: false
* `Boolean bypassCache`(optional) - If the request URL should have a timed parameter added in order to bypass cache. Default: false

### Throws
A `new Error` if the destination parameter is `undefined`
* A `ReferenceError`
* If there's no JHRWHandler function defined.
* A `Error`
* if the base parameter is `undefined`
* if the urlPath parameter is `undefined`
* A `TypeError`
* if the base parameter is not a `String`
* if the urlPath parameter is not a `String`
* if the lazyExecution parameter is not a `Boolean`
* if the bypassCache parameter is not a `Boolean`


### Returns
An Object containing:

#### Properties
* request - The `XMLHttpRequest` Object
* config - The configuration Object
* URI - The re'uest's target
* asynchronous - If the request should be asynchronous
* verb - The HTTP verb
* data - Data to be sent along with the request
* requestHeaders - HTTP headers for the request
* responseType - Expected MIME type of the response
* handlers - Object containing the functions to handle the request
* attempts - Number of attempts to retry the request
* attemptInterval - Interval between attempts
* availableHandlers - Which handlers can be set
* `Object request` - The native [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) Object
* `Object config` - The configuration Object
* `String URI` - The request's target
* `Boolean asynchronous` - If the request should be asynchronous
* `String verb` - The HTTP verb
* `Mixed data` - Data to be sent along with the request
* `Object requestHeaders` - HTTP headers for the request
* `String responseType` - Expected MIME type of the response
* `Object handlers` - The functions to handle the request
* `Number attempts` - # of attempts to retry if the request fails
* `Number attemptInterval` - Interval between attempts, **in seconds**.
* `Number timeout` - The timeout, **in seconds**, for the request - after which it should be retried.
* `Function postTimeout`: The function to be executed if the request times out.
* `Number timer` - The [timer](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) that controls the retry process.

##### Static Properties
* `Array JHRW.availableHandlers`: The types of handlers that can be [re-]defined.
* `String JHRW.handlerList`: Convenience property to be shown in error messages.

#### Methods
##### configure
Overwrites one or more configuration options (see the config object above)
```JavaScript
Undefined configure(Object configureObject);
void configure(Object configureObject);
```
Overwrites one or more configuration options (see the config object above)

##### init
Initializes the request: Sets the expected response MIME Type; Sets the handlers as listeners; Opens the request; Sets the request's headers.
```JavaScript
Undefined init();
void init();
```
Initializes the request: Sets the expected response MIME Type; Sets the handlers as listeners; Opens the request; Sets the request's headers.

##### send
```JavaScript
void send();
```
Sends the request, including data, if available.

##### end
```JavaScript
Undefined send();
void end();
```

Ends the request. Useful if you wish for JHRW to stop retrying on success.

### Basic Usage
```JavaScript
try {
var obj = new JHRW('foo.php', true);
var obj = new JHRW('http://localhost', /foo.php', true);
} catch (Error e) {
// Do something
}
@@ -67,16 +99,23 @@ or
```JavaScript
try {
var req = new JHRW('foo.php');
var req = new JHRW('http://localhost', 'foo.php');

try {
req.init();
req.init();
} catch (ReferenceError e) {
// Do something
}
req.send();
// Do something
}

req.send();
} catch (Error e) {
// Do something
// Do something
}
```
```
For a more advanced usage example see [the testing page](src/example/requestTester.html).
## Credits
* Developed by @galvao.
* Logo font: [Neutra Text Bold](http://fontsgeek.com/fonts/Neutra-Text-Bold)
Binary file added media/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions media/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
222 changes: 170 additions & 52 deletions src/JHRW.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -3,91 +3,161 @@
*
* A wrapper around so-called "AJAX" requests.
*
* @version 2.0.0
* @author Er Galvão Abbott <galvao@galvao.eti.br>
* @link https://github.com/galvao/JHRW
* @license Apache 2.0
* @license MIT
*/

'use strict';

class JHRW
{
attempts;
config;
configList;
request;
timer;

/**
* Constructor
*
* @param string destination The request's destination
* @param string base The request's URL base
* @param string urlPath The request's path on the base URL
* @param boolean lazyExecution If the request should be immediately initialized and sent
* @param boolean bypassCache If the request should generate a timed param to bypass cache
*
* @return object An instance of the JHRW class
* @throws Error If the destination parameter is undefined
*
* @throws ReferenceError If there's no 'JHRWHandler' function defined
* @throws Error If the base parameter is undefined
* If the urlPath parameter is undefined
* @throws TypeError If the base parameter is not a string
* If the urlPath parameter is not a string
* If the lazyExecution parameter is not a boolean
* If the bypassCache parameter is not a boolean
*/
constructor(destination, lazyExecution)
constructor(base, urlPath, lazyExecution = false, bypassCache = false)
{
if (typeof destination === 'undefined') {
throw new Error('Destination is a required parameter.');
if (typeof JHRWHandler !== 'function') {
throw new ReferenceError('There is no handler for JHRW. A function named \'JHRWHandler\' must exist.');
}

if (typeof base === 'undefined') {
throw new Error('base is a required parameter.');
}

if (typeof urlPath === 'undefined') {
throw new Error('urlPath is a required parameter.');
}

if (typeof base !== 'string') {
throw new TypeError('base is a *string* parameter.');
}

if (typeof urlPath !== 'string') {
throw new TypeError('urlPath is a *string* parameter.');
}

if (typeof lazyExecution !== 'boolean') {
throw new TypeError('lazyExecution is a required *boolean* parameter.');
}

if (typeof bypassCache !== 'boolean') {
throw new TypeError('bypassCache is a required *boolean* parameter.');
}

const url = new URL(encodeURI(urlPath), base);

if (bypassCache === true) {
url.searchParams.append('requestTime', new Date().getTime());
}

lazyExecution = typeof lazyExecution !== 'boolean' ? false : lazyExecution;
/**
* Keeps the url object from changing, making it "truly" constant.
* @see https://twitter.com/galvao/status/1249576416196296704
*/

this.availableHandlers = [
'loadstart',
'progress',
'abort',
'error',
'load',
'timeout',
'loadend',
'onreadystatechange'
];
Object.freeze(url);

this.attempts = 0;
this.timer = undefined;

// Default Configuration Object

this.config = {
URI : destination,
URI : url.href,
asynchronous : true,
verb : 'get',
data : null,
requestHeaders : {},
requestType : 'text/plain',
responseType : 'text/plain',
handlers : {'load': defaultHandler, 'error': defaultHandler},
attempts : 0,
attemptInterval : 3000,
timeout : 5000
handlers : {
'load': JHRWHandler,
'error': JHRWHandler,
'timeout': this.timeoutHandler.bind(this)
},
attempts : 1,
attemptInterval : 3,
timeout : 0,
postTimeout : undefined
};

this.request = new XMLHttpRequest();
this.configList = Object.getOwnPropertyNames(this.config).join("\n");

if (this.lazy === true) {
if (lazyExecution === true) {
this.init();
this.send();
}
}

/**
* Configure - Configure the various settings for the request
* Configure - Validates the configuration object and configures the various settings for the request
*
* @param object A configuration object (JSON)
* @param object configureObject A configuration object
*
* @return void
* @throws ReferenceError If a configuration index inside the configuration object is not defined by the class or
* if a handler defined in the configuration object is not made available.
*
* @throws ReferenceError If a configuration index inside the configuration object is not defined by the class.
* If a handler defined in the configuration object is not available.
* @throws Error If trying to set a timeout for synchronous requests.
* If the set timeout is less than a second.
* If trying to set the interval between attempts to less than a second.
*/
configure(configureObject)
{
if (configureObject['timeout'] !== 0) {
if (configureObject['timeout'] < 1) {
throw new Error('Timeout must be at least a second.');
}

if (configureObject['attempts'] > 1) {
if (configureObject['attemptInterval'] < 1) {
throw new Error('Interval between attempts must be at least a second.');
}
}
}

for (let configAttr in configureObject) {
if ((configAttr in this.config) === false) {
let configList = Object.getOwnPropertyNames(this.config).join("\n");
throw new ReferenceError(configAttr + " is not a config attribute.\nAccepted config attributes:\n" + configList);
throw new ReferenceError(configAttr + " is not a config attribute.\nAccepted config attributes:\n" + this.configList);
}

if (configAttr === 'handlers') {
for (let handler in configureObject.handlers) {
if (this.availableHandlers.includes(handler) === false) {
let handlerList = this.availableHandlers.join("\n");
throw new ReferenceError('Invalid handler type: ' + handler + "\nAvailable handlers:\n" + handlerList);
if (JHRW.availableHandlers.includes(handler) === false) {
throw new ReferenceError('Invalid handler type: ' + handler + "\nAvailable handlers:\n" + JHRW.handlerList);
}

this.config.handlers[handler] = configureObject.handlers[handler];
}
} else {
if ((configAttr === 'attemptInterval' || configAttr === 'timeout') && configAttr['asynchronous'] !== false) {
this.config[configAttr] = configureObject[configAttr] * 1000;
continue;
}

this.config[configAttr] = configureObject[configAttr];
}
}
@@ -96,21 +166,29 @@ class JHRW
}

/**
* init - Initializes the Request object (overrides MimeType, adds event listeners for handlers, opens the request and sets
* the request headers, if available).
* init - Initializes the Request object
* (Creates the request, overrides MimeType, adds event listeners for handlers,
* opens the request and sets the request headers, if available).
*
* @return void
*/
init()
{
this.request.overrideMimeType(this.config.responseType);
this.request = undefined;
this.request = new XMLHttpRequest();

this.request.overrideMimeType(this.config.requestType);

for (let handler in this.config.handlers) {
this.request.addEventListener(handler, this.config.handlers[handler]);
}

this.request.open(this.config.verb, this.config.URI, this.config.asynchronous);
this.timeout = this.config.timeout;

if (this.config.asynchronous === true) {
this.request.responseType = this.config.responseType;
this.request.timeout = this.config.timeout;
}

for (let header in this.config.requestHeaders) {
this.request.setRequestHeader(header, this.config.requestHeaders[header]);
@@ -127,25 +205,65 @@ class JHRW
send()
{
this.request.send(this.config.data);

return;
}
}

/**
* An example handler
*/
/**
* The timeout handler.
* A function may be executed after this by passing a 'postTimeout' config.
*
* @return void
*/
timeoutHandler()
{
if (typeof this.config.postTimeout === 'function') {
this.config.postTimeout();
}

function defaultHandler(event)
{
if (event.type === 'error') {
alert('There was an error on the request.');
} else if (event.type === 'load') {
if (this.status === 200) {
let d = document.createElement('div');
d.textContent = this.response;
document.body.appendChild(d);
} else {
alert('Returned status: ' + this.status + '(' + this.statusText + '); Possible error.');
this.timer = window.setInterval(this.retry.bind(this), this.config.attemptInterval);

return;
}

/**
* retry - If all attempts were made, returns. Otherwise, inits and sends the next attempt.
*
* @return void
*/
retry()
{
if ((this.attempts + 1) === this.config.attempts) {
window.clearInterval(this.timer);
return;
}

this.attempts++;

this.init();
this.send();

return;
}

end()
{
this.attempts = this.config.attempts - 1;
}
}

/**
* Static properties
*/

JHRW.availableHandlers = [
'loadstart',
'progress',
'onabort',
'error',
'load',
'loadend',
'onreadystatechange'
];

JHRW.handlerList = JHRW.availableHandlers.join("\n");
131 changes: 131 additions & 0 deletions src/example/controls.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>JHRW - Request Tester</title>
<link rel="stylesheet" href="style/requestTester.css">
</head>
<body>
<main>
<form>
<div>
<label>Base: </label>
<input type="text" name="base" size="32">
</div>
<div>
<label>Path: </label>
<input type="text" name="path" size="32" value="/example/requestTester.php">
</div>
<div>
<label for="async">
<input type="checkbox" checked name="async" id="async" value="true">
Asynchronous
</label>
<label for="overrideCache">
<input type="checkbox" checked name="overrideCache" id="overrideCache" value="true">
Override Cache
</label>
</div>
<div>
<label>Verb: </label>
<select name="verb">
<option selected value="get">GET</option>
<option value="post">POST</option>
<option value="put">PUT</option>
<option value="delete">DELETE</option>
<option value="head">HEAD</option>
<option value="connect">CONNECT</option>
<option value="options">OPTIONS</option>
</select>
</div>
<div>
<label>Attempts: </label>
<input type="number" name="attempts" min="1" max="30" value="1">
<label>Interval: </label>
<input type="number" name="interval" min="1" max="10" value="3">seconds
</div>
<div>
<label>Timeout: </label>
<input type="number" name="timeout" min="1" max="10" value="3">seconds
</div>

<div>
<button type="button">Send</button>
</div>
</form>
</main>
<script type="text/javascript" src="../JHRW.js"></script>
<script type="text/javascript">
console.clear();

f = document.forms[0];
d = parent.frames['results'];

f.base.value = document.location.origin;

document.querySelector('button').onclick = function () {
this.blur();

try {
reqObject = new JHRW(f.base.value, f.path.value, false, f.overrideCache.checked);

try {
reqObject.configure({
'asynchronous' : f.async.checked,
'verb' : f.verb.value,
'requestType' : 'application/x-www-form-urlencoded',
'responseType' : 'text/plain',
'attempts' : parseInt(f.attempts.value),
'attemptInterval': parseInt(f.interval.value),
'timeout' : parseInt(f.timeout.value),
'requestHeaders' : {
'Accept': 'application/json'
},
'handlers': {
'loadstart': function () {
d.contentDocument.querySelector('main').innerHTML+= '<br>Loading Attempt #' + reqObject.attempts + ' has started.<br>';
},
'load': function () {
reqObject.end();
d.contentDocument.querySelector('main').innerHTML += this.response;
d.contentDocument.querySelector('main').innerHTML += '<br>Stopped.';
},
},
'postTimeout': function () {
d.contentDocument.querySelector('main').innerHTML += '<br>Attempt #' + reqObject.attempts + ' timed out.';
}
});

try {
reqObject.init();
reqObject.send();
} catch (re2) {
alert('Init error: ' + re2.message);
}
} catch (re1) {
alert('Configuration error: ' + re1.message);
}
} catch (e) {
alert('Error: ' + e.message);
}
};

/**
* An example handler
*/

function JHRWHandler(event)
{
if (event.type === 'error') {
alert('There was an error on the request.');
} else if (event.type === 'load') {
if (this.status === 200) {
console.log(this.response);
} else {
alert('Returned status: ' + this.status + '(' + this.statusText + '); Possible error.');
}
}
}
</script>
</body>
</html>
59 changes: 59 additions & 0 deletions src/example/eTestParams.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict';

var etBaseParams = [
undefined,
null,
true,
false,
1,
0.2,
'http://jhrw',
'http://localhost',
'http://g.e',
'http://1.2',
[],
{},
];

var etPathParams = [
undefined,
null,
true,
false,
1,
0.2,
'/example/requestTester.php',
'foo',
'e',
[],
{},
];

var etLazyParams = [
undefined,
null,
true,
false,
1,
0.2,
'foo',
'bar',
[],
{},
];

var etCacheParams = [
undefined,
null,
true,
false,
1,
0.2,
'http://jhrw',
'http://localhost',
'http://g.e',
'http://1.2',
[],
{},
];

61 changes: 61 additions & 0 deletions src/example/exceptionTester.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>JHRW - Request Tester</title>
<link rel="stylesheet" href="style/requestTester.css">
</head>
<body>
<script type="text/javascript" src="../JHRW.js"></script>
<script type="text/javascript" src="../example/eTestParams.js"></script>
<script type="text/javascript">
console.clear();

function defaultHandler()
{
}

try {
to = new JHRW('http://jhrw', '/example/requestTester.php', undefined, undefined);
} catch (error) {
console.log(error);
}
/*baseCount = etBaseParams.length;
pathCount = etPathParams.length;
lazyCount = etLazyParams.length;
cacheCount = etCacheParams.length;
for (i = 0; i < 100; i++) {
msg = 'Test #' + i + ': ';
rb = Math.floor(Math.random() * baseCount);
rp = Math.floor(Math.random() * pathCount);
rl = Math.floor(Math.random() * lazyCount);
rc = Math.floor(Math.random() * cacheCount);
pb = etBaseParams[rb];
pp = etPathParams[rp];
pl = etLazyParams[rl];
pc = etCacheParams[rc];
tb = typeof pb;
tp = typeof pp;
tl = typeof pl;
tc = typeof pc;
msg += '( ' + tb + ', ' + tp + ', ' + tl + ', ' + tc + ' )';
console.log(msg);
console.log('( ' +pb + ', ' + pp + ', ' + pl + ', ' + pc + ' )');
try {
to = new JHRW(pb, pp, pl, pc);
console.log('Test OK');
} catch (error) {
console.log(error);
}
}*/
</script>
</body>
</html>
31 changes: 31 additions & 0 deletions src/example/post.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Post Example</title>
</head>
<body>
<script type="text/javascript" src="../JHRW.js"></script>
<script type="text/javascript">
request = new JHRW('/example/post.php', false);
try {
request.configure({
"verb": "post",
"requestHeaders": {
"Content-Type": "application/json"
},
"data": JSON.stringify({
"to": "foo",
"cc": "bar",
"bcc": "baz",
"subject": "quux",
"msg": "bernard"
})
});
} catch(e) {
alert('Configuration error: ' + e.message);
}

request.init();
request.send();
</script>
2 changes: 2 additions & 0 deletions src/example/post.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?php
var_dump(file_get_contents('php://input'));
48 changes: 0 additions & 48 deletions src/example/requestTest.html

This file was deleted.

12 changes: 12 additions & 0 deletions src/example/requestTester.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>JHRW - Request Tester</title>
<link rel="stylesheet" href="style/requestTester.css">
</head>
<body>
<iframe id="controls" src="controls.html"></iframe>
<iframe id="results" src="results.html"></iframe>
</body>
</html>
5 changes: 5 additions & 0 deletions src/example/foo.php → src/example/requestTester.php
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<?php
$intervals = [0, 10];
$rand = random_int(0, 1);

sleep($intervals[$rand]);

header('Content-Type: application/json');
$a = ['foo' => 'bar'];
echo json_encode($a);
12 changes: 12 additions & 0 deletions src/example/results.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>JHRW - Request Tester</title>
<link rel="stylesheet" href="style/requestTester.css">
</head>
<body>
<main>
</main>
</body>
</html>
33 changes: 33 additions & 0 deletions src/example/style/requestTester.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
body {
margin-top: 5px;
margin-left: 5px;
font-family: sans-serif;
}

iframe {
width: 49%;
height: 620px;
}

iframe, input, select, button {
border: 1px solid #000;
border-radius: 15px;
background-color: #fff;
}

label, input, select, button {
margin-top: 10px;
margin-left: 10px;
margin-right: 5px;
font-size: 16pt;
padding: 10px;
}

input[type=number] {
text-align: right;
width: 60px;
}

button {
width: 600px;
}