Get the implicit lang attribute of an HTML element - javascript

Suppose you have an HTML page that contains sections in different languages, like this:
<html lang=en>
<div lang="th">
<p id="test1">ไทย</p>
</div>
<p id="test2">Implicitly English</p>
<div lang="en-CA">
<p id="test3">As Canadian as possible under the circumstances</p>
</div>
<p lang="en-AU"id="test4">Explictly Aussie</p>
</html>
Is there a direct way to discover which particular language code applies to a given HTML element? Something like:
// pseudo-code
var lang = myElement.getLang()
Here's what appears to be a very roundabout solution:
function getLang(element) {
var lang = element.getAttribute("lang")
if (!lang) {
var elements
, languages
, language
, ii
, selector
// Find all elements with an explicit lang attribute
elements = [].slice.call(document.querySelectorAll("*[lang]"))
// Determine which languages are present
languages = []
for (ii in elements) {
lang = elements[ii].getAttribute("lang")
if (languages.indexOf(lang) < 0) {
languages.push(lang)
}
}
lang = "" // reset
for (ii in languages) {
language = languages[ii]
selector = ":lang(" + language + ")"
elements = [].slice.call(document.querySelectorAll(selector))
if (elements.indexOf(element) > -1) {
if (lang.length < language.length) {
lang = language
}
}
}
}
return lang
}
Is there a more obvious way?
jsFiddle

I updated your fiddle with the following code, which you can run in this snippet. This simplifies it greatly.
function getLang(elem) {
var lang = "";
if (elem) {
var elements = [];
var queryResult = document.querySelectorAll("[lang]");
try {
//Wrapping in a try catch block to handle unsupported browsers.
elements = [].slice.call(queryResult);
} catch (error) {
for (var i = 0, len = queryResult.length; i < len; i++) {
elements.push(queryResult[i]);
}
}
if (elements.length > 0) {
//Find in the NodeList where the element is either itself or the first parent with lang attribute of the given element.
var matches = elements.filter(function(e) {
return e === elem || e.contains(elem);
}); //ES2015 -> elements.filter(e => e === elem || e.contains(elem));
var match = matches.length > 0 ? matches[matches.length - 1] : matches[0];
lang = match.lang ? match.lang : lang;
}
}
return lang;
}
var result = getLang(document.querySelector("#test1")) + " ";
result += getLang(document.querySelector("#test2")) + " ";
result += getLang(document.querySelector("#test3")) + " ";
result += getLang(document.querySelector("#test4"));
alert(result);
<body lang=en>
<div lang="th">
<p id="test1">ไทย</p>
</div>
<p id="test2">Implicitly English</p>
<div lang="en-CA">
<p id="test3">As Canadian as possible under the circumstances</p>
</div>
<p lang="en-AU" id="test4">Explictly Aussie</p>
</body>

Related

How to find/remove last text portion of innerHTML

I have a <div> element that contains both html elements and text. I want to find/remove the last or the last nth or the nth text only portion of it.
So for example
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
If I had a method to delete the last text character, the first call would delete z and the second call would delete g. Or if I had a method to find the 4th character, it would return d.
It sounds like you only care about the text nodes, so probably something like this so you can just delete the nth character:
var div = document.getElementById("foo");
const getTextNodes = (el, nodes) => {
nodes = nodes || [];
for (var i = 0; i < el.childNodes.length; i++) {
var curNode = el.childNodes[i];
if (curNode.nodeName === "#text") {
if (curNode.textContent.trim().length) {
nodes.push(curNode);
}
} else {
getTextNodes(curNode, nodes);
}
}
return nodes;
}
console.log(getTextNodes(div).map((el) => el.textContent));
const deleteNthCharacter = (el, n) => {
n--; // since we want to be "1 indexed"
const nodes = getTextNodes(el);
let len = 0;
for (let i = 0; i < nodes.length; i++) {
const curNode = nodes[i];
if (len + curNode.textContent.length > n) {
curNode.textContent = curNode.textContent.substring(0, n - len) + curNode.textContent.substring(n + 1 - len);
break;
} else {
len += curNode.textContent.length;
}
}
}
deleteNthCharacter(div, 2);
deleteNthCharacter(div, 7);
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
If I understood your question correctly this should do the trick:
function deleteLastChar(targetId){
const target = document.getElementById(targetId);
let lastWithText = -1;
//find last child that has text set
target.childNodes.forEach((child, iter) => {
if(child.innerText != undefined && child.innerText.length > 0){
lastWithText = iter;
}
});
// exit if no valid text node was found
if(lastWithText === -1)
return;
const lastNode = target.childNodes[lastWithText];
lastNode.innerText = lastNode.innerText.slice(0, -1);
}
deleteLastChar("foo")
deleteLastChar("foo")
deleteLastChar("foo")
deleteLastChar("foo")
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
If I understand the question this is probably what you're looking for
let x = document.getElementById('foo').children;
function erase() {
for (let i = x.length - 1; i >=0; i--) {
if(x[i].textContent.length > 0) {
const textC = x[i].textContent;
x[i].textContent = textC.substring(0, textC.length - 1);
return;
}
}
}
<div id="foo">
<span id="bar">abcdefg</span>
<span id="baz">z</span>
</div>
<button onclick="erase()">Erase</button>
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div id="foo">
<span id="bar">abcdefg</span><br>
<span id="baz">z</span><br><br>
<div id="result"></div>
<div id="result2"></div>
</div>
<script type="text/javascript">
var s = function(x){
return document.querySelector(x)
}
log = console.log;
var span1 = s("#bar")
var span2 = s("#baz")
var result = s("#result")
var result2 = s("#result2")
var res = span1.innerText.charAt(4)
// with the charAt method
result.innerText = " Result is : " +res+"\n\n"
// with regular Expression
var reg = /e/
result2.innerText = " Result2 is : " +span1.innerText.match(reg)
</script>
</body>
</html>

