MDL-44067 Atto: Improve table editing controls.

Remove flaky inline menu icons - instead clicking on the table button, when
you are in a table shows the context menu for the current cell.

I removed all the special code for 'atto_control' - because it did not work very well. Having non-contenteditable
nodes inside a contenteditable region seems in expose bugs in different browsers.
This commit is contained in:
Damyon Wiese 2014-02-10 22:06:58 +08:00
parent 5ec54dd125
commit 05843fd3ee
9 changed files with 112 additions and 154 deletions

View File

@ -137,7 +137,7 @@ M.atto_table = M.atto_table || {
});
}
// We store the cell of the last click (the control node is transient).
this.lasttarget = e.target.ancestor('td, th');
this.lasttarget = e.target.ancestor('.editor_atto_content td, .editor_atto_content th', true);
this.controlmenu.show();
this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
var bodynode = this.controlmenu.get('boundingBox');
@ -417,6 +417,36 @@ M.atto_table = M.atto_table || {
M.editor_atto.text_updated(elementid);
},
/**
* Handle a button click - this will either open the new table dialogue,
* or the edit table context menu.
*
* @method handle_button
* @param Y.Event event
* @param string elementid
*/
handle_button : function(event, elementid) {
var selection = M.editor_atto.get_selection_parent_node();
var editable = M.editor_atto.get_editable_node(elementid);
var cell;
if (!selection) {
return M.atto_table.display_chooser(event, elementid);
}
Y.one(selection).ancestors('th, td', true).each(function(node) {
if (editable.contains(node)) {
cell = node;
}
});
if (cell) {
event.target = cell;
return M.atto_table.show_menu(event, elementid);
}
return M.atto_table.display_chooser(event, elementid);
},
/**
* Handle a selection from the table control menu.
*
@ -481,45 +511,14 @@ M.atto_table = M.atto_table || {
}
var iconurl = M.util.image_url('e/table', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.display_chooser);
M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.handle_button);
var contenteditable = M.editor_atto.get_editable_node(params.elementid);
contenteditable.delegate('click', this.show_menu, 'td > .atto_control, th > .atto_control', this, params.elementid);
contenteditable.delegate('key', this.show_menu, 'down:enter,space', 'td > .atto_control, th > .atto_control', this, params.elementid);
// Disable mozilla table controls.
if (Y.UA.gecko) {
document.execCommand("enableInlineTableEditing", false, "false");
document.execCommand("enableInlineTableEditing", false, false);
document.execCommand("enableObjectResizing", false, false);
}
this.insert_table_controls(params.elementid);
// Re-add the table controls whenever the content is updated.
M.editor_atto.add_text_updated_handler(params.elementid, this.insert_table_controls);
},
/**
* Add the table editing controls to the content area.
*
* @method insert_table_controls
* @param String elementid - The id of the text area backed by the content editable field.
*/
insert_table_controls : function(elementid) {
var contenteditable = M.editor_atto.get_editable_node(elementid),
allcells = contenteditable.all('td .atto_control,th .atto_control'),
cells = contenteditable.all('td:last-child,th:last-child,tbody tr:last-child > td, tbody tr:last-child > th');
allcells.each(function(node) {
if (cells.indexOf(node) === -1) {
node.remove(true);
}
});
cells.each(function(node) {
if (!node.one('.atto_control')) {
node.append(M.atto_table.menunode.cloneNode(true));
}
}, this);
},
/**

File diff suppressed because one or more lines are too long

View File

@ -137,7 +137,7 @@ M.atto_table = M.atto_table || {
});
}
// We store the cell of the last click (the control node is transient).
this.lasttarget = e.target.ancestor('td, th');
this.lasttarget = e.target.ancestor('.editor_atto_content td, .editor_atto_content th', true);
this.controlmenu.show();
this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
var bodynode = this.controlmenu.get('boundingBox');
@ -417,6 +417,36 @@ M.atto_table = M.atto_table || {
M.editor_atto.text_updated(elementid);
},
/**
* Handle a button click - this will either open the new table dialogue,
* or the edit table context menu.
*
* @method handle_button
* @param Y.Event event
* @param string elementid
*/
handle_button : function(event, elementid) {
var selection = M.editor_atto.get_selection_parent_node();
var editable = M.editor_atto.get_editable_node(elementid);
var cell;
if (!selection) {
return M.atto_table.display_chooser(event, elementid);
}
Y.one(selection).ancestors('th, td', true).each(function(node) {
if (editable.contains(node)) {
cell = node;
}
});
if (cell) {
event.target = cell;
return M.atto_table.show_menu(event, elementid);
}
return M.atto_table.display_chooser(event, elementid);
},
/**
* Handle a selection from the table control menu.
*
@ -481,45 +511,14 @@ M.atto_table = M.atto_table || {
}
var iconurl = M.util.image_url('e/table', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.display_chooser);
M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.handle_button);
var contenteditable = M.editor_atto.get_editable_node(params.elementid);
contenteditable.delegate('click', this.show_menu, 'td > .atto_control, th > .atto_control', this, params.elementid);
contenteditable.delegate('key', this.show_menu, 'down:enter,space', 'td > .atto_control, th > .atto_control', this, params.elementid);
// Disable mozilla table controls.
if (Y.UA.gecko) {
document.execCommand("enableInlineTableEditing", false, "false");
document.execCommand("enableInlineTableEditing", false, false);
document.execCommand("enableObjectResizing", false, false);
}
this.insert_table_controls(params.elementid);
// Re-add the table controls whenever the content is updated.
M.editor_atto.add_text_updated_handler(params.elementid, this.insert_table_controls);
},
/**
* Add the table editing controls to the content area.
*
* @method insert_table_controls
* @param String elementid - The id of the text area backed by the content editable field.
*/
insert_table_controls : function(elementid) {
var contenteditable = M.editor_atto.get_editable_node(elementid),
allcells = contenteditable.all('td .atto_control,th .atto_control'),
cells = contenteditable.all('td:last-child,th:last-child,tbody tr:last-child > td, tbody tr:last-child > th');
allcells.each(function(node) {
if (cells.indexOf(node) === -1) {
node.remove(true);
}
});
cells.each(function(node) {
if (!node.one('.atto_control')) {
node.append(M.atto_table.menunode.cloneNode(true));
}
}, this);
},
/**

View File

@ -135,7 +135,7 @@ M.atto_table = M.atto_table || {
});
}
// We store the cell of the last click (the control node is transient).
this.lasttarget = e.target.ancestor('td, th');
this.lasttarget = e.target.ancestor('.editor_atto_content td, .editor_atto_content th', true);
this.controlmenu.show();
this.controlmenu.align(e.target, [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]);
var bodynode = this.controlmenu.get('boundingBox');
@ -415,6 +415,36 @@ M.atto_table = M.atto_table || {
M.editor_atto.text_updated(elementid);
},
/**
* Handle a button click - this will either open the new table dialogue,
* or the edit table context menu.
*
* @method handle_button
* @param Y.Event event
* @param string elementid
*/
handle_button : function(event, elementid) {
var selection = M.editor_atto.get_selection_parent_node();
var editable = M.editor_atto.get_editable_node(elementid);
var cell;
if (!selection) {
return M.atto_table.display_chooser(event, elementid);
}
Y.one(selection).ancestors('th, td', true).each(function(node) {
if (editable.contains(node)) {
cell = node;
}
});
if (cell) {
event.target = cell;
return M.atto_table.show_menu(event, elementid);
}
return M.atto_table.display_chooser(event, elementid);
},
/**
* Handle a selection from the table control menu.
*
@ -479,45 +509,14 @@ M.atto_table = M.atto_table || {
}
var iconurl = M.util.image_url('e/table', 'core');
M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.display_chooser);
M.editor_atto.add_toolbar_button(params.elementid, 'table', iconurl, params.group, this.handle_button);
var contenteditable = M.editor_atto.get_editable_node(params.elementid);
contenteditable.delegate('click', this.show_menu, 'td > .atto_control, th > .atto_control', this, params.elementid);
contenteditable.delegate('key', this.show_menu, 'down:enter,space', 'td > .atto_control, th > .atto_control', this, params.elementid);
// Disable mozilla table controls.
if (Y.UA.gecko) {
document.execCommand("enableInlineTableEditing", false, "false");
document.execCommand("enableInlineTableEditing", false, false);
document.execCommand("enableObjectResizing", false, false);
}
this.insert_table_controls(params.elementid);
// Re-add the table controls whenever the content is updated.
M.editor_atto.add_text_updated_handler(params.elementid, this.insert_table_controls);
},
/**
* Add the table editing controls to the content area.
*
* @method insert_table_controls
* @param String elementid - The id of the text area backed by the content editable field.
*/
insert_table_controls : function(elementid) {
var contenteditable = M.editor_atto.get_editable_node(elementid),
allcells = contenteditable.all('td .atto_control,th .atto_control'),
cells = contenteditable.all('td:last-child,th:last-child,tbody tr:last-child > td, tbody tr:last-child > th');
allcells.each(function(node) {
if (cells.indexOf(node) === -1) {
node.remove(true);
}
});
cells.each(function(node) {
if (!node.one('.atto_control')) {
node.append(M.atto_table.menunode.cloneNode(true));
}
}, this);
},
/**

View File

@ -90,7 +90,7 @@ M.editor_atto = M.editor_atto || {
buttonhandlers : {},
/**
* List of attached handlers to add inline editing controls to content.
* List of attached handlers.
*/
textupdatedhandlers : {},
@ -288,7 +288,6 @@ M.editor_atto = M.editor_atto || {
/**
* Add a content update handler to be called whenever the content is updated.
* This is used to add inline editing controls to the content that are cleaned on submission.
*
* @param string elementid - the id of the textarea we created this editor from.
* @handler function callback - The function to do the cleaning.
@ -659,10 +658,6 @@ M.editor_atto = M.editor_atto || {
}
});
Y.each(atto.all('.atto_control'), function(node) {
node.remove(true);
});
// Remove any and all nasties from source.
atto.cleanHTML();
@ -1012,6 +1007,8 @@ CONTROLMENU = function(config) {
config.width = 'auto';
config.lightbox = false;
config.footerContent = '';
config.hideOn = [ { eventName: 'clickoutside' } ];
CONTROLMENU.superclass.constructor.apply(this, [config]);
};
@ -1037,16 +1034,6 @@ Y.extend(CONTROLMENU, M.core.dialogue, {
headertext.addClass('accesshide');
headertext.setHTML(this.get('headerText'));
body.prepend(headertext);
body.on('clickoutside', function(e) {
if (this.get('visible')) {
// Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
if (!e.target.ancestor('.atto_control')) {
e.preventDefault();
this.hide();
}
}
}, this);
}
}, {

File diff suppressed because one or more lines are too long

View File

@ -90,7 +90,7 @@ M.editor_atto = M.editor_atto || {
buttonhandlers : {},
/**
* List of attached handlers to add inline editing controls to content.
* List of attached handlers.
*/
textupdatedhandlers : {},
@ -288,7 +288,6 @@ M.editor_atto = M.editor_atto || {
/**
* Add a content update handler to be called whenever the content is updated.
* This is used to add inline editing controls to the content that are cleaned on submission.
*
* @param string elementid - the id of the textarea we created this editor from.
* @handler function callback - The function to do the cleaning.
@ -659,10 +658,6 @@ M.editor_atto = M.editor_atto || {
}
});
Y.each(atto.all('.atto_control'), function(node) {
node.remove(true);
});
// Remove any and all nasties from source.
atto.cleanHTML();
@ -1012,6 +1007,8 @@ CONTROLMENU = function(config) {
config.width = 'auto';
config.lightbox = false;
config.footerContent = '';
config.hideOn = [ { eventName: 'clickoutside' } ];
CONTROLMENU.superclass.constructor.apply(this, [config]);
};
@ -1037,16 +1034,6 @@ Y.extend(CONTROLMENU, M.core.dialogue, {
headertext.addClass('accesshide');
headertext.setHTML(this.get('headerText'));
body.prepend(headertext);
body.on('clickoutside', function(e) {
if (this.get('visible')) {
// Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
if (!e.target.ancestor('.atto_control')) {
e.preventDefault();
this.hide();
}
}
}, this);
}
}, {

View File

@ -16,6 +16,8 @@ CONTROLMENU = function(config) {
config.width = 'auto';
config.lightbox = false;
config.footerContent = '';
config.hideOn = [ { eventName: 'clickoutside' } ];
CONTROLMENU.superclass.constructor.apply(this, [config]);
};
@ -41,16 +43,6 @@ Y.extend(CONTROLMENU, M.core.dialogue, {
headertext.addClass('accesshide');
headertext.setHTML(this.get('headerText'));
body.prepend(headertext);
body.on('clickoutside', function(e) {
if (this.get('visible')) {
// Note: we need to compare ids because for some reason - sometimes button is an Object, not a Y.Node.
if (!e.target.ancestor('.atto_control')) {
e.preventDefault();
this.hide();
}
}
}, this);
}
}, {

View File

@ -88,7 +88,7 @@ M.editor_atto = M.editor_atto || {
buttonhandlers : {},
/**
* List of attached handlers to add inline editing controls to content.
* List of attached handlers.
*/
textupdatedhandlers : {},
@ -286,7 +286,6 @@ M.editor_atto = M.editor_atto || {
/**
* Add a content update handler to be called whenever the content is updated.
* This is used to add inline editing controls to the content that are cleaned on submission.
*
* @param string elementid - the id of the textarea we created this editor from.
* @handler function callback - The function to do the cleaning.
@ -657,10 +656,6 @@ M.editor_atto = M.editor_atto || {
}
});
Y.each(atto.all('.atto_control'), function(node) {
node.remove(true);
});
// Remove any and all nasties from source.
atto.cleanHTML();