src/html/ButtonBox.js
import $ from 'jquery'
import BaseObject from 'ol/Object'
import { cssClasses } from '../globals'
/**
* @typedef {object} ButtonBoxOptions
* @property {string} [className='g4u-menu']
* @property {string} [addClass] adds a classname
* @property {HTMLElement|jQuery} [content] the content of the body of the button box
* @property {boolean} [collapsible]
* @property {boolean} [collapsed]
* @property {boolean} [rtl]
* @property {boolean} [titleButton=false] displays an extra button with a title firing an 'title:click' event
* @property {string} [title] the title appearing on the button
*/
/**
* An Element to easily construct nested HTML Menus
* It contains of a button followed by a body below the button. The button can toggle the visibility of the body.
* If multiple elements with the same classname are nested it gives the last visible element a special class name.
* It can also marks one or multiple element in the tree to be active (same classname is needed, too).
* After a ButtonBox has been added all child elements the method finish should be called.
*/
export class ButtonBox extends BaseObject {
/**
* @param {ButtonBoxOptions} [options={}]
*/
constructor (options = {}) {
super()
/**
* @type {string}
* @private
*/
this.className_ = options.className || 'g4u-menu'
this.classNames_ = {
body: this.className_ + '-content',
collapseButton: this.className_ + '-collapsebutton',
titleButton: this.className_ + '-titlebutton',
title: this.className_ + '-title',
active: this.className_ + '-active',
collapsed: this.className_ + '-collapsed',
lastVisible: this.className_ + '-last-visible'
}
/**
* @type {jQuery}
* @private
*/
this.$element_ = $('<div>')
.addClass(this.className_)
if (options.hasOwnProperty('id')) {
this.$element_.attr('id', options.id)
}
if (options.addClass) {
this.$element_.addClass(options.addClass)
}
/**
* @type {boolean}
* @private
*/
this.titleButton_ = options.titleButton || false
/**
* @type {boolean}
* @private
*/
this.collapsible_ = (options.hasOwnProperty('collapsible')) ? options.collapsible : true
/**
* @type {boolean}
* @private
*/
this.collapsed_ = (options.hasOwnProperty('collapsed')) ? options.collapsed : false
/**
* @type {jQuery}
* @private
*/
this.$body_ = $('<div>')
.addClass(this.classNames_.body)
if (options.hasOwnProperty('content')) {
this.$body_.append(options.content)
}
if (options.hasOwnProperty('title')) {
/**
* @type {jQuery}
* @private
*/
this.$title_ = $('<div>')
.addClass(this.classNames_.title)
.addClass('g4u-layerselector-button-frame')
this.$element_.append(this.$title_)
if (this.collapsible_) {
const $collapseButton = $('<button>')
.addClass(this.classNames_.collapseButton)
$collapseButton.on('click', () => {
this.setCollapsed(!this.collapsed_)
$collapseButton.blur()
})
if (this.titleButton_) {
const $titleButton = $('<span>')
.addClass('button')
.addClass(this.classNames_.titleButton)
.on('click', () => {
this.dispatchEvent('title:click')
$titleButton.blur()
})
.html(options.title)
if (options.rtl) {
$titleButton.prop('dir', 'rtl')
}
this.$title_
.append($collapseButton)
.append($titleButton)
} else {
$collapseButton
.addClass(this.classNames_.titleButton)
.html(options.title)
if (options.rtl) {
$collapseButton.prop('dir', 'rtl')
}
this.$title_
.append($collapseButton)
}
} else {
this.$title_
.html(options.title)
if (options.rtl) {
this.$title_.prop('dir', 'rtl')
}
}
} else {
this.collapsible_ = false
this.collapsed_ = false
}
this.$element_.append(this.$body_)
// this.collapsed_ will be setted in the following functions
if (!this.collapsed_) {
this.setCollapsed(false)
} else {
this.setCollapsed(true)
}
}
/**
* @param {boolean} collapsed
* @param {boolean} silent
*/
setCollapsed (collapsed, silent) {
if (collapsed) {
this.$body_.addClass(cssClasses.hidden)
this.$element_.addClass(this.classNames_.collapsed)
} else {
this.$body_.removeClass(cssClasses.hidden)
this.$element_.removeClass(this.classNames_.collapsed)
}
this.collapsed_ = collapsed
this.lastVisibleClass_()
if (!silent) {
this.dispatchEvent('change:collapsed')
}
}
/**
* @returns {boolean}
*/
getCollapsed () {
return this.collapsed_
}
/**
* distribute the last visible class to the correct element
* @private
*/
lastVisibleClass_ () {
// first take the last visible class name away
if (this.takeLastVisible(this.$element_)) {
// if the last visible class name was taken, distribute it to the correct element
this.giveLastVisible(this.$element_)
}
}
/**
* Returns the body element
* @returns {jQuery}
*/
get$Body () {
return this.$body_
}
/**
* Returns the element itself
* @returns {jQuery}
*/
get$Element () {
return this.$element_
}
/**
* finds and returns the last child in the body of the given element if it is of the same classname as this ButtonBox
* @param {jQuery} $element
* @returns {jQuery}
* @private
*/
get$LastChild_ ($element) {
return $element.children('.' + this.classNames_.body).children(':last-child')
}
setCollapseButtonActive (active) {
this.$title_.children('.' + this.classNames_.collapseButton).toggleClass(this.classNames_.active, active)
}
setTitleButtonActive (active) {
this.$title_.children('.' + this.classNames_.titleButton).toggleClass(this.classNames_.active, active)
}
/**
* Gives the last element in the element the last visible class
* @param $element
*/
giveLastVisible ($element) {
if ($element.hasClass(this.classNames_.collapsed)) {
// if the element is collapsed itself is the last visible element
$element.addClass(this.classNames_.lastVisible)
} else {
if ($element.hasClass(this.className_)) {
// the element is a button box (with the same className) and not collapsed
const $lastChild = this.get$LastChild_($element)
if ($lastChild) {
// -> recursively call this method on the last element
this.giveLastVisible($lastChild)
} else {
// no last element exists
$element.addClass(this.classNames_.lastVisible)
}
} else {
// the element is no buttonbox with the same classname it is declared the last visible element
$element.addClass(this.classNames_.lastVisible)
}
}
}
/**
* Searches for the last visible class in its child elements and removes it.
* Returns true if it was removed.
* @param {jQuery} $element
* @returns {boolean}
*/
takeLastVisible ($element) {
if ($element.hasClass(this.classNames_.lastVisible)) {
$element.removeClass(this.classNames_.lastVisible)
return true
} else if ($element.hasClass(this.className_)) {
const $lastChild = this.get$LastChild_($element)
// reverse call
if ($lastChild) {
return this.takeLastVisible($lastChild)
} else {
return false
}
} else {
return false
}
}
/**
* This method should be called after all child elements have been added to the button box
*/
finish () {
this.takeLastVisible(this.$element_)
this.giveLastVisible(this.$element_)
}
}