Store escaped HTML in an attribute value - html

How can I store escaped HTML in an attribute value? If I try to store e.g. > or < in an attribute value on a div, then try to read it back, the browser is unescaping it.
E.g.
HTML
<div id="content" tooltip="Ref: <Unknown>">one fine day in the middle of the night...</div>
<button id="show-tooltip">Show tooltip</button>
JS
var content = document.getElementById('content');
var show = document.getElementById('show-tooltip');
var tooltip;
show.addEventListener('click', function (event) {
if (tooltip) {
document.body.removeChild(tooltip);
}
tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.innerHTML = content.getAttribute('tooltip');
tooltip = document.body.appendChild(tooltip);
});
CSS
.tooltip {
width: 100px;
height: 50px;
border: 1px solid rgba(0, 0, 0, .3);
background-color: rgba(255, 255, 0, .3);
}
Fiddle: http://jsfiddle.net/axo25vhy/
I want the tooltip text to display "Ref: <Unknown>", however the text <Unknown> is being converted to an HTML tag so does not appear.

The simplest way is probably to encode the ampersand in < as & => &lt;
http://jsfiddle.net/etoz8o0x/
When your content is appended to the html, the browser decodes & to <. If you encode & correctly as &lt;, the browser decodes < which will then presented visually as a <

Related

Get text content in css after

Given this HTML:
<span data-title="Attribution">test</span>
I can use in CSS after to retrieve data-title
span:after{ content: attr(data-title); }
But what I need is the span text content, in the example above test, is it possible to get it in CSS after ?
I don't know of any way to get the inner text of an html element through CSS. Javascript will have to be used at some point, however, you could for instance use Javascript to set an attribute for your span and use CSS to read that attribute -
var elems = document.getElementsByTagName("span");
for(var i = 0; i < elems.length; i++) {
var mytext = elems[i].innerText;
elems[i].dataset.trunc = mytext;
}
span:before {
content: attr(data-trunc);
color: red;
}
<span>Some-Text</span>
<span>More-Text</span>
<span>Other-Text</span>

Explicitly link <span> tag to specific </span>

