Split Content on CKEDITOR and get HTML of each part - javascript

I have a CKEDITOR and i am trying to split the content in n parts, the user indicates how many parts he wants, by puting the cursor on a specific position of the CKEDITOR, after that with an option at the context-menu the user selects "Split Block", this inserts a tag in HTML:
the user can do this n times in CKEDITOR, it is to indicate how many blocks the user wants to split the content, each hr inserted is one block.
So when the user finish, click at the context-menu "Process Split", this action should execute and split the content in n parts.
this is my code to split the content:
var index = 0;
var tmpItem = null;
var ranges = new Array();
var elements = editor.document.getElementsByTag( 'hr' );
for ( var i = 0; i < elements.count() ; i++ )
{
var item = elements.getItem( i );
ranges[index] = new CKEDITOR.dom.range( editor.document );
if(tmpItem!=null)
ranges[index].setStart(tmpItem, CKEDITOR.POSITION_BEFORE_START);
else{
ranges[index].setStartAfter(editor.document.getBody().getFirst());
}
if(item.hasClass('split-end')){
ranges[index].setEnd(item, CKEDITOR.POSITION_BEFORE_START);
ranges[index].select();
index++;
var sel = editor.getSelection();
var ran = sel.getRanges();
var el = new CKEDITOR.dom.element("div");
for (var j = 1, len = ran.length; j < len-1; ++j) {
el.append(ran[j].cloneContents());
}
console.log( el.getHtml() );
}
tmpItem = item;
}
The problem is: how to select from begining of the document to the first HR and so on.
Thanks a lot, i have been trying to do this for over a week, and i dont know what else try.

I'm not analyzing your code carefully, because I don't even know what you're trying to achieve. But here're some notes that can help you.
You use CKEDITOR.dom.range#setStart and #setEnd improperly. You should utilize setStartAt and setEndAt. Methods you used take offset as a second argument, not a position.
To select content from the beginning of the document to the first HR (including it):
var range = new CKEDITOR.dom.range( document );
range.setStart( document.getBody(), 0 );
range.setEndAt( hr, CKEDITOR.POSITION_AFTER_END );
range.select();
In your code I see that you're trying to select many ranges - it won't work this way. AFAIK only Firefox handles multiple selection, but I don't know if CKEditor does. If yes, then CKEDITOR.dom.selection#selectRanges is what you need. However, if you're only trying to extract contents of ranges then you don't have to select them first.

Related

Add Javascript to replace Span tags

I have an online store that has limited access to make any correct edits to code.
I am trying to implement proper Price Schema as they have:
<span itemprop="price">$57.00</span>
This is incorrect.
It needs to be set up like this
<span itemprop="priceCurrency" content="USD">$</span>
<span itemprop="price">57.00</span>
Is there something in JavaScript or jQuery that can manipulate this by separating the Currency Symbol and Price?
Thanks
You get the ELEMENT text:
var value = $("span[itemprop='price'").text();
Then you could generate the html using regex like:
var html = '$57.00'.replace(/([^\d])(\d+)/,
function(all, group1, group2){
return 'some html here =' + group1 + '= more hear =' + group2 });
Might not be 100% bug-free, but it should get you started:
<script type="text/javascript">
var n = document.getElementsByTagName('*')
for(var i=0;i<n.length;i++)
{
if(n[i].hasAttribute('itemprop')) //get elements with itemprop attribute
{
var p = n[i].parentNode
var ih = n[i].innerHTML //grab the innerHTML
var num = parseFloat(ih) //get numeric part of the innerHTML - effectively strips out the $-sign
n[i].innerHTML = num
//create new span & insert it before the old one
var new_span = document.createElement('span')
new_span.innerHTML = '$'
new_span.setAttribute('itemprop', 'priceCurrency')
new_span.setAttribute('currency', 'USD')
p.insertBefore(new_span, n[i])
}
}
</script>
Somthing along the lines of
// find all span's with itemprop price
document.querySelectorAll("span[itemprop='price']").forEach(function(sp){
// grab currency (first char)
var currency = sp.innerText.substr(0,1);
// remove first char from price val
sp.innerText = sp.innerText.substr(1);
// create new element (our price-currency span)
var currencySpan = document.createElement("span");
currencySpan.innerText = currency;
currencySpan.setAttribute("itemprop", "priceCurrency");
currencySpan.setAttribute("content", "USD");
// Append it before the old price span
sp.parentNode.insertBefore(currencySpan, sp);
});
Should do what your after.
See demo at: https://jsfiddle.net/dfufq40p/1/ (updated to make effect more obvious)
This should work -- querySelectorAll should be a bit faster, and the regex will work with more than just USD, I believe.
function fixItemPropSpan() {
var n = document.querySelectorAll('[itemprop]');
for (var i = 0; i < n.length; i++) {
var p = n[i].parentNode;
var ih = n[i].innerHTML;
var num = Number(ih.replace(/[^0-9\.]+/g, ""));
n[i].innerHTML = num;
//create new span & insert it before the old one
var new_span = document.createElement('span');
new_span.innerHTML = '$';
new_span.setAttribute('itemprop', 'priceCurrency');
new_span.setAttribute('currency', 'USD');
p.insertBefore(new_span, n[i]);
}
}
Here is a suggestion of how you can make this work, though i would not suggest doing it like this (too many cases for content="").
Example of the logic you could use to transform the incorrect format to the correct one.
Hope you find it useful. :]

