How to enforce LI formatting in a contenteditable UL - html

I'm trying to allow users to edit a list (UL). In my attempts, it appears that contenteditable doesn't do anything special (like enforcing behind-the-scenes markup) -- it just gives the user a window into the innerHTML.
This is causing issues in that if there is not already a LI, and the user adds something, it doesn't get LI-ized. Similarly, if there are list items, but the user deletes them, then the LI gets deleted, and any new text is added without LI's. See http://jsfiddle.net/JTWSC/ . I've also found that it's sometimes possible for the cursor to "get outside" of an LI that does exist, but I can't reproduce consistently.
I have to include code, so this is what the "result" looks like:
<ul>whatever the user typed in</ul>
How do I fix this? I started down the path of a $('ul').keyup() handler that checks the html and wraps as necessary, but I was running into a handful of gotchas, like timing, losing focus on the element, having to refocus in the right place, etc. I'm sure it's possible if I work at it, but I'm hoping for an easier solution.

I built the following keyup/down handler in order to make my contenteditable <UL>s idiot proof.*
It does two things:
Adds <LI>s to the <UL> when the <UL> is empty. I use some code I found on SO (from Tim Down) to place the caret in the expected place
Cleans up all non-LI / non-BR tags. This is basically a quick-and-dirty paste-cleaner.
This is pushing my comfort-level on jquery and DOM manipulation, so there are probably a few things I could do better, but it works pretty well as-is.
//keyup prevented the user from deleting the bullet (by adding one back right after delete), but didn't add in li's on empty ul's, thus keydown added to check
$('ul').on('keyup keydown', function() {
var $this = $(this);
if (! $this.html()) {
var $li = $('<li></li>');
var sel = window.getSelection();
var range = sel.getRangeAt(0);
range.collapse(false);
range.insertNode($li.get(0));
range = range.cloneRange();
range.selectNodeContents($li.get(0));
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
} else {
//are there any tags that AREN'T LIs?
//this should only occur on a paste
var $nonLI = $this.find(':not(li, br)');
if ($nonLI.length) {
$this.contents().replaceWith(function() {
//we create a fake div, add the text, then get the html in order to strip out html code. we then clean up a bit by replacing nbsp's with real spaces
return '<li>' + $('<div />').text($(this).text()).html().replace(/ /g, ' ') + '</li>';
});
//we could make this better by putting the caret at the end of the last LI, or something similar
}
}
});
jsfiddle at http://jsfiddle.net/aVuEk/5/
*I respectfully disagree with Diodeus that training is the best / easiest solution in all cases. In my situation, I have several contenteditable <UL>s on a page that are very in-line WYSIWYG (ie, not a lot of room for tinymce-style chrome) and used by casual, first-time, non-advanced users.

Related

Display value + text using jQuery

