There are many possible values for list-style-type CSS property (e. g. decimal, lower-latin, upper-greek and so on). However there are none for the Cyrillic alphabet (which, btw, has different variations for different languages).
What is the best way to style an ordered list with Cyrillic letters?
(I'm providing a solution I ended up with despite I'm not very happy with it.)
I know nothing about Cyrillic list schemes so I’m at risk of a bit of cultural embarrassment here, but CSS3 Lists module (still in working draft) defines quite a few Cyrillic alphabetic list types: lower-belorussian, lower-bulgarian, lower-macedonian, lower-russian, lower-russian-full, lower-serbo-croatian, lower-ukrainian, lower-ukrainian-full, upper-belorussian, upper-bulgarian, upper-macedonian, upper-russian, upper-russian-full, upper-serbo-croatian, upper-ukrainian, upper-ukrainian-full. As expected, the state of support for these is deplorable currently (certainly nothing in Gecko or WebKit), but hopefully going forwards these will start to be implemented.
Update: some changes have been made – the definition of list types has been moved into the CSS3 Counter Styles module whose current draft (Feb 2015) has unfortunately lost all alphabetical Cyrillic types. This is in Candidate Recommendation stage so it’s unlikely that additions will be made at the point. Perhaps in CSS4 List Styles?
In this method I'm using CSS-generated content in before each list item.
.lower-ukrainian {
list-style-type: none;
}
.lower-ukrainian li:before {
display: inline-block;
margin-left: -1.5em;
margin-right: .55em;
text-align: right;
width: .95em;
}
.lower-ukrainian li:first-child:before {
content: "а.";
}
.lower-ukrainian li:nth-child(2):before {
content: "б.";
}
/* and so on */
Disadvantages
Hardcoded, restrict list to a certain max length.
Not pixel-perfect as compared to a regular order list
Here is another solution for Cyrillic letters with pretty clear code: jsfiddle.net
(() => {
const selector = 'ol.cyrillic',
style = document.createElement('style');
document.head.appendChild( style );
'абвгдежзиклмнопрстуфхцчшщэюя'.split('').forEach((c, i) =>
style.sheet.insertRule(
`${selector} > li:nth-child(${i+1})::before {
content: "${c})"
}`, 0)
);
})();
PS. You can convert this next-gen code to old one with Babel: babeljs.io
I'm surprised that there is no Cyrillic numbering. Here's a quick JS solution for you:
function base_convert(n, base) {
var dictionary = '0123456789abcdefghijklmnopqrstuvwxyz';
var m = n.toString(base);
var digits = [];
for (var i = 0; i < m.length; i++) {
digits.push(dictionary.indexOf(m.charAt(i)) - 1);
}
return digits;
}
var letters = {
'russian': {
'lower': 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя',
'upper': 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'
}
}
$('ul, ol').each(function() {
if (!(results = $(this).prop('class').match(/(upper|lower)-([a-z]+)/i))) return;
var characters = letters[results[2]][results[1]];
$('> li', this).each(function(index, element) {
var number = '', converted = base_convert(++index, characters.length);
for (var i = 0; i < converted.length; i++) {
number += characters.charAt(converted[i]);
}
$(this).attr('data-letter', number);
});
});
My written Russian is admittedly bad, as you can see by my inability to count with letters, so change the letters object appropriately.
Demo: http://jsfiddle.net/JFFqn/14/
Related
I'm trying to build an accessible audio indicator for a WebRTC video chat. It should basically show how loud you're talking, when you're talking. Here is the isolated code (for Codesandbox, you need to install styled-components).
import React, { useEffect, useRef, useState } from "react";
import styled, { css } from "styled-components";
import "./styles.css";
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
const VolumeMeterWrapper = styled.div`
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.75);
display: flex;
height: 32px;
width: 32px;
`;
const VolumeBar = styled.div`
background: #25a968;
border-radius: 1px;
height: 4px;
margin: 0 2px;
width: 8px;
transition: all 0.2s linear;
transform: ${({ level }) => css`scaleY(${level + 1})`};
`;
function VolumeMeter({ level }) {
return (
<VolumeMeterWrapper>
<VolumeBar level={level > 3 ? ((level - 2) * 5) / 4 : 0} />
<VolumeBar level={level} />
<VolumeBar level={level > 3 ? ((level - 2) * 5) / 4 : 0} />
</VolumeMeterWrapper>
);
}
export default function App() {
const [level, setLevel] = useState(0);
const [count, setCount] = useState(0);
useInterval(() => {
setCount((c) => c + 1);
if (count % 10 > 4) {
setLevel((l) => l - 1);
} else {
setLevel((l) => l + 1);
}
}, 200);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
Level: {level}
<VolumeMeter level={level} />
</div>
);
}
In this example there is no real audio input as you can see. The "level" fakes how loud the person would be talking.
How would you make something like this accessible? Would you even need to do that (because inspecting the UI of various providers didn't show any special tags or aria-labels)?
Preword
This was an interesting one!
Firstly a small apology, the example is a little messy as I have tried to leave in a few parts of my process to give you options, if anything doesn't make sense then let me know!
It should be easy to tidy up and turn into a reusable component.
Answer
The actual part for announcing is simple, you just need a visually hidden paragraph on the page with aria-live on it and update the text inside it.
<p class="visually-hidden" aria-live="assertive">
//update the text in here and it will be announced.
</p>
What is more difficult is making a nice screen reader interface and announcer experience.
How I handled announcement
What I ended up doing was taking the average and peak volume over time and announcing a message based on those two parameters.
If the volume goes above 95 (an arbitrary number assuming volume goes to 100) or is consistently above 80 then I announce that the microphone is too loud.
If the volume is below 40 on average then I announce the microphone is too quiet.
If the average volume is below 10 then I assume the microphone is not working or they are not talking.
Otherwise I announce that the volume is ok.
The numbers will need some tweaking as I obviously simulated fluctuating volume levels and real world numbers may be different.
The other thing I did was make sure that the aria-live region only updated every 2 seconds. However I have a slow announcer speed on my screen reader so you could probably get away with about 1200ms.
This is to avoid flooding the announcer queue.
Alternatively you could set this to a much larger value (say 10 seconds) as it would then be useful for monitoring on an ongoing basis throughout a call. If you decide to do this then set the announcer to aria-live="polite" so it doesn't interrupt other screen reader announcements.
Further improvements I thought of
I didn't implement this but there are two things I can think of to make this more accurate (and less annoying, at the moment if would not be usable throughout a whole call) if you want it to be used as an ongoing monitoring tool throughout a call.
Firstly I would discard any values less than 10 for volume and only take an average of volumes above 10.
This would be more indicative of the actual volume level when the user is actively speaking.
Secondly I would discard all volume level announcements if someone else is talking. You would expect the user to be quiet at that point so no point telling them their microphone is quiet! Plus you don't really want announcements while others are talking.
The other way I tried
I did try announcing the volume as an integer value every 500ms, but as this was a snapshot I felt it was not very accurate as to what was happening. That is why I went for average volume.
I then realised that you could get an average of 50 but peak at 100 (clipping) so added peak volume to the checks as well.
Other things
1. announce on focus
I made it so that when you focus the EQ graph level thing (no idea what to call it! hehe) it announced while it had focus at first.
I realised this was better as a toggle button so that users can switch it on and off at will.
having a toggle is preferable as it allows you to fiddle with volume levels while listening to the announcements, it also allows everyone else to switch it on and off as per their preference
2. Announcing via an average and using phrases
The other advantage to taking the average and then announcing a phrase rather than a number is user experience if the indicator is left on.
It only announces when there is a change to the aria-live region, so as long as the volume stays in acceptable levels it won't announce anything. Obviously if they stop speaking it will announce "the microphone is too quiet", but yet again it will only announce this once.
3. prefers-reduced-motion
Users with vestibular disorders (sensitivity to movement disorders) may be distracted by the bars.
As such I have hidden the EQ altogether for those users.
Now because I have the EQ switched off by default this is not necessary. However if you decide to have the EQ switched on by default you can use the prefers-reduced-motion media query to set a much slower animation speed etc. That is why I have left it in, as an example of how to use it (it was an overhang from when I made the EQ work on focus rather than a toggle so isn't needed anymore).
I decided that the most accessible way to implement this was with the EQ switched off by default. This also helps people with attention disorders who may get easily distracted by the bars as well as making the page safe for people with epilepsy / seizure disorders as the bars could "flash" more than 3 times in a second.
4. Changing the button text.
I take advantage of our visually-hidden class when the EQ is switched on.
I change the text in the EQ and then visually hide it so that screen reader users still get useful button text when they focus the EQ but everyone else just gets to see the EQ bars.
Example
Ignore the first part of the JS that is just my shoddy way to make the fake EQ.
I have added a very large comment block to mark the start of the actual parts you need. It starts around line 65 in the JS.
There are parts left in for your reference for alternative ways to do things (announce on focus and prefers-reduced-motion in the CSS) so it is a little messy.
Any questions just ask.
//just making the random bars work, ignore this except for globalVars.currentVolume which is defined in the next section as that is what we use to announce the volume.
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function removeClass(selector, className){
var el = document.querySelectorAll(selector);
for (var i = 0; i < el.length; i++) {
el[i].classList.remove(className);
}
}
function addClass(selector, className){
var el = document.querySelectorAll(selector);
for (var i = 0; i < el.length; i++) {
el[i].classList.add(className);
}
}
var generateRandomVolume = function(){
var stepHeight = 0.8;
globalVars.currentVolume = randomInt(0,100);
setBars(globalVars.currentVolume * stepHeight);
setTimeout(generateRandomVolume, randomInt(102,250));
return;
}
function setBars(volume){
var bar1 = document.querySelector('.bar1');
var bar2 = document.querySelector('.bar2');
var bar3 = document.querySelector('.bar3');
var smallHeightProportion = 0.75;
var smallHeight = volume * smallHeightProportion;
bar1.style.height = smallHeight + "px";
bar2.style.height = volume + "px";
bar3.style.height = smallHeight + "px";
//console.log(globalVars.currentVolume);
if(globalVars.currentVolume < 80){
addClass('.bar', 'green');
removeClass('.bar', 'orange');
removeClass('.bar', 'red');
}else if(globalVars.currentVolume >= 90){
addClass('.bar', 'red');
removeClass('.bar', 'orange');
removeClass('.bar', 'green');
}else{
addClass('.bar', 'orange');
removeClass('.bar', 'red');
removeClass('.bar', 'green');
}
}
window.addEventListener('load', function() {
setTimeout(generateRandomVolume, 250);
});
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////// ACTUAL ANSWER ///////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//actual code you need, sorry it is a bit messy but you should be able to clean it up (and make it "Reactified")
// global variables, obviously only the currentVolume needs to be global so it can be used by both the bars and the announcer
var globalVars = {};
globalVars.currentVolume = 0;
globalVars.volumes = [];
globalVars.averageVolume = 0;
globalVars.announcementsOn = false;
//globalVars.indicatorFocused = false;
var audioIndicator = document.getElementById('audio-indicator');
var liveRegion = document.querySelector('.audio-indicator-announce');
var buttonText = document.querySelector('.button-text');
var announceTimeout;
//adjust the speech interval, 2 seconds felt right for me but I have a slow announcer speed, I would imagine real screen reader users could handle about 1200ms update times easily.
var config = {};
config.speakInterval = 2000;
//push volume level every 100ms for use in getting averages
window.addEventListener('load', function() {
setInterval(sampleVolume, 100);
});
var sampleVolume = function(){
globalVars.volumes.push(globalVars.currentVolume);
}
audioIndicator.addEventListener('click', function(e) {
toggleActive();
});
audioIndicator.addEventListener('keyup',function(e){
if (e.keyCode === 13) {
toggleActive();
}
});
function toggleActive(){
if(!audioIndicator.classList.contains('on')) {
audioIndicator.classList.add('on');
announceTimeout = setTimeout(announceVolumeInfo, config.speakInterval);
liveRegion.innerHTML = "announcing your microphone volume is now on";
buttonText.classList.add('visually-hidden');
buttonText.innerHTML = 'Mic<span class="visually-hidden">rophone</span> Level indicator on (click to turn off)';
console.log("SPEAK:","announcing your microphone volume is on");
}else{
audioIndicator.classList.remove('on');
clearTimeout(announceTimeout);
liveRegion.innerHTML = "announcing your microphone volume is now off";
buttonText.classList.remove('visually-hidden');
buttonText.innerHTML = 'Mic<span class="visually-hidden">rophone</span> Level indicator off (click to turn on)';
console.log("SPEAK:","announcing your microphone volume is off");
}
}
//switch on the announcer - deprecated idea, instead used toggle switch
//audioIndicator.addEventListener('focus', (e) => {
// setTimeout(announceVolumeInfo, config.speakInterval);
//});
//we take the average over the speakInterval. We also take the peak so we can see if the users microphone is clipping.
function getVolumeInfo(){
var samples = globalVars.volumes.length;
var totalVol = 0;
var avgVol, peakVol = 0;
var sample;
for(var x = 0; x < samples; x++){
sample = globalVars.volumes[x]
totalVol += sample;
if(sample > peakVol){
peakVol = sample;
}
}
globalVars.volumes = [];
var volumes = {};
volumes.average = totalVol / samples;
volumes.peak = peakVol;
return volumes;
}
var announceVolumeInfo = function(){
var volumeInfo = getVolumeInfo();
updateLiveRegion (volumeInfo.average, volumeInfo.peak);
//part of deprecated idea of announcing only on focus
//if(document.activeElement == document.getElementById('audio-indicator')){
// setTimeout(announceVolumeInfo, config.speakInterval);
//}
if(audioIndicator.classList.contains('on')) {
announceTimeout = setTimeout(announceVolumeInfo, config.speakInterval);
}
}
//we announce using this function, if you just want to read the current volume this can be as simple as "liveRegion.innerHTML = globalVars.currentVolume"
var updateLiveRegion = function(avgVolume, peak){
var speak = "Your microphone is ";
//doing it this way has a few advantages detailed in the post.
if(peak > 95 || avgVolume > 80){
speak = speak + "too loud";
}else if(avgVolume < 40){
speak = speak + "too quiet";
}else if(avgVolume < 10){
speak = speak + "not working or you are not talking";
}else{
speak = speak + "at a good volume";
}
console.log("SPEAK:", speak);
console.log("average volume:", avgVolume, "peak volume:", peak);
liveRegion.innerHTML = speak;
//optionally you could just read out the current volume level and that would do away with the need for tracking averages etc..
//liveRegion.innerHTML = "Your microphone volums level is " + globalVars.currentVolume;
}
#audio-indicator{
width: 100px;
height: 100px;
position: relative;
background-color: #333;
}
/** make sure we have a focus indicator, if you decide to make the item interactive with the mouse then also add a different hover state. **/
#audio-indicator:focus{
outline: 2px solid #666;
outline-offset: 3px;
}
#audio-indicator:hover{
background-color: #666;
cursor: pointer;
}
/*************we simply hide the indicator if the user has indicated that they do not want to see animations**********/
#media (prefers-reduced-motion) {
#audio-indicator{
display: none;
}
}
/***********my visually hidden class for hiding content visually but still making it screen reader accessible, preferable to sr-only etc as futureproofed and better compatibility********/
.visually-hidden {
border: 0;
padding: 0;
margin: 0;
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
}
#audio-indicator .bar{
display: none;
}
#audio-indicator.on .bar{
display: block;
}
.bar, .button-text{
position: absolute;
top: 50%;
left: 50%;
min-height: 2px;
width: 25%;
transition: all 0.1s linear;
}
.button-text{
width: 90px;
transform: translate(-50%,-50%);
text-align: center;
color: #fff;
}
.bar1{
transform: translate(-175%,-50%);
}
.bar2{
transform: translate(-50%,-50%);
}
.bar3{
transform: translate(75%,-50%);
}
.green{
background-color: green;
}
.orange{
background-color: orange;
}
.red{
background-color: red;
}
dummy link for focus
<div class="audio-indicator-announce visually-hidden" aria-live="assertive">
</div>
<div id="audio-indicator" tabindex="0">
<div class="button-text">Mic<span class="visually-hidden">rophone</span> Level indicator off (click to turn on)</div>
<div class="bar bar1"></div>
<div class="bar bar2"></div>
<div class="bar bar3"></div>
</div>
dummy link for focus
Final Thoughts
While the above would work the question is "is an EQ graph relevant / a good experience from a UX perspective?".
You would have to user test to find that out.
Using a method similar to Zoom might be preferable (with a minor tweak and an accessible interface).
Before (or during) a call allow a user to listen to a pre recorded sound first. This allows them to set their own volume level.
Then allow the user to speak and then play back their audio so they can check their microphone levels / their microphone is working.
Allow users to switch on "automatically adjust my microphone level" and use a similar method to the one I am using of averages while talking to adjust their volume automagically throughout a call.
You could also have a screen reader option that allows the system to announce if it is adjusting their volume automatically.
You could also have a visual indicator on the microphone icon / button that shows an up or down arrow if the volume is adjusted automatically if you want.
This is obviously just a thought and you might have a good use case of an EQ graph way of doing things, as I said it was just a thought!
Most of us know that now and then some tags get a deprecated status, which means that it has been outdated. Either it is followed by a newer HTML construct, or it can be done in CSS (take for example <center>). The question that I'm wondering about, though, is: when a tag or element gets deprecated will it be removed from browser support in the future? In other words, currently all browsers that I know of support <center>, but I can imagine that it might not be efficient for browsers to keep supporting deprecated content. Therefore, support must drop after some time.
Is it likely that browsers drop support for a tag or element that once was quite common? To provide a question that's better suited for the SO question-answer template, I'll rephrase all of the above: are cases known where browsers have dropped support for a property or element that once was common?
The only thing that I could find was in the docs, stating:
Deprecated A deprecated element or attribute is one that has been outdated by newer constructs. Deprecated elements are defined in the
reference manual in appropriate locations, but are clearly marked as
deprecated. Deprecated elements may become obsolete in future versions
of HTML.
User agents should continue to support deprecated elements for reasons of backward compatibility.
Definitions of elements and attributes clearly indicate which are
deprecated.
As I see it, this is not opinion based. I am asking if there are cases known of tags that are actually not being supported by browsers any more. That's not bound by opinion. However I do understand that this question has quite an open feel to it. Therefore I'd like to clarify that I am looking for actual and factual evidence of browsers dropping support. I'm not asking about any foreseers to come forward and confess their magical powers, I'm merely looking for examples from cases that have occurred in the past.
The code below creates elements from deprecated tags, and it outputs what the browser thinks the newly-created elements really are:
var dep = 'acronym|applet|basefont|bgsound|big|blink|center|dir|font|frame|frameset|hgroup|isindex|listing|marquee|menu|multicol|nextid|nobr|noembed|noframes|plaintext|s|spacer|strike|tt|u|xmp'.split('|'),
s = '<table>',
els = [];
dep.forEach(function(val) {
var el = document.createElement(val),
str = el.toString().slice(8, -1),
style = 'HTMLElement HTMLPhraseElement HTMLBlockElement HTMLPreElement HTMLSpanElement HTMLDivElement'.indexOf(str) > -1 ? 'background:yellow' :
str === 'HTMLUnknownElement' ? 'background:orange' :
'';
el.innerHTML = val;
els.push(el);
s += '<tr style="' + style + '">' +
'<td>' + val +
'<td>' + str +
'<td>';
});
s += '</table>';
document.getElementById('list').innerHTML = s;
var td = document.querySelectorAll('td:last-child');
dep.forEach(function(val, idx) {
td[idx].appendChild(els[idx]);
});
table {
font: 12px verdana;
border-spacing: 0px;
border: 1px solid black;
}
td {
border-right: 1px solid #ddd;
border-bottom: 1px solid #bbb;
}
<div id="list"></div>
We can assume that anything highlighted in orange is not supported by that browser, anything highlighted in yellow is iffy, and the rest should be completely supported.
To determine the degree of "iffyness" of the generic "HTMLElements," we could compare their default CSS styles to the default styles of a span or div element. The Snippet below does this by adding a new column to the listing, which shows styles distinct to each deprecated element.
Elements of type "HTMLUnknownElement" have no distinct styles (as expected). Most other elements do. For those that don't, that doesn't necessarily mean they don't support distinct attributes. For example, the font element's styles match the default styles of a span – but the font element supports attributes size and face, which the span does not support.
function getStyles(el) {
var gcs= getComputedStyle(el),
st= gcs.cssText ? gcs.cssText.split(/; */) : el.currentStyle,
obj= {},
i, j, sp;
for(var i = 0 ; i < st.length ; i++) {
sp= st[i].split(':')[0];
if(j = gcs.getPropertyValue(sp)) {
obj[sp]= j;
}
}
return obj;
} //getStyles
function compStyles(st1, st2) {
var s= '';
for(var i in st1) {
if(st1[i] && st1[i] !== st2[i]) {
s+= i+': '+st1[i]+' - '+st2[i]+'; ';
}
}
return s;
} //compStyles
var dep= 'acronym|applet|basefont|bgsound|big|blink|center|dir|font|frame|frameset|hgroup|isindex|listing|marquee|menu|multicol|nextid|nobr|noembed|noframes|plaintext|spacer|strike|tt|xmp'.split('|'),
s= '<table>',
els= [],
spanStyles=
getStyles(
document.body.appendChild(
document.createElement('span')
)
),
divStyles=
getStyles(
document.body.appendChild(
document.createElement('div')
)
);
dep.forEach(function(val) {
var el= document.createElement(val),
str= el.toString().slice(8,-1),
display,
style= 'HTMLElement HTMLPhraseElement HTMLBlockElement HTMLPreElement HTMLSpanElement HTMLDivElement'.indexOf(str)>-1 ? 'background:yellow' :
str==='HTMLUnknownElement' ? 'background:orange' :
'';
document.body.appendChild(el);
display= getStyles(el).display;
el.innerHTML= val;
els.push(el);
s+= '<tr style="'+style+'">'+
'<td>'+val+
'<td>'+str+
'<td>'+display+
'<td>'+compStyles(
getStyles(el),
display==='block' ? divStyles : spanStyles
)+
'<td>';
});
s+= '</table>';
document.getElementById('list').innerHTML= s;
var td= document.querySelectorAll('td:last-child');
dep.forEach(function(val, idx) {
td[idx].appendChild(els[idx]);
});
table {
font: 12px verdana;
border-spacing: 0px;
border: 1px solid black;
}
td {
vertical-align: top;
border-right: 1px solid #ddd;
border-bottom: 1px solid #bbb;
}
<div id="list"></div>
It has happened before.
The <blink> HTML tag (see wiki and docs) used to be quite common, but it was considered very user-unfriendly and therefore became deprecated. Netscape, Opera and also Firefox used to support it. Firefox was the last to finally completely remove it in version 23.
The <blink> element was exceptionally obtrusive and became very unpopular, so the drop in support was no surprise... but it is also a question of backwards compatibility. Do the benefits of removing something outweigh the loss of its functionality? <blink> could be removed without much repercussion (things would just stop blinking). On the other hand, a tag like <marquee> (which has also received a lot of negative press) is still supported, most likely because removing it may effect content directly.
All in all I think that the issue isn't really if existing browsers will remove deprecated css/html (since it is a relatively rare occurrence), but rather whether new/future browsers will support them. Backwards compatibility will only go so far.
To sum up: Yes, so don't use deprecated features.
This HTML fragment spreads a long list of short items over a series of columns so that it's easier to scan and doesn't take up as much vertical space. Everything is neatly aligned and the number of columns automatically adjusts itself if you resize the browser window. That's all great. The only problem is that the width of each column is hardcoded in the CSS.
<!doctype html>
<style>
ul.hlist {
width: 80%;
column-width: 6em;
-moz-column-width: 6em;
-webkit-column-width: 6em;
}
ul.hlist > li { display: block; }
</style>
<p>This header unconditionally uses one or more of these deprecated
integer typedefs:</p>
<ul class="hlist">
<li><code>caddr_t</code></li>
<li><code>daddr_t</code></li>
<li><code>fsid_t</code></li>
<li><code>quad_t</code></li>
<li><code>u_int_t</code></li>
<li><code>u_quad_t</code></li>
<li><code>u_int16_t</code></li>
<li><code>u_int32_t</code></li>
<li><code>u_int64_t</code></li>
<li><code>u_int8_t</code></li>
<li><code>u_char</code></li>
<li><code>u_short</code></li>
<li><code>u_int</code></li>
<li><code>u_long</code></li>
<li><code>n_short</code></li>
<li><code>n_long</code></li>
<li><code>n_time</code></li>
</ul>
With some JavaScript, one can scan the list and override column-width to the actual width of the widest list item; there's some fiddliness because each <li> is stretched to the current width of the column, so you have to look inside, but that's not a serious hurdle.
function innerWidth (el) {
var kids = el.children;
var w = 0;
for (var i = 0; i < kids.length; i++)
w += kids[i].offsetWidth;
return w;
}
function setColumnWidth (list) {
var items = list.children;
var mW = 0;
for (var i = 0; i < items.length; i++) {
var w = innerWidth(items[i]);
if (w > mW)
mW = w;
}
list.setAttribute("style",
"column-width:" + mW + "px;" +
"-moz-column-width:" + mW + "px;" +
"-webkit-column-width:" + mW + "px");
}
document.addEventListener("DOMContentLoaded", function (e) {
var lists = document.getElementsByClassName("hlist");
for (var i = 0; i < lists.length; i++)
setColumnWidth(lists[i]);
});
The question is, is there any way to get the same (or nearly so) effect as this JS using only CSS? Please note:
In the example, every <li> has its contents wrapped in a <code>, but your answer must not rely on this, because it ain't necessarily so in other cases.
I already have an entirely client-side solution, and additional server-side processing in context is extremely awkward, so I strongly prefer client-only answers.
Answers involving experimental or not-yet-deployed-at-all CSS features are just fine.
Rendering without JS: http://jsfiddle.net/7F8n6/2/ (columns are rather too wide)
Rendering with JS: http://jsfiddle.net/7F8n6/3/
The items are inside <code> elements so by default are rendered with a monospaced font. That means their width only depends on the number of characters.
Assuming you can influence the complete page on the server, determine the item with the most characters on the server. Here it's 9.
Now generate a CSS rule in the page that sets the column width to 9 characters:
column-width: 9ch;
-moz-column-width: 9ch;
-webkit-column-width: 9ch;
The 'ch' unit is the width of a single '0' (zero). It's not supported on every browser, see MDN, so I don't know if this solution is good enough for you.
The implementations of the major browsers seem to have problems with text-transform: uppercase with Turkish characters. As far as I know (I'm not Turkish.) there are four different i characters: ı i I İ where the last two are the uppercase representations of the former two.
However applying text-transform:uppercase to ı i, the browsers (checked IE, Firefox, Chrome and Safari) results in I I which is not correct and may change the meaning of the words so much so that they become insults. (That's what I've been told)
As my research for solutions did not reveal any my question is: Are there workarounds for this issue? The first workaround might be to remove text-transform: uppercase entirely but that's some sort of last resort.
Funny thing, the W3C has tests for this problem on their site, but lack of further information about this issue. http://www.w3.org/International/tests/tests-html-css/tests-text-transform/generate?test=5
I appreciate any help and looking forward to your answers :-)
Here's a codepen
You can add lang attribute and set its value to tr to solve this:
<html lang="tr"> or <div lang="tr">
Here is working example.
Here's a quick and dirty workaround example - it's faster than I thought (tested in a document with 2400 tags -> no delay). But I see that js workarounds are not the very best solution
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-3">
</head>
<body>
<div style="text-transform:uppercase">a b c ç d e f g ğ h ı i j k l m n o ö p r s ş t u ü v y z (source)</div> <div>A B C Ç D E F G Ğ H I İ J K L M N O Ö P R S Ş T U Ü V Y Z (should be like this)</div>
<script>
function getStyle(element, style) {
var result;
if (document.defaultView && document.defaultView.getComputedStyle) {
result = document.defaultView.getComputedStyle(element, '').getPropertyValue(style);
} else if(element.currentStyle) {
style = style.replace(/\-(\w)/g, function (strMatch, p1) {
return p1.toUpperCase();
});
result = element.currentStyle[style];
}
return result;
}
function replaceRecursive(element) {
if (element && element.style && getStyle(element, 'text-transform') == 'uppercase') {
element.innerHTML = element.innerHTML.replace(/ı/g, 'I');
element.innerHTML = element.innerHTML.replace(/i/g, 'İ'); // replaces 'i' in tags too, regular expression should be extended if necessary
}
if (!element.childNodes || element.childNodes.length == 0) return;
for (var n in element.childNodes) {
replaceRecursive(element.childNodes[n]);
}
}
window.onload = function() { // as appropriate 'ondomready'
alert('before...');
replaceRecursive(document.getElementsByTagName('body')[0]);
alert('...after');
}
</script>
</body>
</html>
Here's my enhanced version of alex's code that I am using in production:
(function($) {
function getStyle(element, style) {
var result;
if (document.defaultView && document.defaultView.getComputedStyle) {
result = document.defaultView.getComputedStyle(element, '').getPropertyValue(style);
} else if(element.currentStyle) {
style = style.replace(/\-(\w)/g, function (strMatch, p1) {
return p1.toUpperCase();
});
result = element.currentStyle[style];
}
return result;
}
function replaceRecursive(element, lang) {
if(element.lang) {
lang = element.lang; // Maintain language context
}
if (element && element.style && getStyle(element, 'text-transform') == 'uppercase') {
if (lang == 'tr' && element.value) {
element.value = element.value.replace(/ı/g, 'I');
element.value = element.value.replace(/i/g, 'İ');
}
for (var i = 0; i < element.childNodes.length; ++i) {
if (lang == 'tr' && element.childNodes[i].nodeType == Node.TEXT_NODE) {
element.childNodes[i].textContent = element.childNodes[i].textContent.replace(/ı/g, 'I');
element.childNodes[i].textContent = element.childNodes[i].textContent.replace(/i/g, 'İ');
} else {
replaceRecursive(element.childNodes[i], lang);
}
}
} else {
if (!element.childNodes || element.childNodes.length == 0) return;
for (var i = 0; i < element.childNodes.length; ++i) {
replaceRecursive(element.childNodes[i], lang);
}
}
}
$(document).ready(function(){ replaceRecursive(document.getElementsByTagName('html')[0], ''); })
})(jQuery);
Note that I am using jQuery here only for the ready() function. The jQuery compatibility wrapper is also as a convenient way to namespace the functions. Other than that, the two functions do not rely on jQuery at all, so you could pull them out.
Compared to alex's original version this one solves a couple problems:
It keeps track of the lang attribute as it recurses through, since if you have mixed Turkish and other latin content you will get improper transforms on the non-Turkish without it. Pursuant to this I pass in the base html element, not the body. You can stick lang="en" on any tag that is not Turkish to prevent improper capitalization.
It applies the transformation only to TEXT_NODES because the previous innerHTML method did not work with mixed text/element nodes such as labels with text and checkboxes inside them.
While having some notable deficiencies compared to a server side solution, it also has some major advantages, the chief of which is guaranteed coverage without the server-side having to be aware of what styles are applied to what content. If any of the content is being indexed and shown in Google summaries (for example) it is much better if it stays lowercase when served.
The next version of Firefox Nightly (which should become Firefox 14) has a fix for this problem and should handle the case without any hack (as the CSS3 specs request it).
The gory details are available in that bug : https://bugzilla.mozilla.org/show_bug.cgi?id=231162
They also fixed the problem for font-variant I think (For those not knowing what font-variant does, see https://developer.mozilla.org/en/CSS/font-variant , not yet up-to-date with the change but the doc is browser-agnostic and a wiki, so...)
The root cause of this problem must be incorrect handling of these turkish characters by unicode library used in all these browsers. So I doubt there is an front-end-side fix for that.
Someone has to report this issue to the developers of these unicode libs, and it would be fixed in few weeks/months.
If you can't rely on text-transform and browsers you will have to render your text in uppercase yourself on the server (hope you're not uppercasing the text as the user types it).
You should have a better support for internationalisation there.
This work-around requires some Javascript. If you don't want to do that, but have something server side that can preprocess the text, this idea will work there too (I think).
First, detect if you are running in Turkish. If you are, then scan whatever you are going to uppercase to see if it contains the problem characters. If they do, replace all of those characters with the uppercase version of them. Then apply the uppercase CSS. Since the problem characters are already uppercase, that should be a totally fine (ghetto) work around. For Javascript, I envision having to deal with some .innerHTML on your impacted elements.
Let me know if you need any implementation details, I have a good idea of how to do this in Javascript using Javascript string manipulation methods. This general idea should get you most of the way there (and hopefully get me a bounty!)
-Brian J. Stinar-
You have a file that outputs as such:
<div id="first">1</div>
2
<div id="third">
<? $i = 3; echo(i) ?>
</div>
Which outputs:
1
2
3
Now if I want to have an output as below:
?
?
?
Well, I was hoping I could output the Arabic/Farsi version of 1,2,3 instead of '?'. How can I use HTML/CSS convert to numbers to Arabic/Farsi?
And why does everything change except numbers when I change my language (Windows XP)?
If you want to number a list, you could use an ol element and change the list-style-type of it like:
ol {
list-style: arabic-indic;
}
Expanding on ShirzITCo and Kirk Woll's answer above -- which saved my but when client decided all the content in a WordPress CMS should use Eastern-Arabic numerals instead of the Western Arabic they started with:
function recursiveReplace(node) {
if (node.nodeType === 3) { // text node
node.nodeValue = node.nodeValue
.replace(/0/g,'۰')
.replace(/1/g,'۱')
.replace(/2/g,'۲')
.replace(/3/g,'۳')
.replace(/4/g,'۴')
.replace(/5/g,'۵')
.replace(/6/g,'۶')
.replace(/7/g,'۷')
.replace(/8/g,'۸')
.replace(/9/g,'۹');
} else if (node.nodeType === 1) { // element
$(node).contents().each(function () {
recursiveReplace(this);
});
}
}
recursiveReplace(document.body);
There is no automated way to change numbers, or even digits. Set up a mapping between then and convert manually.
Numbers are never localized, because unlike natural language words, Arabic numerals are understood across most of the world.
Simply use CSS:
ol {
list-style-type: persian
}
Use the following JavaScript code. This will replace numbers:
function recursiveReplace(node) {
if (node.nodeType == 3) { // text node
node.nodeValue = node.nodeValue.replace("1", "۱");
} else if (node.nodeType == 1) { // element
$(node).contents().each(function () {
recursiveReplace(this);
});
}
}
recursiveReplace(document.body);