Declaring and setting variable in JavaScript

I'm currently using the following JavaScript in a Google Chrome Extension to automate the 'add to cart' process for purchasing sneakers on nike.com;
var size_i_want = "11";
function fRun()
{enter code here
// Select size option.
var sizesList=document.getElementsByName("skuAndSize")[0];
for(var i=0; i<sizesList.length; i++)
{
if(sizesList.options[i].text.trim() == size_i_want)
{
sizesList.selectedIndex = i;
}
}
var aButtons = document.getElementsByTagName("button");
for(var i = 0; i < aButtons.length; ++i)
{
if(aButtons[i].className.indexOf("add-to-cart") > -1)
{
aButtons[i].click();
}
}
}
function fTick()
{
if(document.getElementsByName("skuAndSize")[0] != undefined)
{
setTimeout("fRun()", 600);
//fRun();
}else{
setTimeout("fTick()", 300);
}
}
setTimeout("fTick()", 300);
This script works perfectly for nike.com in the States, however does not work correctly for nike websites in other countries like the UK and Sweden.
As you can probably tell I am new to JavaScript and am still researching high and low to understand the language. However I understand this comes down to the fact that
var size_i_want = "11";
value is set as an integer (number) however on the Nike UK website the node that this affects contains letters, for example "UK 10.5".
Would somebody be able to help me declare a new variable and set it's value so that it contains both letters and numbers? I also have a feeling that this will impact the script as well, so help around that area is much appreciated too.
In javascript, variables are not typesafe, so you don't declare them as integers or strings. Coincidently:
var size_i_want = "11";
This is already a string. So you should already be able to add letters to it. Just change it to:
var size_i_want = "UK 10.5";
As millerbr already said javascript is not type safe.
The mix of letters and numbers should not have impact on the script, because
size_i_want = "11";
size_i_want = "UK 10.5";
are both strings.

Javascript array length through user input

I have a problem in Javascript I want to make a form which have one input text field and one button when I click on the button window.prompt is called.
It will prompt depend upon my array length but I want array length get through input text field when I write 10 it will prompt 10 times when I write 2 it will prompt 2 times.
How can i write this type of query?
I tried this code but its not working.
words = new Array (4);
function a() {
for ( k = 0 ; k < words.length ; k = k + 1 ) {
words[ k ] = window.prompt( "Enter word # " + k, "" ) ;
}
}
Maybe you forgot to call your function a().
Some remarks about your code:
You don't have to specify an initial array size, e.g. words = [] or words = new Array() is enough.
Also k=k+1 is usually written as k++.
A remark about asking questions:
Use punctuation to make sentences! Your whole question is one sentence.
Hopefully it's just the snippet of code but I hope you are using var somewhere to declare all those variables.
Otherwise this should do the trick, however not sure what you are trying to achieve but this sounds like a bad user experience.
Here is the jsffidle http://jsfiddle.net/R2bCz/1/
function Handler(event) {
var count = event.target.value;
var i = 0;
var words = [];
var word;
for (; i < count; i++) {
word = window.prompt("Enter word # " + i, "");
words.push(word);
}
}
$("#multi").on("change", Handler);

Is it possible to highlight all words on a web page without destroying the layout?