I'm trying to draw boxes around letters to show ngrams in a word. For example the word test has 3 bigrams, 'te', 'es', 'st'. As I draw the boxes, I'm varying the padding so that each box is clearly visible. However, the problem I run into is that I'm opening a <span> tag, opening a second <span> tag and then trying to close the first tag but the second is what actually gets closed. This results in the following behavior:
The HTML I have right now
<span style='border:3px; padding: 0.1em;'> t
<span style='border:3px; padding: 0.2em;'> e
</span>
<span style='border:3px; padding: 0.3em;'> s
</span> t
</span>
Is there a way to explicitly link a </span> to a particular <span> to achieve my desired output?
Here's a JSFiddle of the problem in action.
Short answer: no
Long answer: Your browser will always treat an ending tag as belonging to the innermost beginning tag. You can work around this by using other tags, i.e. <span> This <i style="font=style:normal">will</span> override nesting rule</i>, but this is against W3C spec, and is not guaranteed to work across all browsers because of that (although I suspect it would).
If all you need to do is draw boxes around certain letters, I suggest you use correctly nested span tags with different width or padding values, an image, or the HTML canvas.
As others have mentioned, a </span> tag closes the last opened <span>.
The elements are nested and can't overlap in the way you suggest.
Here's one solution to group ngrams (actually bigrams) using JavaScript (jQuery).
It creates absolutely-positioned boxes over the letters.
I made it skip spaces and unigrams.
// select all the elements
var $elements = jQuery('.find_bigrams');
// iterate through the elements
$elements.each(function() {
// define this element
var $element = jQuery(this);
// build an array of characters
var text = $element.text().split('');
// initialize padding to zero
var pad = 0;
// empty this element
$element.empty();
// wrap each character in a span with the class "character"
// this allows us to find the position of each character
jQuery.each(text, function(i, character) {
jQuery('<span>', {
'class': 'character',
'text': character
}).appendTo($element);
});
// define character elements
$characters = jQuery('.character', $element);
// interate through character elements for this ngram element
$characters.each(function() {
// define this character element
var $this_char = jQuery(this);
// define the next character element
var $next_char = $this_char.next('.character');
// continue only if there is a next character
if ($next_char.length) {
// if this or next character is a space, skip to next character
if ($this_char.text() == ' ' || $next_char.text() == ' ') {
return true;
}
// define styles for this box element
var top = -pad + 'px';
var left = $this_char.position().left;
var width = $next_char.position().left + $next_char.width() - $this_char.position().left;
var padding = pad + 'px 0';
// add absolutely positioned box element
jQuery('<span>', {
'class': 'bigram'
}).css({
'top': top,
'width': width,
'left': left,
'padding': padding
}).appendTo($element);
// increment padding
pad += 3;
}
});
// remove the positioning elements: .character
$characters.contents().unwrap();
});
body {
font-family: monospace;
font-size: 18px;
margin: 0;
}
.find_bigrams {
position: relative;
display: block;
margin: 3em;
}
.find_bigrams .bigram {
position: absolute;
height: 100%;
border: 1px solid rgba(255, 0, 0, .5);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="find_bigrams">test</span>
<span class="find_bigrams">rhinoceros</span>
<span class="find_bigrams">has some spaces</span>
<span class="find_bigrams">has a unigram</span>

how to show ellipsis on the left side instead of the right [duplicate]

I have a list of paths (for lack of a better word, maybe bread crumb trails describes them better). Some of the values are too long to display in their parent so I'm using text-overflow: ellipsis. The problem is that the important information is on the right, so I'd like the ellipsis to appear on the left. Something like this this ascii art:
----------------------------
|first > second > third |
|...second > third > fourth|
|...fifth > sixth > seventh|
----------------------------
Notice that the first row is short enough so it remains left aligned, but the other two are too long so the ellipsis appears on the left hand side.
I'd prefer a CSS only solution, but JS is fine if it can't be avoided. It's ok if the solution only works in Firefox and Chrome.
EDIT: At this point I'm looking for a work around for the bugs in Chrome that prevent it from rendering properly when a document is mixed RTL and LTR. That was all I really needed from the outset, I just didn't realize it.
How about something like this jsFiddle? It uses the direction, text-align, and text-overflow to get the ellipsis on the left. According to MDN, there may be the possibility of specifying the ellipsis on the left in the future with the left-overflow-type value however it's considered to still be experimental.
p {
white-space: nowrap;
overflow: hidden;
/* "overflow" value must be different from "visible" */
text-overflow: ellipsis;
width: 170px;
border: 1px solid #999;
direction: rtl;
text-align: left;
}
<p>first > second > third<br /> second > third > fourth > fifth > sixth<br /> fifth > sixth > seventh > eighth > ninth</p>​
I finally had to crack and do something in JavaScript. I was hoping that someone would come up with a hail-mary CSS solution but people seem to just be up-voting the answer that should be correct if it weren't for the Chrome bugs. j08691 can have the bounty for his work.
<html>
<head>
<style>
#container {
width: 200px;
border: 1px solid blue;
}
#container div {
width: 100%;
overflow: hidden;
white-space: nowrap;
}
</style>
<script>
function trimRows() {
var rows = document.getElementById('container').childNodes;
for (var i=0, row; row = rows[i]; i++) {
if (row.scrollWidth > row.offsetWidth) {
var textNode = row.firstChild;
var value = '...' + textNode.nodeValue;
do {
value = '...' + value.substr(4);
textNode.nodeValue = value;
} while (row.scrollWidth > row.offsetWidth);
}
}
}
</script>
</head>
<body onload='trimRows();'>
<div id="container" >
<div>first > second > third</div>
<div>second > third > fourth > fifth > sixth</div>
<div>fifth > sixth > seventh > eighth > ninth</div>​
</div>
</body>
</html>
Fiddle
Why not just using direction:rtl;
It's a little buggy, but maybe a point in the right direction
http://jsfiddle.net/HerrSerker/ZfbaD/50/
$('.container')
.animate({'width': 450}, 4000)
.animate({'width': 100}, 4000)
.animate({'width': 170}, 4000)
.container {
white-space: nowrap;
overflow: hidden; /* "overflow" value must be different from "visible" */
text-overflow: ellipsis;
width:170px;
border:1px solid #999;
direction:rtl;
}
.container .part {
direction:ltr;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<span class="part">second</span>
<span class="part">></span>
<span class="part">third</span>
<span class="part">></span>
<span class="part">fourth</span>
<span class="part">></span>
<span class="part">fifth</span>
<span class="part">></span>
<span class="part">sixth</span>
</div>
These solutions solve the problem with misinterpreted preceding or trailing weak or neutral BiDi characters such as /, \, ~, ., etc. (basically any punctuation or special characters).
CSS Solution
Use a combination of:
direction: rtl & ltr
unicode-bidi: bidi-override
p {
direction: rtl;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; /* or pre (e.g. preserve multiple spaces) */
}
span {
direction: ltr;
unicode-bidi: bidi-override; /* or isolate, isolate-override, embed */
}
<p><span>/path/to/a/very/long/file.name</span></p>
<bdo> Solution
Another possibility uses the <bdo> Bidirectional Text Override element:
p {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; /* or pre (e.g. preserve multiple spaces) */
}
<bdo dir="rtl">
<p>
<bdo dir="ltr">/path/to/a/very/long/file.name</bdo>
</p>
</bdo>
Using #Hemlocks, #Brian Mortenson and #Jimbo's solutions, I've built a jQuery plugin to solve this problem.
I've also added support to return the initial value using .html() rather than having it return the current innerHTML. Hopefully it will be useful to someone...
(function($) {
$.trimLeft = function(element, options) {
var trim = this;
var $element = $(element), // reference to the jQuery version of DOM element
element = element; // reference to the actual DOM element
var initialText = element.innerHTML;
trim.init = function() {
overrideNodeMethod("html", function(){ return initialText; });
trimContents(element, element);
return trim;
};
trim.reset = function(){
element.innerHTML = initialText;
return trim;
};
//Overide .html() to return initialText.
var overrideNodeMethod = function(methodName, action) {
var originalVal = $.fn[methodName];
var thisNode = $element;
$.fn[methodName] = function() {
if (this[0]==thisNode[0]) {
return action.apply(this, arguments);
} else {
return originalVal.apply(this, arguments);
}
};
};
var trimContents = function(row, node){
while (row.scrollWidth > row.offsetWidth) {
var childNode = node.firstChild;
if (!childNode)
return true;
if (childNode.nodeType == document.TEXT_NODE){
trimText(row, node, childNode);
}
else {
var empty = trimContents(row, childNode);
if (empty){
node.removeChild(childNode);
}
}
};
};
var trimText = function(row, node, textNode){
var value = '\u2026' + textNode.nodeValue;
do {
value = '\u2026' + value.substr(4);
textNode.nodeValue = value;
if (value == '\u2026'){
node.removeChild(textNode);
return;
}
}
while (row.scrollWidth > row.offsetWidth);
};
trim.init();
};
$.fn.trimLeft = (function(options){
var othat = this;
var single = function(that){
if (undefined == $(that).data('trim')) {
var trim = new $.trimLeft(that, options);
$(that).data('trim', trim);
$(window).resize(function(){
$(that).each(function(){
trim.reset().init();
});
});
}
};
var multiple = function(){
$(othat).each(function() {
single(this);
});
};
if($(othat).length>1)
multiple(othat);
else
single(othat);
//-----------
return this;
});
})(jQuery);
Initiate using:
//Call on elements with overflow: hidden and white-space: nowrap
$('#container>div').trimLeft();
//Returns the original innerHTML
console.log($('#test').html());
fiddle
Using a slightly more complex markup (using the bdi-tag and an extra span for the ellipsis), we can solve the problem fully in CSS, no JS required at all -- cross browser (IE, FF, Chrome) and including keeping punctuation marks to the right:
http://jsbin.com/dodijuwebe/1/edit?html,css,output
Granted, this is something of a hack, involving pseudo-element goodness. However, our team has been using this code in production and we haven't had any issues whatsoever.
The only caveats are: The height of the line needs to be fixed and the background color needs to be known explicitly (inherit won't work).
If you don't care the indexing of those texts, you could use this method (it reverses the text lines):
If you have in your texts other HTML elements besides <br> you need to make some arrangements to use this method.
HTML code:
<p>first > second > third<br/>
second > third > fourth <br>
fifth > sixth > seventh</p>
CSS code:
p{
overflow: hidden;
text-overflow: ellipsis;
unicode-bidi: bidi-override;
direction: rtl;
text-align: left;
white-space: nowrap;
width: 140px;
}
JavaScript code
[].forEach.call(document.getElementsByTagName("p"), function(item) {
var str = item.innerText;
//Change the operators
str = str.replace(/[<>]/g, function(char){ return ({"<" : ">", ">" : "<"})[char] });
//Get lines
var lines = str.split(/\n/);
//Reverse the lines
lines = lines.map(function(l){ return l.split("").reverse().join("") });
//Join the lines
str = lines.join("<br>");
item.innerHTML = str;
});
jsfiddle
Based on your edit:
At this point I'm looking for a work around for the bugs in Chrome
that prevent it from rendering properly when a document is mixed RTL
and LTR. That was all I really needed from the outset, I just didn't
realize it.
Have you looked into the unicode-bidi css property (see Sitepoint or W3C)? I actually just learned about this myself on another recent post. My guess is you would want to use the embed value for those pieces going the opposite direction to the main site. So in j08691's answer where it is direction: rtl add unicode-bidi: embed to the CSS. This should solve "mixed RTL and LTR" issues you are having.
I put some JavaScript together to regex out three items and add the ellipsis in where necessary. This does not explicitly look at how much text will fit in the box but if the box is fixed this may not be an issue.
<style>
p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width:170px;
border:1px solid #999;
direction:rtl;
text-align:left;
}
</style>
<p>first > second > third<br />
second > third > fourth > fifth > sixth<br />
fifth < sixth < seventh < eighth < ninth</p>
<script>
var text = $( 'p' ).text(),
split = text.split( '\n' ),
finalStr = '';
for( i in split ){
finalStr = finalStr.length > 0 ? finalStr + '<br />' : finalStr;
var match = /(\w+\s?(<|>)?\s?){3}$/.exec( split[i] );
finalStr = finalStr + ( split[i].length > match[0].length ? '...' : '' ) + match[0];
}
$( 'p' ).empty().html( finalStr );
</script>

Is it bad to put <span /> tags inside <option /> tags, only for string manipulation not styling?

I would like to make groups of the text content of an <option /> tag. Say I have the following: <option>8:00 (1 hour)</option>, the time pattern 8:00 can be modified, then the text in parenthesis (1 hour) can also be modified.
I was thinking of doing something like
<option>
<span>8:00</span>
<span> (1 hour)</span>
</option>
Is it bad to put <span /> tags inside <option /> tags, only for string manipulation not styling?
From the HTML 5spec:
Content model:
If the element has a label attribute and a value attribute: Nothing.
If the element has a label attribute but no value attribute: Text.
If the element has no label attribute and is not a child of a datalist element: Text that is not inter-element whitespace.
If the element has no label attribute and is a child of a datalist element: Text.
So depending on context there are two things that you can put inside an <option> — text or nothing at all — you may not put a <span> or any other element there.
From the HTML 4.01 spec:
<!ELEMENT OPTION - O (#PCDATA) -- selectable choice -->
(Even the HTML 3.2 and HTML 2 specs say: <!ELEMENT OPTION - O (#PCDATA)*>)
An option element cannot have any child elements. So yes, it is bad.
You can use a Javascript plugin to overcome this limitation. For example jQuery plugin "Select2" Select2 plugin homepage. I use it in a couple of my projects and think that it's pretty flexible and convenient.
There are a number of them, but they do quite same thing - convert traditional <select> into <div> blocks with an extra functionality.
The option element
Content model: Text
No, it’s not ok. Consider keeping the values around in your script so you can recompose them when necessary.
You're better off using an HTML replacement for your <select> if you want to do this.
As established by other people, and I have tried with <b> and other tags, <option> does not take tags within it.
What you can do, since you cannot use <span> inside an <option> tag,
You can use the index number to extract the text via
document.getElementById(selectid).options[x].text where x is the relevant index, as a variable.
Then what you do is use the " (" to split the variable into the time, and remove the last character as well which removes the ")"
Sample:
<script type="text/javascript">
function extractSelectText()
{
var text = document.getElementById("main").options[1].text
/*
var tlength = text.length
var splitno = tlength - 1
var text2 = text.slice(0, splitno)
var textArray = text2.split(" )")
var time = textArray[0]
var hours = textArray[1]
}
</script>
Changing it is much simpler:
<script type="text/javascript">
function changeSelectText()
{
/* add your code here to determine the value for the time (use variable time) */
/* add your code here to determine the value for the hour (use variable hours) */
var textvalue = time + " (" + hours + ")"
document.getElementById("main").options[1].text
}
</script>
If you use a for function you can change each value of the select replacing 1 with 2, 3 and so on, and put a set interval function to constantly update it.
One option for editing would be to use some fancy pattern matching to update the content. It will be slower and more resource intensive, and depends on how regular the format is, but doesn't require any HTML modifications. My concern, however, would be on accessibility and the user experience. Having values change is hard for screen reader software to pick up, and it may also confuse other users.
It is not an answer, but may be it will help sombody, it is possible to mimic select with details tag. This example is not complete, I used javascript to close list on click
const items = document.querySelectorAll(".item");
// Add the onclick listeners.
items.forEach(item => {
item.addEventListener("click", e => {
// Close all details on page
closeList(item);
});
});
function closeList(item) {
document.querySelectorAll("details").forEach(deet => {
if (deet != this && deet.open) {
deet.open = !open;
console.log(item);
}
});
}
details {
border: 1px solid #aaa;
border-radius: 4px;
}
summary {
padding: .5em 0 .5em .5em;
font-weight: bold;
}
details[open] {
}
details[open] .item {
cursor: pointer;
padding: .5em 0 .5em .5em;
border-top: 1px solid #aaa;
}
details[open] .item:hover{
background-color: #f1f1f1;
}
details[open] .title{
padding: .5em 0 .5em .5em;
border-top: 1px solid #aaa;
}
<details>
<summary>Select your choice</summary>
<div class='title'>
This is attempt to mimic native <code>select</code> tag with html for <code>option</code> tag
</div>
<div class='item'>item 1</div>
<div class='item'>item 2</div>
<div class='item'>item 3</div>
</details>

Changing the symbols shown in a HTML password field

Is there any way to change the asterisks (*), or in some browsers a bullet (•), that appears in password fields in HTML?
Create your own font and use #font-face and font-family (and font-size) for input[type="password"]. It should help to solve your problem. But... you must create font with replaced bullet and asterisk character. All character numbers in font may represent the same character. Use google to find free program to edit vector fonts.
Never say "it is impossible". You should find a hack for your problem.
Characters to be replaced with your symbol (for Chrome, Firefox and MSIE):
26AB, 25E6, 25CF, 25D8, 25D9, 2219, 20F0, 2022, 2024, 00B7, 002A.
(18C)
You can't change the password masking character in the standard password field. You can fake it with a textbox, but it makes for a weak security model because you don't get the protection you do from the password textbox. As a side note, it's generally not a good idea to change the behaviour of items like this because users have become used to one form of masking, and you will be introducing a different one - if there's no good reason to do this, I'd avoid it.
As of now, it appears as though this is possible in webkit browsers. Please see http://help.dottoro.com/lcbkewgt.php for examples and documentation.
Does not apply to password input
<input type="text" style="-webkit-text-security: square;" />
There is no good solution for other browsers as of when this answer was written and even in webkit browsers, the characters you are allowed to specify are very limited.
I know that is a very old question, but I faced this problem today, and I solved it using this approach: https://github.com/Mottie/input-password-bullet
Basically, I created a new font where assign the default point, to another icon. Then, only need to import the font files to the project and add a css similar to this:
#font-face {
font-family: 'fontello';
src: url('/fonts/fontello.eot');
src: url('/fonts/fontello.eot') format('embedded-opentype'),
url('/fonts/fontello.woff') format('woff'),
url('/fonts/fontello.ttf') format('truetype'),
url('/fonts/fontello.svg') format('svg');
font-weight: normal;
font-style: normal;
}
input[type="password"] {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: none;
color: red;
font-size: 16px;
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
/* add spacing to better separate each image */
letter-spacing: 2px;
}
Hope this helps!
No - the user agent chooses its own default style, and there is (to my knowledge) no CSS attributes you can change to determine the masking character.
Of course, this would be possible if the password field was just a standard text field, and you manually masked the input with a javascript event handler (onKeyPress, probably). You could even declare the field as type="password" in the HTML, then have your JS function modify the DOM to change its type. I'd be a little wary about doing this, though; the browser implementation is almost certainly pretty solid, and circumventing established security functionality to roll your own is rarely a good idea.
<input type="text" style="-webkit-text-security: circle;" />
Looks like I'm pretty late, but another potential solution is below for any looking for a workaroud. The only "bug" I didn't bother to look into was that you can't highlight and delete everyhting at once. Other than that, it works as intended, the user can input their password and they will see a string of whatever you want (in this example, a star) and their recently pressed key.
//window.onload = () => {
// sessionStorage.text = "";
// sessionStorage.visible = false
//}
//We will use the text and visible variables for demonstration purposes as session storage does not work with code snippets. The star can really be anything you want
const star = "*";
let text = "";
let isVisible = false;
function toggle(id) {
const button = document.getElementById(id);
const input = document.getElementById("password-input");
switch (isVisible) {
case false:
button.innerText = "Hide Password";
input.value = text;
isVisible = true;
break;
case true:
button.innerText = "Show Password";
input.value = star.repeat(text.length);
isVisible = false;
}
console.log(`Text When Button Clicked: ${text}`);
}
function formatInput(id) {
//The tl:dr of this is that each key pressed (so long as it's valid which is to be determined by you) will be added to session storag which you can call from anywhere, allowing you to register stars of the correct length or text of the correct value
const elem = document.getElementById(id);
const keyPressed = event.key;
//event.key should be equal to 1 unless you want it to register "Backspace" and so on as inputs. The elem.value change in the first conditional is necessary to avoid removing more than 1 character on the input; Wihtout it, we would get something like text.length = x and elem.value.length = x - 1
if (keyPressed == "Backspace") {
text = text.substring(0, text.length - 1);
elem.value = elem.value.substring(0, elem.value.length);
console.log(`Text at Backspace: ${text}`)
return;
}
if (keyPressed.length == 1) {
text = text + keyPressed;
elem.value = text;
}
//You could use a conditional here, I just prefer switches in the case that we are checking one simple thing
switch (isVisible) {
case false:
elem.value = star.repeat(text.length - 1)
console.log(`Text When Password = Hidden: ${text}`)
break;
case true:
elem.value = text;
//This is required as wihtout it there is a bug that duplicated the first entry if someone decides to show the password
elem.value = elem.value.substring(0, text.length - 1)
console.log(`Text When Password = Visible: ${text}`)
}
}
div {
display: flex;
flex-direction: column;
align-items: center;
}
input {
width: 50%;
height: 35px;
border: 0px;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.2) inset;
border-radius: 5px;
padding-left: 15px;
letter-spacing: 2px;
outline: none;
}
input:focus {
border: 1px green solid;
}
button {
height: 35px;
background-color: rgb(94, 124, 153);
border-radius: 5px;
border: 0px;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
cursor: pointer;
width: 30%;
margin-top: 30px;
color: white;
}
<div>
<input type="text" id="password-input" placeholder="Password" onkeydown="formatInput(this.id)" value="">
<button id="toggle-button" onclick="toggle(this.id)">Show Passowrd</button>
</div>