JS RegExp capture all groups and pos for each match

Statement:
I am new to RegExp and trying to learn capture groups in javascripts
I am using https://regex101.com/r/COYhIc/1 for testing
see attached image for character pos column of each match by https://regex101.com
Objective:
I want to print all matches and groups at console (Done)
I want to print character position of each match [see image](remaining)
JSFIDDLE: https://jsfiddle.net/bababalcksheep/p28fmdk4/68/
JavaScript:
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
try {
query = new RegExp(isRE[1], isRE[2]);
} catch (e) {}
}
return query;
}
var str = $('#str').val();
var regex = parseQuery($('#reg').val());
//
var result;
var match_no = 0;
var output = '';
while ((result = regex.exec(str)) !== null) {
match_no++;
output += `\nMatch ${match_no}\n`;
output += `Full Match, ${ result[0]} , Pos\n`;
for (i = 1; i < result.length; i++) {
output += `Group ${i}, ${ result[i]} , Pos\n`;
}
}
console.log(output);
In your output field use index and lastIndex. exec returns an object with a index property.
output += `Full Match, ${ result[0]} , Pos ${result.index} - ${regex.lastIndex}\n `;
Update for the groups:
I have used a small logic to get the indices:
var m = new RegExp(result[i]);
output += `Group ${i}, ${ result[i]}, Pos ${$('#str').val().match(m).index} - ${regex.lastIndex} \n`;
function parseQuery(query) {
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
if (isRE) {
try {
query = new RegExp(isRE[1], isRE[2]);
} catch (e) {}
}
return query;
}
var str = $('#str').val();
var regex = parseQuery($('#reg').val());
//
var result;
var match_no = 0;
var output = '';
while ((result = regex.exec(str)) !== null) {
match_no++;
output += `\nMatch ${match_no}\n`;
output += `Full Match, ${ result[0]} , Pos ${result.index} - ${regex.lastIndex}\n `;
for (i = 1; i < result.length; i++) {
var m = new RegExp(result[i]);
output += `Group ${i}, ${ result[i]}, Pos ${$('#str').val().match(m).index} - ${regex.lastIndex} \n`;
}
}
console.log(output);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="form-group">
<label for="str">String:</label>
<input type="text" class="form-control" id="str" value="source=100, delta=2, source=2121, delta=5">
</div>
<div class="form-group">
<label for="regex">Regex:</label>
<input type="text" class="form-control" id="reg" value="/(source=(\d+))/g">
</div>
<div id="result">
</div>
</div>
FIDDLE
According to docs RegExp.exec, you can retrieve it using index property. So I would add this line into your snippet to retrieve column position for your full match:
`${result.index}-${result.index + result[0].length}`
For subgroups, JS doesn't retrieve index, so a workaround can be achieved using indexOf:
const initialSubGroupIndex = str.indexOf(result[i], result.index);
`${initialSubGroupIndex}-${initialSubGroupIndex + result[i].length}`

Loop through div children and bold specific text not working