I've written an extension for firefox which highlights all words on a web page (excluding some words in a given list).
What i've noticed is that (besides that my extension is terribly slow) some web pages get "destroyed", more specifically the layout gets destroyed (particularly websites with overlay advertising or fancy drop-down menus).
My code wraps <span> tags around every "word", or to be precise around every token, because i'm splitting the text nodes with a whitespace as seperator.
So is it possible anyway to realize this task without destroying the page's layout?
I'm iterating over all text nodes, split them, and iterate over every token.
When the token is in my list, i don't highlight it, else i wrap the <span> tag around it.
So any suggestions how this could be done faster would be helpful, too.
Here are some screenshots for a correctly highlighted and a not correctly highlighted web page:
right:
en.wikipedia.org before highlighting,
en.wikipedia.org after highlighting.
wrong:
developer.mozilla.org before highlighting,
developer.mozilla.org after highlighting.
OK. Study this code. It searches for all instances of "is" and highlights if it is not surrounded by word characters. Put this in your scratchpad while this tab is focused. You will see that words like "List" and other words containing "Is" are no highlighted, but all the "Is"'s are.
I basically made an addon here for you. You can now release this as an addon called RegEx FindBar and take all the credit....
var doc = gBrowser.contentDocument;
var ctrler = _getSelectionController(doc.defaultView);
var searchRange = doc.createRange();
searchRange.selectNodeContents(doc.documentElement);
let startPt = searchRange.cloneRange();
startPt.collapse(true);
let endPt = searchRange.cloneRange();
endPt.collapse(false);
let retRane = null;
let finder = Cc["#mozilla.org/embedcomp/rangefind;1"].createInstance().QueryInterface(Ci.nsIFind);
finder.caseSensitive = false;
var i = 0;
while (retRange = finder.Find('is', searchRange, startPt, endPt)) {
i++;
var stCont = retRange.startContainer;
var endCont = retRange.endContainer;
console.log('retRange(' + i + ') = ', retRange);
console.log('var txt = retRange.commonAncestorContainer.data',retRange.commonAncestorContainer.data);
//now test if one posiion before startOffset and one position after endOffset are WORD characters
var isOneCharBeforeStCharWordChar; //var that holds if the character before the start character is a word character
if (retRange.startOffset == 0) {
//no characters befor this characte so obviously not a word char
isOneCharBeforeStCharWordChar = false;
} else {
var oneCharBeforeStChar = stCont.data.substr(retRange.startOffset-1,1);
if (/\w/.test(oneCharBeforeStChar)) {
isOneCharBeforeStCharWordChar = true;
} else {
isOneCharBeforeStCharWordChar = false;
}
console.log('oneCharBeforeStChar',oneCharBeforeStChar);
}
var isOneCharAfterEndCharWordChar; //var that holds if the character before the start character is a word character
if (retRange.endOffset == endCont.length - 1) {
//no characters after this characte so obviously not a word char
isOneCharAfterEndCharWordChar = false;
} else {
var oneCharAferEndChar = endCont.data.substr(retRange.endOffset,1); //no need to subtract 1 from endOffset, it takes into account substr 2nd arg is length and is treated like length I THINK
if (/\w/.test(oneCharAferEndChar)) {
isOneCharAfterEndCharWordChar = true;
} else {
isOneCharAfterEndCharWordChar = false;
}
console.log('oneCharAferEndChar',oneCharAferEndChar);
}
if (isOneCharBeforeStCharWordChar == false && isOneCharAfterEndCharWordChar == false) {
//highlight it as surrounding characters are no word characters
_highlightRange(retRange, ctrler);
console.log('highlighted it as it was not surrounded by word charactes');
} else {
console.log('NOT hilte it as it was not surrounded by word charactes');
}
//break;
startPt = retRange.cloneRange();
startPt.collapse(false);
}
/*********************/
function _getEditableNode(aNode) {
while (aNode) {
if (aNode instanceof Ci.nsIDOMNSEditableElement)
return aNode.editor ? aNode : null;
aNode = aNode.parentNode;
}
return null;
}
function _highlightRange(aRange, aController) {
let node = aRange.startContainer;
let controller = aController;
let editableNode = this._getEditableNode(node);
if (editableNode)
controller = editableNode.editor.selectionController;
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
findSelection.addRange(aRange);
if (editableNode) {
// Highlighting added, so cache this editor, and hook up listeners
// to ensure we deal properly with edits within the highlighting
if (!this._editors) {
this._editors = [];
this._stateListeners = [];
}
let existingIndex = this._editors.indexOf(editableNode.editor);
if (existingIndex == -1) {
let x = this._editors.length;
this._editors[x] = editableNode.editor;
this._stateListeners[x] = this._createStateListener();
this._editors[x].addEditActionListener(this);
this._editors[x].addDocumentStateListener(this._stateListeners[x]);
}
}
}
function _getSelectionController(aWindow) {
// display: none iframes don't have a selection controller, see bug 493658
if (!aWindow.innerWidth || !aWindow.innerHeight)
return null;
// Yuck. See bug 138068.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}
Oh edit my solution out, will update with proper solution, I see you want to highlight all words
This is the code how firefox highlights stuff without changing document: Finder.jsm - _highlight function. You will have to copy this and use it for the whole document, if you need help let me know and I'll do it.
Here was my solution to highlight all matches of single word: https://stackoverflow.com/a/22206366/1828637
Here man this is how you are going to highlight the whole document, I didn't finish the snippet but this is the start of it: Gist - HighlightTextInDocument
Here's the copy paste answer to highlight everything in the document. As you learn more about it share with us, like how you can highlight with a different color, right now its all pink O_O
function _getEditableNode(aNode) {
while (aNode) {
if (aNode instanceof Ci.nsIDOMNSEditableElement)
return aNode.editor ? aNode : null;
aNode = aNode.parentNode;
}
return null;
}
function _highlightRange(aRange, aController) {
let node = aRange.startContainer;
let controller = aController;
let editableNode = this._getEditableNode(node);
if (editableNode)
controller = editableNode.editor.selectionController;
let findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
findSelection.addRange(aRange);
if (editableNode) {
// Highlighting added, so cache this editor, and hook up listeners
// to ensure we deal properly with edits within the highlighting
if (!this._editors) {
this._editors = [];
this._stateListeners = [];
}
let existingIndex = this._editors.indexOf(editableNode.editor);
if (existingIndex == -1) {
let x = this._editors.length;
this._editors[x] = editableNode.editor;
this._stateListeners[x] = this._createStateListener();
this._editors[x].addEditActionListener(this);
this._editors[x].addDocumentStateListener(this._stateListeners[x]);
}
}
}
function _getSelectionController(aWindow) {
// display: none iframes don't have a selection controller, see bug 493658
if (!aWindow.innerWidth || !aWindow.innerHeight)
return null;
// Yuck. See bug 138068.
let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController);
return controller;
}
var doc = gBrowser.contentDocument;
var searchRange = doc.createRange();
searchRange.selectNodeContents(doc.documentElement);
_highlightRange(searchRange,_getSelectionController(gBrowser.contentWindow))
#jervis, I can't make a comment on your comment under #Noitidart code as I don't have 50rep yet. So I have to post here.
Re:
I did it with 'gFindBar._highlightDoc(true, word)' now. I'm using firefox 17, so i dont know if gFindBar is state of the art. – jervis 40 mins ago
But I tested his code and and it works.
Don't use gFindBar.
Copy it and then paste it into your Scratchpad.
Why are you using gFindBar._highlightDoc(true, word) ? I thoght you wanted to highlight everything in the document? Where did you get _highlightDoc from? I don't see that anywhere in #Noitidart's code.
Regading yoru comment on iterate all words and use gFindBar._highlightDoc:
I did it with 'gFindBar._highlightDoc(true, word)' now. I'm using firefox 17, so i dont know if gFindBar is state of the art. – jervis 39 mins ago
Dude why do that.... I saw #Noitidart posted a per word solution on the linked topic: gBrowser.tabContainer.childNodes[0].linkedBrowser.finder.highlight(true, 'YOUR_WORD_HERE'); that is extremely easy, one line and no need to create text nodes spans or anything. You have to run this code on each tab you want to highlight in.

