Merge branch 'MDL-44760-master' of git://github.com/andrewnicols/moodle

Conflicts:
	lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-debug.js
	lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button-min.js
	lib/editor/atto/plugins/table/yui/build/moodle-atto_table-button/moodle-atto_table-button.js
	lib/editor/atto/plugins/table/yui/src/button/js/button.js
	lib/editor/atto/yui/build/moodle-editor_atto-plugin/moodle-editor_atto-plugin-min.js
This commit is contained in:
Sam Hemelryk
2014-04-08 08:59:54 +12:00
16 changed files with 1000 additions and 591 deletions
@@ -0,0 +1,3 @@
.atto_backcolor_button .dropdown-menu {
min-width: inherit;
}
@@ -0,0 +1,3 @@
.atto_fontcolor_button .dropdown-menu {
min-width: inherit;
}
@@ -76,18 +76,6 @@ var COMPONENT = 'atto_table',
'<button class="{{CSS.SUBMIT}}" type="submit">{{get_string "createtable" component}}</button>' +
'</div>' +
'</form>',
CONTEXTMENUTEMPLATE = '' +
'<ul>' +
'<li><a href="#" data-change="addcolumnafter">{{get_string "addcolumnafter" component}}</a></li>' +
'<li><a href="#" data-change="addrowafter">{{get_string "addrowafter" component}}</a></li>' +
'<li><a href="#" data-change="moverowup">{{get_string "moverowup" component}}</a></li>' +
'<li><a href="#" data-change="moverowdown">{{get_string "moverowdown" component}}</a></li>' +
'<li><a href="#" data-change="movecolumnleft">{{get_string "movecolumnleft" component}}</a></li>' +
'<li><a href="#" data-change="movecolumnright">{{get_string "movecolumnright" component}}</a></li>' +
'<li><a href="#" data-change="deleterow">{{get_string "deleterow" component}}</a></li>' +
'<li><a href="#" data-change="deletecolumn">{{get_string "deletecolumn" component}}</a></li>' +
'<li><a href="#" data-change="edittable">{{get_string "edittable" component}}</a></li>' +
'</ul>',
CSS = {
CAPTION: 'caption',
HEADERS: 'headers',
@@ -135,6 +123,15 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
*/
_lastTarget: null,
/**
* The list of menu items.
*
* @property _menuOptions
* @type Object
* @private
*/
_menuOptions: null,
initializer: function() {
this.addButton({
icon: 'e/table',
@@ -569,22 +566,59 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
var boundingBox;
if (!this._contextMenu) {
var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE),
content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
component: COMPONENT
}));
this._menuOptions = [
{
text: M.util.get_string("addcolumnafter", COMPONENT),
data: {
change: "addcolumnafter"
}
}, {
text: M.util.get_string("addrowafter", COMPONENT),
data: {
change: "addrowafter"
}
}, {
text: M.util.get_string("moverowup", COMPONENT),
data: {
change: "moverowup"
}
}, {
text: M.util.get_string("moverowdown", COMPONENT),
data: {
change: "moverowdown"
}
}, {
text: M.util.get_string("movecolumnleft", COMPONENT),
data: {
change: "movecolumnleft"
}
}, {
text: M.util.get_string("movecolumnright", COMPONENT),
data: {
change: "movecolumnright"
}
}, {
text: M.util.get_string("deleterow", COMPONENT),
data: {
change: "deleterow"
}
}, {
text: M.util.get_string("deletecolumn", COMPONENT),
data: {
change: "deletecolumn"
}
}
];
this._contextMenu = new Y.M.editor_atto.Menu({
headerText: M.util.get_string('edittable', 'atto_table'),
bodyContent: content
items: this._menuOptions
});
// Add event handlers for table control menus.
boundingBox = this._contextMenu.get('boundingBox');
boundingBox.delegate('click', this._handleTableChange, 'a', this);
boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this);
}
boundingBox = this._contextMenu.get('boundingBox');
// We store the cell of the last click (the control node is transient).
@@ -595,6 +629,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
// Show the context menu, and align to the current position.
this._contextMenu.show();
this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
this._contextMenu.set('focusAfterHide', this.buttons[this.name]);
// If there are any anchors in the bounding box, focus on the first.
if (boundingBox.one('a')) {
@@ -612,8 +647,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
_handleTableChange: function(e) {
e.preventDefault();
this._contextMenu.set('focusAfterHide', this.get('host').editor);
// Hide the context menu.
this._contextMenu.hide();
this._contextMenu.hide(e);
// Make our changes.
switch (e.target.getData('change')) {
File diff suppressed because one or more lines are too long
@@ -76,18 +76,6 @@ var COMPONENT = 'atto_table',
'<button class="{{CSS.SUBMIT}}" type="submit">{{get_string "createtable" component}}</button>' +
'</div>' +
'</form>',
CONTEXTMENUTEMPLATE = '' +
'<ul>' +
'<li><a href="#" data-change="addcolumnafter">{{get_string "addcolumnafter" component}}</a></li>' +
'<li><a href="#" data-change="addrowafter">{{get_string "addrowafter" component}}</a></li>' +
'<li><a href="#" data-change="moverowup">{{get_string "moverowup" component}}</a></li>' +
'<li><a href="#" data-change="moverowdown">{{get_string "moverowdown" component}}</a></li>' +
'<li><a href="#" data-change="movecolumnleft">{{get_string "movecolumnleft" component}}</a></li>' +
'<li><a href="#" data-change="movecolumnright">{{get_string "movecolumnright" component}}</a></li>' +
'<li><a href="#" data-change="deleterow">{{get_string "deleterow" component}}</a></li>' +
'<li><a href="#" data-change="deletecolumn">{{get_string "deletecolumn" component}}</a></li>' +
'<li><a href="#" data-change="edittable">{{get_string "edittable" component}}</a></li>' +
'</ul>',
CSS = {
CAPTION: 'caption',
HEADERS: 'headers',
@@ -135,6 +123,15 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
*/
_lastTarget: null,
/**
* The list of menu items.
*
* @property _menuOptions
* @type Object
* @private
*/
_menuOptions: null,
initializer: function() {
this.addButton({
icon: 'e/table',
@@ -569,22 +566,59 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
var boundingBox;
if (!this._contextMenu) {
var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE),
content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
component: COMPONENT
}));
this._menuOptions = [
{
text: M.util.get_string("addcolumnafter", COMPONENT),
data: {
change: "addcolumnafter"
}
}, {
text: M.util.get_string("addrowafter", COMPONENT),
data: {
change: "addrowafter"
}
}, {
text: M.util.get_string("moverowup", COMPONENT),
data: {
change: "moverowup"
}
}, {
text: M.util.get_string("moverowdown", COMPONENT),
data: {
change: "moverowdown"
}
}, {
text: M.util.get_string("movecolumnleft", COMPONENT),
data: {
change: "movecolumnleft"
}
}, {
text: M.util.get_string("movecolumnright", COMPONENT),
data: {
change: "movecolumnright"
}
}, {
text: M.util.get_string("deleterow", COMPONENT),
data: {
change: "deleterow"
}
}, {
text: M.util.get_string("deletecolumn", COMPONENT),
data: {
change: "deletecolumn"
}
}
];
this._contextMenu = new Y.M.editor_atto.Menu({
headerText: M.util.get_string('edittable', 'atto_table'),
bodyContent: content
items: this._menuOptions
});
// Add event handlers for table control menus.
boundingBox = this._contextMenu.get('boundingBox');
boundingBox.delegate('click', this._handleTableChange, 'a', this);
boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this);
}
boundingBox = this._contextMenu.get('boundingBox');
// We store the cell of the last click (the control node is transient).
@@ -595,6 +629,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
// Show the context menu, and align to the current position.
this._contextMenu.show();
this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
this._contextMenu.set('focusAfterHide', this.buttons[this.name]);
// If there are any anchors in the bounding box, focus on the first.
if (boundingBox.one('a')) {
@@ -612,8 +647,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
_handleTableChange: function(e) {
e.preventDefault();
this._contextMenu.set('focusAfterHide', this.get('host').editor);
// Hide the context menu.
this._contextMenu.hide();
this._contextMenu.hide(e);
// Make our changes.
switch (e.target.getData('change')) {
+57 -21
View File
@@ -74,18 +74,6 @@ var COMPONENT = 'atto_table',
'<button class="{{CSS.SUBMIT}}" type="submit">{{get_string "createtable" component}}</button>' +
'</div>' +
'</form>',
CONTEXTMENUTEMPLATE = '' +
'<ul>' +
'<li><a href="#" data-change="addcolumnafter">{{get_string "addcolumnafter" component}}</a></li>' +
'<li><a href="#" data-change="addrowafter">{{get_string "addrowafter" component}}</a></li>' +
'<li><a href="#" data-change="moverowup">{{get_string "moverowup" component}}</a></li>' +
'<li><a href="#" data-change="moverowdown">{{get_string "moverowdown" component}}</a></li>' +
'<li><a href="#" data-change="movecolumnleft">{{get_string "movecolumnleft" component}}</a></li>' +
'<li><a href="#" data-change="movecolumnright">{{get_string "movecolumnright" component}}</a></li>' +
'<li><a href="#" data-change="deleterow">{{get_string "deleterow" component}}</a></li>' +
'<li><a href="#" data-change="deletecolumn">{{get_string "deletecolumn" component}}</a></li>' +
'<li><a href="#" data-change="edittable">{{get_string "edittable" component}}</a></li>' +
'</ul>',
CSS = {
CAPTION: 'caption',
HEADERS: 'headers',
@@ -133,6 +121,15 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
*/
_lastTarget: null,
/**
* The list of menu items.
*
* @property _menuOptions
* @type Object
* @private
*/
_menuOptions: null,
initializer: function() {
this.addButton({
icon: 'e/table',
@@ -567,22 +564,59 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
var boundingBox;
if (!this._contextMenu) {
var template = Y.Handlebars.compile(CONTEXTMENUTEMPLATE),
content = Y.Node.create(template({
elementid: this.get('host').get('elementid'),
component: COMPONENT
}));
this._menuOptions = [
{
text: M.util.get_string("addcolumnafter", COMPONENT),
data: {
change: "addcolumnafter"
}
}, {
text: M.util.get_string("addrowafter", COMPONENT),
data: {
change: "addrowafter"
}
}, {
text: M.util.get_string("moverowup", COMPONENT),
data: {
change: "moverowup"
}
}, {
text: M.util.get_string("moverowdown", COMPONENT),
data: {
change: "moverowdown"
}
}, {
text: M.util.get_string("movecolumnleft", COMPONENT),
data: {
change: "movecolumnleft"
}
}, {
text: M.util.get_string("movecolumnright", COMPONENT),
data: {
change: "movecolumnright"
}
}, {
text: M.util.get_string("deleterow", COMPONENT),
data: {
change: "deleterow"
}
}, {
text: M.util.get_string("deletecolumn", COMPONENT),
data: {
change: "deletecolumn"
}
}
];
this._contextMenu = new Y.M.editor_atto.Menu({
headerText: M.util.get_string('edittable', 'atto_table'),
bodyContent: content
items: this._menuOptions
});
// Add event handlers for table control menus.
boundingBox = this._contextMenu.get('boundingBox');
boundingBox.delegate('click', this._handleTableChange, 'a', this);
boundingBox.delegate('key', this._handleTableChange, 'down:enter,space', 'a', this);
}
boundingBox = this._contextMenu.get('boundingBox');
// We store the cell of the last click (the control node is transient).
@@ -593,6 +627,7 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
// Show the context menu, and align to the current position.
this._contextMenu.show();
this._contextMenu.align(this.buttons.table, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
this._contextMenu.set('focusAfterHide', this.buttons[this.name]);
// If there are any anchors in the bounding box, focus on the first.
if (boundingBox.one('a')) {
@@ -610,8 +645,9 @@ Y.namespace('M.atto_table').Button = Y.Base.create('button', Y.M.editor_atto.Edi
_handleTableChange: function(e) {
e.preventDefault();
this._contextMenu.set('focusAfterHide', this.get('host').editor);
// Hide the context menu.
this._contextMenu.hide();
this._contextMenu.hide(e);
// Make our changes.
switch (e.target.getData('change')) {
+7 -67
View File
@@ -98,15 +98,6 @@ div.editor_atto_toolbar div.atto_group {
height: 16px;
}
.atto_menu {
background: white;
padding: 1px;
white-space: inherit;
}
.atto_menu a {
color: black;
}
.atto_menuentry {
clear: left;
}
@@ -167,33 +158,6 @@ div.editor_atto_content:hover .atto_control {
.yui3-menu-hidden {
display: none;
}
.editor_atto_controlmenu li {
list-style-type: none;
padding: 0;
margin: 0;
}
.editor_atto_controlmenu ul {
padding: 0;
margin: 0;
}
.editor_atto_controlmenu .moodle-dialogue-hd,
.editor_atto_controlmenu .moodle-dialogue-ft {
display: none;
}
.editor_atto_controlmenu ul li a:active,
.editor_atto_controlmenu ul li a:hover {
background-color: #0088cc;
color: white;
}
.editor_atto_controlmenu ul li a {
color: #333;
padding: 2px;
padding-left: 6px;
padding-right: 6px;
border-radius: 2px;
display: block;
}
/* Get broken images back in firefox */
.editor_atto_content img:-moz-broken {
@@ -202,41 +166,17 @@ div.editor_atto_content:hover .atto_control {
min-height:24px;
}
/* Atto menu styling */
.moodle-dialogue-base .editor_atto_menu .moodle-dialogue-content .moodle-dialogue-bd {
position: absolute;
text-align:left;
padding: 0;
z-index: 1000;
display: block;
background-color: #fff;
border: 1px solid #ccc;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
-webkit-box-shadow: 5px 5px 20px 0 #666666;
-moz-box-shadow: 5px 5px 20px 0 #666666;
box-shadow: 5px 5px 20px 0 #666666;
padding: 0px;
}
.editor_atto_menu .menu {
margin: 0px;
.editor_atto_menu .dropdown-menu > li > a {
padding: 3px 14px;
}
.editor_atto_menu .menu a {
display: block;
padding: 4px 1em 4px 1em;
color: #333333;
}
.editor_atto_menu .menu a:hover {
color: #ffffff;background-color: #0088cc;
}
.editor_atto_menu .menu a.hidden {
display: none;
}
.editor_atto_menu .menu img {
vertical-align: middle;
}
.editor_atto_menu .menu > li {
display: block;
.editor_atto_menu .open ul.dropdown-menu {
padding-top: 5px;
padding-bottom: 5px;
}
@@ -25,6 +25,20 @@ YUI.add('moodle-editor_atto-menu', function (Y, NAME) {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var MENUDIALOGUE = '' +
'<div class="open {{config.buttonClass}} atto_menu" ' +
'style="min-width:{{config.innerOverlayWidth}};">' +
'<ul class="dropdown-menu">' +
'{{#each config.items}}' +
'<li role="presentation" class="atto_menuentry">' +
'<a href="#" role="menuitem" data-index="{{@index}}" {{#each data}}data-{{@key}}="{{this}}"{{/each}}>' +
'{{{text}}}' +
'</a>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
/**
* A Menu for the Atto editor used in Moodle.
*
@@ -43,24 +57,169 @@ Menu = function() {
Y.extend(Menu, M.core.dialogue, {
/**
* A list of the menu handlers which have been attached here.
*
* @property _menuHandlers
* @type Array
* @private
*/
_menuHandlers: null,
initializer: function(config) {
var body, headertext, bb;
Menu.superclass.initializer.call(this, config);
var headertext,
bb;
this._menuHandlers = [];
// Create the actual button.
var template = Y.Handlebars.compile(MENUDIALOGUE),
menu = Y.Node.create(template({
config: config
}));
this.set('bodyContent', menu);
bb = this.get('boundingBox');
bb.addClass('editor_atto_controlmenu');
// Close the menu when clicked outside (excluding the button that opened the menu).
body = this.bodyNode;
bb.addClass('editor_atto_menu');
bb.one('.moodle-dialogue-wrap')
.removeClass('moodle-dialogue-wrap')
.addClass('moodle-dialogue-content');
headertext = Y.Node.create('<h3/>')
.addClass('accesshide')
.setHTML(this.get('headerText'));
body.prepend(headertext);
this.get('bodyContent').prepend(headertext);
this.get('hideOn');
// Hide the header and footer node entirely.
this.headerNode.hide();
this.footerNode.hide();
this._setupHandlers();
},
/**
* Setup the Event handlers.
*
* @method _setupHandlers
* @private
*/
_setupHandlers: function() {
var contentBox = this.get('contentBox');
// Handle menu item selection.
this._menuHandlers.push(
// Select the menu item on space, and enter.
contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this),
// Move up and down the menu on up/down.
contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this),
// Hide the menu when clicking outside of it.
contentBox.on('focusoutside', this.hide, this),
// Hide the menu on left/right, and escape keys.
contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this)
);
},
/**
* Simulate other types of menu selection.
*
* @method _chooseMenuItem
* @param {EventFacade} e
*/
_chooseMenuItem: function(e) {
e.target.simulate('click');
e.preventDefault();
},
/**
* Hide a menu, removing all of the event handlers which trigger the hide.
*
* @method hide
* @param {EventFacade} e
*/
hide: function(e) {
if (this.get('preventHideMenu') === true) {
return;
}
// We must prevent the default action (left/right/escape) because
// there are other listeners on the toolbar which will focus on the
// editor.
if (e) {
e.preventDefault();
}
return Menu.superclass.hide.call(this, arguments);
},
/**
* Implement arrow-key navigation for the items in a toolbar menu.
*
* @method _handleKeyboardEvent
* @param {EventFacade} e The keyboard event.
* @static
*/
_handleKeyboardEvent: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// Get a list of all buttons in the menu.
var buttons = e.currentTarget.all('a[role="menuitem"]');
// On cursor moves we loops through the buttons.
var found = false,
index = 0,
direction = 1,
checkCount = 0,
current = e.target.ancestor('a[role="menuitem"]', true);
// Determine which button is currently selected.
while (!found && index < buttons.size()) {
if (buttons.item(index) === current) {
found = true;
} else {
index++;
}
}
if (!found) {
Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME);
return;
}
if (e.keyCode === 38) {
// Moving up so reverse the direction.
direction = -1;
}
// Try to find the next
do {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
next = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we are not in a loop and have not already checked every button; and
// * we are on a different button; and
// * the next menu item is not hidden.
} while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
if (next) {
next.focus();
}
e.preventDefault();
e.stopImmediatePropagation();
}
}, {
NAME: "menu",
ATTRS: {
@@ -89,17 +248,6 @@ Y.Base.modifyAttrs(Menu, {
value: 'auto'
},
/**
* TODO - chekc that this is needed
* The footer content.
*
* @attribute footerContent
* @default ''
*/
footerContent: {
value: ''
},
/**
* When to hide this menu.
*
@@ -116,6 +264,62 @@ Y.Base.modifyAttrs(Menu, {
eventName: 'clickoutside'
}
]
},
/**
* The default list of extra classes for this menu.
*
* @attribute extraClasses
* @type Array
* @default editor_atto_menu
*/
extraClasses: {
value: [
'editor_atto_menu'
]
},
/**
* Override the responsive nature of the core dialogues.
*
* @attribute responsive
* @type boolean
* @default false
*/
responsive: {
value: false
},
/**
* The default visibility of the menu.
*
* @attribute visible
* @type boolean
* @default false
*/
visible: {
value: false
},
/**
* Whether to centre the menu.
*
* @attribute center
* @type boolean
* @default false
*/
center: {
value: false
},
/**
* Hide the close button.
* @attribute closeButton
* @type boolean
* @default false
*/
closeButton: {
value: false
}
});
@@ -1 +1 @@
YUI.add("moodle-editor_atto-menu",function(e,t){Menu=function(){Menu.superclass.constructor.apply(this,arguments)},e.extend(Menu,M.core.dialogue,{initializer:function(t){var n,r,i;Menu.superclass.initializer.call(this,t),i=this.get("boundingBox"),i.addClass("editor_atto_controlmenu"),n=this.bodyNode,r=e.Node.create("<h3/>").addClass("accesshide").setHTML(this.get("headerText")),n.prepend(r),this.get("hideOn")}},{NAME:"menu",ATTRS:{headerText:{value:""}}}),e.Base.modifyAttrs(Menu,{width:{value:"auto"},footerContent:{value:""},hideOn:{value:[{eventName:"clickoutside"}]}}),e.namespace("M.editor_atto").Menu=Menu},"@VERSION@",{requires:["moodle-core-notification-dialogue","node","event","event-custom"]});
YUI.add("moodle-editor_atto-menu",function(e,t){var n='<div class="open {{config.buttonClass}} atto_menu" style="min-width:{{config.innerOverlayWidth}};"><ul class="dropdown-menu">{{#each config.items}}<li role="presentation" class="atto_menuentry"><a href="#" role="menuitem" data-index="{{@index}}" {{#each data}}data-{{@key}}="{{this}}"{{/each}}>{{{text}}}</a></li>{{/each}}</ul></div>';Menu=function(){Menu.superclass.constructor.apply(this,arguments)},e.extend(Menu,M.core.dialogue,{_menuHandlers:null,initializer:function(t){var r,i;this._menuHandlers=[];var s=e.Handlebars.compile(n),o=e.Node.create(s({config:t}));this.set("bodyContent",o),i=this.get("boundingBox"),i.addClass("editor_atto_controlmenu"),i.addClass("editor_atto_menu"),i.one(".moodle-dialogue-wrap").removeClass("moodle-dialogue-wrap").addClass("moodle-dialogue-content"),r=e.Node.create("<h3/>").addClass("accesshide").setHTML(this.get("headerText")),this.get("bodyContent").prepend(r),this.headerNode.hide(),this.footerNode.hide(),this._setupHandlers()},_setupHandlers:function(){var e=this.get("contentBox");this._menuHandlers.push(e.delegate("key",this._chooseMenuItem,"32, enter",".atto_menuentry",this),e.delegate("key",this._handleKeyboardEvent,"down:38,40",".dropdown-menu",this),e.on("focusoutside",this.hide,this),e.delegate("key",this.hide,"down:37,39,esc",".dropdown-menu",this))},_chooseMenuItem:function(e){e.target.simulate("click"),e.preventDefault()},hide:function(e){if(this.get("preventHideMenu")===!0)return;return e&&e.preventDefault(),Menu.superclass.hide.call(this,arguments)},_handleKeyboardEvent:function(e){e.preventDefault();var t=e.currentTarget.all('a[role="menuitem"]'),n=!1,r=0,i=1,s=0,o=e.target.ancestor('a[role="menuitem"]',!0);while(!n&&r<t.size())t.item(r)===o?n=!0:r++;if(!n)return;e.keyCode===38&&(i=-1);do r+=i,r<0?r=t.size()-1:r>=t.size()&&(r=0),next=t.item(r),s++;while(s<t.size()&&next!==o&&next.hasAttribute("hidden"));next&&next.focus(),e.preventDefault(),e.stopImmediatePropagation()}},{NAME:"menu",ATTRS:{headerText:{value:""}}}),e.Base.modifyAttrs(Menu,{width:{value:"auto"},hideOn:{value:[{eventName:"clickoutside"}]},extraClasses:{value:["editor_atto_menu"]},responsive:{value:!1},visible:{value:!1},center:{value:!1},closeButton:{value:!1}}),e.namespace("M.editor_atto").Menu=Menu},"@VERSION@",{requires:["moodle-core-notification-dialogue","node","event","event-custom"]});
@@ -25,6 +25,20 @@ YUI.add('moodle-editor_atto-menu', function (Y, NAME) {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var MENUDIALOGUE = '' +
'<div class="open {{config.buttonClass}} atto_menu" ' +
'style="min-width:{{config.innerOverlayWidth}};">' +
'<ul class="dropdown-menu">' +
'{{#each config.items}}' +
'<li role="presentation" class="atto_menuentry">' +
'<a href="#" role="menuitem" data-index="{{@index}}" {{#each data}}data-{{@key}}="{{this}}"{{/each}}>' +
'{{{text}}}' +
'</a>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
/**
* A Menu for the Atto editor used in Moodle.
*
@@ -43,24 +57,168 @@ Menu = function() {
Y.extend(Menu, M.core.dialogue, {
/**
* A list of the menu handlers which have been attached here.
*
* @property _menuHandlers
* @type Array
* @private
*/
_menuHandlers: null,
initializer: function(config) {
var body, headertext, bb;
Menu.superclass.initializer.call(this, config);
var headertext,
bb;
this._menuHandlers = [];
// Create the actual button.
var template = Y.Handlebars.compile(MENUDIALOGUE),
menu = Y.Node.create(template({
config: config
}));
this.set('bodyContent', menu);
bb = this.get('boundingBox');
bb.addClass('editor_atto_controlmenu');
// Close the menu when clicked outside (excluding the button that opened the menu).
body = this.bodyNode;
bb.addClass('editor_atto_menu');
bb.one('.moodle-dialogue-wrap')
.removeClass('moodle-dialogue-wrap')
.addClass('moodle-dialogue-content');
headertext = Y.Node.create('<h3/>')
.addClass('accesshide')
.setHTML(this.get('headerText'));
body.prepend(headertext);
this.get('bodyContent').prepend(headertext);
this.get('hideOn');
// Hide the header and footer node entirely.
this.headerNode.hide();
this.footerNode.hide();
this._setupHandlers();
},
/**
* Setup the Event handlers.
*
* @method _setupHandlers
* @private
*/
_setupHandlers: function() {
var contentBox = this.get('contentBox');
// Handle menu item selection.
this._menuHandlers.push(
// Select the menu item on space, and enter.
contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this),
// Move up and down the menu on up/down.
contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this),
// Hide the menu when clicking outside of it.
contentBox.on('focusoutside', this.hide, this),
// Hide the menu on left/right, and escape keys.
contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this)
);
},
/**
* Simulate other types of menu selection.
*
* @method _chooseMenuItem
* @param {EventFacade} e
*/
_chooseMenuItem: function(e) {
e.target.simulate('click');
e.preventDefault();
},
/**
* Hide a menu, removing all of the event handlers which trigger the hide.
*
* @method hide
* @param {EventFacade} e
*/
hide: function(e) {
if (this.get('preventHideMenu') === true) {
return;
}
// We must prevent the default action (left/right/escape) because
// there are other listeners on the toolbar which will focus on the
// editor.
if (e) {
e.preventDefault();
}
return Menu.superclass.hide.call(this, arguments);
},
/**
* Implement arrow-key navigation for the items in a toolbar menu.
*
* @method _handleKeyboardEvent
* @param {EventFacade} e The keyboard event.
* @static
*/
_handleKeyboardEvent: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// Get a list of all buttons in the menu.
var buttons = e.currentTarget.all('a[role="menuitem"]');
// On cursor moves we loops through the buttons.
var found = false,
index = 0,
direction = 1,
checkCount = 0,
current = e.target.ancestor('a[role="menuitem"]', true);
// Determine which button is currently selected.
while (!found && index < buttons.size()) {
if (buttons.item(index) === current) {
found = true;
} else {
index++;
}
}
if (!found) {
return;
}
if (e.keyCode === 38) {
// Moving up so reverse the direction.
direction = -1;
}
// Try to find the next
do {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
next = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we are not in a loop and have not already checked every button; and
// * we are on a different button; and
// * the next menu item is not hidden.
} while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
if (next) {
next.focus();
}
e.preventDefault();
e.stopImmediatePropagation();
}
}, {
NAME: "menu",
ATTRS: {
@@ -89,17 +247,6 @@ Y.Base.modifyAttrs(Menu, {
value: 'auto'
},
/**
* TODO - chekc that this is needed
* The footer content.
*
* @attribute footerContent
* @default ''
*/
footerContent: {
value: ''
},
/**
* When to hide this menu.
*
@@ -116,6 +263,62 @@ Y.Base.modifyAttrs(Menu, {
eventName: 'clickoutside'
}
]
},
/**
* The default list of extra classes for this menu.
*
* @attribute extraClasses
* @type Array
* @default editor_atto_menu
*/
extraClasses: {
value: [
'editor_atto_menu'
]
},
/**
* Override the responsive nature of the core dialogues.
*
* @attribute responsive
* @type boolean
* @default false
*/
responsive: {
value: false
},
/**
* The default visibility of the menu.
*
* @attribute visible
* @type boolean
* @default false
*/
visible: {
value: false
},
/**
* Whether to centre the menu.
*
* @attribute center
* @type boolean
* @default false
*/
center: {
value: false
},
/**
* Hide the close button.
* @attribute closeButton
* @type boolean
* @default false
*/
closeButton: {
value: false
}
});
@@ -178,20 +178,7 @@ var MENUTEMPLATE = '' +
'<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" '+
'style="background-color:{{config.menuColor}};" src="{{config.iconurl}}" />' +
'<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" src="{{image_url "t/expanded" "moodle"}}"/>' +
'</button>',
MENUDIALOGUE = '' +
'<div class="{{config.buttonClass}} atto_menu" ' +
'style="min-width:{{config.innerOverlayWidth}};">' +
'<ul class="menu">' +
'{{#each config.items}}' +
'<li role="presentation" class="atto_menuentry">' +
'<a href="#" role="menuitem" data-index="{{@index}}">' +
'{{{text}}}' +
'</a>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
'</button>';
var DISABLED = 'disabled',
HIGHLIGHT = 'highlight',
@@ -340,6 +327,7 @@ EditorPluginButtons.prototype = {
} else {
buttonClass = buttonClass + '_' + config.buttonName;
}
config.buttonClass = buttonClass;
// Normalize icon configuration.
config = this._normalizeIcon(config);
@@ -495,6 +483,7 @@ EditorPluginButtons.prototype = {
} else {
buttonClass = buttonClass + '_' + config.buttonName;
}
config.buttonClass = buttonClass;
// Normalize icon configuration.
config = this._normalizeIcon(config);
@@ -529,7 +518,7 @@ EditorPluginButtons.prototype = {
// Add the standard click handler to the menu.
this._buttonHandlers.push(
this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config),
this.toolbar.delegate('key', this._showToolbarMenu, '40, 32, enter', '.' + buttonClass, this, config)
this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config)
);
// Add the button reference to the buttons array for later reference.
@@ -575,45 +564,15 @@ EditorPluginButtons.prototype = {
}
config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em';
// Create the actual button.
var template = Y.Handlebars.compile(MENUDIALOGUE),
menu = Y.Node.create(template({
config: config
}));
this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config);
// Create the dialogue.
this.menus[config.buttonClass] = new M.core.dialogue({
bodyContent: menu,
width: null,
visible: false,
center: false,
closeButton: false,
responsive: false,
extraClasses: ['editor_atto_menu']
});
menuDialogue = this.menus[config.buttonClass];
// Hide the header node entirely.
menuDialogue.headerNode.hide();
// Handle menu item selection.
this._buttonHandlers.push(
menu.delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config, menuDialogue),
// Select the menu item on space, and enter.
menu.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this, config, menuDialogue),
// Move up and down the menu on up/down.
menu.delegate('key', this._menuKeyboardNavigation, 'down:38,40', '.menu', this),
// Hide the menu on left/right.
menu.delegate('key', this._hideMenu, 'down:37,39', '.menu', this, menuDialogue)
);
this.menus[config.buttonClass].get('contentBox').delegate('click',
this._chooseMenuItem, '.atto_menuentry a', this, config);
}
// Ensure that we focus on this button next time.
var creatorButton = this.buttons[config.buttonName];
creatorButton.focus();
this.get('host')._setTabFocus(creatorButton);
// Get a reference to the menu dialogue.
@@ -621,36 +580,31 @@ EditorPluginButtons.prototype = {
// Focus on the button by default after hiding this menu.
menuDialogue.set('focusAfterHide', creatorButton);
menuDialogue.on('focusAfterHide', function(e) {
console.log(e);
});
// Display the menu.
menuDialogue.show();
// Position it next to the button which opened it.
menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
// And focus on the first element in the menu.
menuDialogue.get('boundingBox').one('a').focus();
// Hide the menu when clicking outside of it.
this._menuHideHandlers.push(
menuDialogue.get('boundingBox').on('focusoutside', this._hideMenu, this, menuDialogue)
);
},
/**
* Hide a menu, removing all of the event handlers which trigger the hide.
* Display a toolbar menu and focus upon the first item.
*
* @method _hideMenu
* @method _showToolbarMenuAndFocus
* @param {EventFacade} e
* @param {M.core.dialogue} menuDialogue The Dialogue to hide.
* @param {object} config The configuration for the whole toolbar.
* @param {Number} [config.overlayWidth=14] The width of the menu
* @private
*/
_hideMenu: function(e, menuDialogue) {
if (menuDialogue.get('preventHideMenu') === true) {
return;
}
menuDialogue.hide();
new Y.EventHandle(this._menuHideHandlers).detach();
_showToolbarMenuAndFocus: function(e, config) {
this._showToolbarMenu(e, config);
// Focus on the first element in the menu.
this.menus[config.buttonClass].get('boundingBox').one('a').focus();
},
/**
@@ -663,12 +617,14 @@ EditorPluginButtons.prototype = {
* @private
*/
_chooseMenuItem: function(e, config, menuDialogue) {
// Get the index from the clicked anchor.
// Get the index from the clicked anchor.
var index = e.target.ancestor('a', true).getData('index'),
// And the normalized callback configuration.
buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig);
menuDialogue = this.menus[config.buttonClass];
// Prevent the dialogue to be closed because of some browser weirdness.
menuDialogue.set('preventHideMenu', true);
@@ -678,9 +634,10 @@ EditorPluginButtons.prototype = {
// Cancel the hide menu prevention.
menuDialogue.set('preventHideMenu', false);
console.log('Menu item chosen');
// Set the focus after hide so that focus is returned to the editor and changes are made correctly.
menuDialogue.set('focusAfterHide', this.get('host').editor);
this._hideMenu(e, menuDialogue);
menuDialogue.hide(e);
},
/**
@@ -982,71 +939,6 @@ EditorPluginButtons.prototype = {
return this;
},
/**
* Implement arrow-key navigation for the items in a toolbar menu.
*
* @method _menuKeyboardNavigation
* @param {EventFacade} e The keyboard event.
* @private
*/
_menuKeyboardNavigation: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// Get a list of all buttons in the menu.
var buttons = e.currentTarget.all('a[role="menuitem"]');
// On cursor moves we loops through the buttons.
var found = false,
index = 0,
direction = 1,
checkCount = 0,
current = e.target.ancestor('a[role="menuitem"]', true);
// Determine which button is currently selected.
while (!found && index < buttons.size()) {
if (buttons.item(index) === current) {
found = true;
} else {
index++;
}
}
if (!found) {
Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME);
return;
}
if (e.keyCode === 38) {
// Moving up so reverse the direction.
direction = -1;
}
// Try to find the next
do {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
next = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we are not in a loop and have not already checked every button; and
// * we are on a different button; and
// * the next menu item is not hidden.
} while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
if (next) {
next.focus();
}
},
/**
* Get the default meta key to use with keyboard events.
*
File diff suppressed because one or more lines are too long
@@ -178,20 +178,7 @@ var MENUTEMPLATE = '' +
'<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" '+
'style="background-color:{{config.menuColor}};" src="{{config.iconurl}}" />' +
'<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" src="{{image_url "t/expanded" "moodle"}}"/>' +
'</button>',
MENUDIALOGUE = '' +
'<div class="{{config.buttonClass}} atto_menu" ' +
'style="min-width:{{config.innerOverlayWidth}};">' +
'<ul class="menu">' +
'{{#each config.items}}' +
'<li role="presentation" class="atto_menuentry">' +
'<a href="#" role="menuitem" data-index="{{@index}}">' +
'{{{text}}}' +
'</a>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
'</button>';
var DISABLED = 'disabled',
HIGHLIGHT = 'highlight',
@@ -340,6 +327,7 @@ EditorPluginButtons.prototype = {
} else {
buttonClass = buttonClass + '_' + config.buttonName;
}
config.buttonClass = buttonClass;
// Normalize icon configuration.
config = this._normalizeIcon(config);
@@ -493,6 +481,7 @@ EditorPluginButtons.prototype = {
} else {
buttonClass = buttonClass + '_' + config.buttonName;
}
config.buttonClass = buttonClass;
// Normalize icon configuration.
config = this._normalizeIcon(config);
@@ -527,7 +516,7 @@ EditorPluginButtons.prototype = {
// Add the standard click handler to the menu.
this._buttonHandlers.push(
this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config),
this.toolbar.delegate('key', this._showToolbarMenu, '40, 32, enter', '.' + buttonClass, this, config)
this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config)
);
// Add the button reference to the buttons array for later reference.
@@ -573,45 +562,15 @@ EditorPluginButtons.prototype = {
}
config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em';
// Create the actual button.
var template = Y.Handlebars.compile(MENUDIALOGUE),
menu = Y.Node.create(template({
config: config
}));
this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config);
// Create the dialogue.
this.menus[config.buttonClass] = new M.core.dialogue({
bodyContent: menu,
width: null,
visible: false,
center: false,
closeButton: false,
responsive: false,
extraClasses: ['editor_atto_menu']
});
menuDialogue = this.menus[config.buttonClass];
// Hide the header node entirely.
menuDialogue.headerNode.hide();
// Handle menu item selection.
this._buttonHandlers.push(
menu.delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config, menuDialogue),
// Select the menu item on space, and enter.
menu.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this, config, menuDialogue),
// Move up and down the menu on up/down.
menu.delegate('key', this._menuKeyboardNavigation, 'down:38,40', '.menu', this),
// Hide the menu on left/right.
menu.delegate('key', this._hideMenu, 'down:37,39', '.menu', this, menuDialogue)
);
this.menus[config.buttonClass].get('contentBox').delegate('click',
this._chooseMenuItem, '.atto_menuentry a', this, config);
}
// Ensure that we focus on this button next time.
var creatorButton = this.buttons[config.buttonName];
creatorButton.focus();
this.get('host')._setTabFocus(creatorButton);
// Get a reference to the menu dialogue.
@@ -619,36 +578,31 @@ EditorPluginButtons.prototype = {
// Focus on the button by default after hiding this menu.
menuDialogue.set('focusAfterHide', creatorButton);
menuDialogue.on('focusAfterHide', function(e) {
console.log(e);
});
// Display the menu.
menuDialogue.show();
// Position it next to the button which opened it.
menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
// And focus on the first element in the menu.
menuDialogue.get('boundingBox').one('a').focus();
// Hide the menu when clicking outside of it.
this._menuHideHandlers.push(
menuDialogue.get('boundingBox').on('focusoutside', this._hideMenu, this, menuDialogue)
);
},
/**
* Hide a menu, removing all of the event handlers which trigger the hide.
* Display a toolbar menu and focus upon the first item.
*
* @method _hideMenu
* @method _showToolbarMenuAndFocus
* @param {EventFacade} e
* @param {M.core.dialogue} menuDialogue The Dialogue to hide.
* @param {object} config The configuration for the whole toolbar.
* @param {Number} [config.overlayWidth=14] The width of the menu
* @private
*/
_hideMenu: function(e, menuDialogue) {
if (menuDialogue.get('preventHideMenu') === true) {
return;
}
menuDialogue.hide();
new Y.EventHandle(this._menuHideHandlers).detach();
_showToolbarMenuAndFocus: function(e, config) {
this._showToolbarMenu(e, config);
// Focus on the first element in the menu.
this.menus[config.buttonClass].get('boundingBox').one('a').focus();
},
/**
@@ -661,12 +615,14 @@ EditorPluginButtons.prototype = {
* @private
*/
_chooseMenuItem: function(e, config, menuDialogue) {
// Get the index from the clicked anchor.
// Get the index from the clicked anchor.
var index = e.target.ancestor('a', true).getData('index'),
// And the normalized callback configuration.
buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig);
menuDialogue = this.menus[config.buttonClass];
// Prevent the dialogue to be closed because of some browser weirdness.
menuDialogue.set('preventHideMenu', true);
@@ -676,9 +632,10 @@ EditorPluginButtons.prototype = {
// Cancel the hide menu prevention.
menuDialogue.set('preventHideMenu', false);
console.log('Menu item chosen');
// Set the focus after hide so that focus is returned to the editor and changes are made correctly.
menuDialogue.set('focusAfterHide', this.get('host').editor);
this._hideMenu(e, menuDialogue);
menuDialogue.hide(e);
},
/**
@@ -978,70 +935,6 @@ EditorPluginButtons.prototype = {
return this;
},
/**
* Implement arrow-key navigation for the items in a toolbar menu.
*
* @method _menuKeyboardNavigation
* @param {EventFacade} e The keyboard event.
* @private
*/
_menuKeyboardNavigation: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// Get a list of all buttons in the menu.
var buttons = e.currentTarget.all('a[role="menuitem"]');
// On cursor moves we loops through the buttons.
var found = false,
index = 0,
direction = 1,
checkCount = 0,
current = e.target.ancestor('a[role="menuitem"]', true);
// Determine which button is currently selected.
while (!found && index < buttons.size()) {
if (buttons.item(index) === current) {
found = true;
} else {
index++;
}
}
if (!found) {
return;
}
if (e.keyCode === 38) {
// Moving up so reverse the direction.
direction = -1;
}
// Try to find the next
do {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
next = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we are not in a loop and have not already checked every button; and
// * we are on a different button; and
// * the next menu item is not hidden.
} while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
if (next) {
next.focus();
}
},
/**
* Get the default meta key to use with keyboard events.
*
+25 -133
View File
@@ -35,20 +35,7 @@ var MENUTEMPLATE = '' +
'<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" '+
'style="background-color:{{config.menuColor}};" src="{{config.iconurl}}" />' +
'<img class="icon" aria-hidden="true" role="presentation" width="16" height="16" src="{{image_url "t/expanded" "moodle"}}"/>' +
'</button>',
MENUDIALOGUE = '' +
'<div class="{{config.buttonClass}} atto_menu" ' +
'style="min-width:{{config.innerOverlayWidth}};">' +
'<ul class="menu">' +
'{{#each config.items}}' +
'<li role="presentation" class="atto_menuentry">' +
'<a href="#" role="menuitem" data-index="{{@index}}">' +
'{{{text}}}' +
'</a>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
'</button>';
var DISABLED = 'disabled',
HIGHLIGHT = 'highlight',
@@ -197,6 +184,7 @@ EditorPluginButtons.prototype = {
} else {
buttonClass = buttonClass + '_' + config.buttonName;
}
config.buttonClass = buttonClass;
// Normalize icon configuration.
config = this._normalizeIcon(config);
@@ -352,6 +340,7 @@ EditorPluginButtons.prototype = {
} else {
buttonClass = buttonClass + '_' + config.buttonName;
}
config.buttonClass = buttonClass;
// Normalize icon configuration.
config = this._normalizeIcon(config);
@@ -386,7 +375,7 @@ EditorPluginButtons.prototype = {
// Add the standard click handler to the menu.
this._buttonHandlers.push(
this.toolbar.delegate('click', this._showToolbarMenu, '.' + buttonClass, this, config),
this.toolbar.delegate('key', this._showToolbarMenu, '40, 32, enter', '.' + buttonClass, this, config)
this.toolbar.delegate('key', this._showToolbarMenuAndFocus, '40, 32, enter', '.' + buttonClass, this, config)
);
// Add the button reference to the buttons array for later reference.
@@ -432,45 +421,15 @@ EditorPluginButtons.prototype = {
}
config.overlayWidth = parseInt(config.overlayWidth, 10) + 'em';
// Create the actual button.
var template = Y.Handlebars.compile(MENUDIALOGUE),
menu = Y.Node.create(template({
config: config
}));
this.menus[config.buttonClass] = new Y.M.editor_atto.Menu(config);
// Create the dialogue.
this.menus[config.buttonClass] = new M.core.dialogue({
bodyContent: menu,
width: null,
visible: false,
center: false,
closeButton: false,
responsive: false,
extraClasses: ['editor_atto_menu']
});
menuDialogue = this.menus[config.buttonClass];
// Hide the header node entirely.
menuDialogue.headerNode.hide();
// Handle menu item selection.
this._buttonHandlers.push(
menu.delegate('click', this._chooseMenuItem, '.atto_menuentry a', this, config, menuDialogue),
// Select the menu item on space, and enter.
menu.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this, config, menuDialogue),
// Move up and down the menu on up/down.
menu.delegate('key', this._menuKeyboardNavigation, 'down:38,40', '.menu', this),
// Hide the menu on left/right.
menu.delegate('key', this._hideMenu, 'down:37,39', '.menu', this, menuDialogue)
);
this.menus[config.buttonClass].get('contentBox').delegate('click',
this._chooseMenuItem, '.atto_menuentry a', this, config);
}
// Ensure that we focus on this button next time.
var creatorButton = this.buttons[config.buttonName];
creatorButton.focus();
this.get('host')._setTabFocus(creatorButton);
// Get a reference to the menu dialogue.
@@ -478,36 +437,31 @@ EditorPluginButtons.prototype = {
// Focus on the button by default after hiding this menu.
menuDialogue.set('focusAfterHide', creatorButton);
menuDialogue.on('focusAfterHide', function(e) {
console.log(e);
});
// Display the menu.
menuDialogue.show();
// Position it next to the button which opened it.
menuDialogue.align(this.buttons[config.buttonName], [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
// And focus on the first element in the menu.
menuDialogue.get('boundingBox').one('a').focus();
// Hide the menu when clicking outside of it.
this._menuHideHandlers.push(
menuDialogue.get('boundingBox').on('focusoutside', this._hideMenu, this, menuDialogue)
);
},
/**
* Hide a menu, removing all of the event handlers which trigger the hide.
* Display a toolbar menu and focus upon the first item.
*
* @method _hideMenu
* @method _showToolbarMenuAndFocus
* @param {EventFacade} e
* @param {M.core.dialogue} menuDialogue The Dialogue to hide.
* @param {object} config The configuration for the whole toolbar.
* @param {Number} [config.overlayWidth=14] The width of the menu
* @private
*/
_hideMenu: function(e, menuDialogue) {
if (menuDialogue.get('preventHideMenu') === true) {
return;
}
menuDialogue.hide();
new Y.EventHandle(this._menuHideHandlers).detach();
_showToolbarMenuAndFocus: function(e, config) {
this._showToolbarMenu(e, config);
// Focus on the first element in the menu.
this.menus[config.buttonClass].get('boundingBox').one('a').focus();
},
/**
@@ -520,12 +474,14 @@ EditorPluginButtons.prototype = {
* @private
*/
_chooseMenuItem: function(e, config, menuDialogue) {
// Get the index from the clicked anchor.
// Get the index from the clicked anchor.
var index = e.target.ancestor('a', true).getData('index'),
// And the normalized callback configuration.
buttonConfig = this._normalizeCallback(config.items[index], config.globalItemConfig);
menuDialogue = this.menus[config.buttonClass];
// Prevent the dialogue to be closed because of some browser weirdness.
menuDialogue.set('preventHideMenu', true);
@@ -535,9 +491,10 @@ EditorPluginButtons.prototype = {
// Cancel the hide menu prevention.
menuDialogue.set('preventHideMenu', false);
console.log('Menu item chosen');
// Set the focus after hide so that focus is returned to the editor and changes are made correctly.
menuDialogue.set('focusAfterHide', this.get('host').editor);
this._hideMenu(e, menuDialogue);
menuDialogue.hide(e);
},
/**
@@ -839,71 +796,6 @@ EditorPluginButtons.prototype = {
return this;
},
/**
* Implement arrow-key navigation for the items in a toolbar menu.
*
* @method _menuKeyboardNavigation
* @param {EventFacade} e The keyboard event.
* @private
*/
_menuKeyboardNavigation: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// Get a list of all buttons in the menu.
var buttons = e.currentTarget.all('a[role="menuitem"]');
// On cursor moves we loops through the buttons.
var found = false,
index = 0,
direction = 1,
checkCount = 0,
current = e.target.ancestor('a[role="menuitem"]', true);
// Determine which button is currently selected.
while (!found && index < buttons.size()) {
if (buttons.item(index) === current) {
found = true;
} else {
index++;
}
}
if (!found) {
Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME);
return;
}
if (e.keyCode === 38) {
// Moving up so reverse the direction.
direction = -1;
}
// Try to find the next
do {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
next = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we are not in a loop and have not already checked every button; and
// * we are on a different button; and
// * the next menu item is not hidden.
} while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
if (next) {
next.focus();
}
},
/**
* Get the default meta key to use with keyboard events.
*
+223 -19
View File
@@ -23,6 +23,20 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
var MENUDIALOGUE = '' +
'<div class="open {{config.buttonClass}} atto_menu" ' +
'style="min-width:{{config.innerOverlayWidth}};">' +
'<ul class="dropdown-menu">' +
'{{#each config.items}}' +
'<li role="presentation" class="atto_menuentry">' +
'<a href="#" role="menuitem" data-index="{{@index}}" {{#each data}}data-{{@key}}="{{this}}"{{/each}}>' +
'{{{text}}}' +
'</a>' +
'</li>' +
'{{/each}}' +
'</ul>' +
'</div>';
/**
* A Menu for the Atto editor used in Moodle.
*
@@ -41,24 +55,169 @@ Menu = function() {
Y.extend(Menu, M.core.dialogue, {
/**
* A list of the menu handlers which have been attached here.
*
* @property _menuHandlers
* @type Array
* @private
*/
_menuHandlers: null,
initializer: function(config) {
var body, headertext, bb;
Menu.superclass.initializer.call(this, config);
var headertext,
bb;
this._menuHandlers = [];
// Create the actual button.
var template = Y.Handlebars.compile(MENUDIALOGUE),
menu = Y.Node.create(template({
config: config
}));
this.set('bodyContent', menu);
bb = this.get('boundingBox');
bb.addClass('editor_atto_controlmenu');
// Close the menu when clicked outside (excluding the button that opened the menu).
body = this.bodyNode;
bb.addClass('editor_atto_menu');
bb.one('.moodle-dialogue-wrap')
.removeClass('moodle-dialogue-wrap')
.addClass('moodle-dialogue-content');
headertext = Y.Node.create('<h3/>')
.addClass('accesshide')
.setHTML(this.get('headerText'));
body.prepend(headertext);
this.get('bodyContent').prepend(headertext);
this.get('hideOn');
// Hide the header and footer node entirely.
this.headerNode.hide();
this.footerNode.hide();
this._setupHandlers();
},
/**
* Setup the Event handlers.
*
* @method _setupHandlers
* @private
*/
_setupHandlers: function() {
var contentBox = this.get('contentBox');
// Handle menu item selection.
this._menuHandlers.push(
// Select the menu item on space, and enter.
contentBox.delegate('key', this._chooseMenuItem, '32, enter', '.atto_menuentry', this),
// Move up and down the menu on up/down.
contentBox.delegate('key', this._handleKeyboardEvent, 'down:38,40', '.dropdown-menu', this),
// Hide the menu when clicking outside of it.
contentBox.on('focusoutside', this.hide, this),
// Hide the menu on left/right, and escape keys.
contentBox.delegate('key', this.hide, 'down:37,39,esc', '.dropdown-menu', this)
);
},
/**
* Simulate other types of menu selection.
*
* @method _chooseMenuItem
* @param {EventFacade} e
*/
_chooseMenuItem: function(e) {
e.target.simulate('click');
e.preventDefault();
},
/**
* Hide a menu, removing all of the event handlers which trigger the hide.
*
* @method hide
* @param {EventFacade} e
*/
hide: function(e) {
if (this.get('preventHideMenu') === true) {
return;
}
// We must prevent the default action (left/right/escape) because
// there are other listeners on the toolbar which will focus on the
// editor.
if (e) {
e.preventDefault();
}
return Menu.superclass.hide.call(this, arguments);
},
/**
* Implement arrow-key navigation for the items in a toolbar menu.
*
* @method _handleKeyboardEvent
* @param {EventFacade} e The keyboard event.
* @static
*/
_handleKeyboardEvent: function(e) {
// Prevent the default browser behaviour.
e.preventDefault();
// Get a list of all buttons in the menu.
var buttons = e.currentTarget.all('a[role="menuitem"]');
// On cursor moves we loops through the buttons.
var found = false,
index = 0,
direction = 1,
checkCount = 0,
current = e.target.ancestor('a[role="menuitem"]', true);
// Determine which button is currently selected.
while (!found && index < buttons.size()) {
if (buttons.item(index) === current) {
found = true;
} else {
index++;
}
}
if (!found) {
Y.log("Unable to find this menu item in the menu", 'debug', LOGNAME);
return;
}
if (e.keyCode === 38) {
// Moving up so reverse the direction.
direction = -1;
}
// Try to find the next
do {
index += direction;
if (index < 0) {
index = buttons.size() - 1;
} else if (index >= buttons.size()) {
// Handle wrapping.
index = 0;
}
next = buttons.item(index);
// Add a counter to ensure we don't get stuck in a loop if there's only one visible menu item.
checkCount++;
// Loop while:
// * we are not in a loop and have not already checked every button; and
// * we are on a different button; and
// * the next menu item is not hidden.
} while (checkCount < buttons.size() && next !== current && next.hasAttribute('hidden'));
if (next) {
next.focus();
}
e.preventDefault();
e.stopImmediatePropagation();
}
}, {
NAME: "menu",
ATTRS: {
@@ -87,17 +246,6 @@ Y.Base.modifyAttrs(Menu, {
value: 'auto'
},
/**
* TODO - chekc that this is needed
* The footer content.
*
* @attribute footerContent
* @default ''
*/
footerContent: {
value: ''
},
/**
* When to hide this menu.
*
@@ -114,6 +262,62 @@ Y.Base.modifyAttrs(Menu, {
eventName: 'clickoutside'
}
]
},
/**
* The default list of extra classes for this menu.
*
* @attribute extraClasses
* @type Array
* @default editor_atto_menu
*/
extraClasses: {
value: [
'editor_atto_menu'
]
},
/**
* Override the responsive nature of the core dialogues.
*
* @attribute responsive
* @type boolean
* @default false
*/
responsive: {
value: false
},
/**
* The default visibility of the menu.
*
* @attribute visible
* @type boolean
* @default false
*/
visible: {
value: false
},
/**
* Whether to centre the menu.
*
* @attribute center
* @type boolean
* @default false
*/
center: {
value: false
},
/**
* Hide the close button.
* @attribute closeButton
* @type boolean
* @default false
*/
closeButton: {
value: false
}
});
+67
View File
@@ -1804,3 +1804,70 @@ body.lockscroll {
height: 100%;
overflow: hidden;
}
/* Dropdown Menu styling */
.dropdown-menu {
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
list-style: none;
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.dropdown-menu > li > a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 20px;
white-space: nowrap;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
text-decoration: none;
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
text-decoration: none;
outline: 0;
}
.dropdown-menu > .disabled > a:hover,
.dropdown-menu > .disabled > a:focus {
text-decoration: none;
background-color: transparent;
background-image: none;
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
cursor: default;
}
.open {
*z-index: 1000;
}
.open > .dropdown-menu {
display: block;
}
.editor_atto_menu .moodle-dialogue-content {
padding: 0px;
border: inherit;
}