How to get value from listbox listitem - javascript

In my firefox addon I have a <listbox>. I want to be able to work a javascript function when I left-click on an item in the box. The function should retrieve the item's textual value.
Now, my function does get called when I click on a listitem, as I've placed this in my event listener's onLoad call:
var myListBox = document.getElementById("myListBoxID");
myListBox.addEventListener("click", function(event){
var target = event.target;
while (target && target.localName != "listitem"){
target = target.parentNode;
}
if (!target){
return; // Event target isn't a list item
}
alert(target); //returns blank
alert(target.id); //returns blank
alert(target.getAttribute("value")); //returns blank
alert(target.getAttribute("text")); //returns blank
alert(target.getAttribute("id")); //returns blank
var targetid = document.getElementById(target.id);
alert(targetid); //returns null
}, false);
},
The xul goes something like this:
<listbox id="listbox1">
<listcols /><listcol flex="1"/><listcol flex="1"/></listcols>
<listitem><listcell class="column1" label="label1" value="value1"</listcell><listcell label="cell1"></listcell></listitem>
<listitem><listcell class="column2" label="label2" value="value2"</listcell></listitem><listcell label="cell2"></listcell>
</listbox>
However, I can't get it to display the text of the items. As you can see above, I don't seem to have a proper handle on the target
I've gotten the original code from here, and got the EventListener working here.
How can I get the value of the listcells? I've tried everything!

You are using this code:
while (target && target.localName != "listitem"){
target = target.parentNode;
}
It will go up from the actual click target looking for a <listitem> tag. Yet the text isn't stored in the <listitem> tag, it's in the <listcell> - so you simply should be looking for that one in the hierarchy:
while (target && target.localName != "listcell"){
target = target.parentNode;
}
alert(target.getAttribute("value"));

