Примерно так:
// Destroyable.js - поведение удаляемых объектов
$jin.property( 'Destroyable__destroyed', function Destroyable__destroyed( state ){
if( !arguments.length ) return false
if( state && !this.destroyed() ) this.proceedDestroy()
return Boolean( state )
})
$jin.method( function Destroyable__proceedDestroy( ){ } )
// Спокойно примешиваем ко всем классам, потому что поведение это нужно всем, а тупые разработчики фреймворка этого не предусмотрели
$jin.mixin( 'Destroyable', '$jin_klass' )
// Renderable.js - поведение объектов, которые умеют рендерить себя в хтмл-элемент
$jin.property( 'Renderable__element', null )
$jin.method( function Renderale__rendered( state ){
if( !arguments.length ) return Boolean( this.element() )
state = Boolean( state )
var rendered = this.rendered()
if( state === rendered ) return this
if( rendered ) this.proceedDeRender()
else this.proceedRender()
return this
})
// Observable.js - поведение объектов, на события которых можно подписываться
$jin.property( 'Observable__mapping', Object )
$jin.method( function Observable__listen( eventName, handler ){
var mapping = this.mapping()
var handlerList = mapping[ eventName ] = mapping[ eventName ] || []
if( !~handlerList.indexOf( handler ) ) handlerList.push( handler )
return { destroy: function( ){
handlerList.splice( handlerList.indexOf( handler ), 1 )
} }
} )
$jin.method( function Observable__scream( event ){
var handlers = this.mapping()[ event.name ] || []
handlers.forEach( function( handler ){
handler( event )
})
return this
})
// Widget.js - класс с базовым поведением виджета
$jin.klass( 'Observable', 'Renderable', 'Widget' )
$jin.property( 'Widget__elementClass', String )
$jin.method( function Widget__visible( state ){
if( !arguments.length ){
return this.element().style.display !== 'none'
}
this.element().style.display = state ? '' : 'none'
} )
$jin.method( function Widget__proceedRender( ){
var element = document.createElement( 'div' )
element.className = this.elementClass()
document.body.appendChild( element )
this
.element( element )
.scream({ name: 'rendered' })
return this
})
$jin.method( function Widget__proceedDeRender( ){
var element = this.element()
element.parentNode.removeChild( element )
this
.element( null )
.scream({ name: 'derendered' })
return this
})
$jin.method( 'Destroyable__proceedDestroy', function Widget__proceedDestroy( ){
return this.rendered( false )
})
// ModalWindow.js - конкретный полностью работоспособный виджет
$jin_klass( 'Widget', 'ModalWindow' )
$jin.property( 'Widget__elementClass', 'ModalWindow__elementClass',
function( value ){
return String( value || 'popup' )
} )
$jin.property( 'ModalWindow__title', String )
$jin.property( 'ModalWindow__titleClass', function( value ){
return String( value || 'popup-title' )
} )
$jin.property( 'ModalWindow__content', String )
$jin.property( 'ModalWindow__contentClass', function( value ){
return String( value || 'popup-content' )
} )
$jin.method( function ModalWindow__proceedRender( ){
this.Widget__proceedRender()
var element = this.element()
var title = document.createElement( 'div' )
title.className = this.titleClass()
title.innerText = this.text()
element.appendChild( title )
var content = document.createElement( 'div' )
content.className = this.contentClass()
content.innerText = this.content()
element.appendChild( content )
return this
})
ModalWindow()
.elementClass( 'introduction-popup' )
.title( 'Intruduction' )
.content( '<iframe src="//youtu.be/xAHJIyfUSwU"></iframe>' )
.rendered( true )