2019-02-17 16:08:10 +01:00
|
|
|
enum ElementType {
|
|
|
|
HEADER,
|
|
|
|
BODY,
|
|
|
|
FOOTER
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2018-08-11 11:28:25 +02:00
|
|
|
type BodyCreator = (() => JQuery | JQuery[] | string) | string | JQuery | JQuery[];
|
2018-02-27 17:20:49 +01:00
|
|
|
const ModalFunctions = {
|
|
|
|
divify: function (val: JQuery) {
|
|
|
|
if(val.length > 1) return $.spawn("div").append(val);
|
|
|
|
return val;
|
|
|
|
},
|
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
jqueriefy: function(val: BodyCreator, type?: ElementType) : JQuery {
|
2018-02-27 17:20:49 +01:00
|
|
|
if($.isFunction(val)) val = val();
|
2018-08-11 11:28:25 +02:00
|
|
|
if($.isArray(val)) {
|
|
|
|
let result = $.spawn("div");
|
|
|
|
for(let element of val)
|
2019-02-17 16:08:10 +01:00
|
|
|
this.jqueriefy(element, type).appendTo(result);
|
2018-08-11 11:28:25 +02:00
|
|
|
return result;
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
switch (typeof val){
|
2019-02-17 16:08:10 +01:00
|
|
|
case "string":
|
|
|
|
if(type == ElementType.HEADER)
|
|
|
|
return $.spawn("h5").addClass("modal-title").text(val);
|
|
|
|
return $("<div>" + val + "</div>");
|
2018-02-27 17:20:49 +01:00
|
|
|
case "object": return val as JQuery;
|
2018-03-07 19:06:52 +01:00
|
|
|
case "undefined":
|
2019-02-17 16:08:10 +01:00
|
|
|
return undefined;
|
2018-02-27 17:20:49 +01:00
|
|
|
default:
|
2018-12-05 20:46:33 +01:00
|
|
|
console.error(("Invalid type %o"), typeof val);
|
2018-02-27 17:20:49 +01:00
|
|
|
return $();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
warpProperties(data: ModalProperties | any) : ModalProperties {
|
|
|
|
if(data instanceof ModalProperties) return data;
|
|
|
|
else {
|
2019-02-17 16:08:10 +01:00
|
|
|
const props = new ModalProperties();
|
|
|
|
for(const key of Object.keys(data))
|
2018-02-27 17:20:49 +01:00
|
|
|
props[key] = data[key];
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class ModalProperties {
|
2019-02-17 16:08:10 +01:00
|
|
|
template?: string;
|
2018-02-27 17:20:49 +01:00
|
|
|
header: BodyCreator = () => "HEADER";
|
|
|
|
body: BodyCreator = () => "BODY";
|
|
|
|
footer: BodyCreator = () => "FOOTER";
|
|
|
|
|
2018-03-08 15:40:31 +01:00
|
|
|
closeListener: (() => void) | (() => void)[] = () => {};
|
|
|
|
registerCloseListener(listener: () => void) : this {
|
|
|
|
if(this.closeListener) {
|
|
|
|
if($.isArray(this.closeListener))
|
|
|
|
this.closeListener.push(listener);
|
|
|
|
else
|
|
|
|
this.closeListener = [this.closeListener, listener];
|
|
|
|
} else this.closeListener = listener;
|
|
|
|
return this;
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
width: number | string = "60%";
|
2018-10-14 13:27:48 +02:00
|
|
|
height: number | string = "auto";
|
2018-02-27 17:20:49 +01:00
|
|
|
|
|
|
|
closeable: boolean = true;
|
2018-03-08 15:40:31 +01:00
|
|
|
|
|
|
|
triggerClose(){
|
|
|
|
if($.isArray(this.closeListener))
|
|
|
|
for(let listener of this.closeListener)
|
|
|
|
listener();
|
|
|
|
else
|
|
|
|
this.closeListener();
|
|
|
|
}
|
2019-02-17 16:08:10 +01:00
|
|
|
|
|
|
|
template_properties?: any = {};
|
|
|
|
trigger_tab: boolean = true;
|
|
|
|
full_size?: boolean = false;
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
|
2018-11-26 20:52:56 +01:00
|
|
|
class Modal {
|
2019-02-17 16:08:10 +01:00
|
|
|
|
2018-04-16 20:38:35 +02:00
|
|
|
private _htmlTag: JQuery;
|
2018-02-27 17:20:49 +01:00
|
|
|
properties: ModalProperties;
|
2018-10-07 18:21:28 +02:00
|
|
|
shown: boolean;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2018-10-20 19:58:06 +02:00
|
|
|
close_listener: (() => any)[] = [];
|
|
|
|
|
2018-02-27 17:20:49 +01:00
|
|
|
constructor(props: ModalProperties) {
|
|
|
|
this.properties = props;
|
2018-10-07 18:21:28 +02:00
|
|
|
this.shown = false;
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
|
2018-04-16 20:38:35 +02:00
|
|
|
get htmlTag() : JQuery {
|
|
|
|
if(!this._htmlTag) this._create();
|
|
|
|
return this._htmlTag;
|
|
|
|
}
|
|
|
|
|
2018-02-27 17:20:49 +01:00
|
|
|
private _create() {
|
2019-02-17 16:08:10 +01:00
|
|
|
const header = ModalFunctions.jqueriefy(this.properties.header, ElementType.HEADER);
|
|
|
|
const body = ModalFunctions.jqueriefy(this.properties.body, ElementType.BODY);
|
|
|
|
const footer = ModalFunctions.jqueriefy(this.properties.footer, ElementType.FOOTER);
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
//FIXME: cache template
|
|
|
|
const template = $(this.properties.template || "#tmpl_modal");
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
const properties = {
|
|
|
|
modal_header: header,
|
|
|
|
modal_body: body,
|
|
|
|
modal_footer: footer,
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
closeable: this.properties.closeable,
|
|
|
|
full_size: this.properties.full_size
|
|
|
|
};
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
if(this.properties.template_properties)
|
|
|
|
Object.assign(properties, this.properties.template_properties);
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
const tag = template.renderTag(properties);
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
this._htmlTag = tag;
|
|
|
|
this._htmlTag.on('hide.bs.modal', event => !this.properties.closeable || this.close());
|
|
|
|
this._htmlTag.on('hidden.bs.modal', event => this._htmlTag.detach());
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
open() {
|
2018-10-07 18:21:28 +02:00
|
|
|
this.shown = true;
|
2018-02-27 17:20:49 +01:00
|
|
|
this.htmlTag.appendTo($("body"));
|
2019-02-17 16:08:10 +01:00
|
|
|
|
|
|
|
this.htmlTag.bootstrapMaterialDesign().modal(this.properties.closeable ? 'show' : {
|
|
|
|
backdrop: 'static',
|
|
|
|
keyboard: false,
|
|
|
|
});
|
|
|
|
|
|
|
|
if(this.properties.trigger_tab)
|
|
|
|
this.htmlTag.one('shown.bs.modal', () => this.htmlTag.find(".tab").trigger('tab.resize'));
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
close() {
|
2018-10-20 19:58:06 +02:00
|
|
|
if(!this.shown) return;
|
|
|
|
|
2018-10-07 18:21:28 +02:00
|
|
|
this.shown = false;
|
2019-02-17 16:08:10 +01:00
|
|
|
this.htmlTag.modal('hide');
|
2018-03-08 15:40:31 +01:00
|
|
|
this.properties.triggerClose();
|
2018-10-20 19:58:06 +02:00
|
|
|
for(const listener of this.close_listener)
|
|
|
|
listener();
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function createModal(data: ModalProperties | any) : Modal {
|
|
|
|
return new Modal(ModalFunctions.warpProperties(data));
|
|
|
|
}
|
|
|
|
|
|
|
|
class InputModalProperties extends ModalProperties {
|
2019-02-17 16:08:10 +01:00
|
|
|
maxLength?: number;
|
|
|
|
|
|
|
|
field_title?: string;
|
|
|
|
field_label?: string;
|
|
|
|
field_placeholder?: string;
|
|
|
|
|
|
|
|
error_message?: string;
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function createInputModal(headMessage: BodyCreator, question: BodyCreator, validator: (input: string) => boolean, callback: (flag: boolean | string) => void, props: InputModalProperties | any = {}) : Modal {
|
|
|
|
props = ModalFunctions.warpProperties(props);
|
2019-02-17 16:08:10 +01:00
|
|
|
props.template_properties || (props.template_properties = {});
|
|
|
|
props.template_properties.field_title = props.field_title;
|
|
|
|
props.template_properties.field_label = props.field_label;
|
|
|
|
props.template_properties.field_placeholder = props.field_placeholder;
|
|
|
|
props.template_properties.error_message = props.error_message;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
props.template = "#tmpl_modal_input";
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
props.header = headMessage;
|
|
|
|
props.template_properties.question = ModalFunctions.jqueriefy(question);
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
const modal = createModal(props);
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
const input = modal.htmlTag.find(".container-value input");
|
|
|
|
const button_cancel = modal.htmlTag.find(".button-cancel");
|
|
|
|
const button_submit = modal.htmlTag.find(".button-submit");
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
let submited = false;
|
|
|
|
input.on('keyup change', event => {
|
|
|
|
const str = input.val() as string;
|
|
|
|
const valid = str !== undefined && validator(str);
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
input.attr("pattern", valid ? null : "^[a]{1000}$").toggleClass("is-invalid", !valid);
|
|
|
|
button_submit.prop("disabled", !valid);
|
2018-02-27 17:20:49 +01:00
|
|
|
});
|
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
button_submit.on('click', event => {
|
|
|
|
if(!submited) {
|
|
|
|
submited = true;
|
|
|
|
const str = input.val() as string;
|
|
|
|
if(str !== undefined && validator(str))
|
|
|
|
callback(str);
|
|
|
|
else
|
|
|
|
callback(false);
|
2018-02-27 17:20:49 +01:00
|
|
|
}
|
|
|
|
modal.close();
|
2019-02-17 16:08:10 +01:00
|
|
|
}).prop("disabled", !validator("")); /* disabled if empty input isn't allowed */
|
|
|
|
|
|
|
|
button_cancel.on('click', event => {
|
|
|
|
if(!submited) {
|
|
|
|
submited = true;
|
|
|
|
callback(false);
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
modal.close();
|
|
|
|
});
|
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
modal.close_listener.push(() => button_cancel.trigger('click'));
|
2018-02-27 17:20:49 +01:00
|
|
|
return modal;
|
|
|
|
}
|
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
function createErrorModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
|
2018-02-27 17:20:49 +01:00
|
|
|
props = ModalFunctions.warpProperties(props);
|
2019-02-17 16:08:10 +01:00
|
|
|
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-error";
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
props.header = header;
|
|
|
|
props.body = message;
|
|
|
|
return createModal(props);
|
|
|
|
}
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
function createInfoModal(header: BodyCreator, message: BodyCreator, props: ModalProperties | any = { footer: undefined }) {
|
|
|
|
props = ModalFunctions.warpProperties(props);
|
|
|
|
(props.template_properties || (props.template_properties = {})).header_class = "modal-header-info";
|
|
|
|
|
|
|
|
props.header = header;
|
|
|
|
props.body = message;
|
2018-02-27 17:20:49 +01:00
|
|
|
|
2018-09-25 12:57:47 +02:00
|
|
|
return createModal(props);
|
|
|
|
}
|
|
|
|
|
2019-02-17 16:08:10 +01:00
|
|
|
/* extend jquery */
|
|
|
|
|
|
|
|
interface ModalElements {
|
|
|
|
header?: BodyCreator;
|
|
|
|
body?: BodyCreator;
|
|
|
|
footer?: BodyCreator;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface JQuery<TElement = HTMLElement> {
|
|
|
|
modalize(entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal;
|
|
|
|
}
|
|
|
|
|
|
|
|
$.fn.modalize = function (this: JQuery, entry_callback?: (header: JQuery, body: JQuery, footer: JQuery) => ModalElements | void, properties?: ModalProperties | any) : Modal {
|
|
|
|
properties = properties || {} as ModalProperties;
|
|
|
|
entry_callback = entry_callback || ((a,b,c) => undefined);
|
|
|
|
|
|
|
|
let tag_modal = this[0].tagName.toLowerCase() == "modal" ? this : undefined; /* TODO may throw exception? */
|
|
|
|
|
|
|
|
let tag_head = tag_modal ? tag_modal.find("modal-header") : ModalFunctions.jqueriefy(properties.header);
|
|
|
|
let tag_body = tag_modal ? tag_modal.find("modal-body") : this;
|
|
|
|
let tag_footer = tag_modal ? tag_modal.find("modal-footer") : ModalFunctions.jqueriefy(properties.footer);
|
|
|
|
|
|
|
|
const result = entry_callback(tag_head, tag_body, tag_footer) || {};
|
|
|
|
properties.header = result.header || tag_head;
|
|
|
|
properties.body = result.body || tag_body;
|
|
|
|
properties.footer = result.footer || tag_footer;
|
|
|
|
return createModal(properties);
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2018-09-25 12:57:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
|