You can't detect a click on just a listcell. The closest you can go is to detect a click on a listitem. After that, you have to drill down using code.
So, use .childNode on your target. Since you have only two listcells in your listitem, if you are trying to capture the second cell, use childNodes[1]. `childNodes[0] will refer to the first cell.
onLoad: function(){
var mylistbox= document.getElementById("mylistboxID");
mylistbox.addEventListener("click", function(event){
var target = event.target.childNodes[1];
if (!target){
return; // In case there is not target
}
alert(target.getAttribute("label") + target.getAttribute("label"));
}, false);
},

Related

How to convert links to text using JQuery?

I try to think up function which can replace links to text. Image inside a tag should be moved to the wrapper, the original a should be removed.
JS:
var selectors = 'a.mark-video;a.sp5;a>img[alt!=""]'
selectors = selectors.split(";").join(", ");
$(selectors).each(function() {
var current = this;
if (this.nodeName == "IMG") {
current = this.parentNode;
if (current.nodeName != "A")
current = this.parentNode;
if (current.nodeName != "A")
return false;
current = $(current);
}
var wrapper = current.parent();
var new_context = $().append(current.html());
current.remove();
wrapper.append(new_context);
}
);
The problem is
1) that the image is not inserted into the wrapper
2) if it would be inserted it would not have correct position.
I am experimenting using webextensions API (Firefox addon) and I injected the code to site:
http://zpravy.idnes.cz/zahranicni.aspx
In the debugger you can see two wrappers with class="art ". I have removed the first link but image is not inserted. The second one has not been removed yet when debugger was paused after first iteraction.
I hope you can find out why the image is not appended and how to append it to the original position of the element a. I need to find out position of the element a first, and then to move the image into to correct position - that is before div.art-info.
Note: please do not change the selectors string. This is the users input from form field.
Edit:
Almost there:
function(){
if (this.parentNode.nodeName == "A")
$(this.parentNode).replaceWith($(this));
else
$(this).html().replaceWith($(this)); // error: html() result does not have replaceWith...
}
Is this what you're looking for?
https://jsfiddle.net/4tmz92to/
var images = $('a > img');
$.each(images, function(key, image) {
$(this).parent().replaceWith(image);
});
First select all the images that you want to remove the link from, then loop through them and simply replace the parent() with the image.
I have finally solved the problem:
$(selectors).each(
function(){
if (this.parentNode.nodeName == "A")
$(this.parentNode).replaceWith($(this));
else
$(this).replaceWith(
$(this).html()
);
}
);
This works similar. Novocaine suggested not to use $(this).html() but this would skip some images so I prefer to use it.

Find Caret Position Anywhere on page

I'm a part time newish developer working on a Chrome extension designed to work as an internal CR tool.
The concept is simple, on a keyboard shortcut, the extension gets the word next to the caret, checks it for a pattern match, and if the match is 'true' replaces the word with a canned response.
To do this, I mostly used a modified version of this answer.
I've hit a roadblock in that using this works for the active element, but it doesn't appear to work for things such as the 'compose' window in Chrome, or consistently across other services (Salesforce also seems to not like it, for example). Poking about a bit I thought this might be an issue with iFrames, so I tinkered about a bit and modified this peace of code:
function getActiveElement(document){
document = document || window.document;
if( document.body === document.activeElement || document.activeElement.tagName == 'IFRAME' ){// Check if the active element is in the main web or iframe
var iframes = document.getElementsByTagName('iframe');// Get iframes
for(var i = 0; i<iframes.length; i++ ){
var focused = getActiveElement( iframes[i].contentWindow.document );// Recall
if( focused !== false ){
return focused; // The focused
}
}
}
else return document.activeElement;
};
(Which I originally got from another SO post I can no longer find). Seems as though I'm out of luck though, as no dice.
Is there a simple way to always get the active element with the active caret on every page, even for the Gmail compose window and similar services, or am I going to be stuck writting custom code for a growing list of servcies that my code can't fetch the caret on?
My full code is here. It's rough while I just try to get this to work, so I understand there's sloppy parts of it that need tidied up:
function AlertPrevWord() {
//var text = document.activeElement; //Fetch the active element on the page, cause that's where the cursor is.
var text = getActiveElement();
console.log(text);
var caretPos = text.selectionStart;//get the position of the cursor in the element.
var word = ReturnWord(text.value, caretPos);//Get the word before the cursor.
if (word != null) {//If it's not blank
return word //send it back.
}
}
function ReturnWord(text, caretPos) {
var index = text.indexOf(caretPos);//get the index of the cursor
var preText = text.substring(0, caretPos);//get all the text between the start of the element and the cursor.
if (preText.indexOf(" ") > 0) {//if there's more then one space character
var words = preText.split(" ");//split the words by space
return words[words.length - 1]; //return last word
}
else {//Otherwise, if there's no space character
return preText;//return the word
}
}
function getActiveElement(document){
document = document || window.document;
if( document.body === document.activeElement || document.activeElement.tagName == 'IFRAME' ){// Check if the active element is in the main web or iframe
var iframes = document.getElementsByTagName('iframe');// Get iframes
for(var i = 0; i<iframes.length; i++ ){
var focused = getActiveElement( iframes[i].contentWindow.document );// Recall
if( focused !== false ){
return focused; // The focused
}
}
}
else return document.activeElement;
};
I've got it working for the Gmail window (and presumably other contenteditable elements, rather than just input elements).
Edit: the failure around linebreaks was because window.getSelection().anchorOffset returns the offset relative to that particular element, whereas ReturnWord was getting passed the text of the entire compose window (which contained multiple elements). window.getSelection().anchorNode returns the node that the offset is being calculated within.
function AlertPrevWord() {
var text = getActiveElement();
var caretPos = text.selectionStart || window.getSelection().anchorOffset;
var word = ReturnWord(text.value || window.getSelection().anchorNode.textContent, caretPos);
if (word != null) {return word;}
}
I originally used a MutationObserver to account for the Gmail compose div being created after the page load, just to attach an event listener to it.
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
var nodes = mutation.addedNodes; //list of new nodes in the DOM
for (var i = 0; i < nodes.length; ++i) {
//attach key listener to nodes[i] that calls AlertPrevWord
}
});
});
observer.observe(document, {childList: true, subtree:true });
//childList:true notifies observer when nodes are added or removed
//subtree:true observes all the descendants of document as well
Edit: The delegated click handler I've been testing with. Key event handlers so far not working.
$(document).on( "click", ":text,[contenteditable='true']", function( e ) {
e.stopPropagation();
console.log(AlertPrevWord());
});

Attaching eventListener to dynamic elements in Javascript

I'm making a todo list and I have li and button tags added dynamically when adding a new list item. The button is an x which is supposed to remove the list item. I have tried several things but can't figure out how to make an eventListener for each individual x button and remove the corresponding list item when it is clicked.
The renderTodos function is where all of the dynamically added content is created. I have a data-index set to each button in which I was trying to use to access each button to attach an eventListener on each dynamic button, but I wasn't sure how to implement that. From what I have read there should be a way to do this using the currentTarget or target of the event but I don't understand how that works.
var input = document.querySelector('input[name=todoItem]'),
btnAdd = document.querySelector('button[name=add]'),
btnClear = document.querySelector('button[name=clear]'),
list = document.querySelector('.todo'),
storeList = [];
function renderTodos(){
var el = document.createElement('li'),
x = document.createElement('button');
listLength = storeList.length;
//Set text for remove button
x.innerHTML = 'x';
for(var i = 0; i < listLength; i++){
el.innerHTML = storeList[i];
list.appendChild(el);
x.setAttribute('data-index', i);
el.appendChild(x);
}
// check for correct data-index property on x button
}
function addTodos(){
storeList.push(input.value);
// Check that input is getting pushed to list array
console.log(storeList);
renderTodos();
}
function clearList(){
// make list empty
list.innerHTML = '';
storeList.splice(0, storeList.length);
//render empty list
renderTodos();
//Check that list array is empty
console.log(storeList);
}
btnAdd.addEventListener('click', addTodos);
btnClear.addEventListener('click', clearList);
Everything else on the list works so far I just can't figure out how to implement this eventListener.
One simple example can be
//a click hadler is added to #mylist which is already present in the dom
document.querySelector('#mylist').addEventListener('click', function(e) {
//assuming that the the `x` is in a span and it is the only span in the `li` we check for that, we can improve this check more to make sure we have actually clicked on the delete button
if (e.target.tagName == 'SPAN') {
//if so then since we know the structure we can delete the parent node of the target which is the span element
e.target.parentNode.parentNode.removeChild(e.target.parentNode);
}
}, false);
//kindly forgive the use of jQuery here
for (var i = 0; i < 10; i++) {
$('<li />', {
text: i
}).append('<span class="x">X</span>').appendTo('#mylist');
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul id="mylist"></ul>
This is a very basic implementation of event delegation, where the actual event is bound to an ancestor element but then we use the actual event target to determine whether to act on it. We can improve the if condition to test for an class for any other attribute!!!
You can add a listener to each button using something like:
x.innerHTML = '';
x.onclick = function(){
var node = this.parentNode;
node.parentNode.removeChild(node);
};
Or you can keep the renderTodos code as it is and delegate the remove to the parent UL:
// Add the listener
list.addEventListener('click', removeItem);
// The listener function
function removeItem(event) {
var node = event.target;
// Check that the click came from an X button
// better to check against a class name though
if (node.tagName &&
node.tagName.toLowerCase() == 'button' &&
node.innerHTML == 'x') {
node = node.parentNode;
node.parentNode.removeChild(node);
}
}
basically what you want to do is add an event on the parent container and wait for the event to bubble up and identify if the event originating is from your x mark and if it is then trigger the callback function.. This is the concept I think most of the libraries use..
Or use a library like jQuery, why solve a problem that has already been solved by others.

IE 11 Select with Focus Losing Focus When Clicked

I'm making a spreadsheet where double-clicking a cell brings up the 'edit' control, gives it focus, and sets up an onblur event handler to hide the 'edit' control and set the value of the cell to be that of the control once it loses focus. The assumed flow is the user double-clicks to bring up the edit control, selects/enters a value, then clicks/tabs to something else, thus moving focus to another element and triggering the edit control's onblur handler.
It works just fine, except with SELECT tags in IE 10 and 11 (Chrome and FF work fine): Every time I click on the carat to drop the drop-down, the SELECT loses focus and triggers the onblur handler, which then hides the edit control, thus preventing me from editing a value.
Does anyone know why this is happening or how to fix it?
I found this blog post which describes the problem and proposes a solution of adding a background image to the SELECT, but that didn't seem to fix the problem (or I did it wrong).
Below is the code where I generate and insert the HTML for the edit control into the cell.
/**Changes the contents of the cell to be its editing control instead of plain text.
#method SetCellEditMode
#param {String} mappingId: The Id of the PropertyMapping representing cell to switch over to edit mode.*/
this.SetCellEditMode = function (mappingId)
{
if (this.Editing === true) return false;
var cell = this.GetCell(mappingId);
var tagType = null;
if (cell == null) return false;
var propInfo = cell.SourceObject.PropertyInfo;
if (propInfo.IsReadOnly === true) return false;
var innerHtml = null;
if (propInfo.PropertyType === SDCMS.Resources.PropertyType.Boolean)
{
innerHtml = makeBoolHTML(cell.SourceObject);
}
else if (propInfo.PropertyType === SDCMS.Resources.PropertyType.Enum)
{
innerHtml = makeEnumHTML(cell.SourceObject);
}
else if (propInfo.PropertyType === SDCMS.Resources.PropertyType.Number || propInfo.PropertyType === SDCMS.Resources.PropertyType.String)
{
innerHtml = makeStringEntryHTML(cell.SourceObject);
}
cell.Node[0].innerText = "";
cell.Node.html(innerHtml);
cell.Node[0].firstChild.focus();
this.Editing = true;
return true;
};
/**Makes the HTML for a boolean <select> control.
#method makeBoolHTML
#param {Object} propertyMapping: The SDCMS.Resources.PropertyMapping for which we are making an <select> control.*/
var makeBoolHTML = function (propertyMapping)
{
if (propertyMapping == null) return null;
var innerHtml = "<select mappingId=\"" + propertyMapping.Id + "\" onblur=\"SD_Click(event, 'SD_ChangeValue')\">" +
"<option>true</option>" +
"<option>false</option>" +
"</select>";
if (propertyMapping.PropertyValue === true)
{
innerHtml.replace("<option>true</option>", "<option selected=\"selected\">true</option>")
}
else
{
innerHtml.replace("<option>false</option>", "<option selected=\"selected\">false</option>")
}
return innerHtml;
};
Found a solution. Turns out it was all input/select type nodes were losing focus, and what was happening was that the nodes that were in a table would, when clicked, bubble the event from the control to the table cell, which would then get focus and cause blur() to trigger on the internal control. The solution was to hook up event handlers for onclick, onmousedown, and onmouseup (for good measure) that do nothing but preventDefault() and stopPropagation(). Once the event stopped propagating to the containing table cell, everything worked as it should.
Instead of calling focus directly call it using settimeout (), an interval of 1 to 10 ms should be enough.

Erratic mouseover behavior with nested items inside mouseover layer

So let's say we have:
A container for everything
A baseDiv inside that container
//let's create a base layer
var container = document.getElementById('container')
var baseDiv = document.createElement('div')
baseDiv.id = 'baseDiv'
baseDiv.innerText = 'this is the base div'
baseDiv.addEventListener('mouseover', createLayer)
container.appendChild(baseDiv)
When the user mouses over:
A layerOnTop, of the same size is put on top of the baseDiv.
When the user mouses out:
The layerOnTop is removed.
function createLayer(){
console.log('creating layer')
layerOnTop = document.createElement('div')
layerOnTop.id = 'layerOnTop'
layerOnTop.addEventListener('mouseout',
function(){
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})
container.appendChild(layerOnTop) }
Simple and works great.
However, when layerOnTop contains elements as well (buttons, inputs), the behavior gets very erratic and starts flicking as you're technically exiting the layerOnTop.
//it contains two textareas
layerOnTop.appendChild(document.createElement('textarea'))
layerOnTop.appendChild(document.createElement('textarea'))
I wish I could use mouseenter but it doesn't seem to be supported by Chrome.
Here's my jsfiddle: http://jsfiddle.net/DjRBP/
How can I stop this? I wish I could merge the textareas and layerOnTop into one large mouseover-handling conglomerate.
You need to check in your mouse out event that it's actually leaving the element. Change your mouseout function to:
function(event) {
var e = event.toElement || event.relatedTarget;
if (e.parentNode == this || e == this) {
// We're not actually leaving the parent node so don't remove layer
return;
}
console.log('removing layer')
return layerOnTop.parentElement.removeChild(layerOnTop)
})

Categories

Resources