IE8 (javascript): very slow to load large list of options in SELECT element

I'm loading SELECT element with 6000 items using createElement and add methods. The code is shown below, and can also be accessed here. In IE8 it takes around 16 seconds to load the list and about the same time to clear it. In IE9 and Firefox the loading time is < 2 seconds and clearing time is < 1 second. Any ideas on how I can improve the speed in IE8?
Thank you.
<script type="text/javascript">
window.onload = loadList;
function loadList() {
clearList();
var start = new Date().getTime();
var o = document.getElementById("listLookupAvailableItems")
for (var i = 0; i < 6000; i++) {
var option = document.createElement("option");
option.text = 'ABCDF ' + i;
option.value = option.text;
o.add(option, o.options[null]);
}
log('Load time: ' + (new Date().getTime() - start));
}
function clearList() {
var start = new Date().getTime();
document.getElementById("listLookupAvailableItems").options.length = 0;
log('Clear time: ' + (new Date().getTime() - start));
return false;
}
function log(txt) {
document.getElementById('infoPanel').innerHTML += '</br>' + txt;
}
</script>
My guess is that that particular DOM operation is just really slow in IE8. In general, manipulating the DOM is the slowest type of operation in any browser. To get around that I typically try to find ways to combine my changes into one DOM update (e.g. add an HTML "batch" of 6000 rows to a table instead of individually adding 6000 rows to a table).
In this example the only way to do that would probably be to create all of the <option> elements as HTML and then use innerHTML to insert them into the <select>. See this jsfiddle example: http://jsfiddle.net/pseudosavant/bVAFF/
I don't have IE8 to test with, but it is much faster even in Firefox (22ms vs 500ms) for me.
Update
Looks like it didn't work with innerHTML in IE for loading the list, but it did work for clearing it. Loading it works using jQuery $(o).html(html); though. I updated the jsfiddle example and it works in IE9, and hopefully IE8 now.
Javascript:
$(document).ready(function(){
loadListBatch();
});
function loadListBatch() {
clearListBatch();
var start = new Date().getTime();
var o = document.getElementById("listLookupAvailableItems")
var html = "";
for (var i = 0; i < 6000; i++) {
html += "<option value='"+'XYZ' + i+"'>"+'XYZ ' + i+"</option>";
}
// o.innerHTML = html; // Only one DOM operation, but still doesn't work in IE
$(o).html(html); // Using jQuery to insert the HTML makes it work with IE
console.log('Load time: ' + (new Date().getTime() - start));
}
function clearListBatch() {
var start = new Date().getTime();
document.getElementById("listLookupAvailableItems").innerHTML = ""; // It was already only one DOM call, but this is faster it seems.
console.log('Clear time: ' + (new Date().getTime() - start));
return false;
}
If you are supporting IE7/IE8 you should minimize JavaScript manipulation of the DOM. So if you are appending, inserting or deleting nodes you need to minimize DOM manipulation in general. The best solution is to bulk update items.
So, if you have a select list and you are doing JQuery.append() you will get better performance if you concatenate your entire options string before appending.
var str = $('<option value="x">Item 1</option>' + '<option value="y">Item 2</option>');
$('#selectMenu').append(str);
//or in a loop
var array = ['orange','apple','grapes','mangoes'];
var str = '';
for (var x= 0; x < array.length; x++) {
str = str + '<option value="' + x + '">' + x + '</option>';
}
$('#selectMenu').append(str);
Additionally, if you want to see how slowly JavaScript is executed by IE8 run the SunSpider JS test. Firefox 22 and Chrome 27 are around 300 ms while IE8 is around 4,000 ms. That tells a lot about why your JS speeds are slow. Interestingly IE10 comes in at less than 200 ms now. http://www.webkit.org/perf/sunspider/sunspider.html
I had a very similar situation.
I have a set of inputs with 1700+ so I provided a "filter" option that would copy the select and apply a filter based on a besides the copied list. (It "opens" a dialog that expands the dropdownlistbox to a list almost as big as 80% of the screen)
Copying the worked unnoticeably in other browsers but took 8-15 secs in IE.
The solution, based on previous answers, and also based on this post (Learn the slow (and fast) way to append elements to the DOM) was to add all the items to a HTL string, then assigning this to the innerHTML of a new object, one that is not yet part of the DOM. And finally, replacing the object from the DOM with the new one.
This apparently reduces the number of "reflow" operations performed by the browser, which is most likely the culprit of such slow performance.
Some of the test before implementing this style, was to run the full for loop without adding the options to the list, and in such test, the code executed very fast, it was clear that selectElement.add(optionElement) was the slow part.
Here is an example of what my function ended like:
function fillselector(IdSelect){
var selector = document.getElementById(IdSelect);
if( !selector ){
alert('Original select element not found.');
return;
}
var lista = document.getElementById('selectFilter_lista');
if( !lista ){
alert('Copied list element not found.');
return;
}
var filterText = noSpecialChars(document.getElementById('selectFilter_text').value);
var options =''
for (var i = 0; i < selector.length; i++){
if (filterText == '' || noSpecialChars(selector[i].text).indexOf(filterText) != -1 ){
//Commented code works but is painfuly slow on IE
//var option = document.createElement("option");
//option.value = selector[i].value;
//option.text = selector[i].text;
//lista.add(option);
options += '<option value="'+selector[i].value+'">'+selector[i].text+'</option>';
}
}
var newList = document.createElement('select');
newList.id='selectFilter_list';
newList.className='selectFilter_list';
newList.size = 20;
newList.ondblclick= function(){closeselector(IdSelect, true);}
newList.innerHTML = options;
newList.value = selector.value;
var listParent = lista.parentElement; //<div> that only contains the <select>
listParent.removeChild(lista);
listParent.appendChild(newList);
}

Categories