Conditionally displaying React components with hover and timeouts - javascript

I ran into a tricky React layout problem. It's a simple enough problem, just hard to explain so I tried to be as explicit as possible. I have some data which maps individual components like this:
map => <TableRow name={props.name} actionOne={props.someAction} />
Name
Type
Show/Hide Controls
Buttons (hidden by default)
John Deere
Tractor
show/hide
<div style="hidden"><button id="actionOneButton"></div>
John Smith
Person
show/hide
<div style="hidden"><button id="actionOneButton"></div>
The control buttons are hidden by default. The idea here is to show the button tools only when the user dwells on a certain row OR if the user decides to have some of the control buttons permanently visible on certain rows
We need two things to happen:
When entering a table and hovering over a row, there is a 500msec delay, after which the action buttons only for that row appear. If at this point after that we move the cursor up or down to different rows within the, there is no delay, and the newly hovered rows reveal their corresponding button immediately and the previously shown button in the previous row is hidden immediately
When a user moves the cursor outside the table, there is a countdown of 500msec. During this time, a button that's last been displayed on a row, stays displayed. Once the timer is up, the row which last had a revealed button, now hides it. Think of it as "show on hover," but with a 500msec delay upon entering and exiting the table.
Almost done!
One caveat: clicking on show link will display the button in that row permanently, while the hide link returns the button to the original condition by hiding it. (We're back to #1)
Manually shown buttons stick around permanently until closed, at which point they behave the same as at the start.
Important note:
If the cursor exits the table and then hovers back inside the table before the "exit timer" ticks down:
Previously highlighted row displaying buttons remains visible while the cursor is outside the table; it is then hidden when the exit timeout of 500ms is reached.
Meanwhile, as the above is timing out and is about to disappear, the cursor which has now entered the table again initiates a 500ms count to show the hidden button of the row it happens to have re-entered at. At this point #1 times out and hides and if hovered over from inside the table, would appear instantly as per the first set of criteria in the beginning: any hidden button in a row instantly shows up if the "entry" 500ms gate is passed.
Questions
I have some loose ideas but what comes to mind in designing something like this so that all of the state and timeouts (is that even the way to go) are encapsulated in a maximum of two components - something like a table and rows?
How do I design this component functionality? Am I barking up the wrong tree here and can the show/hide be done by clever CSS?

I believe this could be a possible approach to a working solution. With some more tweaking, I think it can achieve the required behavior you outlined.
This captures the last element the cursor left the table on, from the list of rows, by using the mouse coordinate at the point of leaving the table. In the example the exited element will remain visible, but you can decide how to handle it.
[...table.children[0].children].forEach(tr => {
tr.classList.remove('exited');
if(evt.offsetY >= tr.offsetTop
&& evt.offsetY <= tr.offsetTop + tr.clientHeight
){
tr.classList.add('exited'); // in react you could instead set this element to state.
}
});
Hope it won't be an issue translating to JSX. You can keep a reference to the table's DOM element with refObject.
const table = document.getElementById("table");
let hoverTimer = 0;
table.addEventListener("mouseenter", () => {
clearTimeout(hoverTimer);
hoverTimer = setTimeout(() =>
table.classList.add('active'),
500
);
});
table.addEventListener("mouseleave", (evt) => {
[...table.children[0].children].forEach(tr => {
tr.classList.remove('exited');
if(evt.offsetY >= tr.offsetTop
&& evt.offsetY <= tr.offsetTop + tr.clientHeight
){
tr.classList.add('exited');
}
});
clearTimeout(hoverTimer);
hoverTimer = setTimeout(() =>
table.classList.remove('active'),
500
);
});
tr.table-row button.action-btn {
pointer-events: none;
}
table.active tr.table-row:hover button.action-btn {
pointer-events: auto;
}
tr.table-row {
opacity: 0;
transition: opacity 0.5s;
}
table.active tr.table-row:hover {
opacity: 1;
}
tr.exited {
opacity: 1;
}
<table id="table">
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
<tr class="table-row"><td><button class="action-btn">click me!</button></td></tr>
</table>

I would go about this the following way
Table States:
lastHovered - the id of the last hovered row (or null)
hoverTimerId - the timer id of the current timer
hoverRowId - the row id of the current timer
Row Props:
isLastHovered - boolean, true when this is the last shown prop
Row State:
alwaysShow - boolean, true when the always show controls button is selected
Hover Event for rows / outside the table
//can be called with null for outside hover or rowId for row hover
onRowHover = (rowId) => {
if(rowId === hoverRowId)
return
if(hoverTimerId !== null)
clearTimeout(hoverTimerId)
const timerId = setTimeout(() => {
setLastHovered(rowId)
}, 500)
setHoverTimerId(timerId)
setHoverRowId(rowId)
}
Finally, make sure the controlls are shown if the isLastHovered prop is true, or the always show state is true.
(The isLastHovered state is controlled by the show/hide button| in the row)

Related

Using Conditional If statements with JavaScript DOM Table?

I have been given a table that has been created using the DOM and now I have to use if statements to print specific areas of that table. For example in the second photo, when i click 1 - 2 million, it should show the table but only display the countries that have a population that's between 1 and 2 million. My teacher has barely taught us JavaScript deeply and now gives us a DOM assignment that uses JavaScript If Statements. I would appreciate if someone could give an explanation on how i can print specific parts of the table when i click the links/button above. Thanks!
Here a roadmap:
Loop through your submenus with a for (or for ... in) statement and attach a click event listener on each one with addEventListener()
In the callback, this will refer to the <li> (or <a>, or whatever) element you clicked (and which is linked to an event). So you can access the DOM clicked element's data nor attributes.
In function of the clicked submenu, filter your <table> the way you want thanks to if statements. (even better: switch statement) Visually, rows will be hidden. In Javascript, you will update style attribute of the element.
Below an example. I propose to you to try to do it yourself with elements I gave you. Open the snippet if you are really lost.
Exemple:
Other functions/methods/statements I used below: querySelectorAll(), dataset, instanceof, parseInt(), onload, children
// Always interact with the DOM when it is fully loaded.
window.onload = () => {
// Gets all <button> with a "data-filter-case" attribute.
const buttons = document.querySelectorAll('button[data-filter-case]');
// For each <button>, attach a click event listener.
for (let i in buttons) {
const button = buttons[i];
// The last item of "buttons" is its length (number), so we have to check if
// it is a HTMLElement object.
if (button instanceof HTMLElement) {
button.addEventListener('click', filterTable); // Will execute the "filterTable" function on click.
}
}
}
// The function which will filter the table.
function filterTable(e) {
// Useless in my exemple, but if you have <a> instead of <button>,
// it will not execute its original behavior.
e.preventDefault();
// Get the value of "data-filter-case" attribute.
const filterCase = this.dataset.filterCase;
// Get all table body rows.
const tableRows = document.querySelectorAll('table > tbody > tr');
// Update display style of each row in function of the case.
for (let i in tableRows) {
const row = tableRows[i];
if (row instanceof HTMLElement) {
if (filterCase === 'more-than-44') {
if (parseInt(row.children[1].innerText) <= 44) {
// Hide the row.
row.style.display = 'none';
} else {
// Reset display style value.
row.style.display = '';
}
} else if (filterCase === 'less-than-27') {
if (parseInt(row.children[1].innerText) >= 27) {
row.style.display = 'none';
} else {
row.style.display = '';
}
} else if (filterCase === 'reset') {
row.style.display = '';
}
}
}
}
<button type="button" data-filter-case="more-than-44">More than 44</button>
<button type="button" data-filter-case="less-than-27">Less than 27</button>
<button type="button" data-filter-case="reset">Show all</button>
<table>
<thead>
<tr>
<th>ID</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<th>1</th>
<td>45</td>
</tr>
<tr>
<th>2</th>
<td>50</td>
</tr>
<tr>
<th>3</th>
<td>24</td>
</tr>
</tbody>
</table>

ScrollTo fires too early resulting in not scrolling all the way

I am making an Angular application which shows a table with a single tr. This row contains multiple td's which contain data. the table is built up like this:
<div class="col" id="TableCol">
<table id="Table">
<tbody>
<tr>
<td *ngFor="let item of items;">
<div
(click)="ItemSelected(item)"
draggable="true"
[class.selected]="item.id == selecteditem?.id"
(dragstart)="dragStart($event, item)"
(drop)="dropItem($event, item)"
(dragover)="dragoverItem($event, item)">
{{item.description}}
</div>
</td>
</tr>
</tbody>
</table>
</div>
The table is scrollable when it overflows its X value
#TableCol{ overflow-x: scroll; }
Now i have a function which adds a td at the right side of this tr.
When this function is called an extra td shows up into my table and scrolling works fine.
The thing I want to achieve is that the table automatically scrolls all the way to the right when I add a new td.
I've tried to call this function right AFTER I've added the new item to the items array.
this.items.push(item);
scrollRight() {
document.querySelector('#mapLocationTableCol').scrollLeft = 10000;}
and
scrollRight() {
document.querySelector('#mapLocationTableCol').scrollTo(10000, 0);}
Both these give the same result:
They scroll my row all the way to the right except for the last element.
I think this is due to the scrollRight() being called before the table is redrawn.
Anyone have a solution to make it scroll after the table is drawn?
edit: I've made a stackblitz example:
https://stackblitz.com/edit/angular-d6lm6k
You should monitor the creation of the table cell with ViewChildren and the QueryList.changes event. In the markup, set a template reference variable on the td elements:
<td #cells *ngFor="let item of items;">
In the code, use ViewChildren to get the list of these elements, and subscribe to the QueryList.changes event in ngAfterViewInit. If a new cell was added, do the scrolling. In the code below, I set a flag to make sure that automatic scrolling is performed only when desired.
#ViewChildren("cells") cells: QueryList<ElementRef>;
private shouldScrollRight = false;
...
addItem() {
this.shouldScrollRight = true;
this.items.push(item);
}
ngAfterViewInit() {
this.cells.changes.subscribe((cellList) => {
if (this.shouldScrollRight) {
this.shouldScrollRight = false;
this.scrollRight();
}
});
}
See this stackblitz for a demo.

Use jQuery ID Selector with CSS

I'm actually creating a list with streamers and checking if they're online, if so they'll get a green background color.
Now I want to create an additional list next to my "team list" which includes every streamer.
For this I'm trying to use the same method like the one I'm using for the background color.
$(".".concat(user)).css("background-color","lightgrey");
(to color every background color for the streamers)
But now I want to just hide the streamers in one list and not everywhere.
So I can't use this:
$(".".concat(user)).css("display", "none");
(would hide every offline streamer in every list)
I already tried to apply the ID but actually I don't know how.
If I use this:
$("#online".concat(user)).css("display", "none");
nothing happens.
(I named my ID online)
<tr class="header" id ="online">
(I already tried to apply the id to a new , to the and now to the but nothing works)
TL;DR:
I want to hide offline streamers in a new list but I can't use the same ID selector as I used before. How can I do it?
Not much to start with, but if you have a structure like
<table>
<tr class="header Peter" id ="onlinePeter">
<td>Peter</td>
</tr>
<tr class="header John" id ="onlineJohn">
<td>John</td>
</tr>
<tr class="header Michael" id ="onlineMichael">
<td>Michael</td>
</tr>
<tr class="header Bob" id ="onlineBob">
<td>Bob</td>
</tr>
<tr class="header Daniel" id ="onlineDaniel">
<td>Daniel</td>
</tr>
</table>
and a script like
var user = "John";
$("#online".concat(user)).css("display", "none");
user = "Daniel"
$(".".concat(user)).css("background","lightgrey");
It works. the row that contains John disappears
On the other piece of code where you try to change the background color, you are calling an element with the class = user, but the HTML you provided only has the class "header", once the class is added, the background changes as well.
I looked at the jsfiddle you linked and got a better understanding of what is happening.
The server returns an empty stream all the time, so the code never goes into the "true" part of the statement. Furthermore, in the "false" part of the statement, the code changes the background color and then hides the table cell.
function colorize(user){
$.getJSON('https://api.twitch.tv/kraken/streams/'+user, function(channel) {
/*
* Stream always returns as null
*/
console.log(channel["stream"]); // returns null in all tests
var data = channel["stream"]; // data == null
var boolAnswer = ((typeof(data) !== 'undefined') && (data !== null));
console.log(data);
if ( boolAnswer === true) {
$( ".".concat(user) ).css("background-color", "lightgreen");
/*
* boolAnswer is always null, so this piece is skipped all the time
*/
}
else if ( boolAnswer === false){
/*
* First it changes the color and then it hides it
*/
$(".".concat(user)).css("background-color","lightgrey");
$(".".concat(user)).css("display", "none");
}
}).success(function(response){
console.log(response);
});
}
colorize('jankos'); //hides the cell of jankos
colorize(); // doesn't do anything
if you comment the line that says
$(".".concat(user)).css("display", "none");
colorize('jankos'); //changes the color of the cell of jankos
colorize() // still doesn't do anything

Changing hidden to visible state of Table > TR on click

I've a table with almost 100 rows, I'm displaying 20 as default by adding a class to the tr as visible and hiding the rest of rows by the class hidden
<tbody>
<tr class="visible"></tr>
<tr class="visible"></tr>
<tr class="hidden"></tr>
<tr class="hidden"></tr>
<tr class="hidden"></tr>
</tbody>
I've added an Add More button to display 5 rows each time the button is clicked but my jQuery logic is completely wrong, Have a look at it
$(".more-show").click(function (e) {
e.preventDefault();
for (var i = 0; i<5; i++) {
$('#ranking-table tr').each(function(i) {
$(this).removeClass("hidden").addClass("visible");
});
}
});
PROBLEM
Instead of displaying 5 rows each time upon click and it has to be the very first 5 hidden rows, It's displaying all the rows by changing the class to visible
You could use the selector $('#ranking-table tr.hidden:lt(5)') to select the first 5 tr elements with class .hidden. It makes use of :lt(5).
Example Here
$(".more-show").click(function (e) {
e.preventDefault();
$('#ranking-table tr.hidden:lt(5)').each(function(i) {
$(this).removeClass("hidden").addClass("visible");
});
});

How to block editing on certain part of content in CKEDITOR textarea?

I have my CKEDITOR form prepopulated with hidden table which is being submitted together with user inputed text. This works fine, but sometimes user presses backspace too many times and deletes the hidden table.
Is there a way to block editing on this hidden table inside ckeditor textarea? So when user presses backspace the hidden table isn't affected and stays in.
As soon as CKEDITOR instance is ready this source (bellow) is put inside CkEditor Textarea (using setData() attribute) and User only sees the returned <p></p> value. In this case its <p>I really think I can do this!</p>. Its a description of his profile and he can keep it and edit it. The rest is hidden and only visible in e-mail when form is submitted. Its strange that <p></p> is on top but if user presses Backspace couple times the table gets deleted and therefor not submitted.
<span id="messageTemplate1" class="message">
<p>I really think I can do this!</p>
<table class="hide" style="font-size: 12px;">
<tbody>
<tr class="hide">
<td>
Application sent by <strong>Matt Faro</strong> for Audition: Actors Needed
</td>
</tr>
<tr class="hide">
<td>
Reply to applicant directly: mantas#mantas.co or visit full profile: http://www.globalcastingcenter.com/talent/jack-bolton
</td>
</tr>
</tbody>
</table>
<table class="hide" style="font-size: 12px;">
<tbody>
<tr class="hide">
<td><strong>Short Profile Summary:</strong></td>
</tr>
</tbody>
</table>
<table class="hide" style="font-size: 12px;">
<tbody>
<tr class="hide">
<td>
<img alt="" src="http://globalcastingcenter.com/talent_images/4164035_258551_foto.png?Action=thumbnail&Width=144&Height=215" />
</td>
</tr>
</tbody>
</table>
<table style="font-size: 12px;" class="hide">
<tbody>
<tr class="hide">
<td><strong>Areas:</strong></td>
<td>Actor,Extra</td>
</tr>
<tr class="hide">
<td><strong>Country:</strong></td>
<td>WORLDWIDE,Any</td>
</tr>
<tr class="hide">
<td><strong>Age:</strong></td>
<td>26</td>
</tr>
</tbody>
</table>
</span>
Now when I load your plugin my CKeditor box disapears, please press "Apply" on testing page http://gcc-july.themantas.co.uk/auditions/actors-needed please login first to be able to access the message box Login name: tiknius#gmail.com pssw: test
My config file:
CKEDITOR.editorConfig = function( config )
{
config.toolbar = 'MyToolbar';
config.toolbar_MyToolbar =
[
{ name: 'clipboard', items : [ 'Undo','Redo' ] },
{ name: 'styles', items : ['FontSize' ] },
{ name: 'basicstyles', items : [ 'Bold','Italic'] },
{ name: 'paragraph', items : ['Outdent','Indent' ] },
];
config.removePlugins = 'contextmenu';
config.forcePasteAsPlainText = true;
config.pasteFromWordRemoveFontStyles = true;
config.pasteFromWordRemoveStyles = true;
config.extraPlugins = 'cwjdsjcsconfineselection';
config.startupShowBorders = false;
config.disableObjectResizing = true;
};
This is how the box looks when I disable your plugin: http://screencast.com/t/Kc2bIOU8md2
I use your suggested HTML structure.
I had to play around with it a bit to get it to work. I added lots of documentation to the plugin code, if you have any questions after reading it through let me know.
I'm including an updated version of your content block and the plugin code block.
Here is your updated content block. It wasn't working when wrapped in the <span> tag, so I wrapped it in a table.
You may not like the border and resizing outlines that appear around the data cell, if that's the case, add these settings to your configuration:
config.startupShowBorders = false;
config.disableObjectResizing = true;
Some notes:
The empty <td> before your starting content is needed, it prevents the user from using "Ctrl A" to select everything which would allow them to delete the hidden table.
I removed the <p> tag from the starting content as it acted funky in this structure.
The <td> that holds the hidden tables has a character, it prevents the user from using "Ctrl A" to select everything which would allow them to delete the hidden table. It causes the cursor to get lost if you delete everything to the right of the cursor, but you can click on the content to begin editing again.
The contenteditable="false" attribute is used by CkEditor and is needed, but it doesn't do the whole job. You can try out the new HTML without activating the plugin to see what effect it has by itself.
There are notes in the plugin code about the classes and ID I used.
<!-- Begin Wrapper Table that Replaces <span> element -->
<table id="messageTemplate1" class="message cwjdsjcs_editable">
<tbody>
<tr>
<td class="cwjdsjcs_not_editable" contenteditable="false">
</td>
<td id="cwjdsjcs_editable_id">
I really think I can do this!
</td>
</tr>
<tr class="cwjdsjcs_not_editable" contenteditable="false">
<td colspan="2">
<!-- Begin Original Content -->
<table class="hide" style="font-size: 12px; display:none;">
<tbody>
<tr class="hide">
<td>
Application sent by <strong>Matt Faro</strong> for Audition: Actors Needed
</td>
</tr>
<tr class="hide">
<td>
Reply to applicant directly: mantas#mantas.co or visit full profile: http://www.globalcastingcenter.com/talent/jack-bolton
</td>
</tr>
</tbody>
</table>
<table class="hide" style="font-size: 12px; display:none;">
<tbody>
<tr class="hide">
<td><strong>Short Profile Summary:</strong></td>
</tr>
</tbody>
</table>
<table class="hide" style="font-size: 12px; display:none;">
<tbody>
<tr class="hide">
<td>
<img alt="" src="http://globalcastingcenter.com/talent_images/4164035_258551_foto.png?Action=thumbnail&Width=144&Height=215" />
</td>
</tr>
</tbody>
</table>
<table style="font-size: 12px; display:none;" class="hide">
<tbody>
<tr class="hide">
<td><strong>Areas:</strong></td>
<td>Actor,Extra</td>
</tr>
<tr class="hide">
<td><strong>Country:</strong></td>
<td>WORLDWIDE,Any</td>
</tr>
<tr class="hide">
<td><strong>Age:</strong></td>
<td>26</td>
</tr>
</tbody>
</table>
<!-- End Original Content -->
</td>
</tr>
</tbody>
</table>
<!-- End Wrapper Table that Replaces <span> element -->
Here's the plugin code, it's called "cwjdsjcsconfineselection".
To add the plugin:
Create a folder called "cwjdsjcsconfineselection" in the plugins directory: ckeditor/plugins/
Create a file called "plugins.js" in that directory and paste the code below into that file. My mistake: file is named plugin.js, not plugin(s).js.
If you already have extra plugins, add "cwjdsjcsconfineselection" to the extraPlugins config setting, otherwise add this setting to your configuration:
config.extraPlugins = 'cwjdsjcsconfineselection';
The plugin should work the next time you load the editor.
For my situation, I have a dialog box appear when the user clicks in a non-editable area to explain why the cursor was moved back to the previous selection. That doesn't seem necessary for your usage, so I commented it out.
/*
Plugin that prevents editing of elements with the "non-editable" class as well as elements outside of blocks with "editable" class.
*/
//* ************************** NOTES *************************** NOTES ****************************
/*
The "lastSelectedElement" variable is used to store the last element selected.
This plugin uses the "elementspath" plugin which shows all elements in the DOM
parent tree relative to the current selection in the editing area.
When the selection changes, "elementsPathUpdate" is fired,
we key on this and loop through the elements in the tree checking the classes assigned to each element.
Three outcomes are possible.
1) The non-editable class is found:
Looping stops, the current action is cancelled and the cursor is moved to the previous selection.
The "selectionChange" hook is fired to set the reverted selection throughout the instance.
2) The editable class is found during looping, the "in_editable_area" flag is set to true.
3) Neither the editable or the non-editable classes are found (user clicked outside your main container).
The "in_editable_area" flag remains set to false.
If the "in_editable_area" flag is false, the current action is cancelled and the cursor is moved to the previous location.
The "selectionChange" hook is fired to set the reverted selection throughout the instance.
If the "in_editable_area" flag is true,
the "lastSelectedElement" is updated to the currently selected element and the plugin returns true.
---------------
If you don't want the elements path to be displayed at the bottom of the editor window,
you can hide it with CSS rather than disabling the "elementspath" plugin.
The elementspath plugin creates and is left active because we are keying on changes to the path in our plugin.
#cke_path_content
{
visibility: hidden !important;
}
---------------
CSS Classes and ID that the plugin keys on. Use defaults or update variables to use your preferred classes and ID:
var starting_element_id = ID of known editable element that always occurs in the instance.
Don't use elements like <table>, <tr>, <br /> that don't contain HTML text.
Default value = cwjdsjcs_editable_id
var editable_class = class of editable containers.
Should be applied to all top level elements that contain editable elements.
Default = cwjdsjcs_editable
var non_editable_class = class of non-editable elements within editable containers
Apply to elements where all child elements are non-editable.
Default = cwjdsjcs_not_editable
*/
//* ************************** END NOTES *************************** END NOTES ****************************
// Register the plugin with the editor.
// http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.plugins.html
CKEDITOR.plugins.add( 'cwjdsjcsconfineselection',
{
requires : [ 'elementspath' ],
// The plugin initialization logic goes inside this method.
// http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.pluginDefinition.html#init
init: function( editor )
{
editor.on( 'instanceReady', function( instance_ready_data )
{
// Create variable that will hold the last allowed selection (for use when a non-editable selection is made)
var lastSelectedElement;
editor.cwjdsjcs_just_updated = false;
// This section starts things off right by selecting a known editable element.
// *** Enter the ID of the element that should have initial focus *** IMPORTANT *** IMPORTANT ***
var starting_element_id = "cwjdsjcs_editable_id";
var resInitialRange = new CKEDITOR.dom.range( editor.document );
resInitialRange.selectNodeContents( editor.document.getById( starting_element_id ) );
resInitialRange.collapse();
var selectionObject = new CKEDITOR.dom.selection( editor.document );
editor.document.focus();
selectionObject.selectRanges( [ resInitialRange ] );
var sel = editor.getSelection();
var firstElement = sel.getStartElement();
var currentPath = new CKEDITOR.dom.elementPath( firstElement );
// Set path for known editable element, fire "selectionChange" hook to update selection throughout instance.
editor._.selectionPreviousPath = currentPath;
editor.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } );
}); // *** END - editor.on( 'instanceReady', function( e )
// When a new element is selected by the user, check if it's ok for them to edit it,
// if not move cursor back to last know editable selection
editor.on( 'elementsPathUpdate', function( resPath )
{
// When we fire the "selectionChange" hook at the end of this code block, the "elementsPathUpdate" hook fires.
// No need to check because we just updated the selection, so bypass processing.
if( editor.cwjdsjcs_just_updated == true )
{
editor.cwjdsjcs_just_updated = false;
return true;
}
var elementsList = editor._.elementsPath.list;
var in_editable_area = false;
var non_editable_class = "cwjdsjcs_not_editable";
var editable_class = "cwjdsjcs_editable";
for(var w=0;w<elementsList.length;w++){
var currentElement = elementsList[w];
// Sometimes a non content element is selected, catch them and return selection to editable area.
if(w == 0)
{
// Could change to switch.
if( currentElement.getName() == "tbody" )
{
in_editable_area = false;
break;
}
if( currentElement.getName() == "tr" )
{
in_editable_area = false;
break;
}
}
// If selection is inside a non-editable element, break from loop and reset selection.
if( currentElement.hasClass(non_editable_class) )
{
in_editable_area = false;
break;
}
if( currentElement.hasClass(editable_class) ) {
in_editable_area = true;
}
console.log(currentElement);
console.log(currentElement.getName());
}
// if selection is within an editable element, exit the plugin, otherwise reset selection.
if( in_editable_area ) {
lastSelectedElement = elementsList[0];
return true;
}
var resRange = new CKEDITOR.dom.range( editor.document );
resRange.selectNodeContents( lastSelectedElement );
resRange.collapse();
editor.getSelection().selectRanges( [ resRange ] );
resRange.endContainer.$.scrollIntoView();
// Open dialog window:
// It tells user they selected a non-editable area and cursor has been returned to previous selection
// currentEditorName = editor.name;
// openResDefaultDialog(currentEditorName);
try
{
var sel = editor.getSelection();
var firstElement = sel.getStartElement();
var currentPath = new CKEDITOR.dom.elementPath( firstElement );
editor.cwjdsjcs_just_updated = true;
editor._.selectionPreviousPath = currentPath;
editor.fire( 'selectionChange', { selection : sel, path : currentPath, element : firstElement } );
}
catch (e)
{}
});
} // *** END - init: function( editor )
}); // ************************************************************************************* END - CKEDITOR.plugins.add
To test that the plugin is loaded add an alert after the instance ready trigger:
editor.on( 'instanceReady', function( instance_ready_data )
{
alert("instanceReady");
To test that the plugin is being triggered when the selection changes, add an alert after the elementsPathUpdate trigger:
editor.on( 'elementsPathUpdate', function( resPath )
{
alert("elementsPathUpdate");
I realize that this is closed and solved, but here's an option:
Add the table just in time after the user submits your form or however your CKE content is being used. Simply don't add the invisible table, but when the user clicks "submit", add it then to whatever is being posted. Alternatively, if you need to edit it later, just remove it before inserting to the editor and then add it once again Just In Time before posting. No hacking of the CKE core and no plugins required as the action happens outside CKE.
I had a similar requirement but didn't find my answer online.
In my case, I want to support the tab character in the editor by adding a pre element to the content:
editor.on('key', function(ev) {
if (ev.data.keyCode == 9) { // TAB
// data-tab attribute added so we can identify it later
var tabHtml = '<pre data-tab="true" style="display:inline;"> </pre>';
var tabElement = CKEDITOR.dom.element.createFromHtml(tabHtml, editor.document);
editor.insertElement(tabElement);
ev.cancel();
}
});
But now the user can use the arrow keys or click the mouse and start editing inside the pre element. So I listen to the 'click' and 'keyup' events and move the cursor if they are inside the pre.
// this prevents the user from typing inside the tab span
var moveCursorOutOfTab = function() {
var ranges = editor.getSelection().getRanges();
if (ranges.length == 1) {
var parent = ranges[0].startContainer.getParent();
// is the cursor is inside a tab element
if (parent && parent.getAttribute('data-tab') == 'true') {
var newRange = editor.createRange();
newRange.setStartAfter(parent);
newRange.setEndAfter(parent);
// move the cursor after the tab element
editor.getSelection().selectRanges([newRange]);
}
}
};
// wait until the editor is initialized
editor.on('contentDom', function() {
var editable = editor.editable();
// listen for clicks
editable.attachListener(editable, 'click', function(ev) {
moveCursorOutOfTab();
});
// must use the keyup event because we want to
// act after any cursor movements
editor.document.on('keyup', function(ev) {
// check for left and right arrow keys
if (ev.data.getKey() == 37 || ev.data.getKey() == 39) {
moveCursorOutOfTab();
}
});
});

Categories