MDL-56364 javascript: Prevent bad prototype chains in core dialogue

The DIALOGUE object was using Y.clone to copy the input config, messing
up the prototype chains for any objects in the config param. Really,
this doesn't need to clone the config, so I've refactored to avoid doing
so. Also moved some of the other attribute init and setup calls to the
appropriate places (initializer and modifyAttrs).
This commit is contained in:
Jake Dallimore
2017-02-24 08:25:31 +08:00
parent 0f59b6dd75
commit 35fcaac78b
4 changed files with 260 additions and 95 deletions
@@ -55,10 +55,14 @@ var DIALOGUE_NAME = 'Moodle dialogue',
* @class M.core.dialogue
* @extends Panel
*/
DIALOGUE = function(c) {
var config = Y.clone(c);
config.COUNT = Y.stamp(this);
var id = 'moodle-dialogue-' + config.COUNT;
DIALOGUE = function(config) {
// The code below is a hack to add the custom content node to the DOM, on the fly, per-instantiation and to assign the value
// of 'srcNode' to this newly created node. Normally (see docs: https://yuilibrary.com/yui/docs/widget/widget-extend.html),
// this node would be pre-existing in the DOM, and an id string would simply be passed in as a property of the config object
// during widget instantiation, however, because we're creating it on the fly (and 'config.srcNode' isn't set yet), care must
// be taken to add it to the DOM and to properly set the value of 'config.srcNode' before calling the parent constructor.
// Note: additional classes can be added to this content node by setting the 'additionalBaseClass' config property (a string).
var id = 'moodle-dialogue-' + Y.stamp(this); // Can't use this.get('id') as it's not set at this stage.
config.notificationBase =
Y.Node.create('<div class="' + CSS.BASE + '">')
.append(Y.Node.create('<div id="' + id + '" role="dialog" ' +
@@ -67,33 +71,8 @@ DIALOGUE = function(c) {
.append(Y.Node.create('<div class="' + CSS.BODY + ' yui3-widget-bd"></div>'))
.append(Y.Node.create('<div class="' + CSS.FOOTER + ' yui3-widget-ft"></div>')));
Y.one(document.body).append(config.notificationBase);
if (config.additionalBaseClass) {
config.notificationBase.addClass(config.additionalBaseClass);
}
config.srcNode = '#' + id;
// closeButton param to keep the stable versions API.
if (config.closeButton === false) {
config.buttons = null;
} else {
config.buttons = [
{
section: Y.WidgetStdMod.HEADER,
classNames: 'closebutton',
action: function() {
this.hide();
}
}
];
}
DIALOGUE.superclass.constructor.apply(this, [config]);
if (config.closeButton !== false) {
// The buttons constructor does not allow custom attributes
this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
}
};
Y.extend(DIALOGUE, Y.Panel, {
// Window resize event listener.
@@ -130,6 +109,11 @@ Y.extend(DIALOGUE, Y.Panel, {
initializer: function() {
var bb;
if (this.get('closeButton') !== false) {
// The buttons constructor does not allow custom attributes
this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
}
// Initialise the element cache.
this._hiddenSiblings = [];
@@ -181,6 +165,13 @@ Y.extend(DIALOGUE, Y.Panel, {
}
}, this);
// Add any additional classes to the content node if required.
var nBase = this.get('notificationBase');
var additionalClasses = this.get('additionalBaseClass');
if (additionalClasses !== '') {
nBase.addClass(additionalClasses);
}
// Remove the dialogue from the DOM when it is destroyed.
this.after('destroyedChange', function() {
this.get(BASE).remove(true);
@@ -536,6 +527,23 @@ Y.extend(DIALOGUE, Y.Panel, {
NAME: DIALOGUE_NAME,
CSS_PREFIX: DIALOGUE_PREFIX,
ATTRS: {
/**
* Any additional classes to add to the base Node.
*
* @attribute additionalBaseClass
* @type String
* @default ''
*/
additionalBaseClass: {
value: ''
},
/**
* The Notification base Node.
*
* @attribute notificationBase
* @type Node
*/
notificationBase: {
},
@@ -616,9 +624,13 @@ Y.extend(DIALOGUE, Y.Panel, {
* @attribute COUNT
* @type String
* @default null
* @writeonce
*/
COUNT: {
value: null
writeOnce: true,
valueFn: function() {
return Y.stamp(this);
}
},
/**
@@ -656,7 +668,6 @@ Y.extend(DIALOGUE, Y.Panel, {
focusOnShowSelector: {
value: null
}
}
});
@@ -741,6 +752,50 @@ Y.Base.modifyAttrs(DIALOGUE, {
*/
extraClasses: {
value: []
},
/**
* Identifier for the widget.
*
* @attribute id
* @type String
* @default a product of guid().
* @writeOnce
*/
id: {
writeOnce: true,
valueFn: function() {
var id = 'moodle-dialogue-' + Y.stamp(this);
return id;
}
},
/**
* Collection containing the widget's buttons.
*
* @attribute buttons
* @type Object
* @default {}
*/
buttons: {
// Readonly is really important. We don't want to allow users of the plugin to pass in buttons. closeButton handles this.
readOnly: true,
getter: Y.WidgetButtons.prototype._getButtons,
valueFn: function() {
if (this.get('closeButton') === false) {
return null;
} else {
return [
{
section: Y.WidgetStdMod.HEADER,
classNames: 'closebutton',
action: function() {
this.hide();
}
}
];
}
}
}
});
File diff suppressed because one or more lines are too long
@@ -55,10 +55,14 @@ var DIALOGUE_NAME = 'Moodle dialogue',
* @class M.core.dialogue
* @extends Panel
*/
DIALOGUE = function(c) {
var config = Y.clone(c);
config.COUNT = Y.stamp(this);
var id = 'moodle-dialogue-' + config.COUNT;
DIALOGUE = function(config) {
// The code below is a hack to add the custom content node to the DOM, on the fly, per-instantiation and to assign the value
// of 'srcNode' to this newly created node. Normally (see docs: https://yuilibrary.com/yui/docs/widget/widget-extend.html),
// this node would be pre-existing in the DOM, and an id string would simply be passed in as a property of the config object
// during widget instantiation, however, because we're creating it on the fly (and 'config.srcNode' isn't set yet), care must
// be taken to add it to the DOM and to properly set the value of 'config.srcNode' before calling the parent constructor.
// Note: additional classes can be added to this content node by setting the 'additionalBaseClass' config property (a string).
var id = 'moodle-dialogue-' + Y.stamp(this); // Can't use this.get('id') as it's not set at this stage.
config.notificationBase =
Y.Node.create('<div class="' + CSS.BASE + '">')
.append(Y.Node.create('<div id="' + id + '" role="dialog" ' +
@@ -67,33 +71,8 @@ DIALOGUE = function(c) {
.append(Y.Node.create('<div class="' + CSS.BODY + ' yui3-widget-bd"></div>'))
.append(Y.Node.create('<div class="' + CSS.FOOTER + ' yui3-widget-ft"></div>')));
Y.one(document.body).append(config.notificationBase);
if (config.additionalBaseClass) {
config.notificationBase.addClass(config.additionalBaseClass);
}
config.srcNode = '#' + id;
// closeButton param to keep the stable versions API.
if (config.closeButton === false) {
config.buttons = null;
} else {
config.buttons = [
{
section: Y.WidgetStdMod.HEADER,
classNames: 'closebutton',
action: function() {
this.hide();
}
}
];
}
DIALOGUE.superclass.constructor.apply(this, [config]);
if (config.closeButton !== false) {
// The buttons constructor does not allow custom attributes
this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
}
};
Y.extend(DIALOGUE, Y.Panel, {
// Window resize event listener.
@@ -130,6 +109,11 @@ Y.extend(DIALOGUE, Y.Panel, {
initializer: function() {
var bb;
if (this.get('closeButton') !== false) {
// The buttons constructor does not allow custom attributes
this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
}
// Initialise the element cache.
this._hiddenSiblings = [];
@@ -181,6 +165,13 @@ Y.extend(DIALOGUE, Y.Panel, {
}
}, this);
// Add any additional classes to the content node if required.
var nBase = this.get('notificationBase');
var additionalClasses = this.get('additionalBaseClass');
if (additionalClasses !== '') {
nBase.addClass(additionalClasses);
}
// Remove the dialogue from the DOM when it is destroyed.
this.after('destroyedChange', function() {
this.get(BASE).remove(true);
@@ -536,6 +527,23 @@ Y.extend(DIALOGUE, Y.Panel, {
NAME: DIALOGUE_NAME,
CSS_PREFIX: DIALOGUE_PREFIX,
ATTRS: {
/**
* Any additional classes to add to the base Node.
*
* @attribute additionalBaseClass
* @type String
* @default ''
*/
additionalBaseClass: {
value: ''
},
/**
* The Notification base Node.
*
* @attribute notificationBase
* @type Node
*/
notificationBase: {
},
@@ -613,9 +621,13 @@ Y.extend(DIALOGUE, Y.Panel, {
* @attribute COUNT
* @type String
* @default null
* @writeonce
*/
COUNT: {
value: null
writeOnce: true,
valueFn: function() {
return Y.stamp(this);
}
},
/**
@@ -653,7 +665,6 @@ Y.extend(DIALOGUE, Y.Panel, {
focusOnShowSelector: {
value: null
}
}
});
@@ -738,6 +749,50 @@ Y.Base.modifyAttrs(DIALOGUE, {
*/
extraClasses: {
value: []
},
/**
* Identifier for the widget.
*
* @attribute id
* @type String
* @default a product of guid().
* @writeOnce
*/
id: {
writeOnce: true,
valueFn: function() {
var id = 'moodle-dialogue-' + Y.stamp(this);
return id;
}
},
/**
* Collection containing the widget's buttons.
*
* @attribute buttons
* @type Object
* @default {}
*/
buttons: {
// Readonly is really important. We don't want to allow users of the plugin to pass in buttons. closeButton handles this.
readOnly: true,
getter: Y.WidgetButtons.prototype._getButtons,
valueFn: function() {
if (this.get('closeButton') === false) {
return null;
} else {
return [
{
section: Y.WidgetStdMod.HEADER,
classNames: 'closebutton',
action: function() {
this.hide();
}
}
];
}
}
}
});
+86 -31
View File
@@ -25,10 +25,14 @@ var DIALOGUE_NAME = 'Moodle dialogue',
* @class M.core.dialogue
* @extends Panel
*/
DIALOGUE = function(c) {
var config = Y.clone(c);
config.COUNT = Y.stamp(this);
var id = 'moodle-dialogue-' + config.COUNT;
DIALOGUE = function(config) {
// The code below is a hack to add the custom content node to the DOM, on the fly, per-instantiation and to assign the value
// of 'srcNode' to this newly created node. Normally (see docs: https://yuilibrary.com/yui/docs/widget/widget-extend.html),
// this node would be pre-existing in the DOM, and an id string would simply be passed in as a property of the config object
// during widget instantiation, however, because we're creating it on the fly (and 'config.srcNode' isn't set yet), care must
// be taken to add it to the DOM and to properly set the value of 'config.srcNode' before calling the parent constructor.
// Note: additional classes can be added to this content node by setting the 'additionalBaseClass' config property (a string).
var id = 'moodle-dialogue-' + Y.stamp(this); // Can't use this.get('id') as it's not set at this stage.
config.notificationBase =
Y.Node.create('<div class="' + CSS.BASE + '">')
.append(Y.Node.create('<div id="' + id + '" role="dialog" ' +
@@ -37,33 +41,8 @@ DIALOGUE = function(c) {
.append(Y.Node.create('<div class="' + CSS.BODY + ' yui3-widget-bd"></div>'))
.append(Y.Node.create('<div class="' + CSS.FOOTER + ' yui3-widget-ft"></div>')));
Y.one(document.body).append(config.notificationBase);
if (config.additionalBaseClass) {
config.notificationBase.addClass(config.additionalBaseClass);
}
config.srcNode = '#' + id;
// closeButton param to keep the stable versions API.
if (config.closeButton === false) {
config.buttons = null;
} else {
config.buttons = [
{
section: Y.WidgetStdMod.HEADER,
classNames: 'closebutton',
action: function() {
this.hide();
}
}
];
}
DIALOGUE.superclass.constructor.apply(this, [config]);
if (config.closeButton !== false) {
// The buttons constructor does not allow custom attributes
this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
}
};
Y.extend(DIALOGUE, Y.Panel, {
// Window resize event listener.
@@ -100,6 +79,11 @@ Y.extend(DIALOGUE, Y.Panel, {
initializer: function() {
var bb;
if (this.get('closeButton') !== false) {
// The buttons constructor does not allow custom attributes
this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
}
// Initialise the element cache.
this._hiddenSiblings = [];
@@ -151,6 +135,13 @@ Y.extend(DIALOGUE, Y.Panel, {
}
}, this);
// Add any additional classes to the content node if required.
var nBase = this.get('notificationBase');
var additionalClasses = this.get('additionalBaseClass');
if (additionalClasses !== '') {
nBase.addClass(additionalClasses);
}
// Remove the dialogue from the DOM when it is destroyed.
this.after('destroyedChange', function() {
this.get(BASE).remove(true);
@@ -506,6 +497,23 @@ Y.extend(DIALOGUE, Y.Panel, {
NAME: DIALOGUE_NAME,
CSS_PREFIX: DIALOGUE_PREFIX,
ATTRS: {
/**
* Any additional classes to add to the base Node.
*
* @attribute additionalBaseClass
* @type String
* @default ''
*/
additionalBaseClass: {
value: ''
},
/**
* The Notification base Node.
*
* @attribute notificationBase
* @type Node
*/
notificationBase: {
},
@@ -586,9 +594,13 @@ Y.extend(DIALOGUE, Y.Panel, {
* @attribute COUNT
* @type String
* @default null
* @writeonce
*/
COUNT: {
value: null
writeOnce: true,
valueFn: function() {
return Y.stamp(this);
}
},
/**
@@ -626,7 +638,6 @@ Y.extend(DIALOGUE, Y.Panel, {
focusOnShowSelector: {
value: null
}
}
});
@@ -711,6 +722,50 @@ Y.Base.modifyAttrs(DIALOGUE, {
*/
extraClasses: {
value: []
},
/**
* Identifier for the widget.
*
* @attribute id
* @type String
* @default a product of guid().
* @writeOnce
*/
id: {
writeOnce: true,
valueFn: function() {
var id = 'moodle-dialogue-' + Y.stamp(this);
return id;
}
},
/**
* Collection containing the widget's buttons.
*
* @attribute buttons
* @type Object
* @default {}
*/
buttons: {
// Readonly is really important. We don't want to allow users of the plugin to pass in buttons. closeButton handles this.
readOnly: true,
getter: Y.WidgetButtons.prototype._getButtons,
valueFn: function() {
if (this.get('closeButton') === false) {
return null;
} else {
return [
{
section: Y.WidgetStdMod.HEADER,
classNames: 'closebutton',
action: function() {
this.hide();
}
}
];
}
}
}
});