@qooxdoo/framework
Version:
The JS Framework for Coders
545 lines (477 loc) • 15.3 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Fabian Jakobs (fjakobs)
* Martin Wittemann (martinwittemann)
************************************************************************ */
/**
* The label class brings typical text content to the widget system.
*
* It supports simple text nodes and complex HTML (rich). The default
* content mode is for text only. The mode is changeable through the property
* {@link #rich}.
*
* The label supports heightForWidth when used in HTML mode. This means
* that multi line HTML automatically computes the correct preferred height.
*
* *Example*
*
* Here is a little example of how to use the widget.
*
* <pre class='javascript'>
* // a simple text label without wrapping and markup support
* var label1 = new qx.ui.basic.Label("Simple text label");
* this.getRoot().add(label1, {left:20, top:10});
*
* // a HTML label with automatic line wrapping
* var label2 = new qx.ui.basic.Label().set({
* value: "A <b>long label</b> text with auto-wrapping. This also may contain <b style='color:red'>rich HTML</b> markup.",
* rich : true,
* width: 120
* });
* this.getRoot().add(label2, {left:20, top:50});
* </pre>
*
* The first label in this example is a basic text only label. As such no
* automatic wrapping is supported. The second label is a long label containing
* HTML markup with automatic line wrapping.
*
* *External Documentation*
*
* <a href='http://qooxdoo.org/docs/#desktop/widget/label.md' target='_blank'>
* Documentation of this widget in the qooxdoo manual.</a>
*
* NOTE: Instances of this class must be disposed of after use
*
*/
qx.Class.define("qx.ui.basic.Label", {
extend: qx.ui.core.Widget,
implement: [qx.ui.form.IStringForm],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param value {String} Text or HTML content to use
*/
construct(value) {
super();
if (value != null) {
this.setValue(value);
}
if (qx.core.Environment.get("qx.dynlocale")) {
this.__changeLocaleLabelListenerId = qx.locale.Manager.getInstance().addListener(
"changeLocale",
this._onChangeLocale,
this
);
}
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
/**
* Switches between rich HTML and text content. The text mode (<code>false</code>) supports
* advanced features like ellipsis when the available space is not
* enough. HTML mode (<code>true</code>) supports multi-line content and all the
* markup features of HTML content.
*/
rich: {
check: "Boolean",
init: false,
event: "changeRich",
apply: "_applyRich"
},
/**
* Controls whether text wrap is activated or not. But please note, that
* this property works only in combination with the property {@link #rich}.
* The {@link #wrap} has only an effect if the {@link #rich} property is
* set to <code>true</code>, otherwise {@link #wrap} has no effect.
*/
wrap: {
check: "Boolean",
init: true,
apply: "_applyWrap"
},
/**
* Controls whether line wrapping can occur in the middle of a word; this is
* typically only useful when there is a restricted amount of horizontal space
* and words would otherwise overflow beyond the width of the element. Words
* are typically considered as separated by spaces, so "abc/def/ghi" is a 11
* character word that would not be split without `breakWithWords` set to true.
*/
breakWithinWords: {
check: "Boolean",
init: false,
apply: "_applyBreakWithinWords"
},
/**
* Contains the HTML or text content. Interpretation depends on the value
* of {@link #rich}. In text mode entities and other HTML special content
* is not supported. But it is possible to use unicode escape sequences
* to insert symbols and other non ASCII characters.
*/
value: {
check: "String",
apply: "_applyValue",
event: "changeValue",
nullable: true
},
/**
* The buddy property can be used to connect the label to another widget.
* That causes two things:
* <ul>
* <li>The label will always take the same enabled state as the buddy
* widget.
* </li>
* <li>A tap on the label will focus the buddy widget.</li>
* </ul>
* This is the behavior of the for attribute of HTML:
* http://www.w3.org/TR/html401/interact/forms.html#adef-for
*/
buddy: {
check: "qx.ui.core.Widget",
apply: "_applyBuddy",
nullable: true,
init: null,
dereference: true
},
/** Control the text alignment */
textAlign: {
check: ["left", "center", "right", "justify"],
nullable: true,
themeable: true,
apply: "_applyTextAlign",
event: "changeTextAlign"
},
// overridden
appearance: {
refine: true,
init: "label"
},
// overridden
selectable: {
refine: true,
init: false
},
// overridden
allowGrowX: {
refine: true,
init: false
},
// overridden
allowGrowY: {
refine: true,
init: false
},
// overridden
allowShrinkY: {
refine: true,
init: false
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
/* eslint-disable @qooxdoo/qx/no-refs-in-members */
members: {
__font: null,
__invalidContentSize: null,
__tapListenerId: null,
__webfontListenerId: null,
/*
---------------------------------------------------------------------------
WIDGET API
---------------------------------------------------------------------------
*/
// overridden
_getContentHint() {
if (this.__invalidContentSize) {
this.__contentSize = this.__computeContentSize();
delete this.__invalidContentSize;
}
return {
width: this.__contentSize.width,
height: this.__contentSize.height
};
},
// overridden
_hasHeightForWidth() {
return this.getRich() && this.getWrap();
},
// overridden
_applySelectable(value) {
// This is needed for all browsers not having text-overflow:ellipsis
// but supporting XUL (firefox < 4)
// https://bugzilla.mozilla.org/show_bug.cgi?id=312156
if (
!qx.core.Environment.get("css.textoverflow") &&
qx.core.Environment.get("html.xul")
) {
if (value && !this.isRich()) {
if (qx.core.Environment.get("qx.debug")) {
this.warn(
"Only rich labels are selectable in browsers with Gecko engine!"
);
}
return;
}
}
super._applySelectable(value);
},
// overridden
_getContentHeightForWidth(width) {
if (!this.getRich() && !this.getWrap()) {
return null;
}
return this.__computeContentSize(width).height;
},
// overridden
_createContentElement() {
return new qx.html.Label();
},
// property apply
_applyTextAlign(value, old) {
this.getContentElement().setStyle("textAlign", value);
},
// overridden
_applyTextColor(value, old) {
if (value) {
this.getContentElement().setStyle(
"color",
qx.theme.manager.Color.getInstance().resolve(value)
);
} else {
this.getContentElement().removeStyle("color");
}
},
/*
---------------------------------------------------------------------------
LABEL ADDONS
---------------------------------------------------------------------------
*/
/**
* @type {Map} Internal fallback of label size when no font is defined
*
* @lint ignoreReferenceField(__contentSize)
*/
__contentSize: {
width: 0,
height: 0
},
// property apply
_applyFont(value, old) {
if (old && this.__font && this.__webfontListenerId) {
this.__font.removeListenerById(this.__webfontListenerId);
this.__webfontListenerId = null;
}
// Apply
var styles;
if (value) {
if (qx.lang.Type.isString(value)) {
value = qx.theme.manager.Font.getInstance().resolve(value);
}
this.__font = value;
if (
this.__font instanceof qx.bom.webfonts.WebFont &&
!this.__font.isValid()
) {
this.__webfontListenerId = this.__font.addListener(
"changeStatus",
evt => {
if (evt.getData().valid) {
this.__invalidContentSize = true;
qx.ui.core.queue.Layout.add(this);
}
}
);
}
styles = this.__font.getStyles();
} else {
this.__font = null;
styles = qx.bom.Font.getDefaultStyles();
}
// check if text color already set - if so this local value has higher priority
if (this.getTextColor() != null) {
delete styles["color"];
}
this.getContentElement().setStyles(styles);
// Invalidate text size
this.__invalidContentSize = true;
// Update layout
qx.ui.core.queue.Layout.add(this);
},
/**
* Internal utility to compute the content dimensions.
*
* @param width {Integer?null} Optional width constraint
* @return {Map} Map with <code>width</code> and <code>height</code> keys
*/
__computeContentSize(width) {
var Label = qx.bom.Label;
var font = this.getFont();
var styles = font
? this.__font.getStyles()
: qx.bom.Font.getDefaultStyles();
var content = this.getValue() || "A";
var rich = this.getRich();
if (this.__webfontListenerId) {
this.__fixEllipsis();
}
if (rich && this.getBreakWithinWords()) {
styles = qx.lang.Object.clone(styles);
styles.wordBreak = "break-all";
}
return rich
? Label.getHtmlSize(content, styles, width)
: Label.getTextSize(content, styles);
},
/**
* Firefox > 9 on OS X will draw an ellipsis on top of the label content even
* though there is enough space for the text. Re-applying the content forces
* a recalculation and fixes the problem. See qx bug #6293
*/
__fixEllipsis() {
if (!this.getContentElement()) {
return;
}
if (
qx.core.Environment.get("os.name") == "osx" &&
qx.core.Environment.get("engine.name") == "gecko" &&
parseInt(qx.core.Environment.get("engine.version"), 10) < 16 &&
parseInt(qx.core.Environment.get("engine.version"), 10) > 9
) {
var domEl = this.getContentElement().getDomElement();
if (domEl) {
/* eslint-disable-next-line no-self-assign */
domEl.innerHTML = domEl.innerHTML;
}
}
},
/*
---------------------------------------------------------------------------
PROPERTY APPLIER
---------------------------------------------------------------------------
*/
// property apply
_applyBuddy(value, old) {
if (old != null) {
this.removeRelatedBindings(old);
this.removeListenerById(this.__tapListenerId);
this.__tapListenerId = null;
}
if (value != null) {
value.bind("enabled", this, "enabled");
this.__tapListenerId = this.addListener("tap", () => {
// only focus focusable elements [BUG #3555]
if (value.isFocusable()) {
value.focus.apply(value);
}
// furthermore toggle if possible [BUG #6881]
if (
"toggleValue" in value &&
typeof value.toggleValue === "function"
) {
value.toggleValue();
}
});
}
},
// property apply
_applyRich(value) {
// Sync with content element
this.getContentElement().setRich(value);
// Mark text size cache as invalid
this.__invalidContentSize = true;
// Update layout
qx.ui.core.queue.Layout.add(this);
},
// property apply
_applyWrap(value, old) {
if (value && !this.isRich()) {
if (qx.core.Environment.get("qx.debug")) {
this.warn("Only rich labels support wrap.");
}
}
if (this.isRich()) {
// apply the white space style to the label to force it not
// to wrap if wrap is set to false [BUG #3732]
var whiteSpace = value ? "normal" : "nowrap";
this.getContentElement().setStyle("whiteSpace", whiteSpace);
}
},
// property apply
_applyBreakWithinWords(value, old) {
this.getContentElement().setStyle(
"wordBreak",
this.isRich() && value ? "break-all" : "normal"
);
},
/**
* Locale change event handler
*
* @signature function(e)
* @param e {Event} the change event
*/
_onChangeLocale: qx.core.Environment.select("qx.dynlocale", {
true(e) {
var content = this.getValue();
if (content && content.translate) {
this.setValue(content.translate());
}
},
false: null
}),
// property apply
_applyValue: qx.core.Environment.select("qx.dynlocale", {
true(value, old) {
// Sync with content element
if (value && value.translate) {
this.getContentElement().setValue(value.translate());
} else {
this.getContentElement().setValue(value);
}
// Mark text size cache as invalid
this.__invalidContentSize = true;
// Update layout
qx.ui.core.queue.Layout.add(this);
},
false(value, old) {
this.getContentElement().setValue(value);
// Mark text size cache as invalid
this.__invalidContentSize = true;
// Update layout
qx.ui.core.queue.Layout.add(this);
}
})
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct() {
if (qx.core.Environment.get("qx.dynlocale") && this.__changeLocaleLabelListenerId) {
qx.locale.Manager.getInstance().removeListenerById(
this.__changeLocaleLabelListenerId
);
}
if (this.__font && this.__webfontListenerId) {
this.__font.removeListenerById(this.__webfontListenerId);
}
this.__font = null;
}
});