-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added HTML support for Button labels #1163
Changes from 10 commits
584a81f
45c04fa
48a6b63
aa44a3a
44e2abe
1b4758e
379bc02
f1c0683
c6210fc
53291c7
672d56f
5dfafaa
3e3bfdd
2aad2f3
91518bb
b582cf5
e2c46c5
af22f98
652fd97
08ae388
12d93ac
77340c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,7 @@ Y.extend(Button, Y.Widget, { | |
* @type {String} | ||
* @default null | ||
*/ | ||
CONTENT_TEMPLATE : null, | ||
CONTENT_TEMPLATE : null, | ||
|
||
/** | ||
* @method initializer | ||
|
@@ -58,6 +58,10 @@ Y.extend(Button, Y.Widget, { | |
if (config.disabled) { | ||
this.set('disabled', config.disabled); | ||
} | ||
|
||
if (config.label) { | ||
this.set('label', config.label); | ||
} | ||
}, | ||
|
||
/** | ||
|
@@ -68,7 +72,7 @@ Y.extend(Button, Y.Widget, { | |
*/ | ||
bindUI: function() { | ||
var button = this; | ||
button.after('labelChange', button._afterLabelChange); | ||
button.after('labelHTMLChange', button._afterLabelHTMLChange); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this mean this change will break backwards compatibility? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
FWIW, an intent of this pull request is to not break backwards compat in Button. I'm only introducing new features, as well as fixes to meet expected behavior. |
||
button.after('disabledChange', button._afterDisabledChange); | ||
}, | ||
|
||
|
@@ -78,28 +82,24 @@ Y.extend(Button, Y.Widget, { | |
*/ | ||
syncUI: function() { | ||
var button = this; | ||
Y.ButtonCore.prototype._uiSetLabel.call(button, button.get('label')); | ||
Y.ButtonCore.prototype._uiSetDisabled.call(button, button.get('disabled')); | ||
Y.ButtonCore.prototype._setLabelHTML.call(button, button.get('labelHTML')); | ||
Y.ButtonCore.prototype._setDisabled.call(button, button.get('disabled')); | ||
}, | ||
|
||
/** | ||
* @method _afterLabelChange | ||
* @method _afterLabelHTMLChange | ||
* @private | ||
*/ | ||
_afterLabelChange: function(e) { | ||
Y.ButtonCore.prototype._uiSetLabel.call(this, e.newVal); | ||
_afterLabelHTMLChange: function(e) { | ||
Y.ButtonCore.prototype._setLabelHTML.call(this, e.newVal); | ||
}, | ||
|
||
/** | ||
* @method _afterDisabledChange | ||
* @private | ||
*/ | ||
_afterDisabledChange: function(e) { | ||
// Unable to use `this._uiSetDisabled` because that points | ||
// to `Y.Widget.prototype._uiSetDisabled`. | ||
// This works for now. | ||
// @TODO Investigate most appropriate solution. | ||
Y.ButtonCore.prototype._uiSetDisabled.call(this, e.newVal); | ||
Y.ButtonCore.prototype._setDisabled.call(this, e.newVal); | ||
} | ||
|
||
}, { | ||
|
@@ -127,15 +127,27 @@ Y.extend(Button, Y.Widget, { | |
* @static | ||
*/ | ||
ATTRS: { | ||
|
||
/** | ||
* The text of the button (the `value` or `text` property) | ||
* | ||
* @attribute label | ||
* @type String | ||
*/ | ||
label: { | ||
value: Y.ButtonCore.ATTRS.label.value | ||
getter: Y.ButtonCore.ATTRS.label.getter, | ||
setter: Y.ButtonCore.ATTRS.label.setter | ||
}, | ||
|
||
/** | ||
* The HTML of the button's label | ||
* | ||
* IMPORTANT: Sanitize all input passed into this attribute | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please explain what this means. As is, it's a little vague and I can imagine someone glossing over it because they're not sure it applies to them, or because they don't know how to sanitize input. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated with a more detailed notice. https://github.com/yui/yui3/pull/1163/files#L4R246 |
||
* | ||
* @attribute labelHTML | ||
* @type {HTML|String} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next push |
||
*/ | ||
labelHTML: { | ||
setter: Y.ButtonCore.ATTRS.labelHTML.setter | ||
} | ||
}, | ||
|
||
|
@@ -146,9 +158,9 @@ Y.extend(Button, Y.Widget, { | |
* @static | ||
*/ | ||
HTML_PARSER: { | ||
label: function(node) { | ||
this._host = node; // TODO: remove | ||
return this._getLabel(); | ||
|
||
labelHTML: function(node) { | ||
return this._getLabelFromNode(node, true); | ||
}, | ||
|
||
disabled: function(node) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -70,10 +70,6 @@ ButtonCore.prototype = { | |
* @private | ||
*/ | ||
_initAttributes: function(config) { | ||
var host = this._host, | ||
node = host.one('.' + ButtonCore.CLASS_NAMES.LABEL) || host; | ||
|
||
config.label = config.label || this._getLabel(node); | ||
Y.AttributeCore.call(this, ButtonCore.ATTRS, config); | ||
}, | ||
|
||
|
@@ -97,7 +93,7 @@ ButtonCore.prototype = { | |
|
||
/** | ||
* @method enable | ||
* @description Sets the button's `disabled` DOM attribute to false | ||
* @description Sets the button's `disabled` DOM attribute to `false` | ||
* @public | ||
*/ | ||
enable: function() { | ||
|
@@ -106,7 +102,7 @@ ButtonCore.prototype = { | |
|
||
/** | ||
* @method disable | ||
* @description Sets the button's `disabled` DOM attribute to true | ||
* @description Sets the button's `disabled` DOM attribute to `true` | ||
* @public | ||
*/ | ||
disable: function() { | ||
|
@@ -115,59 +111,105 @@ ButtonCore.prototype = { | |
|
||
/** | ||
* @method getNode | ||
* @description Gets the host DOM node for this button instance | ||
* @description Gets the host's DOM node for this button instance | ||
* @return {Node} The host node instance | ||
* @public | ||
*/ | ||
getNode: function() { | ||
return this._host; | ||
}, | ||
|
||
/** | ||
* @method _getLabel | ||
* @description Getter for a button's 'label' ATTR | ||
* @method _getLabelFromNode | ||
* @param node {Node} The Y.Node instance to obtain the label from | ||
* @return {HTML|String} The label for a given node | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method should return either HTML or plain text, and should be documented as such. Currently, it appears to return HTML sometimes and plain text sometimes, which is unsafe. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have removed all |
||
* @private | ||
*/ | ||
_getLabel: function () { | ||
var node = this.getNode(), | ||
tagName = node.get('tagName').toLowerCase(), | ||
_getLabelFromNode: function (node, html) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In next push, |
||
var tagName = node.get('tagName').toLowerCase(), | ||
label; | ||
|
||
if (tagName === 'input') { | ||
label = node.get('value'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As of 08ae388, |
||
} | ||
else { | ||
label = (node.one('.' + ButtonCore.CLASS_NAMES.LABEL) || node).get('text'); | ||
node = (node.one('.' + ButtonCore.CLASS_NAMES.LABEL) || node); | ||
if (html) { | ||
label = node.getHTML(); | ||
} | ||
else { | ||
label = node.get('text'); | ||
} | ||
} | ||
|
||
return label; | ||
}, | ||
|
||
/** | ||
* @method _uiSetLabel | ||
* @method _getLabel | ||
* @description Getter for a button's 'label' ATTR | ||
* @return {String} The label for a given node | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See: #1163 (comment) |
||
* @private | ||
*/ | ||
_getLabel: function () { | ||
var node = this.getNode(), | ||
label = this._getLabelFromNode(node); | ||
|
||
return label; | ||
}, | ||
|
||
/** | ||
* @method _setLabel | ||
* @description Getter for a button's 'label' ATTR | ||
* @private | ||
*/ | ||
_setLabel: function (value) { | ||
var labelHTML = Y.Escape.html(value); | ||
|
||
this.set('labelHTML', labelHTML); | ||
|
||
return labelHTML; | ||
}, | ||
|
||
/** | ||
* @method _getLabelHTML | ||
* @description Getter for a button's 'label' ATTR | ||
* @return {HTML|String} The label for a given node | ||
* @private | ||
*/ | ||
_getLabelHTML: function () { | ||
var node = this.getNode(); | ||
|
||
return this._getLabelFromNode(node, true); | ||
}, | ||
|
||
/** | ||
* @method _setLabelHTML | ||
* @description Setter for a button's 'label' ATTR | ||
* @param label {string} | ||
* @param label {HTML|String} The label to set | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type is always There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next push |
||
* @return {String} The label for a given node | ||
* @private | ||
*/ | ||
_uiSetLabel: function (label) { | ||
_setLabelHTML: function (label) { | ||
var node = this.getNode(), | ||
tagName = node.get('tagName').toLowerCase(); | ||
|
||
if (tagName === 'input') { | ||
node.set('value', label); | ||
} else { | ||
(node.one('.' + ButtonCore.CLASS_NAMES.LABEL) || node).set('text', label); | ||
(node.one('.' + ButtonCore.CLASS_NAMES.LABEL) || node).setHTML(label); | ||
} | ||
|
||
return label; | ||
}, | ||
|
||
/** | ||
* @method _uiSetDisabled | ||
* @method _setDisabled | ||
* @description Setter for the 'disabled' ATTR | ||
* @param value {boolean} | ||
* @private | ||
*/ | ||
_uiSetDisabled: function(value) { | ||
_setDisabled: function(value) { | ||
var node = this.getNode(); | ||
|
||
node.getDOMNode().disabled = value; // avoid rerunning setter when this === node | ||
|
@@ -191,26 +233,39 @@ Y.mix(ButtonCore.prototype, Y.AttributeCore.prototype); | |
ButtonCore.ATTRS = { | ||
|
||
/** | ||
* The text of the button (the `value` or `text` property) | ||
* The text of the button's label | ||
* | ||
* @attribute label | ||
* @type String | ||
* @config label | ||
* @type {String} | ||
*/ | ||
label: { | ||
setter: '_uiSetLabel', | ||
setter: '_setLabel', | ||
getter: '_getLabel', | ||
lazyAdd: false | ||
}, | ||
/** | ||
* The HTML of the button's label | ||
* | ||
* IMPORTANT: Sanitize all input passed into this attribute | ||
* | ||
* @config labelHTML | ||
* @type {HTML|String} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Type is always There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next push |
||
*/ | ||
labelHTML: { | ||
setter: '_setLabelHTML', | ||
getter: '_getLabelHTML', | ||
lazyAdd: false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this attribute There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As the Attribute docs state
This applies to This snippet demonstrates the difference.
|
||
}, | ||
|
||
/** | ||
* The button's enabled/disabled state | ||
* | ||
* @attribute disabled | ||
* @config disabled | ||
* @type Boolean | ||
*/ | ||
disabled: { | ||
value: false, | ||
setter: '_uiSetDisabled', | ||
setter: '_setDisabled', | ||
lazyAdd: false | ||
} | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,8 @@ | |
"requires": [ | ||
"attribute-core", | ||
"classnamemanager", | ||
"node-base" | ||
"node-base", | ||
"escape" | ||
] | ||
}, | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ YUI.add('button-core-test', function (Y) { | |
name: 'Attributes', | ||
|
||
setUp : function () { | ||
Y.one("#container").setContent('<button id="testButton">Hello</button>'); | ||
Y.one("#container").setContent('<button id="testButton"><div>Hello</div><div>World</div></button>'); | ||
this.button = new Y.ButtonCore({ | ||
host: Y.one("#testButton") | ||
}); | ||
|
@@ -97,7 +97,31 @@ YUI.add('button-core-test', function (Y) { | |
Assert.areEqual(1, eventsTriggered); | ||
button.set('label', 'somethingElse'); | ||
Assert.areEqual(2, eventsTriggered); | ||
} | ||
}, | ||
|
||
'Getting the `label` attribute should NOT respect nested HTML': function () { | ||
var button = this.button, | ||
expected = 'HelloWorld', | ||
actual = button.get('label').replace(/\r\n/g, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test has been updated to longer use |
||
|
||
Assert.areSame(expected, actual); | ||
}, | ||
|
||
'Getting the `labelHTML` attribute should respect nested HTML': function () { | ||
var button = this.button, | ||
expected = '<div>hello</div><div>world</div>', | ||
actual = button.get('labelHTML').toLowerCase().replace(/\r\n/g, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HTML is obviously likely to differ across browsers so this is less unexpected than the plaintext label test, but it would still be better to generate the For example, you could generate There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So something like...
Which essentially points to the same DOM method, one is just passed through ButtonCore before the test gets the result? I guess that makes sense though considering this unit test isn't to ensure the DOM's behavior is correct, but rather that ButtonCore does the right thing with what the DOM (and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in next push. |
||
|
||
Assert.areSame(expected, actual); | ||
}, | ||
|
||
'Setting the `label` attribute with HTML should escape the value': function () { | ||
var button = this.button; | ||
|
||
button.set('label', '<div>foo</div>'); | ||
Assert.areSame('<div>foo</div>', button.get('label')); | ||
Assert.areSame('<div>foo</div>', button.getNode().get('innerHTML')); | ||
} | ||
})); | ||
|
||
suite.add(new Y.Test.Case({ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why this needs to be here? And, for that matter, the
config.disabled
block above as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally
label
isn't a lazy attribute, butlabel
's setter can't properly execute untilthis._host
is set, which doesn't occur until a few lines before this one in the initializer....... and nevermind, I just found the solution. ButtonCore should try to populate
this._host
withthis.get('boundingBox')
if it isn't already set.Oh, and the
disabled
setter can also be removed. After some experimentation, #406 did fix targeted issue, but executingdisabled
's setter in the initializer wasn't required. I think at least. Regardless, it works as expected now.So looks like I can actually remove this entire method now. Update coming soon.
Update: Resolved.