I am attempting to create a jQuery script which will convert an indented text list of arbitrary length and depth into a properly formatted HTML list. The lists on which I will be running this script are simple tree structures for directories. Within the tree structures, folders are denoted by a semicolon following the folder name (and files have no ending punctuation). Given this, I would like to attach a <span class="folder"></span> or <span class="file"></span> to the lines as appropriate.
I've found it to be fairly easy to generate most of the structure, but I cannot seem to get the recursion (which I suspect will be necessary) down to ensure that the tags are properly nested. The page on which this will be implemented will include the most recent (i.e., 3.0.3) version of Bootstrap, so feel free to use any of its functionality. I have about two dozen (generally abortive) fragments of code which I've tried or which I'm currently attempting to tweak to produce the desired result. Instead of posting a mass of (likely unhelpful) code, I've created a JSFiddle with the basic form which will be used for input/output, a bit of jQuery, and an example list and some external libraries loaded.
Any help or suggestions will be greatly appreciated.
Try this. I copied it to your fiddle and it seems to work.
var indentedToHtmlList = function indentedToHtmlList (text, indentChar, folderChar, listType, showIcons) {
indentChar = indentChar || '\t';
folderChar = folderChar || ':';
listType = listType || 'ul';
showIcons = !!showIcons;
var lastDepth,
lines = text.split(/\r?\n/),
output = '<' + listType + '>\n',
depthCounter = new RegExp('^(' + indentChar + '*)(.*)');
for (var i = 0; i < lines.length; i++) {
var splitted = lines[i].match(depthCounter),
indentStr = splitted[1],
fileName = splitted[2],
currentDepth = (indentStr === undefined) ? 0 : (indentStr.length / indentChar.length),
isFolder = (fileName.charAt(fileName.length - 1) === folderChar);
if (isFolder) {
fileName = fileName.substring(0, fileName.length -1);
}
if (lastDepth === currentDepth) {
output += '</li>\n';
} else if (lastDepth > currentDepth) {
while (lastDepth > currentDepth) {
output += '</li>\n</' + listType + '>\n</li>\n';
lastDepth--;
}
} else if (lastDepth < currentDepth) {
output += '\n<' + listType + '>\n';
}
output += '<li>';
if (showIcons) {
output += '<span class=" glyphicon glyphicon-' +
(isFolder ? 'folder-open' : 'file') +
'"></span> ';
}
output += fileName;
lastDepth = currentDepth;
}
while (lastDepth >= 0) {
output += '\n</li>\n</' + listType + '>';
lastDepth--;
}
return output;
};
You could use spans and classes to denote files and folders, but you should consider using ul and li elements, they were built for that.
The whole list should be enclosed within an ul element. Each entry on the top level list should create an li element inside of the main element. If the element is a folder, then it should also append another ul. This is where you'll need recursion to allow proper nesting.
However, if you intend to use indentation (no pun indented) the tab and or whitespace parsing is a problem by itself which I'm not solving in this answer. For the sake of this example, I'll just pretend you have a magic function that turns text into a parsed list called MyList, and that files that belong to a folder are whatever lies after the first semicolon of each list element.
var turnTextIntoList=function(AText) {
//magic stuff;
return SomeList;
};
var populateList=function(AList) {
var mainelement=jQuery('<ul></ul>');
for(element in AList) {
var the_li=jQuery('<li></li>');
if(element.indexOf(';')!=-1) {
the_li.append('<span class="file">'+element+'</span>');
} else {
var thefolder=element.split(';')
the_li.append('<span class="folder">'+thefolder[0]+'</span>');
the_li.append(populateList(turnTextIntoList(thefolder[1])));
}
mainelement.append(the_li);
}
return mainelement;
};
var MyList=turnTextIntoList(MyText);
jQuery('#targetdiv').append(populateList(MyList));
See, the recursion part is where you do
the_li.append(populateList(turnTextIntoList(thefolder[1])));
which will keep drilling into nesting levels until it reaches a file so it can start its way back.
It appears that someone already created a script which does this. Unfortunately, that script is in CoffeeScript, not JavaScript. However, there are a number online converters which will convert from CoffeeScript to JavaScript. Thanks to #charlietfl who provided a link to a working converter, supra.
Here is the converted, working code:
var bind, blank, convert, index, li, lineToMap, linesToMaps, parse, parseTuples, ptAccum, runConvert, tabCount, ul, ulEnd;
convert = function(text) {
return parse(text.split('\n'));
};
li = function(t) {
var html;
html = "<li>" + t['line'] + "</li>";
ptAccum.push(html);
return html;
};
ul = function(t) {
return ptAccum.push("<ul>" + (li(t)));
};
ulEnd = function() {
return ptAccum.push("</ul>");
};
ptAccum = [];
index = 0;
parse = function(lines) {
var ts;
ts = linesToMaps(lines);
ptAccum = ["<ul>"];
index = 0;
parseTuples(ts, 0);
ulEnd();
return ptAccum.join("\n");
};
parseTuples = function(tuples, level) {
var stop, _p, _results;
stop = false;
_p = function() {
var curLevel, t;
t = tuples[index];
curLevel = t['level'];
index++;
if (curLevel === level) {
return li(t);
} else if (curLevel < level) {
index--;
return stop = true;
} else {
ul(t);
parseTuples(tuples, level + 1);
return ulEnd();
}
};
_results = [];
while (!stop && index < tuples.length) {
_results.push(_p());
}
return _results;
};
tabCount = function(line) {
var c, count, i, inc, isTab, tc;
tc = 0;
c = '\t';
count = 0;
if (line) {
count = line.length;
}
i = 0;
isTab = function() {
return c === '\t';
};
inc = function() {
c = line.charAt(i);
if (isTab()) {
tc++;
}
return i++;
};
while (isTab() && i < count) {
inc();
}
return tc;
};
lineToMap = function(line) {
return {
line: line,
level: tabCount(line)
};
};
blank = function(line) {
return !line || line.length === 0 || line.match(/^ *$/);
};
linesToMaps = function(lines) {
var line, _i, _len, _results;
_results = [];
for (_i = 0, _len = lines.length; _i < _len; _i++) {
line = lines[_i];
if (!(blank(line))) {
_results.push(lineToMap(line));
}
}
return _results;
};
runConvert = function() {
var result;
result = convert($('#textarea-plain-text').val());
$('#textarea-converted-text').val(result);
return $('#div-converted-text').html(result);
};
bind = function() {
return $('#list-conversion-button').click(runConvert);
};
$(bind);
JSFiddle
Related
HTML
<body>
<div class="lol">
<a class="rightArrow" href="javascriptVoid:(0);" title"Next image">
</div>
</body>
Pseudo Code
$(".rightArrow").click(function() {
rightArrowParents = this.dom(); //.dom(); is the pseudo function ... it should show the whole
alert(rightArrowParents);
});
Alert message would be:
body div.lol a.rightArrow
How can I get this with javascript/jquery?
Here is a native JS version that returns a jQuery path. I'm also adding IDs for elements if they have them. This would give you the opportunity to do the shortest path if you see an id in the array.
var path = getDomPath(element);
console.log(path.join(' > '));
Outputs
body > section:eq(0) > div:eq(3) > section#content > section#firehose > div#firehoselist > article#firehose-46813651 > header > h2 > span#title-46813651
Here is the function.
function getDomPath(el) {
var stack = [];
while ( el.parentNode != null ) {
console.log(el.nodeName);
var sibCount = 0;
var sibIndex = 0;
for ( var i = 0; i < el.parentNode.childNodes.length; i++ ) {
var sib = el.parentNode.childNodes[i];
if ( sib.nodeName == el.nodeName ) {
if ( sib === el ) {
sibIndex = sibCount;
}
sibCount++;
}
}
if ( el.hasAttribute('id') && el.id != '' ) {
stack.unshift(el.nodeName.toLowerCase() + '#' + el.id);
} else if ( sibCount > 1 ) {
stack.unshift(el.nodeName.toLowerCase() + ':eq(' + sibIndex + ')');
} else {
stack.unshift(el.nodeName.toLowerCase());
}
el = el.parentNode;
}
return stack.slice(1); // removes the html element
}
Using jQuery, like this (followed by a solution that doesn't use jQuery except for the event; lots fewer function calls, if that's important):
$(".rightArrow").click(function () {
const rightArrowParents = [];
$(this)
.parents()
.addBack()
.not("html")
.each(function () {
let entry = this.tagName.toLowerCase();
const className = this.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
});
console.log(rightArrowParents.join(" "));
return false;
});
Live example:
$(".rightArrow").click(function () {
const rightArrowParents = [];
$(this)
.parents()
.addBack()
.not("html")
.each(function () {
let entry = this.tagName.toLowerCase();
const className = this.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
});
console.log(rightArrowParents.join(" "));
return false;
});
<div class=" lol multi ">
Click here
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
(In the live examples, I've updated the class attribute on the div to be lol multi to demonstrate handling multiple classes.)
That uses parents to get the ancestors of the element that was clicked, removes the html element from that via not (since you started at body), then loops through creating entries for each parent and pushing them on an array. Then we use addBack to add the a back into the set, which also changes the order of the set to what you wanted (parents is special, it gives you the parents in the reverse of the order you wanted, but then addBack puts it back in DOM order). Then it uses Array#join to create the space-delimited string.
When creating the entry, we trim className (since leading and trailing spaces are preserved, but meaningless, in the class attribute), and then if there's anything left we replace any series of one or more spaces with a . to support elements that have more than one class (<p class='foo bar'> has className = "foo bar", so that entry ends up being p.foo.bar).
Just for completeness, this is one of those places where jQuery may be overkill, you can readily do this just by walking up the DOM:
$(".rightArrow").click(function () {
const rightArrowParents = [];
for (let elm = this; elm; elm = elm.parentNode) {
let entry = elm.tagName.toLowerCase();
if (entry === "html") {
break;
}
const className = elm.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
}
rightArrowParents.reverse();
console.log(rightArrowParents.join(" "));
return false;
});
Live example:
$(".rightArrow").click(function () {
const rightArrowParents = [];
for (let elm = this; elm; elm = elm.parentNode) {
let entry = elm.tagName.toLowerCase();
if (entry === "html") {
break;
}
const className = elm.className.trim();
if (className) {
entry += "." + className.replace(/ +/g, ".");
}
rightArrowParents.push(entry);
}
rightArrowParents.reverse();
console.log(rightArrowParents.join(" "));
return false;
});
<div class=" lol multi ">
Click here
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
There we just use the standard parentNode property (or we could use parentElement) of the element repeatedly to walk up the tree until either we run out of parents or we see the html element. Then we reverse our array (since it's backward to the output you wanted), and join it, and we're good to go.
I needed a native JS version, that returns CSS standard path (not jQuery), and deals with ShadowDOM. This code is a minor update on Michael Connor's answer, just in case someone else needs it:
function getDomPath(el) {
if (!el) {
return;
}
var stack = [];
var isShadow = false;
while (el.parentNode != null) {
// console.log(el.nodeName);
var sibCount = 0;
var sibIndex = 0;
// get sibling indexes
for ( var i = 0; i < el.parentNode.childNodes.length; i++ ) {
var sib = el.parentNode.childNodes[i];
if ( sib.nodeName == el.nodeName ) {
if ( sib === el ) {
sibIndex = sibCount;
}
sibCount++;
}
}
// if ( el.hasAttribute('id') && el.id != '' ) { no id shortcuts, ids are not unique in shadowDom
// stack.unshift(el.nodeName.toLowerCase() + '#' + el.id);
// } else
var nodeName = el.nodeName.toLowerCase();
if (isShadow) {
nodeName += "::shadow";
isShadow = false;
}
if ( sibCount > 1 ) {
stack.unshift(nodeName + ':nth-of-type(' + (sibIndex + 1) + ')');
} else {
stack.unshift(nodeName);
}
el = el.parentNode;
if (el.nodeType === 11) { // for shadow dom, we
isShadow = true;
el = el.host;
}
}
stack.splice(0,1); // removes the html element
return stack.join(' > ');
}
Here is a solution for exact matching of an element.
It is important to understand that the selector (it is not a real one) that the chrome tools show do not uniquely identify an element in the DOM. (for example it will not distinguish between a list of consecutive span elements. there is no positioning/indexing info)
An adaptation from a similar (about xpath) answer
$.fn.fullSelector = function () {
var path = this.parents().addBack();
var quickCss = path.get().map(function (item) {
var self = $(item),
id = item.id ? '#' + item.id : '',
clss = item.classList.length ? item.classList.toString().split(' ').map(function (c) {
return '.' + c;
}).join('') : '',
name = item.nodeName.toLowerCase(),
index = self.siblings(name).length ? ':nth-child(' + (self.index() + 1) + ')' : '';
if (name === 'html' || name === 'body') {
return name;
}
return name + index + id + clss;
}).join(' > ');
return quickCss;
};
And you can use it like this
console.log( $('some-selector').fullSelector() );
Demo at http://jsfiddle.net/gaby/zhnr198y/
The short vanilla ES6 version I ended up using:
Returns the output I'm used to read in Chrome inspector e.g body div.container input#name
function getDomPath(el) {
let nodeName = el.nodeName.toLowerCase();
if (el === document.body) return 'body';
if (el.id) nodeName += '#' + el.id;
else if (el.classList.length)
nodeName += '.' + [...el.classList].join('.');
return getDomPath(el.parentNode) + ' ' + nodeName;
};
I moved the snippet from T.J. Crowder to a tiny jQuery Plugin. I used the jQuery version of him even if he's right that this is totally unnecessary overhead, but i only use it for debugging purpose so i don't care.
Usage:
Html
<html>
<body>
<!-- Two spans, the first will be chosen -->
<div>
<span>Nested span</span>
</div>
<span>Simple span</span>
<!-- Pre element -->
<pre>Pre</pre>
</body>
</html>
Javascript
// result (array): ["body", "div.sampleClass"]
$('span').getDomPath(false)
// result (string): body > div.sampleClass
$('span').getDomPath()
// result (array): ["body", "div#test"]
$('pre').getDomPath(false)
// result (string): body > div#test
$('pre').getDomPath()
Repository
https://bitbucket.org/tehrengruber/jquery.dom.path
I've been using Michael Connor's answer and made a few improvements to it.
Using ES6 syntax
Using nth-of-type instead of nth-child, since nth-of-type looks for children of the same type, rather than any child
Removing the html node in a cleaner way
Ignoring the nodeName of elements with an id
Only showing the path until the closest id, if any. This should make the code a bit more resilient, but I left a comment on which line to remove if you don't want this behavior
Use CSS.escape to handle special characters in IDs and node names
~
export default function getDomPath(el) {
const stack = []
while (el.parentNode !== null) {
let sibCount = 0
let sibIndex = 0
for (let i = 0; i < el.parentNode.childNodes.length; i += 1) {
const sib = el.parentNode.childNodes[i]
if (sib.nodeName === el.nodeName) {
if (sib === el) {
sibIndex = sibCount
break
}
sibCount += 1
}
}
const nodeName = CSS.escape(el.nodeName.toLowerCase())
// Ignore `html` as a parent node
if (nodeName === 'html') break
if (el.hasAttribute('id') && el.id !== '') {
stack.unshift(`#${CSS.escape(el.id)}`)
// Remove this `break` if you want the entire path
break
} else if (sibIndex > 0) {
// :nth-of-type is 1-indexed
stack.unshift(`${nodeName}:nth-of-type(${sibIndex + 1})`)
} else {
stack.unshift(nodeName)
}
el = el.parentNode
}
return stack
}
All the examples from other ответов did not work very correctly for me, I made my own, maybe my version will be more suitable for the rest
const getDomPath = element => {
let templateElement = element
, stack = []
for (;;) {
if (!!templateElement) {
let attrs = ''
for (let i = 0; i < templateElement.attributes.length; i++) {
const name = templateElement.attributes[i].name
if (name === 'class' || name === 'id') {
attrs += `[${name}="${templateElement.getAttribute(name)}"]`
}
}
stack.push(templateElement.tagName.toLowerCase() + attrs)
templateElement = templateElement.parentElement
} else {
break
}
}
return stack.reverse().slice(1).join(' > ')
}
const currentElement = document.querySelectorAll('[class="serp-item__thumb justifier__thumb"]')[7]
const path = getDomPath(currentElement)
console.log(path)
console.log(document.querySelector(path))
console.log(currentElement)
var obj = $('#show-editor-button'),
path = '';
while (typeof obj.prop('tagName') != "undefined"){
if (obj.attr('class')){
path = '.'+obj.attr('class').replace(/\s/g , ".") + path;
}
if (obj.attr('id')){
path = '#'+obj.attr('id') + path;
}
path = ' ' +obj.prop('tagName').toLowerCase() + path;
obj = obj.parent();
}
console.log(path);
hello this function solve the bug related to current element not show in the path
check this now
$j(".wrapper").click(function(event) {
selectedElement=$j(event.target);
var rightArrowParents = [];
$j(event.target).parents().not('html,body').each(function() {
var entry = this.tagName.toLowerCase();
if (this.className) {
entry += "." + this.className.replace(/ /g, '.');
}else if(this.id){
entry += "#" + this.id;
}
entry=replaceAll(entry,'..','.');
rightArrowParents.push(entry);
});
rightArrowParents.reverse();
//if(event.target.nodeName.toLowerCase()=="a" || event.target.nodeName.toLowerCase()=="h1"){
var entry = event.target.nodeName.toLowerCase();
if (event.target.className) {
entry += "." + event.target.className.replace(/ /g, '.');
}else if(event.target.id){
entry += "#" + event.target.id;
}
rightArrowParents.push(entry);
// }
where $j = jQuery Variable
also solve the issue with .. in class name
here is replace function :
function escapeRegExp(str) {
return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
}
function replaceAll(str, find, replace) {
return str.replace(new RegExp(escapeRegExp(find), 'g'), replace);
}
Thanks
$(".rightArrow")
.parents()
.map(function () {
var value = this.tagName.toLowerCase();
if (this.className) {
value += '.' + this.className.replace(' ', '.', 'g');
}
return value;
})
.get().reverse().join(", ");
I have a MultiSelectDropDown, that is, several RadComboBox controls are used in a combined way. For example, I can have a dropdown for regions, another for depots and another for user. The idea is to change the content of lower levels dynamically whenever items are selected or unselected on a higher level. The problem is that in the case when many items are selected, this becomes brutally slow due to some Telerik functions, but I do not understand why. This is a chunk from the client-side of the MultiSelectDropDown prototype:
changeLowerLevels: function (valueIndex, values, value) {
if (!this.canChange) return;
//Get selected values from combobox
var combo = $find(this.ddlIDs[valueIndex - 1]);
var cbItems = combo.get_checkedItems();
var selectedItems = [];
var change = null;
var counter = 0;
if (cbItems.length) this.filterString = "";
for (var i = 0; i < cbItems.length; i++) {
counter++;
if (this.filterString == "") this.filterString = cbItems[i].get_text();
selectedItems.push(cbItems[i].get_value());
}
if (counter > 1) this.filterString += " with " + (counter - 1) + " other" + ((counter > 2) ? "s" : "");
if (JSON.stringify(selectedItems) === JSON.stringify(this.selectedItems[valueIndex - 1]) || selectedItems == [])
return;
this.selectedItems[valueIndex - 1] = selectedItems;
var controlObject = this;
var combo = $find(this.ddlIDs[valueIndex]);
var comboItems = combo.get_items();
if(!this.disabled) combo.enable();
combo.clearItems();
if (valueIndex == 1) this.twoLevelCache = values;
var val = values;
//break if all items are found
var nrOfSelectedItems = this.selectedItems[valueIndex - 1].length;
var nrOfFoundItems = 0;
var index = 0;
var indexes = [];
var found = false;
while (nrOfFoundItems < nrOfSelectedItems && val[index] !== undefined) {
found = (this.selectedItems[valueIndex - 1].indexOf(val[index].Value) != -1);
if (!(found))
index++;
else {
indexes.push(index)
nrOfFoundItems++;
index++;
}
}
//separators from valuesIndex - 1 level
var controlObject = this;
for (var i = 0; i < indexes.length; i++) {
var separator = new Telerik.Web.UI.RadComboBoxItem();
separator.set_text("<span><a class=\"checkAll tt-multi-uncheck-icon\" index=\"" + index + "\">U</a>" + $find(this.ddlIDs[valueIndex - 1]).findItemByValue(val[indexes[i]].Value).get_text() + "</span>");
separator.set_value("");
separator.set_isSeparator(true);
comboItems.add(separator);
this.twoLevelCache.push(val[indexes[i]].Levels);
//valuesIndex level
var valuesArray = val;
var comboItem = new Telerik.Web.UI.RadComboBoxItem();
for (var depot in valuesArray[indexes[i]].Levels) {
comboItem = new Telerik.Web.UI.RadComboBoxItem();
comboItem.set_text(valuesArray[indexes[i]].Levels[depot].Name);
comboItem.set_value(valuesArray[indexes[i]].Levels[depot].Value);
comboItems.add(comboItem);
comboItem = null;
}
$('#' + this.ddlIDs[valueIndex] + '_DropDown a.checkAll').unbind().on("click", function () {
checkAllLowerItems(this, controlObject.ddlIDs[valueIndex]);
});
}
combo.set_emptyMessage(this.allText);
//$("#" + this.ddlIDs[valueIndex]).html(returnValue);
if (this.ddlIDs.length > valueIndex + 1) {
var paramToPass = (((val == undefined) || (val[index] === undefined)) ? ("") : (val[index]));
if (this.allText.length > 0)
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
else {
if (paramToPass !== "")
paramToPass = paramToPass.Levels;
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
}
else {
this.changeLowerLevels(valueIndex + 1, paramToPass, val[index].Levels[0].Value);
}
}
}
else {
if (this.allText.length > 0)
this.selectedItems[valueIndex] = "";
else
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.selectedItems[valueIndex] = "";
}
else {
this.selectedItems[valueIndex] = val[index].Levels[0].Value;
}
}
this.setText();
}
combo.clearItems() is extremeley slow. I have take a look on how it is implemented:
function (){var f=this._parent._getControl();?if(f._checkBoxes){f._checkedIndicesJson="[]";?f._checkedIndices=[];?var g=f.get_items();?for(var d=0,e=g.get_count();?d<e;?d++){var c=f.get_items().getItem(d);?c.set_checked(false);?}f.updateClientState();?}a.RadComboBoxItemCollection.callBaseMethod(this,"clear");?}
How can I make sure that this Javascript function speeds up?
I have finally solved the problem by rewriting Telerik client-side functionalities. It was a long and difficult debugging, but it yielded a large performance boost in the most difficult circumstances. From ~30 000 milliseconds, to ~300. Let's see the parts of the optimization:
The actual rewrite
/* Overriding Telerik functions Start */
var overridenTelerikControls = false;
function overrideTelerikFunctionalities() {
if (!overridenTelerikControls) {
overridenTelerikControls = true;
Telerik.Web.UI.RadComboBox.prototype.clearItems = function (isMultiSelectDropDown) {
this.get_items().clear(isMultiSelectDropDown);
this._itemData = null;
};
Telerik.Web.UI.RadComboBoxItemCollection.prototype.clear = function (isMultiSelectDropDown){
var f=this._parent._getControl();
if(f._checkBoxes){
f._checkedIndicesJson="[]";
f._checkedIndices=[];
var g = f.get_items();
for(var d=0,e=g.get_count();d<e;d++){
var c=f.get_items().getItem(d);
c.set_checked(false, isMultiSelectDropDown);
}
if (isMultiSelectDropDown) {
f._updateComboBoxText();
if (f._checkAllCheckBoxElement != null) {
f._updateCheckAllState();
}
}
f.updateClientState();
}
Telerik.Web.UI.RadComboBoxItemCollection.callBaseMethod(this, "clear");
};
Telerik.Web.UI.RadComboBoxItem.prototype.set_checked = function (d, isMultiSelectDropDown){
if(!this.get_enabled()){
return;
}
this._setChecked(d);
var c=this.get_comboBox();
if(c){
if(d){
c._registerCheckedIndex(this.get_index());
}else{
c._unregisterCheckedIndex(this.get_index());
}
if (!isMultiSelectDropDown) {
c._updateComboBoxText();
}
if((!isMultiSelectDropDown) && (c._checkAllCheckBoxElement!=null)){
c._updateCheckAllState();
}
}
};
}
}
/* Overriding Telerik functions End*/
My approach was to keep the old way of their working by default, but if an isMultiSelectDropDown parameter is passed, then work in the optimized manners. So we have a switch materialized as a parameter and we can turn it on/off. The main difference was that the old way was to change the label text showing the selected elements each time a checkbox is checked/unchecked. The main improvement was to do this change after all the checkboxes were checked/unchecked. This extremely simple idea was the driving force behind the boost of performance.
Actual usage
overrideTelerikFunctionalities();
combo.clearItems(true);
This was the functionalities were overriden if they were not already and the parameter was true, therefore the new approach was chosen.
Test, test, test
I'm trying to extract text from a pdf and then return a number that represents how many pages of the pdf are matched by a regex that I define.
My problem is that, rather than periodically checking whether or not the text of a single page is part of the match, my function divides the pieces up into smaller sections than pages. Count is meant to increment only after an entire page has been read.
getnopages: function(){
var fulltext = ""
var partialmatch;
var somerx = /something/
return pdfjs.getDocument(data).then(function(pdf) {
var pages = [];
pageNumbers = [];
for (var i = 0; i <= 6; i++) {
pages.push(i);
}
var found = false;
var count = 1;
return Promise.all(pages.map(function(pageNumber) {
pageNumbers.push(pageNumber);
return pdf.getPage(pageNumber + 1).then(function(page)
return page.getTextContent().then(function(textContent) {
return textContent.items.map(function(item) {
fulltext+=item.str+'&&&';
return item.str;
}).join('&&&');
});
}).then(function(){
count++;
console.log('the count is ' + count)
var partialmatch;
try {
partialmatch = fulltext.match(somerx)[0]
console.log('the match: ' + partialmatch)
var full = fulltext.slice(0, fulltext.length-3)
console.log('the full text ' + full)
if (fulltext && partialmatch!==full && !found){
found = true;
console.log('now we found our number: ' + count) // this finds where the full text differs from the partial text but returns a number too large to be a page number
}
}
catch(e){
console.log(e)
}
});
}));
}
Can anyone help me figure out how to rewrite this so that count is incrementing page numbers correctly?
I don't really know where is the problem in your code but I just suggest you to avoid too many nestings with promises. You can reduce nesting by chaining your promise like below:
getnopages: function() {
var somerx = /something/
return pdfjs.getDocument(data).then(function(pdf) {
var pages = [];
pageNumbers = [];
for (var i = 0; i <= 6; i++) {
pages.push(i);
}
var found = false;
var count = 1;
var promises = pages.map(pageNumber => {
pageNumbers.push(pageNumber);
return pdf.getPage(pageNumber + 1).then(page => {
return page.getTextContent();
}).then(textContent => {
return textContent.items.map(item => {
fulltext += item.str +'&&&';
return item.str;
}).join('&&&');
});
});
return Promise.all(promises).then(() => {
...
});
});
}
I have some html page with text and need to output all inner HTML from tag b by alphabetical order in lower case. I'm just a begginer, so don't be strict.
My code is here (text is just for example): http://jsfiddle.net/pamjaranka/ebeptLzj/1/
Now I want to: 1) save upper case for inner HTML from tag abbr; 2) delete all similar element from the array (as MABs).
I was trying to find the way to split the array by tag, but all that I've done is:
for(var i=0; i<allbold.length; i++){
labels[i] = allbold[i].innerHTML;
}
var searchTerm = ['abbr'];
var abbr = [];
var keywordIndex;
$.each(labels, function(i) {
$.each(searchTerm, function(j) {
var rSearchTerm = new RegExp('\\b' + searchTerm[j] + '\\b','i');
if (labels[i].match(rSearchTerm)) {
keywordIndex = i;
for(var j=0; j<labels.length; j++){
abbr[i] = labels[i];
}
}
});
});
Vanilla JS solution (no library required, see jsFiddle):
var allbold = document.querySelectorAll("b"),
words = document.querySelector("#words"),
labels = {}, i, word, keys, label;
// first, collect all words in an object (this eliminates duplicates)
for(i = 0; i < allbold.length; i++) {
word = allbold[i].textContent.trim();
if (word === 'Labels:') continue;
labels[word.toLowerCase()] = word;
}
// then sort the object keys and output the words in original case
keys = Object.keys(labels).sort();
for(i = 0; i < keys.length; i++){
label = document.createTextNode("SPAN");
label.textContent = labels[keys[i]];
words.appendChild(label);
// add a comma if necessary
if (i < keys.length - 1) {
words.appendChild(document.createTextNode(", "));
}
}
with one helper:
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g, "");
};
jQuery solution (see jsFiddle):
$(".content b").map(function () {
return $("<span>", {text: $.trim(this.textContent)})[0];
}).unique(function () {
return lCaseText(this);
}).sort(function (a, b) {
return lCaseText(a) < lCaseText(b) ? -1 : 1;
}).appendTo("#words");
with two helpers:
$.fn.extend({
unique: function (keyFunc) {
var keys = {};
return this.map(function () {
var key = keyFunc.apply(this);
if (!keys.hasOwnProperty(key)) {
keys[key] = true;
return this;
}
});
}
});
function lCaseText(element) {
return element.textContent.toLowerCase();
}
use the mapping element Is THIS FIDDLE for all upper case else this fiddle after your comment what you need
var maplabels = [];
for(var i=0; i<allbold.length; i++){
if (allbold[i].innerHTML != "Labels:") {
if(maplabels.indexOf(allbold[i].innerHTML) == -1){
maplabels.push(allbold[i].innerHTML);
labels.push('<i>' + allbold[i].innerHTML.toUpperCase() + '</i>');
}
}
}
I am trying to make a function in javascript that would expand/split a string with dashes and show the process ( line by line ) using recursion.
for example, the string "anna" would become:
expand("anna") = expand("an")+"---"+expand("na") ->
"a"+"---"+"n"+"---"+"n"+"---"+"a"
and the desired output would be:
anna
an---na
a---n---n---a
I have achieved doing the following so far (I know it might not be the solution I am looking):
expand("anna") = an+"---"+expand("na")
= an+"---"+n+"---"+expand("a");
= an+"---"+n+"---+"a"
the output I am getting is:
an---n---a
I can't seem to concatenate the head though to do the first example.
My javascript function of expand is as follows:
function expand(word) {
if (word.length<=1) {
return word;
} else {
mid = word.length/2;
return word.substr(0,mid) + " " + expand(word.substr(mid,word.length));
}
}
document.write(expand("anna"));
I would need some tips to do this, otherwise (if it's the wrong stackexchange forum), please guide me where to post it.
this is my crazy attempt
var Word = function(str) {
this.isSplitable = function() {
return str.length > 1;
}
this.split = function() {
var p = Math.floor(str.length / 2);
return [
new Word(str.substr(0,p)),
new Word(str.substr(p,p+1))
];
}
this.toString = function() {
return str;
}
}
var expand = function(words) {
var nwords = [];
var do_recur = false;
words.forEach(function(word){
if(word.isSplitable()) {
var splitted = word.split();
nwords.push(splitted[0]);
nwords.push(splitted[1]);
do_recur = true;
}else{
nwords.push(word);
}
});
var result = [];
nwords.forEach(function(word){
result.push( word.toString() );
});
var result = result.join("--") + "<br/>";
if(do_recur) {
return result + expand(nwords);
}else{
return "";
}
}
document.write( expand([new Word("anna")]) );
This is what you need
expand = function(word) {
return [].map.call(word, function(x) {return x+'---'}).join('')
};
The joy of functional programming.
And with added code to deal with last character:
function expand(word) {
return [].map.call(word, function(x, idx) {
if (idx < word.length - 1)
return x+'---';
else return x
}).join('')
}
As I said that it is impossible to display the "process" steps of recursion while using recursion, here is a workaround that will output your desired steps:
var levels = [];
function expand(word, level) {
if (typeof level === 'undefined') {
level = 0;
}
if (!levels[level]) {
levels[level] = [];
}
levels[level].push(word);
if (word.length <= 1) {
return word;
} else {
var mid = Math.ceil(word.length/2);
return expand(word.substr(0, mid), level+1) + '---' + expand(word.substr(mid), level+1);
}
}
expand('anna');
for (var i = 0; i < levels.length; i++) {
console.log(levels[i].join('---'));
}
to see all steps the best that I whold do is:
function expand(word) {
if (word.length<=1) {
return word;
} else {
var mid = word.length/2;
var str1 = word.substr(0,mid);
var str2 = word.substr(mid,word.length);
document.write(str1 + "---" + str2 + "<br></br>");
return expand(str1) + "---" + expand(str2);
}
}
document.write(expand("anna"));
You have to return the two parts of the string:
function expand(word) {
output="";
if (word.length<=1) {
output+=word;
return output;
} else
{
var mid = word.length/2;
output+=word.substr(0,mid)+"---"+word.substr(mid)+" \n";//this line will show the steps.
output+=expand(word.substr(0,mid))+"---"+expand(word.substr(mid,word.length-1))+" \n";
return output;
}
}
console.log(expand("anna"));
Edit:
I added the output var and in every loop I concatenate the new output to it.
It should do the trick.
Hope the problem is in your first part. According to your algorithm, you are splitting your string anna in to two parts,
an & na
so you need to expand both parts until the part length is less than or equal to one. so your required function is the below one.
function expand(word) {
if (word.length<=1) {
return word;
} else {
mid = word.length/2;
return expand(word.substr(0,mid)) + " --- " + expand(word.substr(mid,word.length));
}
}
document.write(expand("anna"));