I have a suggestion dropdown under an input field and I am trying to make the text in the suggestion divs bold for the portion that matches what is currently in the input field.
e.g
input: AB
dropdown: ABCDE
My current code doesn't seem to be replacing the div content with the span
JS:
BoldMatchedText(inputToMatch:string){
var outerDiv = document.getElementById("dropdown");
if(outerDiv != null){
var subDiv = outerDiv.getElementsByTagName("div");
for (var i = 0; i < subDiv.length; i++){
subDiv[i].innerHTML.replace(inputToMatch, "<span id=\"strong\">" + inputToMatch + "</span>");
}
}
}
html:
<form>
<input type="text" id="dropdown-input">
<div id="dropdown">
<div class="reg-list-item">{{reg1}}</div>
<div class="reg-list-item">{{reg2}}</div>
<div class="reg-list-item">{{reg3}}</div>
<div class="reg-list-item">{{reg4}}</div>
</div>
</form>
You need to assign the result of calling the function replace.
subDiv[i].innerHTML = subDiv[i].innerHTML.replace(inputToMatch, "<span id=\"strong\">" + inputToMatch + "</span>");
function BoldMatchedText(inputToMatch) {
var outerDiv = document.getElementById("dropdown");
if (outerDiv != null) {
var subDiv = outerDiv.getElementsByTagName("div");
for (var i = 0; i < subDiv.length; i++) {
subDiv[i].innerHTML = subDiv[i].innerHTML.replace(inputToMatch, "<span id=\"strong\">" + inputToMatch + "</span>");
}
}
}
BoldMatchedText('Go');
#strong {
font-weight: 700
}
<form>
<input type="text" id="dropdown-input">
<div id="dropdown">
<div class="reg-list-item">Ele</div>
<div class="reg-list-item">Gomez</div>
<div class="reg-list-item">Rod</div>
<div class="reg-list-item">Enr</div>
</div>
</form>
Try this working sample with a benchmark. Compared with the previous answer.
function BoldMatchedText1(inputToMatch) {
var outerDiv = document.getElementById("dropdown");
if (outerDiv != null) {
var subDiv = outerDiv.getElementsByTagName("div");
for (var i = 0; i < subDiv.length; i++) {
subDiv[i].innerHTML = subDiv[i].innerHTML.replace(inputToMatch, "<span id=\"strong\">" + inputToMatch + "</span>");
}
}
}
function BoldMatchedText2(inputToMatch) {
var outerDiv = document.getElementById("dropdown");
if(outerDiv !== null) {
// Use `getElementsByClassName` instead using `getElementsByTagName('div')` JS will traverse your entire HTML file and look for all div tags, may take a little longer if you have a lot
var items = outerDiv.getElementsByClassName("reg-list-item");
// Getting the iteration length before the loop will give you performance benefit since items.length will not be checked per iteration
var len = items.length;
// Using while loop evaluating only if len is any positive number (true) except 0 (false) with reverse iteration making it faster
while(len--) {
var item = items[len].innerHTML;
// ONLY replace the text that contains the `inputToMatch`
if(item.indexOf(inputToMatch) !== -1) {
items[len].innerHTML = item.replace(inputToMatch, "<span id=\"strong\">" + inputToMatch + "</span>");
}
}
}
}
console.time('filter1');
BoldMatchedText1('Gom');
console.timeEnd('filter1');
console.time('filter2');
BoldMatchedText2('Gom');
console.timeEnd('filter2');
#strong {
font-weight: 700
}
<form>
<input type="text" id="dropdown-input">
<div id="dropdown">
<div class="reg-list-item">Ele</div>
<div class="reg-list-item">Gomez</div>
<div class="reg-list-item">Rod</div>
<div class="reg-list-item">Enr</div>
</div>
</form>

Add space after dot

