I need to be able to get the coordinates of the caret (as in the blinking cursor line) on the page. The intention is then I am going to overlay a tooltip-like element absolutely positioned to look like a tooltip is appearing below the caret.
I already am getting the range (CKEDITOR.dom.range) from the selection before this runs, and the range is always collapsed, so the method I'm writing needs to get the position for a collapsed range.
This is what I have so far:
/**
* Get the position of a range
*
* #param {CKEDITOR.dom.range} range The range (i.e. indicating where the caret is)
* #returns {void}
*/
this.getRangePosition = function( range ){
/* Start with wherever we are scrolled to */
var top = (window.pageYOffset || document.documentElement.scrollTop) - (document.documentElement.clientTop || 0);
var left = (window.pageXOffset || document.documentElement.scrollLeft) - (document.documentElement.clientLeft || 0);
/* Figure out where the containing element (usually the <p>) is and add that's position on */
var containingElement = range.startContainer.getParent(); // We know range range.startContainer is always text so getParent() takes us to the element
var rect = containingElement.getClientRect();
top += rect.top + rect.height;
left += rect.left;
/* And then figure out how far along we are... */
};
The problem is I'm not sure how to figure out what to do from there. How I have it now gets the correct value for top but not for left as I'm not sure to figure out how far along the range is from the outside of the element.
Any ideas on how I could achieve this?
For anyone else coming across this, I ended up doing this:
Create a snapshot of the editor and a bookmark for the current selection
Insert an element, then use jQuery's .offset() method on that tp figure out the position
Restore the snapshot (without that element) and the selection bookmark
var snapshot = editor.getSnapshot();
var bookmarks = editor.getSelection().createBookmarks2( true );
var fakeElement = new CKEDITOR.dom.element( 'span' );
editor.insertElement( fakeElement );
var position = $(fakeElement.$).offset();
editor.loadSnapshot( snapshot );
editor.focus();
editor.getSelection().selectBookmarks( bookmarks );
Related
This is a script I am writing to help me figure out how to write a more complex script.
In my spreadsheet, I have one column that contains a list of values ("testRange","testRangeValues"). I also have a dropdown list ("testCell","testCellValue"). The dropdown list contains some values that are in the "testRange" list and some that are not.
I would like my script to function in such a way that when I select a value from the dropdown list that matches value from the testRange list, the background color of the corresponding value in the testRange list changes to red.
function onEdit3(e) {
var testRange = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A4:A8");
var testRangeValues = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("A4:A8").getValues();
var testCell = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("C4");
var testCellValue = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("C4").getValue();
for(var i = 0;i<testRange.length;i++){
if(testCellValue==testRangeValues[i]){
testRange[i].setBackground("Red");
}
}
}
Currently no color change is happening. I guess I'm not quite sure how to correctly write my for loop/if statement to achieve the intended effect. Is there perhaps a way to do this using just some sort of conditional formatting formula in my spreadsheet?
You want to change the background color of cell in the range of "A4:A8" that the value of dropdown list of "C4" is the same.
You want to achieve this using Google Apps Script.
From your script, you don't want to use the event object.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
Modification points:
The sheet object can be written by var sheet = SpreadsheetApp.getActiveSheet();. By this, you can use sheet for retrieving the range.
sheet.getRange("A4:A8") and sheet.getRange("C4") can be used one time.
In this case, the range object cannot be used in the loop, because testRange.length returns null.
When above points are reflected to your script, it becomes as follows.
Pattern 1:
In this pattern, only background color of the selected value is changed to the red color. So for example, when "A" is selected, the background color of "A" is changed to the red color. And when "C" is selected, the background color of "C" is changed to the red color. In this case, the background color of "A" is reset.
Modified script:
function onEdit3(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var testRange = sheet.getRange("A4:A8");
var testRangeValues = testRange.getValues();
var testCell = sheet.getRange("C4");
var testCellValue = testCell.getValue();
var backgroundColors = testRangeValues.map(function([a]) {return [a == testCellValue ? "Red" : ""]});
testRange.setBackgrounds(backgroundColors);
}
Pattern 2:
In this pattern, the background color of the selected value is saved. So for example, when "A" is selected, the background color of "A" is changed to the red color. And when "C" is selected, the background color of "C" is changed to the red color. In this case, the background color of "A" is kept the red color.
Modified script:
function onEdit3(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var testRange = sheet.getRange("A4:A8");
var testRangeValues = testRange.getValues();
var testCell = sheet.getRange("C4");
var testCellValue = testCell.getValue();
for (var i = 0; i < testRangeValues.length; i++) {
if (testRangeValues[i] == testCellValue) {
sheet.getRange("A" + (4 + i)).setBackground("Red");
break;
}
}
}
Modern javascript:
You can use modern javascript syntax. For this you need to enable V8 runtime. After this you can
/**
*
* #param {GoogleAppsScript.Events.SheetsOnEdit} e
*/
const onEdit = e => {
const sheet = e.range.getSheet();
const value = e.range.getValue();
if (sheet.getName() == 'Sheet1' && e.range.getA1Notation() === 'C2') {
const range = sheet.getRange(2, 1, sheet.getLastRow() - 1, 1);
range.setBackgrounds(
range.getValues().map(row => [row[0] === value ? 'red' : ''])
);
}
};
References:
setBackground(color)
setBackgrounds(color)
If I misunderstood your question and this was not the direction you want, I apologize.
I do a growth animation of a fixed number of items, and after growth, i moved it to left.
Make growth by apply matrix
var trans_vector = new THREE.Matrix4().makeTranslation(0, height / 2, 0);
var graphics = new Graphics();
var rectangle = graphics.box(width, height, material);
rectangle.geometry.applyMatrix(trans_vector);
When a new item is added, i remove one from the container that will be added to scene
var children = this.container.children;
if (this.current_number === this.max_number) {
this.container.remove(children[0]);
this.current_number = this.max_number - 1;
}
object.position.copy(this.position); // this is a fixed position
this.container.add(object);
this.current_number++;
I write a function to translate to left, using tweenjs (sole)
animateTranslation : function(object, padding, duration) {
var new_x = object.position.x - padding; // Move to left
console.log(new_x); // Duplicated item here :(
new TWEEN.Tween(object.position).to({
x : new_x
}, duration).start();
},
And I remove all the "previous" items, using for loop
for (var i = 0; i < this.current_number-1; i++) {
this.animateTranslation(this.container.children[i],
this.padding,
this.duration/4)
}
The above code run correctly if we open and keep this current tab.
The problem is when we move to another tab, do something and then move back, some objects have the same position, that cause the translation to the same position. It looks weird.
It appears on both Chrome, Firefox and IE11.
I dont know why, please point me out what happened.
Most modern browsers choose to limit the delay in setInterval and pause requestAnimationFrame in inactive tabs. This is done in order to preserve CPU cycles and to reduce power consumption, especially important for mobile devices. You can find more details about each browser in this answer
That means, while the tab is inactive, your tween animation is not working the way it is normally expected to.
A simple solution would be to halt the main animation loop if the tab is inactive using a variable.
window.onfocus = function () {
isActive = true;
};
window.onblur = function () {
isActive = false;
};
At the moment i have the following problem, i'm applying span tags with the applyStyle Method from CKEDITOR 4.x. But when a span is partial selected and i execute the applyStyle method a new span will be made with the selection, but the other half of the selected span isn't restored and loses his span.
First Question: Is it possible to prevent partial selection of a certain element?
IF NOT My Second Question: Is it possible to extend the Selection only on one side, the side where the span(With a certain class or attribute) is partial selected. So that it will be fully selected for processing.
A Example:
This is 'my text <span class"testClass">, This' is </span> Other Text
And now we want a solution to create:
This is <span class"testClass2"> my text, This</span> <span class"testClass"> is </span> Other Text
Please take notice of the following:
The hard part in this is to maintain the html structure. when half of the selection is in an other block level element, it may not brake! That is the reason that i started using the applyStyle method.
First Question: Is it possible to prevent partial selection of a certain element?
Hmm... You can check placeholder plugin's sample - it uses non-editable inline elements to create those placeholders which at least on Chrome cannot be partially selected. Though, I think it's not a satisfying solution for you :)
Another possible solution is using editor#selectionChange event on which you can check if one of selection ends is located inside that element and if yes, set it before or after that element. It'd look like (I haven't tested this code, it's just a proto):
editor.on( 'selectionChange', function( evt ) {
var sel = evt.data.selection,
range = sel.getRanges()[ 0 ];
if ( protectedElement.contains( range.startContainer ) || protectedElement.equals( range.startContainer ) )
range.setStartAt( protectedElement, CKEDITOR.POSITION_BEFORE_START );
if ( protectedElement.contains( range.endContainer ) || protectedElement.equals( range.endContainer ) )
range.setEndAt( protectedElement, CKEDITOR.POSITION_AFTER_END );
sel.selectRanges( [ range ] );
} );
Although, this kind of solutions are always dangerous and can cause many unpredictable situations. But it may be worth checking it.
Back to the root of your problem - I understand that you want to create styles which work on the same level - i.e. only one can be applied in one place. This isn't possible using styling system. You would have to prepare range before applying style. The code would be similar to the selectionChange listener - you check if ends are anchored in style element, if yes you need to move range's ends out of it. The only question is how to exclude entire element from range in this situation:
<p>foo[bar<span class="st1">bom</span>bim]foo</p>
The result should be two ranges:
<p>foo[bar]<span class="st1">bom</span>[bim]foo</p>
Unfortunately current range's API does not include helpful method like range#exclude, therefore you need to implement yours. I would try doing this with walker. Iterate from range's start to end and remember all style elements. If you'll do this in both directions you'll gather also partially selected elements on both ends, so the first step I described will be unnecessary. When you'll have list of elements which you want to exclude from range, then you just need to create ranges at both ends and between these elements - this part should be easy. Element#getPosition will be helpful, but you'll need to check its code to understand how to use it because it isn't documented.
I have been looking and trying for hours. And chose to make an enlarge function myself to expand the selection. I made my own enlarge/expand function as i wanted to have more control which the enlarge of CKEDITOR doesn't provide.
The code:
//Vars
var firstNode = range.startContainer.getParent();
var lastNode = range.endContainer.getParent();
//Make end Get full if is tcElement
if(lastNode.type === CKEDITOR.NODE_ELEMENT && lastNode.getName() === "myElement")
{
range.setEndAfter(lastNode);
}
//Make end Get full if is tcElement
if(firstNode.type === CKEDITOR.NODE_ELEMENT && firstNode.getName() === "myElement")
{
range.setStartBefore(firstNode);
}
range.select();
Other nice piece of code, which isn't very hard but can be useful for other people.
This code i used to split the code in 2 or 3 parts.. where part 1 and 3 are the partial selection if existed.
Spliting to multiple ranges
//Vars
var newRanges = [];
var allWithinRangeParent = range.getCommonAncestor().getChildren();
var firstNode = range.startContainer;
var lastNode = range.endContainer;
var firstNodeStart = range.startOffset;
var lastNodeEnd = range.endOffset;
//TODO make if to check if this needs to be made.
//make end partial
var newEndRange = new CKEDITOR.dom.range( editor.document );
newEndRange.selectNodeContents( lastNode );
newEndRange.endOffset = lastNodeEnd;
newRanges.push(newEndRange);
//TODO make if to check if this needs to be made.
//Make start partial
var newStartRange = new CKEDITOR.dom.range( editor.document );
newStartRange.selectNodeContents( firstNode );
newStartRange.startOffset = firstNodeStart;
newRanges.push(newStartRange);
//Make center selection.
var tempRange = new CKEDITOR.dom.range( editor.document );
tempRange.setStartBefore(firstNode.getParent().getNext());
tempRange.setEndAfter(lastNode.getParent().getPrevious());
newRanges.push(tempRange);
selection.selectRanges(newRanges);
How can I get information about the states of styles present on the toolbar, at the current cursor position.
The documentation is completely silent on this issue. As far as I can tell from digging into the source code, CKEditor doesn't keep an internal log of what the styles are at the current position. It simply recalculates them on an as-needed basis, namely whenever it needs to add new styles to a selection.
Please keep in mind that CKEditor is actually building and modifying an entire DOM tree, and so the styles it applies cascade down the nodes. It appears that the only way you can pull the style information is to traverse up the DOM tree from your current cursor position, recording the style information from each ancestor until you reach the body node of the editor.
The following code should get you started traversing up the ancestor nodes:
//Or however you get your current editor
var editor = CKEDITOR.currentInstance;
//This will pull the minimum ancestor that encompasses the entire selection,
//so if you just want to use the cursor it will give you the direct parent
//node that the cursor is inside
var node = editor.getSelection().getCommonAncestor();
//This is all the ancestors, up to the document root
var ancestors = node.getParents();
//This is the editors body node; you don't want to go past this
var editor_body = editor.getBody();
var body_ancestors = editor_body.getParents();
//The ancestors list descends from the root node, whereas we want
//to ascend towards the root
for (var i = ancestors.length - 1; i >= 0; i--;) {
//Pull the node
var a = ancestors[i];
//You've hit the body node, break out of the loop
if (a.getText() == editor_body.getText()) break;
//This is a node between the cursor's node and the editor body,
//pull your styling information from the node here
}
Thanks to the customizability of CKEditors style interface, there isn't a single set of styles that can be checked for, nor do they follow the same form (for instance, some will be CSS styles, while others will be span elements with a particular class).
My suggestion is to check for just those styles which you actually care about, and ignore the rest. It'll make the code much simpler.
Here is another way (based on a few attached links).
You can get the current element position by editor.getSelection().getStartElement() - (editor is CKEDITOR.instances.%the editor instance%.
Now, you can then wrap the actual element for jquery (or use the jquery adapter..):
$(editor.getSelection().getStartElement().$)
This will give you an access to use the following plugin which resolves all the styles of a given element (both inline and inherited):
/*
* getStyleObject Plugin for jQuery JavaScript Library
* From: http://upshots.org/?p=112
*
* Copyright: Unknown, see source link
* Plugin version by Dakota Schneider (http://hackthetruth.org)
*/
(function($){
$.fn.getStyleObject = function(){
var dom = this.get(0);
var style;
var returns = {};
if(window.getComputedStyle){
var camelize = function(a,b){
return b.toUpperCase();
}
style = window.getComputedStyle(dom, null);
for(var i=0;i<style.length;i++){
var prop = style[i];
var camel = prop.replace(/\-([a-z])/g, camelize);
var val = style.getPropertyValue(prop);
returns[camel] = val;
}
return returns;
}
if(dom.currentStyle){
style = dom.currentStyle;
for(var prop in style){
returns[prop] = style[prop];
}
return returns;
}
return this.css();
}
})(jQuery);
(Taken from: jQuery CSS plugin that returns computed style of element to pseudo clone that element?)
All that is left to do is:
$(editor.getSelection().getStartElement().$).getStyleObject()
Now you can check for any style asigned to the element.
Another small tip will be - what are the styles for the current cursor position, every time the position or styles are changed:
In which case you can use attachStyleStateChange callback (which is pretty atrophied by itself since is can only return boolean indication for weather or not a certain style is applied to current position).
The good thing about it is - callback is being recieved when ever the style state is changed - that is - whenever the cursor position is moved to a position with different style attributes - Any different attribute and not just the attribute the listener was ment to verify (Taken from the API http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.editor.html#attachStyleStateChange)
Combining everything together to figure out what is the current applied styles on the current cursor position Every time something is changed:
editor.on('instanceReady', function () {
//editor.setReadOnly(true);
var styleBold = new CKEDITOR.style(CKEDITOR.config.coreStyles_bold);
editor.attachStyleStateChange(styleBold, function (state) {
var currentCursorStyles = $(editor.getSelection().getStartElement().$).getStyleObject();
// For instance, the font-family is:
var fontFamily = currentCursorStyles.fontFamily;
});
});
Is there a way to set the cursor to be at the end of the contents of a CKEditor?
This developer asked too, but received no answers:
http://cksource.com/forums/viewtopic.php?f=11&t=19877&hilit=cursor+end
I would like to set the focus at the end of the text inside a CKEditor. When I use:
ckEditor.focus();
It takes me to the beginning of the text already inside the CKEditor.
Dan's answer got strange results for me, but minor change (in addition to typo fix) made it work:
var range = me.editor.createRange();
range.moveToElementEditEnd( range.root );
me.editor.getSelection().selectRanges( [ range ] );
According to the documentation for CKEditor 4, you can do the following once you have the editor object.
var range = editor.createRange();
range.moveToPosition( range.root, CKEDITOR.POSITION_BEFORE_END );
editor.getSelection().selectRanges( [ range ] );
Link: http://docs.ckeditor.com/#!/api/CKEDITOR.dom.selection (under selectRanges function).
After a bit of fiddling, I've got it to work with the following code:
$(document).ready(function() {
CKEDITOR.on('instanceReady', function(ev) {
ev.editor.focus();
var s = ev.editor.getSelection(); // getting selection
var selected_ranges = s.getRanges(); // getting ranges
var node = selected_ranges[0].startContainer; // selecting the starting node
var parents = node.getParents(true);
node = parents[parents.length - 2].getFirst();
while (true) {
var x = node.getNext();
if (x == null) {
break;
}
node = x;
}
s.selectElement(node);
selected_ranges = s.getRanges();
selected_ranges[0].collapse(false); // false collapses the range to the end of the selected node, true before the node.
s.selectRanges(selected_ranges); // putting the current selection there
}
});
The idea is:
Get the root node (not body)
Advance to next node, until there are no more nodes to advance to.
Select last node.
Collapse it
Set range
Here's a similar answer to #peter-tracey. In my case my plugin is inserting a citation. If the user has made a selection, I needed to disable the selection and place the cursor at the end of the sentence.
// Obtain the current selection & range
var selection = editor.getSelection();
var ranges = selection.getRanges();
var range = ranges[0];
// Create a new range from the editor object
var newRange = editor.createRange();
// assign the newRange to move to the end of the current selection
// using the range.endContainer element.
var moveToEnd = true;
newRange.moveToElementEditablePosition(range.endContainer, moveToEnd);
// change selection
var newRanges = [newRange];
selection.selectRanges(newRanges);
// now I can insert html without erasing the previously selected text.
editor.insertHtml("<span>Hello World!</span>");
CKEditor 3.x:
on : {
'instanceReady': function(ev) {
ev.editor.focus();
var range = new CKEDITOR.dom.range( ev.editor.document );
range.collapse(false);
range.selectNodeContents( ev.editor.document.getBody() );
range.collapse(false);
ev.editor.getSelection().selectRanges( [ range ] );
}
}
based on pseudo-code provided by the developers here:
https://dev.ckeditor.com/ticket/9546#comment:3
You have to focus editor, get document object, put it in range,
collapse range (with false parameter), select body (with
selectNodeContents), collapse it (with false parameter) and finally
select range. It is best to do it all in instanceReady event.
This is the easiest solution provided by the ckeditor API. I have tested it on IE10+, ff, safari and Chrome:
range = editor.createRange();
// the first parameter is the last line text element of the ckeditor instance
range.moveToPosition(new CKEDITOR.dom.node(editor.element.$.children[pos - 1]), CKEDITOR.POSITION_BEFORE_END)
range.collapse()
editor.getSelection().selectRanges([ range ])
This will work for sure.
CKEDITOR.config.startupFocus = 'end';
have you tried ckEditor.Selection.Collapse(false);
According to CKEditor 4 documentation, another option is:
const range = this.ckeditor.createRange()
range.moveToElementEditablePosition(range.root, true)
this.ckeditor.getSelection().selectRanges([range])
Link: https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-moveToElementEditablePosition