Browse Source

Convert Modal module into ts (#4433)

* Covert Modal module into class

* Convert Modal into ts

* Convert rest of the Modal to ts

* Update ModalView test
pull/4487/head
Alex Ritter 4 years ago
committed by GitHub
parent
commit
0185350259
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      src/abstract/Module.ts
  2. 283
      src/modal_dialog/index.js
  3. 264
      src/modal_dialog/index.ts
  4. 5
      src/modal_dialog/model/Modal.ts
  5. 28
      src/modal_dialog/view/ModalView.ts
  6. 5
      test/specs/modal/index.js
  7. 6
      test/specs/modal/view/ModalView.js

32
src/abstract/Module.ts

@ -3,8 +3,7 @@ import { Collection, View } from '../common';
import EditorModel from '../editor/model/Editor';
import { createId, isDef, deepMerge } from '../utils/mixins';
export interface IModule<TConfig extends any = any>
extends IBaseModule<TConfig> {
export interface IModule<TConfig extends any = any> extends IBaseModule<TConfig> {
init(cfg: any): void;
destroy(): void;
postLoad(key: any): any;
@ -32,9 +31,7 @@ export interface IStorableModule extends IModule {
postLoad(key: any): any;
}
export default abstract class Module<T extends ModuleConfig = ModuleConfig>
implements IModule<T>
{
export default abstract class Module<T extends ModuleConfig = ModuleConfig> implements IModule<T> {
private _em: EditorModel;
private _config: T;
private _name: string;
@ -70,7 +67,7 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
onLoad?(): void;
init(cfg: T) {}
abstract destroy(): void;
abstract render(): HTMLElement;
abstract render(): HTMLElement | JQuery<HTMLElement> | undefined;
postLoad(key: any): void {}
get name(): string {
@ -92,7 +89,7 @@ export default abstract class Module<T extends ModuleConfig = ModuleConfig>
* Move the main DOM element of the module.
* To execute only post editor render (in postRender)
*/
__appendTo() {
__appendTo() {
const elTo = this.getConfig().appendTo;
if (elTo) {
@ -135,10 +132,7 @@ export abstract class ItemManagerModule<
return obj;
}
loadProjectData(
data: any = {},
param: { all?: TCollection; onResult?: Function; reset?: boolean } = {}
) {
loadProjectData(data: any = {}, param: { all?: TCollection; onResult?: Function; reset?: boolean } = {}) {
const { all, onResult, reset } = param;
const key = this.storageKey;
const opts: any = { action: 'load' };
@ -189,12 +183,8 @@ export abstract class ItemManagerModule<
em &&
all
.on('add', (m: any, c: any, o: any) => em.trigger(events.add, m, o))
.on('remove', (m: any, c: any, o: any) =>
em.trigger(events.remove, m, o)
)
.on('change', (p: any, c: any) =>
em.trigger(events.update, p, p.changedAttributes(), c)
)
.on('remove', (m: any, c: any, o: any) => em.trigger(events.remove, m, o))
.on('change', (p: any, c: any) => em.trigger(events.update, p, p.changedAttributes(), c))
.on('all', this.__catchAllEvent, this);
// Register collections
this.cls = [all].concat(opts.collections || []);
@ -203,7 +193,7 @@ export abstract class ItemManagerModule<
entity.on('all', (ev: any, model: any, coll: any, opts: any) => {
const options = opts || coll;
const opt = { event: ev, ...options };
[em, all].map((md) => md.trigger(event, model, opt));
[em, all].map(md => md.trigger(event, model, opt));
});
});
}
@ -263,13 +253,11 @@ export abstract class ItemManagerModule<
}
__listenUpdate(model: TCollection, event: string) {
model.on('change', (p, c) =>
this.em.trigger(event, p, p.changedAttributes(), c)
);
model.on('change', (p, c) => this.em.trigger(event, p, p.changedAttributes(), c));
}
__destroy() {
this.cls.forEach((coll) => {
this.cls.forEach(coll => {
coll.stopListening();
coll.reset();
});

283
src/modal_dialog/index.js

@ -1,283 +0,0 @@
/**
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/modal_dialog/config/config.js)
* ```js
* const editor = grapesjs.init({
* modal: {
* // options
* }
* })
* ```
*
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
*
* ```js
* const modal = editor.Modal;
* ```
*
* ## Available Events
* * `modal:open` - Modal is opened
* * `modal:close` - Modal is closed
* * `modal` - Event triggered on any change related to the modal. An object containing all the available data about the triggered event is passed as an argument to the callback.
*
* ## Methods
* * [open](#open)
* * [close](#close)
* * [isOpen](#isopen)
* * [setTitle](#settitle)
* * [getTitle](#gettitle)
* * [setContent](#setcontent)
* * [getContent](#getcontent)
* * [onceClose](#onceclose)
* * [onceOpen](#onceopen)
*
* @module Modal
*/
import { debounce, isFunction, isString } from 'underscore';
import { createText } from '../utils/dom';
import defaults from './config/config';
import ModalM from './model/Modal';
import ModalView from './view/ModalView';
export default () => {
var c = {};
var model, modal;
const triggerEvent = (enable, em) => {
em && em.trigger(`modal:${enable ? 'open' : 'close'}`);
};
return {
/**
* Name of the module
* @type {String}
* @private
*/
name: 'Modal',
getConfig() {
return c;
},
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
init(config = {}) {
c = {
...defaults,
...config,
};
const em = c.em;
this.em = em;
var ppfx = c.pStylePrefix;
if (ppfx) c.stylePrefix = ppfx + c.stylePrefix;
model = new ModalM(c);
model.on('change:open', (m, enb) => triggerEvent(enb, em));
model.on(
'change',
debounce(() => {
const data = this._evData();
const { custom } = this.getConfig();
isFunction(custom) && custom(data);
em.trigger('modal', data);
})
);
return this;
},
_evData() {
const titl = this.getTitle();
const cnt = this.getContent();
const { open, attributes } = model.attributes;
return {
open,
attributes,
title: isString(titl) ? createText(titl) : titl,
content: isString(cnt) ? createText(cnt) : cnt.get ? cnt.get(0) : cnt,
close: () => this.close(),
};
},
postRender(view) {
const el = view.model.getConfig().el || view.el;
const res = this.render();
res && res.appendTo(el);
},
/**
* Open the modal window
* @param {Object} [opts={}] Options
* @param {String|HTMLElement} [opts.title] Title to set for the modal
* @param {String|HTMLElement} [opts.content] Content to set for the modal
* @param {Object} [opts.attributes] Updates the modal wrapper with custom attributes
* @returns {this}
* @example
* modal.open({
* title: 'My title',
* content: 'My content',
* attributes: { class: 'my-class' },
* });
*/
open(opts = {}) {
const attr = opts.attributes || {};
opts.title && this.setTitle(opts.title);
opts.content && this.setContent(opts.content);
model.set('attributes', attr);
model.open();
modal && modal.updateAttr(attr);
return this;
},
/**
* Close the modal window
* @returns {this}
* @example
* modal.close();
*/
close() {
model.close();
return this;
},
/**
* Execute callback when the modal will be closed.
* The callback will be called one only time
* @param {Function} clb Callback to call
* @returns {this}
* @example
* modal.onceClose(() => {
* console.log('The modal is closed');
* });
*/
onceClose(clb) {
this.em.once('modal:close', clb);
return this;
},
/**
* Execute callback when the modal will be opened.
* The callback will be called one only time
* @param {Function} clb Callback to call
* @returns {this}
* @example
* modal.onceOpen(() => {
* console.log('The modal is opened');
* });
*/
onceOpen(clb) {
this.em.once('modal:open', clb);
return this;
},
/**
* Checks if the modal window is open
* @returns {Boolean}
* @example
* modal.isOpen(); // true | false
*/
isOpen() {
return !!model.get('open');
},
/**
* Set the title to the modal window
* @param {string | HTMLElement} title Title
* @returns {this}
* @example
* // pass a string
* modal.setTitle('Some title');
* // or an HTMLElement
* const el = document.createElement('div');
* el.innerText = 'New title';
* modal.setTitle(el);
*/
setTitle(title) {
model.set('title', title);
return this;
},
/**
* Returns the title of the modal window
* @returns {string | HTMLElement}
* @example
* modal.getTitle();
*/
getTitle() {
return model.get('title');
},
/**
* Set the content of the modal window
* @param {string | HTMLElement} content Content
* @returns {this}
* @example
* // pass a string
* modal.setContent('Some content');
* // or an HTMLElement
* const el = document.createElement('div');
* el.innerText = 'New content';
* modal.setContent(el);
*/
setContent(content) {
model.set('content', ' ');
model.set('content', content);
return this;
},
/**
* Get the content of the modal window
* @returns {string | HTMLElement}
* @example
* modal.getContent();
*/
getContent() {
return model.get('content');
},
/**
* Returns content element
* @return {HTMLElement}
* @private
*/
getContentEl() {
return modal.getContent().get(0);
},
/**
* Returns modal model
* @return {Model}
* @private
*/
getModel() {
return model;
},
/**
* Render the modal window
* @return {HTMLElement}
* @private
*/
render() {
if (this.getConfig().custom) return;
const View = ModalView.extend(c.extend);
const el = modal && modal.el;
modal = new View({
el,
model,
config: c,
});
return modal.render().$el;
},
destroy() {
modal && modal.remove();
[c, model, modal].forEach(i => (i = {}));
this.em = {};
},
};
};

264
src/modal_dialog/index.ts

@ -0,0 +1,264 @@
/**
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/modal_dialog/config/config.js)
* ```js
* const editor = grapesjs.init({
* modal: {
* // options
* }
* })
* ```
*
* Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance
*
* ```js
* const modal = editor.Modal;
* ```
*
* ## Available Events
* * `modal:open` - Modal is opened
* * `modal:close` - Modal is closed
* * `modal` - Event triggered on any change related to the modal. An object containing all the available data about the triggered event is passed as an argument to the callback.
*
* ## Methods
* * [open](#open)
* * [close](#close)
* * [isOpen](#isopen)
* * [setTitle](#settitle)
* * [getTitle](#gettitle)
* * [setContent](#setcontent)
* * [getContent](#getcontent)
* * [onceClose](#onceclose)
* * [onceOpen](#onceopen)
*
* @module Modal
*/
import { EventHandler } from 'backbone';
import { debounce, isFunction, isString } from 'underscore';
import { Module } from '../abstract';
import EditorModel from '../editor/model/Editor';
import { createText } from '../utils/dom';
import defaults from './config/config';
import ModalM from './model/Modal';
import ModalView from './view/ModalView';
export default class ModalManager extends Module<typeof defaults> {
modal?: ModalView;
/**
* Initialize module. Automatically called with a new instance of the editor
* @param {Object} config Configurations
* @private
*/
constructor(em: EditorModel) {
super(em, 'Modal', defaults);
this.model = new ModalM(this);
this.model.on('change:open', (enable: boolean) => {
em.trigger(`modal:${enable ? 'open' : 'close'}`);
});
this.model.on(
'change',
debounce(() => {
const data = this._evData();
const { custom } = this.config;
//@ts-ignore
isFunction(custom) && custom(data);
em.trigger('modal', data);
}, 0)
);
return this;
}
_evData() {
const titl = this.getTitle();
const cnt = this.getContent();
const { open, attributes } = this.model.attributes;
return {
open,
attributes,
title: isString(titl) ? createText(titl) : titl,
//@ts-ignore
content: isString(cnt) ? createText(cnt) : cnt.get ? cnt.get(0) : cnt,
close: () => this.close(),
};
}
postRender(view: ModalView) {
//@ts-ignore
const el = view.model.config.el || view.el;
const res = this.render();
res && res.appendTo(el);
}
/**
* Open the modal window
* @param {Object} [opts={}] Options
* @param {String|HTMLElement} [opts.title] Title to set for the modal
* @param {String|HTMLElement} [opts.content] Content to set for the modal
* @param {Object} [opts.attributes] Updates the modal wrapper with custom attributes
* @returns {this}
* @example
* modal.open({
* title: 'My title',
* content: 'My content',
* attributes: { class: 'my-class' },
* });
*/
open(opts: any = {}) {
const attr = opts.attributes || {};
opts.title && this.setTitle(opts.title);
opts.content && this.setContent(opts.content);
this.model.set('attributes', attr);
this.model.open();
this.modal && this.modal.updateAttr(attr);
return this;
}
/**
* Close the modal window
* @returns {this}
* @example
* modal.close();
*/
close() {
this.model.close();
return this;
}
/**
* Execute callback when the modal will be closed.
* The callback will be called one only time
* @param {Function} clb Callback to call
* @returns {this}
* @example
* modal.onceClose(() => {
* console.log('The modal is closed');
* });
*/
onceClose(clb: EventHandler) {
this.em.once('modal:close', clb);
return this;
}
/**
* Execute callback when the modal will be opened.
* The callback will be called one only time
* @param {Function} clb Callback to call
* @returns {this}
* @example
* modal.onceOpen(() => {
* console.log('The modal is opened');
* });
*/
onceOpen(clb: EventHandler) {
this.em.once('modal:open', clb);
return this;
}
/**
* Checks if the modal window is open
* @returns {Boolean}
* @example
* modal.isOpen(); // true | false
*/
isOpen() {
return !!this.model.get('open');
}
/**
* Set the title to the modal window
* @param {string | HTMLElement} title Title
* @returns {this}
* @example
* // pass a string
* modal.setTitle('Some title');
* // or an HTMLElement
* const el = document.createElement('div');
* el.innerText = 'New title';
* modal.setTitle(el);
*/
setTitle(title: string) {
this.model.set('title', title);
return this;
}
/**
* Returns the title of the modal window
* @returns {string | HTMLElement}
* @example
* modal.getTitle();
*/
getTitle() {
return this.model.get('title');
}
/**
* Set the content of the modal window
* @param {string | HTMLElement} content Content
* @returns {this}
* @example
* // pass a string
* modal.setContent('Some content');
* // or an HTMLElement
* const el = document.createElement('div');
* el.innerText = 'New content';
* modal.setContent(el);
*/
setContent(content: string | HTMLElement) {
this.model.set('content', ' ');
this.model.set('content', content);
return this;
}
/**
* Get the content of the modal window
* @returns {string | HTMLElement}
* @example
* modal.getContent();
*/
getContent(): string | HTMLElement {
return this.model.get('content');
}
/**
* Returns content element
* @return {HTMLElement}
* @private
*/
getContentEl() {
//@ts-ignore
return this.modal?.getContent().get(0);
}
/**
* Returns modal model
* @return {Model}
* @private
*/
getModel() {
return this.model;
}
/**
* Render the modal window
* @return {HTMLElement}
* @private
*/
render() {
if (this.config.custom) return;
const View = ModalView.extend(this.config.extend);
const el = this.modal && this.modal.el;
this.modal = new View({
el,
model: this.model,
config: this.config,
});
return this.modal?.render().$el;
}
destroy() {
this.modal?.remove();
}
}

5
src/modal_dialog/model/Modal.js → src/modal_dialog/model/Modal.ts

@ -1,6 +1,7 @@
import { Model } from '../../common';
import ModalManager from '..';
import { Model } from '../../abstract';
export default class Modal extends Model {
export default class Modal extends Model<ModalManager> {
defaults() {
return {
title: '',

28
src/modal_dialog/view/ModalView.js → src/modal_dialog/view/ModalView.ts

@ -1,7 +1,8 @@
import { View } from '../../common';
import { View } from '../../abstract';
import Modal from '../model/Modal';
export default class ModalView extends View {
template({ pfx, ppfx, content, title }) {
export default class ModalView extends View<Modal> {
template({ pfx, ppfx, content, title }: any) {
return `<div class="${pfx}dialog ${ppfx}one-bg ${ppfx}two-color">
<div class="${pfx}header">
<div class="${pfx}title">${title}</div>
@ -22,19 +23,19 @@ export default class ModalView extends View {
};
}
initialize(o) {
$title?: JQuery<HTMLElement>;
$content?: JQuery<HTMLElement>;
$collector?: JQuery<HTMLElement>;
constructor(o: any) {
super(o);
const model = this.model;
const config = o.config || {};
const pfx = config.stylePrefix || '';
this.config = config;
this.pfx = pfx;
this.ppfx = config.pStylePrefix || '';
this.listenTo(model, 'change:open', this.updateOpen);
this.listenTo(model, 'change:title', this.updateTitle);
this.listenTo(model, 'change:content', this.updateContent);
}
onClick(e) {
onClick(e: Event) {
const bkd = this.config.backdrop;
bkd && e.target === this.el && this.hide();
}
@ -52,7 +53,6 @@ export default class ModalView extends View {
/**
* Returns content element
* @return {HTMLElement}
* @private
*/
getContent() {
const pfx = this.pfx;
@ -69,7 +69,7 @@ export default class ModalView extends View {
* @return {HTMLElement}
* @private
*/
getTitle(opts = {}) {
getTitle(opts: any = {}) {
if (!this.$title) this.$title = this.$el.find('.' + this.pfx + 'title');
return opts.$ ? this.$title : this.$title.get(0);
}
@ -93,6 +93,7 @@ export default class ModalView extends View {
* */
updateTitle() {
const title = this.getTitle({ $: true });
//@ts-ignore
title && title.empty().append(this.model.get('title'));
}
@ -120,8 +121,9 @@ export default class ModalView extends View {
this.model.open();
}
updateAttr(attr) {
updateAttr(attr?: any) {
const { pfx, $el, el } = this;
//@ts-ignore
const currAttr = [].slice.call(el.attributes).map(i => i.name);
$el.removeAttr(currAttr.join(' '));
$el.attr({

5
test/specs/modal/index.js

@ -1,11 +1,14 @@
import Modal from 'modal_dialog';
import Editor from 'editor';
describe('Modal dialog', () => {
describe('Main', () => {
var em;
var obj;
beforeEach(() => {
obj = new Modal().init();
em = new Editor({});
obj = new Modal(em);
});
afterEach(() => {

6
test/specs/modal/view/ModalView.js

@ -1,13 +1,15 @@
import ModalView from 'modal_dialog/view/ModalView';
import Modal from 'modal_dialog/model/Modal';
import Editor from 'editor';
describe('ModalView', () => {
var model;
var view;
var editorModel;
var em;
beforeEach(() => {
model = new Modal();
em = new Editor({});
model = new Modal(em);
view = new ModalView({
model,
});

Loading…
Cancel
Save