Good day. I've got some problem.
I've got input where I wrote some information.
Example:
<div class="wizard wizardstep1" ng-controller='someCtrl'>
<p class="wizardtitle">Put you Theme</p>
<input id="taskTheme" required type="text" placeholder="Put you Theme" ng-model="taskThemeWizardInputValue" ng-change="checkThemeWizardInputValue()">
</div>
And I've got my controller.
Example:
$scope.checkThemeWizardInputValue = function () {
if ($scope.taskThemeWizardInputValue === undefined) {
$scope.taskThemeWizardInputValue = "";
console.log($scope.taskThemeWizardInputValue);
console.log($scope.taskThemeWizardInputValue.length);
} else {
var strt = $scope.taskThemeWizardInputValue.split('.');
for (var i = 0 ; i < strt.length; i++) {
strt[i] = strt[i].charAt(0).toUpperCase() + strt[i].substr(1);
}
$scope.taskThemeWizardInputValue = strt.join('.');
console.log($scope.taskThemeWizardInputValue);
console.log(strt);
}
}
How I can add space after dot? Who knows?
Here is link to jsfiddle with my example.
We achieve it by adding space to each splitted string other than first one and an empty string
function someCtrl($scope) {
$scope.checkThemeWizardInputValue = function () {
if ($scope.taskThemeWizardInputValue === undefined) {
$scope.taskThemeWizardInputValue = "";
console.log($scope.taskThemeWizardInputValue);
console.log($scope.taskThemeWizardInputValue.length);
} else {
var strt = $scope.taskThemeWizardInputValue.split('.');
for (var i = 0 ; i < strt.length; i++) {
var addSpace='';
if(i>0 && strt[i].trim().length>0){
addSpace=' ';
}
strt[i] = addSpace+strt[i].trim().charAt(0).toUpperCase() + strt[i].trim().substr(1);
}
$scope.taskThemeWizardInputValue = strt.join('.');
console.log($scope.taskThemeWizardInputValue);
console.log(strt);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app>
<div class="wizard wizardstep1" ng-controller='someCtrl'>
<p class="wizardtitle">Put you Theme</p>
<input id="taskTheme" required type="text" placeholder="Put you Theme" ng-model="taskThemeWizardInputValue" ng-change="checkThemeWizardInputValue()">
</div>
</div>
You can do this simply by changing strt.join('.') to strt.join('. ').
$scope.checkThemeWizardInputValue = function () {
if ($scope.taskThemeWizardInputValue === undefined) {
$scope.taskThemeWizardInputValue = "";
console.log($scope.taskThemeWizardInputValue);
console.log($scope.taskThemeWizardInputValue.length);
} else {
var strt = $scope.taskThemeWizardInputValue.split('.');
for (var i = 0 ; i < strt.length; i++) {
strt[i] = strt[i].trim();
if(strt[i].length > 0) {
strt[i] = ' '+strt[i].charAt(0).toUpperCase() + strt[i].substr(1);
}
}
$scope.taskThemeWizardInputValue = strt.join('.');
console.log($scope.taskThemeWizardInputValue);
console.log(strt);
}
}
This is working fiddle
I suggest creating a directive so that you can plugin this behaviour whenever required., rather than writing your ng-change in every controller.
In directive simple line element.val(event.target.value.split(".").join(". ")); will work for you., with help of directive controller parameter.
See example fiddle

Get DOM path from a node to another

I need a method that, taken as parameters two nodes (node1 and node2), returns the minimum path that leads to node2 from node1.
Ideally, it returns an array of nodes, but for the moment it's OK to a string. So for example:
P
/ \
#text U
/ \
B I
| |
#text #text
function foo(node1, node2) {
...
}
when I run it in this way, for example on the nodes P (root) and B:
var res = foo(P, B);
console.log(res);
I obtain:
res = Array[3] {
0: P (class=..., id=...)
1: U (class=..., id=...)
2: B (class=..., id=...)
}
or, in the form of string:
res = "P(class=..., id=...) > U(class=..., id=...) > B(class=..., id=...)";
If the nodes have attributes (such as id or class), then returns even those (as in the example).
I searched the internet methods that did similar things but I found only methods that return the full path of the entire document and not between two nodes.
For example, I tried this method doesn't work for me because it returns the full path of a single node.
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
}
Another thing, I would use pure JavaScript, no jQuery.
I have no idea how to do what I need, a your help would be greatly appreciated.
Thank you
<!DOCTYPE html>
<html>
<script>
window.onload = function() {
console.log(min_path(
document.getElementById("4"),
document.getElementById("9")
));
};
function min_path(node1, node2) {
if(node1 === node2) {
return node1;
}
var node_1_ancestors = get_ancestors(node1);
var node_2_ancestors = get_ancestors(node2);
var divergent_index = 0;
while(node_1_ancestors[divergent_index] === node_2_ancestors[divergent_index]) {
divergent_index++;
}
var path = [];
for(var i = node_1_ancestors.length - 1; i >= divergent_index - 1; i--) {
path.push(node_1_ancestors[i]);
}
for(var i = divergent_index; i < node_2_ancestors.length; i++) {
path.push(node_2_ancestors[i]);
}
return path;
}
function get_ancestors(node) {
var ancestors = [node];
while(ancestors[0] !== null) {
ancestors.unshift(ancestors[0].parentElement);
}
return ancestors;
}
</script>
</head>
<body>
<div id="0">
<div id="1">
<div id="2">
<span id="3"></span>
<span id="4">node1</span>
</div>
<div id="5">
<p id="6"></p>
<span id="7">
<div id="8">
<div id="9">node2</div>
<div id="10"></div>
</div>
</span>
</div>
</div>
<div id="11"></div>
</div>
</body>
</html>
Edit: It was going in to an infinite loop when the nodes were equal, so I added a check for that.

Categories

Resources