Vanilla JS Modal with good iFrame support - javascript

Hi Im trying to trying to track down a good modern Vanilla Javascript modal/lytebox with iFrame support, essentially I have I number of links like below:
<a class="edit-html" href="/iframe.html?para=123"></a>
That I want to trigger the modal with iframe content, without having to embed anything other than JS/CSS in the page (i.e. no modal markup)
HighslideJS (http://highslide.com/examples/iframe.html) meets the main requirements(although it doesn't have a modern look and isn't open source) does anyone know of any alternatives?
I've had a look at this link http://planetozh.com/projects/lightbox-clones/ although the list looks quite old and only HighSlideJS meets my requirements on that list
So my main requirements are:
Vanilla JS (No dependencies)
Iframe Content determined by href tag
Actively Maintained, ideally on Github
Modal markup does not need to be manually embedded on page

Interesting to try to see how we could accomplish your iframe manipulation in a way that would degrade gracefully without script. The anchor tag attributes can do most of the heavy lifting.
Link
Link
<iframe name="iframe1" src="about:blank""></iframe>
Personally I think the best lightweight approach to dialogs is to use something sparse like the code below. They aren't often required to do much and therefore don't really require much in the way of being "maintained".
Fiddle here.
var Dialog = function(content, config){
/*
content: selector, element, or text to wrap into dialog body
config object parameters:
modal: boolean,
dialogClass: text,
createCallBack, renderCallBack, showCallBack, hideCallBack, toggleCallBack: functions
*/
var self = this;
this.config = config || {};
this.init = function(){
//check for an element passed as content or a selector corresponding to an element
self.content = content.tagName ? content : document.querySelector(content);
if( ! self.content){
//otherwise content is text to be appended to the dialog body
self.content = document.createElement("div");
self.content.innerText = content;
}
self.container = self.create();
self.body.appendChild(self.content);
if(document.body){
self.render();
}else{
document.body.addEventListener("load", self.render);
}
window.addEventListener("resize", function(){
self.size();
})
return self;
}
this.create=function create(){
self.container = document.createElement("div");
self.dialog = document.createElement("div");
self.head = document.createElement("h2");
self.closeButton = document.createElement("button");
self.body = document.createElement("div");
self.head.innerText = self.config.headerText || "";
self.dialog.appendChild(self.head);
self.dialog.appendChild(self.closeButton);
self.container.appendChild(self.dialog);
self.dialog.appendChild(self.body);
self.body.appendChild(self.content);
self.container.className = "dialog-container" + (self.config.modal ? " modal" : "");
self.dialog.className = "dialog " + self.config.dialogClass || "";
self.head.className = "dialog-head";
self.body.className = "dialog-body";
self.closeButton.className = "dialog-close";
self.closeButton.innerText = self.config.closeButtonText || "close";
self.closeButton.title = self.config.closeButtonText || "close"; self.closeButton.addEventListener("click", self.hide);
self.closeButton.setAttribute("type","button");
self.checkCallBack();
return self.container;
}
this.render = function render(){
document.body.appendChild(self.container);
self.checkCallBack(arguments);
return self.dialog;
}
this.show = function(){
setTimeout(function(){
self.container.classList.add("visible");
self.closeButton.focus();
self.checkCallBack(arguments);
return self.container;
},0);
}
this.hide = function hide(){
var iframe = self.dialog.querySelector("iframe");
if(iframe){
iframe.setAttribute("src","about:blank");
}
self.container.classList.remove("visible");
self.checkCallBack(arguments);
return self.container;
}
this.toggle = function(){
if(self.dialog.classList.contains("visible")){
self.hide();
}else{
self.show();
}
self.checkCallBack(arguments);
return self.container;
}
this.size = function(){
var padding = 80;
self.body.style.maxHeight = window.innerHeight - self.head.offsetHeight - padding + "px";
console.log(self.body.style.maxHeight);
return self.body.style.maxHeight;
}
this.checkCallBack = function(args){
var action = arguments.callee.caller.name,
callBackName = action + "CallBack",
args = Array.prototype.slice.call(args || []),
fn = self.config[callBackName];
if(fn){
args.unshift(action);
fn.apply(self, args);
}
return !!fn;
}
this.init();
}
//sample usage
var.modal = new Dialog("iframe", {modal: true});

Related

Force CKEditor image2 plugin to set width and heigth inside the syle

I'm using CKEditor 4.4, and I tried to use ACF to force the image2 plugin to set width and height as CSS properties (in the style attribute), instead to use the corresponding <img> tag attributes.
In other terms, what I get now using the editor.getData() method is something like that:
<img src="text.jpg" width="100" height="100" />
but I want this other form:
<img src="text.jpg" style="width:100px;height:100px" />
I tried to reach this result using allowedContent and disallowedContent in the config.js file. This is what I tried (see this for references):
//Allow everything
config.allowedContent = {
$1: {
// Use the ability to specify elements as an object.
elements: CKEDITOR.dtd,
attributes: true,
styles: true,
classes: true
}
};
config.disallowedContent = "img[width,height]";
With this, the result is simply that width and height are no more set (neither as attributes nor in the style), the image cannot be resized and the Image Properties dialog no longer include the input boxes related to image size.
I also tried to reverse the solution propesed by Marco Cortellino in this StackOverflow answer, without positive results.
Can someone help me?
I solved the problem by override the downcast and upcast methods of image2 plugin (as Reinmar has suggested).
This method processes the image element before it is processed when the editor.getData() method is called.
Therefore, the following code represents a possible solution:
CKEDITOR.on("instanceCreated", function (ev) {
ev.editor.on("widgetDefinition", function (evt) {
var widgetData = evt.data;
if (widgetData.name != "image" || widgetData.dialog != "image2") return;
//Override of upcast
if (!widgetData.stdUpcast) {
widgetData.stdUpcast = widgetData.upcast;
widgetData.upcast = function (el, data) {
var el = widgetData.stdUpcast(el, data);
if (!el) return el;
var attrsHolder = el.name == 'a' ? el.getFirst() : el;
var attrs = attrsHolder.attributes;
if (el && el.name == "img") {
if (el.styles) {
attrs.width = (el.styles.width + "").replace('px', '');
attrs.height = (el.styles.height + "").replace('px', '');
delete el.styles.width;
delete el.styles.height;
attrs.style = CKEDITOR.tools.writeCssText(el.styles);
}
}
return el;
}
}
//Override of downcast
if (!widgetData.stdDowncast) {
widgetData.stdDowncast = widgetData.downcast;
widgetData.downcast = function (el) {
el = this.stdDowncast(el);
var attrsHolder = el.name == 'a' ? el.getFirst() : el;
var attrs = attrsHolder.attributes;
var realWidth, realHeight;
var widgets = ev.editor.widgets.instances;
for (widget in widgets) {
if (widgets[widget].name != "image" || widgets[widget].dialog != "image2") {
continue;
}
realWidth = $(widgets[widget].element.$).width();
realHeight = $(widgets[widget].element.$).height();
}
var style = CKEDITOR.tools.parseCssText(attrs.style)
if (attrs.width) {
style.width = realWidth + "px";
delete attrs.width;
}
if (attrs.height) {
style.height = realHeight + "px";
delete attrs.height;
}
attrs.style = CKEDITOR.tools.writeCssText(style);
return el;
}
}
});
});

Dynamic img creation through a link without attaching multiple instances

I have a link that is attached with an "onclick" function. When pressed it attaches an img element into a separate div called "mediaBox". The problem I'm having is that if it's pressed multiple times then it attaches more instances of the img. How can I control this. I'm still new to JavaScript and I prefer to receive this answer in pure Javascript not jQuery, as I will cross that bridge after I have a full understanding of Javascript.
var rkf = document.getElementById("submenulinks").getElementsByTagName("li")[0];
rkf.onclick = function(){
var client = document.getElementById('client');
var description2 = document.getElementById('description2');
var role = document.getElementById('role');
var mediaBox = document.getElementById('mediaBox');
var thumb = document.getElementById("thumb");
var client2 = document.getElementById("client2");
var newImage = document.createElement("img");
client2.innerHTML = "Role - Applications";
client.innerHTML = "RKF Real Estate";
client2.innerHTML = "Role - Applications";
description2.innerHTML = "Quarterly Catalog of Exclusive Listings managed by RKF";
role.innerHTML = "Custom designed Cover and listings content. Tables were also utilized within Indesign. <br><br><b><i> Photoshop and Indesign</i></b>";
newImage.setAttribute("src", "../images/rkf_cover.jpg");
newImage.setAttribute("height", "500px");
newImage.setAttribute("width", "387px");
newImage.setAttribute("alt", "rkf");
newImage.setAttribute("href", "#");
mediaBox.style.backgroundImage = "none";
document.getElementById("mediaBox").appendChild(newImage);
newImage.style.display = "block";
newImage.style.marginLeft = "auto";
newImage.style.marginRight = "auto";
newImage.style.marginTop = "25px";
}
rkf.onclick = function(){
var client = document.getElementById('client');
...
...
...
// Remove the handler after it ran once.
this.onclick = null; // <<<<<========================
}
Since you do want to use jQuery in the future, it's equal to:
$('#submenulinks li:first').one('click', handler);

Javascript Create Clickable Text

EDIT 2 - I decided to create a simple example with jsfiddle.
http://jsfiddle.net/VqA9g/61/
As you can see, I am trying to reference the new div.
EDIT - d/t negative votes and unclear question
I have a linked-list like so:
var struct_list = function () {
this.id = 0;
this.name = 0;
this._head = null;
};
struct_list.prototype = {
// .. adding code , delete code ...
list_contents: function () {
var current = this._head;
while ( current != null ) {
var div = document.createElement("div");
div.style.width = "100px";
div.style.height = "100px";
div.style.background = "white";
div.style.color = "black";
div.style.top = "0px";
div.style.left = "0px";
div.style.margin = "400px 1000px auto";
div.style.cursor = "pointer";
div.innerHTML = current.name;
div.onclick = function ( v ) { var d = document.getElementById('div'); alert(d)};
document.body.appendChild(div);
current = current.next;
}
return null;
},};
I want to be able to display this linked list, and each item displayed be able to interact with an "onclick".
Example:
struct_list.add ( 0 , "Zero" );
struct_list.add ( 1 , "One" );
struct_list.list_contents();
_________________________________________________________________________
| |
| <clickable> "Zero" that does a function(id) that passes over its ID(0) |
|________________________________________________________________________|
| |
| <clickable> "One" <same as above> |
|________________________________________________________________________|
Sorry if I was unclear. Will reedit if still unclear. My apologies.
I have a linked-list struct that I hold data in (it changes data frequently) and I have a setInterval to refresh it. My question is how can I list the struct's contents while still being able to click the exposed content, I have it set up right now that each content in the linked-list contains an id. Also , how can I make sure that overflow is automatic for the y axis? I am guessing I have to place it into a div that has that enabled.
But my real question is how to expose the linked-lists elements while also being able to interact with them via an onclick.
I also do not want to use anything other than pure javascript.
Example (in my mind) would maybe be something like:
<div id="PopUp">
<script>
setInterval(function() {
if ( struct_qued_list ) {
struct_qued_list = false;
main.struct_list.list_contents(); // the linked list
}
}, 100);
</script>
</div>
list_contents: function () {
var current = this._head;
while ( current != null ) {
var div = document.createElement("div");
div.style.width = "100px";
div.style.height = "100px";
div.style.background = "white";
div.style.color = "black";
div.style.top = "0px";
div.style.left = "0px";
div.style.margin = "400px 1000px auto";
div.style.cursor = "pointer";
div.innerHTML = current.name;
div.onclick = function ( v ) { var d = document.getElementById('div'); alert(d)};
document.body.appendChild(div);
current = current.next;
}
return null;
},
Any help or logical way to do this would be appreciated.
This is mainly a scope problem, in your Edit 2 fiddle, the alert gives undefined because your i got the value 2 in order to leave the loop.
Here is a possible solution : Live demo (jsfiddle)
!function(){
var index = i; // Make it independant of i
div.onclick = function () { alert(list[index]); };
}();
You could also use attributes to store any value, and using this in the function to retrieve it.
Or export the whole process to another function to obtain something like this :
for ( var i = 0; i < 2 ; i++ ) {
doSomething(i);
}
When you add new content to the DOM JavaScript sometimes has a hard time picking that up. You may need to use a DOM mutation event (like DOMNodeInserted) to add the event listeners to your text nodes.
document.addEventListener('DOMNodeInserted', function(){
document.getElementById('thing').addEventListener('click', function(){
alert('yey!');
});
});
you may need to name your functions so you can remove them as well, if nodes are going to be inserted without removing all the old ones. Yes this is pure javascript.
EDIT: for your overflow issue, you could assign a class to each node as you insert it and style the class via CSS
.classname {
overflow: auto;
}

Smilie system: Event to add image in textarea fails

I'm trying to create a personalized smilies system to train my JavaScript.
To accomplish that I have an offline system to save the various user smilies (I only save the urls).
After obtaining the data I try to make them appear above the textarea I want. So far so good!
Now, the problem comes when it's time to add the events to the images.
I try to add event listeners to each image but no matter which image I press only the last image event is triggered.
This is: all images appear side by side correctly but what is inserted in the textarea is the last image that is iterated in the cycle.
Meaningful code:
/* Insert the code in the right place in the textarea*/
function putInTxtarea(text, textarea) {
// Mozilla text range replace.
if (typeof(textarea.selectionStart) != "undefined") {
var begin = textarea.value.substr(0, textarea.selectionStart);
var end = textarea.value.substr(textarea.selectionEnd);
var scrollPos = textarea.scrollTop;
textarea.value = begin + text + end;
if (textarea.setSelectionRange)
{
textarea.focus();
textarea.setSelectionRange(begin.length + text.length, begin.length + text.length);
}
textarea.scrollTop = scrollPos;
}
// Just put it on the end.
else {
textarea.value += text;
textarea.focus(textarea.value.length - 1);
}
var elem = document.createElement("div");
elem.id = "mySmilies";
elem.innerHTML = "";
for each (url in smiliesUrl){
var img = document.createElement("img");
img.src = url;
img.style.cursor = "pointer";
img.addEventListener('click',
function(){putInTxtarea('[img]'+url+'[/img]', document.getElementsByName('message')[0]);
};, false); // here is the event attaching
elem.appendChild(img);
}
Don't use for each...in. This is a construct only available in Firefox (at least it is non-standard).
You are making the typical creating-a-function-in-a-loop mistake. JavaScript has only function scope. Every function you create in the loop has references to the same variables (url in your case) and this variable will have the value of the last URL after the loop finished. You need to introduce a new scope:
function createClickHandler(url) {
var target = document.getElementsByName('message')[0];
return function() {
putInTxtarea('[img]'+url+'[/img]', target);
}
}
// assuming smiliesUrl is an array
for(var i = smiliesUrl.length;i--;) {
var url = smiliesUrl[i];
var img = document.createElement("img");
img.src = url;
img.style.cursor = "pointer";
img.addEventListener('click', createClickHandler(url), false);
elem.appendChild(img);
}
Another possible is to simply access the image from the event handler. It should available via this:
img.addEventListener('click', function(){
putInTxtarea('[img]'+this.src+'[/img]',
document.getElementsByName('message')[0]);
};, false);
This seams to be a closure issue. the variable url points to the url used in the loop, and at the time of execution it is always be the last url.
for(url in smiliesUrl){
var img = document.createElement("img");
img.src = url;
img.style.cursor = "pointer";
var func = function(jUrl){
return function(){
putInTxtarea('[img]' + jUrl + '[/img]',
document.getElementsByName('message')[0]);
};
}(url);
img.addEventListener('click', func, false);
}
For more on closures in javascript see This question

Confirm replacement in JavaScript

Yes, I've searched high and low on Stack Overflow and seen some great solutions to this problem that's been solved time and time again with things like SimpleModal, jQuery.confirm and the like.
Problem is, I am developing for this low level device that doesn't allow for a JS framework to be utilized AND I am having to shoehorn this modal confirm into existing JS.
There is an existing script that I am at liberty to edit (but not rewrite) that does a few things like validate, concatenate a few inputs into a single variable, and more.
The script was written to:
Take some session variables and assign new variable names to them and format accordingly
Present a confirm to the user to see whether they want to use those variables to pre-populate the form on the page
Get some functions ready to validate inputs.
other stuff, like offer an abandonment scenario, among other things
Now, all was good when the "confirm" was in place as the script would pause until an OK or Cancel was provided. I am now presenting a modal on the page that I want to mock this behavior and the only way I can think of doing it is to remove that reliance on the line that goes through the confirm thing and NOT run the script until the user interacts with the modal.
Does anyone have an idea how to take what's in place and "wrap" it in a "listening" if/else scenario for each of the YES or NO possibilities?
Sorry if this is jumbled... my brain is all blended up at the moment, too.
As far as I know there is - so far - no way to halt scripts like the Browser specific alert() or confirm() Dialog does.
Frameworks like dojo for example try to mock this behaviour by putting a transparent DIV over the whole window to prevent clicks or other input while the Dialog is showing.
This is quite tricky as I have experienced, since Keyboard-Input may be able to activate Input Fields or Buttons behind this curtain. Keyboard Shortcuts or Field-Tabbing for example.
One sollution is to disable active Elements manually, which works quite well with me in most cases.
One or more function is passed to this "mock" Dialog to execute when an option was chosen.
Escpecially with ajax background activity the responsibilty to stop conflicting function calls while the Dialog is open lies with the developer.
Here is an example I came up with:
<html>
<head>
<title>Modal Dialog example</title>
<script type="text/javascript">
<!--
var ModalDialog = function(text,choices){
this._text = text;
this._choices = choices;
this._panel = null;
this._modalDialog = null;
this._disableElements = function(tag){
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++){
elements[i].disabled = true;
}
};
this._enableElements = function(tag){
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++){
elements[i].disabled = false;
}
};
this._disableBackground = function(){
if(this._panel){
this._panel.style.display = 'block';
}
else{
// lower the curtain
this._panel = document.createElement('div');
this._panel.style.position = 'fixed';
this._panel.style.top = 0;
this._panel.style.left = 0;
this._panel.style.backgroundColor = 'gray';
this._panel.style.opacity = '0.2';
this._panel.style.zIndex = 99; // make sure the curtain is in front existing Elements
this._panel.style.width = '100%';
this._panel.style.height = '100%';
document.body.appendChild(this._panel);
// Disable active Elements behind the curtain
this._disableElements('INPUT');
this._disableElements('BUTTON');
this._disableElements('SELECT');
this._disableElements('TEXTAREA');
}
};
this.close = function(){
// Hide Curtain
this._panel.style.display = 'none';
// Hide Dialog for later reuse - could also be removed completely
this._modalDialog.style.display = 'none';
// reactivate disabled Elements
this._enableElements('INPUT');
this._enableElements('BUTTON');
this._enableElements('SELECT');
this._enableElements('TEXTAREA');
};
this.open = function(){
var _this = this;
this._disableBackground();
if(this._modalDialog){
this._modalDialog.style.display = 'block';
}
else{
// create the Dialog
this._modalDialog = document.createElement('div');
this._modalDialog.style.position = 'absolute';
this._modalDialog.style.backgroundColor = 'white';
this._modalDialog.style.border = '1px solid black';
this._modalDialog.style.padding = '10px';
this._modalDialog.style.top = '40%';
this._modalDialog.style.left = '30%';
this._modalDialog.style.zIndex = 100; // make sure the Dialog is in front of the curtain
var dialogText = document.createElement('div');
dialogText.appendChild(document.createTextNode(this._text));
// add Choice Buttons to the Dialog
var dialogChoices = document.createElement('div');
for(i = 0; i < this._choices.length; i++){
var choiceButton = document.createElement('button');
choiceButton.innerHTML = this._choices[i].label;
var choiceAction = _this._choices[i].action
var clickAction = function(){
_this.close();
if(choiceAction)choiceAction();
};
choiceButton.onclick = clickAction;
dialogChoices.appendChild(choiceButton);
}
this._modalDialog.appendChild(dialogText);
this._modalDialog.appendChild(dialogChoices);
document.body.appendChild(this._modalDialog);
}
};
};
var myConfirm = function(text,okAction){
var dialog = new ModalDialog(text,[
{
label:'ok',
action : function(){
console.log('ok')
okAction();
}
},
{
label:'cancel'
}
]);
dialog.open();
};
-->
</script>
</head>
<body>
<form name="identity" action="saveIdentity.do">
<label>Firstname</label><input name="name" type="text"><br>
<label>Lastname</label><input name="name" type="text"><br>
<input type="button"
value="submit"
onclick="if(myConfirm('Do you really want to Commit?',function(){ document.forms['identity'].submit();}));">
</form>
</body>
</html>
In this code there is still an error concerning the availability of the stored choice-function (undefined) at execution time. The function variable is no longer available in the closure. If anyone has a sollution for this you are welcome to add to it.
Hope that comes near to what you need to know.
Updated version: fixed choiceAction undefined, added IE compatibility. Internet Explorer is one main reason to use this, since confirm() is now blocked by default.
<!doctype html>
<html><head>
<title>Modal Dialog example</title>
<script type="text/javascript"><!-- //http://stackoverflow.com/questions/4739740/yet-another-confirm-replacement-quesiton
var ModalDialog = function(text,choices) {
this._text = text;
this._choices = choices;
this._panel = null;
this._modalDialog = null;
this._disableElements = function(tag) {
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++) {
elements[i].disabled = true;
}
};
this._enableElements = function(tag) {
var elements = document.getElementsByTagName(tag);
for(i=0; i < elements.length; i++) {
elements[i].disabled = false;
}
};
this._disableBackground = function() {
if(this._panel) {
this._panel.style.display = 'block';
}
else {
// lower the curtain
this._panel = document.createElement('div');
this._panel.style.position = 'fixed';
this._panel.style.top = 0;
this._panel.style.left = 0;
this._panel.style.backgroundColor = '#000';
this._panel.style.opacity = '0.3';
this._panel.style.filter = 'alpha(opacity=30)'; //ie7+
this._panel.style.zIndex = 99; // make sure the curtain is in front existing Elements
this._panel.style.width = '100%';
this._panel.style.height = '100%';
document.body.appendChild(this._panel);
// Disable active Elements behind the curtain
this._disableElements('INPUT');
this._disableElements('BUTTON');
this._disableElements('SELECT');
this._disableElements('TEXTAREA');
}
};
this.close = function() {
// Hide Curtain
this._panel.style.display = 'none';
// Hide Dialog for later reuse - could also be removed completely
this._modalDialog.style.display = 'none';
// reactivate disabled Elements
this._enableElements('INPUT');
this._enableElements('BUTTON');
this._enableElements('SELECT');
this._enableElements('TEXTAREA');
};
this.open = function() {
var _this = this;
this._disableBackground();
if(this._modalDialog) {
this._modalDialog.style.display = 'block';
}
else {
// create the Dialog
this._modalDialog = document.createElement('div');
this._modalDialog.style.position = 'absolute';
this._modalDialog.style.backgroundColor = 'white';
this._modalDialog.style.border = '1px solid black';
this._modalDialog.style.padding = '16px';
this._modalDialog.style.top = '35%';
this._modalDialog.style.left = '30%';
this._modalDialog.style.zIndex = 100; // make sure the Dialog is in front of the curtain
var dialogText = document.createElement('div');
dialogText.style.padding = '0 10px 10px 0';
dialogText.style.fontFamily = 'Arial,sans-serif';
dialogText.appendChild(document.createTextNode(this._text));
// add Choice Buttons to the Dialog
var dialogChoices = document.createElement('div');
for(i = 0; i < this._choices.length; i++) {
var choiceButton = document.createElement('button');
choiceButton.style.marginRight = '8px';
choiceButton.name = i;
choiceButton.innerHTML = this._choices[i].label;
var clickAction = function() {
_this.close();
if(_this._choices[this.name].action) _this._choices[this.name].action();
};
choiceButton.onclick = clickAction;
dialogChoices.appendChild(choiceButton);
}
this._modalDialog.appendChild(dialogText);
this._modalDialog.appendChild(dialogChoices);
document.body.appendChild(this._modalDialog);
}
};
};
var myConfirm = function(text,okAction){
var dialog = new ModalDialog(text,[
{
label : 'OK',
action : function() {
console.log('ok');
okAction();
}
},
{
label : 'Cancel'
}
]);
dialog.open();
};
-->
</script>
</head>
<body>
<form name="identity" action="saveIdentity.do">
<label>Firstname</label><input name="name" type="text"><br>
<label>Lastname</label><input name="name" type="text"><br>
<input type="button" value="submit"
onclick="if(myConfirm('Do you really want to Commit?',function(){ alert('submitted') }));">
<!-- document.forms['identity'].submit(); -->
</form>
</body>
</html>

Categories

Resources