I am a total beginner. I have a form in HTML and am trying to calculate a specific value using jQuery. I want this value to be displayed in paragraph <p id="final"></p> under the submit button, but am actually not sure, why my code isn't working.
jQuery(document).on("ready", function() {
jQuery("final").hide();
jQuery("#form").submit(function(e){
e.preventDefault();
const data = jQuery(this).serializeArray();
/*
some calculations
*/
$('#final').html($('#final').html().replace('','result + " text"'));
jQuery("#final").show();
}
}
Do you have any idea, what could I be doing wrong??
You've got a several issues here.
Firstly, don't mix jQuery and $. If you're using the former, it's normally to avoid jQuery's alias, $, from conflicting with other code that might use $.
Secondly, you don't actually do any calculation (from what I can see in your code), so I'm not sure what you're wanting to output. I'll assume you're going to fill that in later.
Thirdly, jQuery('final').hide() is missing the # denoting you're targeting by element ID.
Fourthly, the line
$('#final').html($('#final').html().replace('','result + " text"'));
...doesn't quite do what you think it does. For one thing, it makes no reference to your data variable. And running replace() on an empty string doesn't make much sense.
All in all I'm guessing you want something like (note also how I cache the #final element - that's better for perforamnce):
jQuery(function() { //<-- another way to write a document-ready handler
let el = jQuery('#final');
el.hide();
jQuery("#form").submit(function(e){
e.preventDefault();
const data = jQuery(this).serializeArray();
let calc = 5+2; //<-- do what you need to here
el.html(calc).show();
}
}
Guessing result is your variable and your above code is your current status, you should fix the html replacement to something like (depending on your acutal usecase):
$('#final').html(result + " text"));

Want `­` to be always visible

I'm working on a web app and users sometimes paste in things they've copy/pasted from other places and that input may come with the ­ character (0xAD). I don't want to filter it out, I simply need the user to see that there is an invisible character there, so they have no surprises later.
Does anyone know a way to make the ­ always be visible? To show a hyphen, rather than remain hidden? I suspect a custom web font might be needed, if so, does anyone know of a pre-existing one?
You would need to either use JavaScript or a custom typeface that has a visible glyph for the soft-hyphen character. Given the impracticalities of working with typefaces for the web (and burdening the user with an additional hundred-kilobyte download) I think the JavaScript approach is best, like so:
document.addEventListener("DOMContentLoaded", function(domReadyEvent) {
var textBoxes = document.querySelectorAll("input[type=text]");
for(var i=0;i<textBoxes.length;i++) {
textBoxes[i].addEventListener("paste", function(pasteEvent) {
var textBox = pasteEvent.target;
textBox.value = textBox.value.replace( "\xAD", "-" );
} );
}
} );

Is there a way to search for searchable text in <map...><area ... title="searchable text" /></map> and <img alt="searchable text" />?

Using Ctrl-F in most browsers will allow you to search for text, but only in only the text areas. I would like to search for text in what should be accessible areas that are not necessarily text rendered areas such as <map ...><area title="searchable text" /></map> and <img alt="searchable text" />. Is there a browser or addon that will do what I'm asking for? This stuff is here for accessibility, but it doesn't seem to be really all that accessible (except by mouse hover, which again isn't all that accessible).
NOTE
An answer that is required, does not use something that is decoupled from the view. I.e. searching through the source code isn't an option as this is largely difficult to read (esp on complex pages) and doesn't show where the information is located on the rendered page.
Is there a browser or addon that will do what I'm asking for?
Oh yes. Lynx browser does it.
But I guess it's not a solution ;-)
If your question is so, there is no way to override what CTRL+F is doing in your browser.
You can design a custom plugin inside your website, or an addon for your browser. This would be quite easy... but will require other shortcut.
If your main problem is to locate tags based on their alt or title attributes content, this is quite easy in javascript:
var search='enter image';
var nodes=document.querySelectorAll("[alt*='"+search+"'],[title*='"+search+"']");
You can then highlight the matching nodes using jquery or what you want.
for (i in nodes) {
nodes[i].className+=' resultHighlighted';
}
and scroll to the first result:
nodes[0].scrollIntoView();
If you intend to create a browser plugin, you can create your custom a bookmarklet or a custom plugin, and associate a shortcut to this bookmark (see https://github.com/iSunilSV/Chrome-Bookmark-Shortcut)
A simple bookmarklet to find the first match by title or alt attribute and scroll to it will be something like that:
javascript:text=prompt("search inside alt or title attribute");
document.querySelector("[alt*='"+text+"'],[title*='"+text+"']").scrollIntoView();
In your browser, use the "View Source" or "Source Code" function, and then within that window that pops up, use the Ctrl-F for Find.
You can also use the "Inspect Element" directly on an element to split the screen into two windows- one for code and one that's rendered.
For more information, here's a sample article for Chrome:
https://support.google.com/adsense/answer/181951?hl=en
Would something like the Web Developer browser plugin work? It's available for Chrome, FF and Opera. There are a few features that toggle the display of various attributes such as title, alt and even ARIA roles. This injects the attribute text inline with the element.
In my opinion, it's not a bug; they were just not designed for this use.
As I'm sure you are aware, the alt attribute replaces the image when it's not available. So how could you scroll to something that is not always displayed? Whereas you seem to be after a permanent description; a figcaption would be more appropriate for this.
As for the title attribute, it was intended to merely clarify the purpose of a link. There should not be any new information to the user in the title; therefore I think it would be redundant to have two lots of the same information highlighted in one place.
The purpose of searching is to find text on screen, seeing as neither title or alt are always displayed I think the user would be more confused by the fact that nothing is highlighted, and that they are just taken to an image or random link/area. If the image has a figcaption that becomes highlighted, then it would make sense to them. Besides, how are they going to search for the title if they don't know what to search for? Title and alt do not come up in text displayed by search engines; the user will never know about it unless they've been to your site before, in which case they'll know where to look.
Also you state the following:
This stuff is here for accessibility, but it doesn't seem to be really all that accessible
Which, understandably, seems true to you as you probably do not need it. However alt and title are read out to those who use screen readers so isn't entirely useless.
Idea 1
I assume you have Windows and Firefox installed
I have my Firefox installed with 2 add-ons.
Install a add-on called Tile Tabs, it make it possible for example left side is web view and the same page on right side with source code.
Install add-on called Web page to source code & viceversa that make it possible to toggle between view and source code by pressing on CTRL+SHIFT+S
Since what you required is not a default thing in all nowadays browses as far as I know.
Screen shot of the solution:
Idea 2
Install FireBug, you can view/edit/debug source codes and view HTML live and what you highlight on the code will be also highlighted on the view.
Screen shot:
Note: Btw idea 1 is not only good for view / source code but it is also good to compare two views or read article to the right and answer question to the left.
You can use the search funktion in Chrome's developer tools "Elements" Tab (Press F12 -> Tab "Elements" -> Press CTRL + F) and use XPath on your searches. Example:
//*[#title="Google"]
Matches will be shown with a yellow background in the code and when you hover it, its position will be hightlited in the view.
Dev Tools "Element" Search with XPath
It is coupled with the view, allows you to see the element's position and it's also an out-of-the-box solution in Chrome (tested in Chromium 45 for Ubuntu).
Hope it helps!
Regards
EDIT
Forgot - If you want to use wildcards on your searches, you can also do it like this:
//*[contains(#title, 'Google')]
EDIT 2
For the posterity! Further research shows that your goal might be possible to achieve using the Firefox-Addon Greasemonkey, which allows you to customize the way a web page displays or behaves, by using small bits of JavaScript.
I performed several tests with this addon and could achieve a nice effect with simple images (display the ALT attribute as a DIV overlapping the image), but with area sections the thing gets a lot more complicated, as area regions can be squares, circles, and polygons with infinite coordinates plus retrieving the exact positioning of the area itself can be a bit tricky but maybe gives you or someone else a start point.
Based on the ALT Tooltips Script (http://greasemonkey-user-scripts.arantius.com/alt-tooltips-for-firefox), I created the following script and defined it in Greasemonkey:
// ==UserScript==
// #name Alt Tooltips 2
// #namespace http://www.biterion.com
// #description Alt Tooltips 2
// #include *
// #grant all
// ==/UserScript==
function getPosition(element) {
var xPosition = 0;
var yPosition = 0;
while(element) {
xPosition += (element.offsetLeft - element.scrollLeft + element.clientLeft);
yPosition += (element.offsetTop - element.scrollTop + element.clientTop);
element = element.offsetParent;
}
return { x: xPosition, y: yPosition };
}
function getAreaPosition(element) {
var position = element.coords.split(',');
xPosition = position[0];
yPosition = position[1];
return { x: xPosition, y: yPosition}
}
var res = document.evaluate("//img",document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
var i, el;
for (i=0; el=res.snapshotItem(i); i++) {
if(el.alt) {
alternate = el.alt
} else {
alternate = "No alt text";
}
position = getPosition(el);
var newDIV = document.createElement ('div');
newDIV.innerHTML = "<div style='position:absolute;background:yellow;color:black;top:" + position["y"] + ";left:" + position["x"] + "' id=" + i + ">" + alternate + "</div>";
document.body.appendChild(newDIV);
}
var res2 = document.evaluate("//area",document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
var i2, el2;
for (i2=0; el2=res2.snapshotItem(i2); i2++) {
if(el2.alt) {
alternate2 = el2.alt
} else {
alternate2 = "No alt text";
}
position2 = getAreaPosition(el2);
var newDIV2 = document.createElement('div');
newDIV2.innerHTML = "<div style='position:absolute;background:yellow;color:black;top:" + position2["y"] + ";left:" + position2["x"] + "' id=" + i2 + ">" + alternate2 + "</div>";
document.body.appendChild(newDIV2);
}
As you can see, the script firstly detects all "img" and "area" elements, extracts its positioning and creates a new DIV element containing the "alt" attribute, which is then positioned on the upper left corner of the image.
As stated, the problem with areas is, that the positioning should be relative to the parent image and not absolute like in the script, plus the coordinates should be extracted accordingly to the type of area shape (currently only extracting the two first coordinates of each area, which will work for squares but will surely fail for other shapes).
Hope this will help someone :-D
Regards

TabIndex - hitting tab moves me to Address Bar - unable to work around this using Focus or +tab indexes

I read several threads that talk about how the Address Bar in IE is basically the first one to get focus when using TAB (MSDN's own docs talk about this).
Yet, I have seen situations where this doesn't always have to be the case....
I have a master page and inside my content area is a formView.
It defaults to INSERT view and can never leave it (they can only insert not edit and reading is handled elsewhere)
So on my page load for the page I have:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If fvwLogEntry.CurrentMode = FormViewMode.Insert = True Then
'Set the default field to position the cursor there...hopefully
Dim FCtxtHrEmployeeId As TextBox
FCtxtHrEmployeeId = CType(fvwLogEntry.FindControl("txtHrEmployeeId"), TextBox)
Page.SetFocus(FCtxtHrEmployeeId.ClientID.ToString)
End If
Now that works, when the page loads it sets the cursor to the employeeID text box inside the formview's INSERT template.
HOWEVER, when I hit TAB it takes me to the address bar and THEN if I hit tab again it takes me through the rest of the items on the page.
I set the tab index of the first item to 11 and then incrimented from there (I had read that IE's toolbars have tab indexes too so I thought perhaps using a higher number would bypass those, but again that doesn't REALLY make sense since it would still start at the lowest number, but I gave it a shot thinking it would move forward from where the focus was set.) If I click on the textbox and then hit TAB it DOES move through the page like I would expect.
It is just when the page loads and gets the focus set to the employeeID textbox that hitting tab moves it to the address bar.
I also tried setting the other controls to -1 (those I didn't want it to tab to), still no luck there.
So... what can I do to get around this?
There MUST be a simple way to set the focus to the employeeID textbox and ensure that pressing TAB after that moves to the next control in the formview's insert template and does NOT jump up to the address bar?
The following jquery code seems to be working fine for me..
$(window).load(function () {
$('.myClass :visible:input:enabled:first').focus();
});
$('body').on('keydown', '.myClass :visible:input:enabled:first', function (e) {
if ((e.which == 9) || (e.keyCode == 9)) {
$('.myClass :visible:input:enabled:first').focus();
}
});
I found another better option which is fastest as of what I tried.
Here's the code for that
function handleTabOrder() {
$('.myClass :visible:input:enabled').each(function (index) {
$(this).attr('tabindex', index + 10);
});
$('.myClass :visible:input:enabled:first').keydown(function (e) {
if (e.keyCode == 9 || e.which == 9) {
$("[tabindex=10]").focus();
}
});
}
What I have done here is to assign Tab order to all the visible controls on the page, then I have handled the key down event of only first control(that shifts the control to address bar) and now it shifts the control to next visible input item on the screen..
Its just a work around but works faster than any of the other things mentioned in the thread.
Just write the above function and all it in on-load event of page.
I was having this issue as well. For me, it was being caused by the use of the .select() method in order to bring focus automatically on a text field as soon as the page loaded. I changed my code to instead use JQuery's .focus() method and that resolved the issue.
I faced similar problem in IE. After some analysis I found that, this problem occurs if there is any HTML content outside form.
for example:
<html>
<div id="1">
</div>
<form>
//other code
</form>
</html>
It worked for me, after I moved all HTML inside form tag.
<html>
<form>
<div id="1">
</div>
//other code
</form>
</html>
Have a look at: http://www.w3schools.com/tags/att_global_tabindex.asp
Your txtHrEmployeeId element should have tabindex 1 and all other elements should have higher values.
-1 is not valid
Also verify that the tabindex are correct in the html that gets rendered (right-click in page and "view source").
I realize this is an old post, but an even simpler method is to add a "tab-stop" attribute to the form element with the last tabindex. Then bind a keydown listener and force focus to the first tabindex when the tab-stop is encountered.
Here's a simple example:
<input type="text" tab-stop />
$document.bind("keydown", function(event) {
var attrs = event.currentTarget.activeElement.attributes;
if (attrs['tab-stop']) {
angular.element.find('select')[0].focus();
event.preventDefault();
}
});
};
The answer mentioned in my other post works fine but it made the page take a huge performance hit because with every key press on the page the whole DOM was being searched for the elements.
So I found a new more optimized solution
var myNameSpace = function(){
this.selector = '.myClass :visible:input:enabled:first';
this.myElement = $(selector);
this._body = $('body');
var _self= this;
this._body.on('keydown',_self.selector,function(e){
if ((e.which == 9) || (e.keyCode == 9)) {
_self.myElement.focus();
}
});
};
The general idea being to 'cache' the node to be accessed. No need to traverse the DOM again and again for just selecting.
I had this same problem. It turns out mine was related to the ajax modal popup extenders. a modal popup was being shown, even though technically i could not see it because it was wrapped inside a parent div that was hidden. if you are using modal popup extenders, this could be causing an issue like this.
If you are using JSF or Primefaces, you can make use of:
<p:focus for"formname"></p:focus>

mootools - using addEvent to element not working properly?

bangin' my head against this and it's starting to hurt.
I'm having trouble with adding an event to an element.
I'm able to add the event, and then call it immediately with element.fireEvent('click'), but once the element is attached to the DOM, it does not react to the click.
example code:
var el = new Element('strong').setStyle('cursor','pointer');
el.addEvent('click',function () { alert('hi!'); });
el.replaces(old_element); // you can assume old_element exists
el.fireEvent('click'); // alert fires
however, once I attach this to the DOM, the element is not reactive to the click. styles stick (cursor is pointer when I mouseover), but no event fires. tried mouseover as well, to no avail.
any clues here? am I missing something basic? I am doing this all over the place, but in this one instance it doesn't work.
EDIT----------------
ok here's some more code. unfortunately I can't expose the real code, as it's for a project that is still under tight wraps.
basically, the nodes all get picked up as "replaceable", then the json found in the rel="" attribute sets the stage for what it should be replaced by. In this particular instance, the replaced element is a user name that should pop up some info when clicked.
again, if I fire the event directly after attaching it, all is good, but the element does not react to the click once it's attached.
HTML-----------
<p>Example: <span class='_mootpl_' rel="{'text':'foo','tag':'strong','event':'click','action':'MyAction','params':{'var1': 'val1','var2': 'val2'}}"></span></p>
JAVASCRIPT-----
assumptions:
1. below two functions are part of a larger class
2. ROOTELEMENT is set at initialize()
3. MyAction is defined before any parsing takes place (and is properly handled on the .fireEvent() test)
parseTemplate: function() {
this.ROOTELEMENT.getElements('span._mootpl_').each(function(el) {
var _c = JSON.decode(el.get('rel'));
var new_el = this.get_replace_element(_c); // sets up the base element
if (_c.hasOwnProperty('event')) {
new_el = this.attach_event(new_el, _c);
}
});
},
attach_event: function(el, _c) {
el.store(_c.event+'-action',_c.action);
el.store('params',_c.params);
el.addEvent(_c.event, function() {
eval(this.retrieve('click-action') + '(this);');
}).setStyle('cursor','pointer');
return el;
},
Works just fine. Test case: http://jsfiddle.net/2GX66/
debugging this is not easy when you lack content / DOM.
first - do you use event delegation or have event handlers on a parent / the parent element that do event.stop()?
if so, replace with event.preventDefault()
second thing to do. do not replace an element but put it somewhere else in the DOM - like document.body's first node and see if it works there.
if it does work elsewhere, see #1
though I realsie you said 'example code', you should write this as:
new Element('strong', {
styles: {
cursor: "pointer"
},
events: {
click: function(event) {
console.log("hi");
}
}
}).replaces(old_element);
no point in doing 3 separate statements and saving a reference if you are not going to reuse it. you really ought to show the ACTUAL code if you need advice, though. in this snippet you don't even set content text so the element won't show if it's inline. could it be a styling issue, what is the display on the element, inline? inline-block?
can you assign it a class that changes it on a :hover pseudo and see it do it? mind you, you say the cursor sticks which means you can mouseover it - hence css works. this also eliminates the possibility of having any element shims above it / transparent els that can prevent the event from bubbling.
finally. assign it an id in the making. assign the event to a parent element via:
parentEl.addEvent("click:relay(strong#idhere)", fn);
and see if it works that way (you need Element.delegate from mootools-more)
good luck, gotta love the weird problems - makes our job worth doing. it wouldn't be the worst thing to post a url or JSFIDDLE too...