Just getting started with jQuery, and I've had some success so far. I've created a handler that updates a <table> every time a user makes a selection from a dropdown. It looks mostly like this:
function loadAttributes() {
$.ajax({
type: "POST",
url: "../ws/itemSearch/getAttributesForItemType",
contentType: 'application/xml',
data: '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://service.itemsearch.foo.com/">'
+ '<soapenv:Header/><soapenv:Body><ser:getAttributesForItemType>'
+ '<arg0>' + this.value + '</arg0>'
+ '</ser:getAttributesForItemType></soapenv:Body></soapenv:Envelope>',
processData: false,
dataType: "xml",
success: function(data) {
var attributes = '<table summary="Attribute Section"><tr class="underline">';
var attCount = 0;
$(data).find("return").each(
function() {
if (++attCount % 4 == 0) {
attributes += '</tr><tr class="underline">';
}
// TODO PMA click handler to the new <td> element
attributes += '<td>' + this.textContent + '</td>';
}
);
attributes += '</tr></table>';
$("div[id=attributes]").html(attributes);
}
});
}
As you can see my next step is to not just add literal <td> elements to the row containing the attributes, but to add a click handler to them. This click handler would append the contents of the <td> to a text box; something like:
tdItem.click(function() {
$("input[name=search]").append(tdItem.textContent);
}
To do that, I'd prefer to create each td item as a separate Element and build up the table in an object-oriented manner using <tr> elements rather than pasting together a literal string which is what I'm doing now. Most of the examples on the jQuery site have to do with adding listeners to existing elements and not to building up a new section of the document like this on the fly every time. At the very least, can someone point me to a good tutorial on how to accomplish what I'm trying to do here?
This is actually fairly simple to achieve and I would recommend taking advantage of some jQuery functions when writing your html building.
First $("<tr></tr") will create a tr element stored in a jQuery object as if you had just selected it. If you are building html as above I would recommend changing to something like this in your success function.
var attributes = $('<table summary="Attribute Section"></table>');
var attCount = 0;
var attributeRow = $('<tr class="underline"></tr>');
$(data).find("return").each(
function() {
if (++attCount % 4 == 0 && attCount != 0) {
attributes.append(attributeRow.clone());
attributeRow = $('<tr class="underline"></tr>');
}
// TODO PMA click handler to the new <td> element
var attribute = $('<td>' + this.textContent + '</td>');
attribute.click(function(){//click function});
attributeRow.append(attribute );
}
);
//add the last attribute row
attributes.append(attributeRow);
$("div[id=attributes]").append(attributes);
Above you will notice that You now have the attribute td generated as a jQuery object that you can apply click functions to.
The only way to do that with string literals is to add an onclick attribute to your td elements like
attributes += '<td onclick="somefunction();">' + this.textContent + '</td>';
that would work, but I would not recommend that. To stay unobtrusive, use jQuery objects for table creation. For instance
success: function(data) {
var $table = $('<table summary="Attribute Section"></table>');
$table.append($('<tr class="underline"></tr>'));
var attCount = 0;
$(data).find("return").each(
function() {
if (++attCount % 4 == 0) {
$table.append($('<tr class="underline"></tr>').append($('<td/>', {
click: function(e){
alert($(this).text());
}
})));
}
}
);
$("div[id=attributes]").empty().append($table);
}
To me, a more elegant approach to your click handling for the td's is to put one click handler on the table itself, and let it catch the click events that bubble up from the td's. That way you don't need to worry about adding handlers to each td in the table. It's also better for performance, though in this situation, I doubt it matters much in that regard.
Here's what you would do (you could add this right after you declare the $table variable):
$table.click(function(e) {
var target = $(e.target);
if (target.is('td')) {
var input = $('input[name=search]');
input.val( input.val() + target.text() );
e.stopPropagation();
}
});
Related
I am working on a personal project in Javascript to allow people to create a table of "dream plays" in Scrabble. Here is the link: http://psyadam.neoclaw.net/tables8.html
I am currently trying to allow the user to edit a dream play.
Here is my code:
function editRow(e)
{
var eventEl = e.srcElement || e.target,
parent = eventEl.parentNode
parent = parent.parentNode
var test = $(parent.cells[0]).text()
$('tr').each(function(){
$(this).children("td:contains(test)").each(
function()
{
pageEnd.innerHTML += "success"
$(this).addClass('selected')
}
)
})
pageEnd.innerHTML += test
//pageEnd.innerHTML += $(parent.cells[3]).text()
insertRow()
}
// insertRow assumes that the correct row in the table has the "selected" class added to it
function insertRow()
{
var Row = $('<tr>').append(
$('<td>').append('<input id="input1">'),
$('<td>').append('<select id="input2"><option value=""></option><option value="Yes">Yes</option><option value="No">No</option></select>'),
$('<td>').append('<select id="input3"><option value=""></option><option value="Natural">Natural</option><option value="1 Blank Used">1 Blank Used</option><option value="2 Blanks Used">2 Blanks Used</option></select>'),
$('<td>').append('<select id="input4"><option value=""></option><option value="vs Computer">vs Computer</option><option value="Online Game">Online Game</option><option value="Friendly Game">Friendly Game</option><option value="Club Game">Club Game</option><option value="Tournament Game">Tournament Game</option></select>'),
$('<td>').append('<input id="input5">')
)
$("#myTable tr.selected").after(Row)
}
Right now I'm just trying to get my code to insert a row into the table. I am trying to do this by using the code $(this).addClass('selected') to tag the row the user selected and then use it in my insert function to insert a row. However, nothing seems to happen. I am using pageEnd.innerHTML += "success" as a debugging tool to see if it is even getting there. Unexpectedly, it prints success twice when it should only print once, as in the test I ran every word was unique.
In any case I can't figure out why it's not working. Any help is appreciated. Thanks, ~Adam
You have two options to achieve this:
The first one as the others are suggesting i.e. by keeping a variable of outer this and then using this:
$('tr').each(function() {
var outerThis = this;
// inside another loop (not sure about children method, just an example)
$(outerThis).children("td:contains(test)").each(function() {
// do something with outerThis to operate on further
});
});
Another option to use Javascript's bind() method:
$('tr').each(function(i, trElement) {
// this == trElement
// inside another loop
$(outerThis).children("td:contains(test)").each(function(index, tdElement) {
$(this) // will be now tr element
$(tdElement) // will be td element
}.bind(this));
});
Try this:
function editRow(e) {
var eventEl = e.srcElement || e.target,
parent = eventEl.parentNode
parent = parent.parentNode
var test = $(parent.cells[0]).text()
$('tr').each(function(){
var outerThis = this;
outerThis.children("td:contains(test)").each(
function()
{
pageEnd.innerHTML += "success";
$(this).children().css('text-align', 'center');
}
)
})
pageEnd.innerHTML += test
//pageEnd.innerHTML += $(parent.cells[3]).text()
insertRow()
}
I set the outer this to a variable which you can use. Also textAlign is not a jQuery function. You need to use .css() and then specify text-align in that.
I have to pass HTML around in as a string (as I'm using postmessage for communication). To apply modifications to the html, I'm doing:
function foo(my_string) {
var temp, element_list;
temp = document.createElement("div")
temp.innerHTML = my_string;
element_list = temp.querySelectorAll(".foo");
...
My problem is that my_string can be anything and in case I'm passing a string with table rows and cells like this:
'<tr>' +
'<td>' +
'<a href="#gadget._key=project_module%2F1&gadget.view=view">' +
'My Test Project 2014/12/16 14:24:48.930904 GMT' +
'</a>' +
'</td>' +
'...' +
'</tr>'
appending this to a <div> removes the table rows and cells and I'm left with links only. Something like this:
'<a href="#gadget._key=project_module%2F1&gadget.view=view">' +
'My Test Project 2014/12/16 14:24:48.930904 GMT' +
'</a>' +
Question:
Is there a generic element, which accepts any type of child elements and does not modify whatever it's passed via innerHTML?
Thanks!
Edit:
The method is used to translate html snippets. When I'm updating a table, it will only pass the generated table rows vs receiving the whole table on the initial page rendering.
There isn't such an element. <tr> is a very good example of this. According to W3C standards, the "Permitted parent elements" for <tr> are "A <table>, <thead>, <tbody> or <tfoot> element."
If you must have these strings coming in as they are, your best bet is to perform some sort of detection as to the type of element(s) you are inserting, and wrap them in the appropriate HTML if required.
For example: (View as a CodePen)
HTML
<div id="container"></div>
JavaScript
var anyone = "<div>In a Div</div>";
var tableOnly = "<tr><td>In a..</td></tr>" +
"<tr><td>...table</td></tr>";
$(function () {
var $container = $("#container");
appendContent(anyone);
appendContent(tableOnly);
function appendContent(html) {
var $html = $(html),
$parent = $(),
lastParent = "";
$html.each(function () {
var parent = parentTag(this.tagName);
if(parent !== lastParent)
{
$container.append($parent);
$parent = $(parent);
}
$parent.append(this);
lastParent = parent;
});
$container.append($parent);
}
function parentTag(tagName) {
switch (tagName.toLowerCase()) {
case "tr":
return "<table></table>";
default:
return "<div></div>";
}
}
});
Edit: Note that the technique used here to detect the tags used in your HTML can have problems if your HTML contains content that cannot be part of the same parent. For example, the following code would fail:
appendContent("<tr><td>Also in a table</td></tr><div>Also in a div</div>");
This is because of how jQuery internally builds its selectors. Since you can't have a div tag as a sibling to a tr, effectively the div element gets dropped. Here's a CodePen demonstrating this, but from the sound of things, this wouldn't be an issue for the OP's needs. If it is, you could use some alternative method of detecting the tags such as Regular Expressions.
If you append the mal-formatted HTML data (as you've noticed) with missing tags you're at the Browser DOM parser mercy removing every single one till a conformable HTML is returned.
If your main concern (project-wise) is just about table HTML content than you could
treat the string as an XML data structure and get the needed wrapping tag and act accordingly:
jsBin demo
function sanitizeHTML( string ) {
// Treat friendly a HTMLString as XML structure:
var par = new DOMParser();
var doc = par.parseFromString(string, 'text/xml');
var chd = doc.firstChild;
var tag = chd.nodeName.toUpperCase(); // Get the tag
var ele;
function wrapWith(parent, childEl){ // Wrap a node into a parent
var p = document.createElement(parent);
p.appendChild(childEl);
return p; // And return that parent element.
}
if(/^(THEAD|TBODY|TR)$/.test(tag)){ // If THEAD or TBODY or TR
ele = wrapWith("table", chd); // just wrap in TABLE.
}else if(/^(TD|TH)$/.test(tag)){ // Else if TD or TH
ele = wrapWith("tr", chd); // wrap first in TR
ele = wrapWith("table", ele); // and than in TABLE.
}else{
// All fine. Do we need something here?
}
return ele || chd; // Returns a HTMLElement
}
// This will return the final HTMLElement:
// var myEl = sanitizeHTML( str );
// Let's see in console:
console.log( sanitizeHTML( str ).outerHTML );
For simplicity sake the above code will consider strings with only one children.
To extend it - loop all the children of the doc object.
See this jsfiddle: http://jsfiddle.net/Grezzo/x1qxjx5y/
With <tr>s in <table>s they are ok.
It's because you are putting a <tr> in a <div> which isn't valid.
Putting unsanitized content in the page like this is a real security risk
Update: I updated the jsfiddle to include two <div>s that were not modified by javascript and you can see that the <tr>s are stripped if they are not in a <table> parent: http://jsfiddle.net/Grezzo/x1qxjx5y/1/
This question already has answers here:
JavaScript DOM remove element
(4 answers)
Closed 8 years ago.
Working on building this to-do list app to learn JS better.
I am able to insert text into box and have it add a new div element to the page with the relevant text residing within the div. The code that adds the div to the page automatically applies the class .newItem then adds an incrementing id i0, i1, i2, etc each time it's clicked. Everything works without an issue. Now, I have been fiddling around with the code to be able to click a single div element and have it remove itself from the page, but can't seem to get it to work.
var iDN = 0;
//Function that adds a new div with a custom incrementing id number
document.getElementById('add_button').onclick = function () {
var taskName = document.getElementById('add_task').value; // Store the value in the textbox into a variable
document.querySelector('.shit_to_do').innerHTML += '<div class = "newItem" id = "i' + iDN + '"' + 'onclick = removeEl()' + '>' + taskName + '</div>';
iDN += 1;
};
document.getElementById('testing').onclick = function () {
var parentNode = document.querySelector('.shit_to_do');
parentNode.removeChild(parentNode.children[0]);
}
function removeEl() {
for (i = 0; i < iDN; i++) {
if (document.getElementById('i' + i).onclick) {
document.getElementById('i' + i).display.style = 'none';
}
alert(i);
}
}
The for loop was really some random option I was trying to figure out how things were working onclick for each div, but didn't get to far with that one.
tl;dr:
I want to add click events to the new divs added onto the page in a single, universal function.
The value of document.getElementById('i' + i).onclick will be null if you've not set a handler to this attribute/property, otherwise it will be a function. null is always falsy, a function is always truthy.
To remove your element, you'll either have to look at this or e.target where e is the click event, and then call the DOM method node.removeChild(child).
The "quick and dirty" solution is to pass this into removeEl and remove it that way,
// ...
document.querySelector('.shit_to_do').innerHTML += '<div class="newItem" id="i' + iDN + '" onclick="removeEl(this)">' + taskName + '</div>';
// ...
function removeEl(elm) {
elm.parentNode.removeChild(elm);
}
I also removed the strange spacing between attribute names and values in your HTML
A perhaps "cleaner" solution is to create your nodes and attach listeners all by using DOM methods
function createDiv(index, taskname) {
var d = document.createElement('div');
d.setAttribute('id', 'i' + index);
d.textContent = taskname;
return d;
}
function removeElm() {
this.parentNode.removeChild(this);
}
var iDN = 0;
document.getElementById('add_button').addEventListener('click', function () {
var taskName = document.getElementById('add_task').value,
list = querySelector('.shit_to_do'),
div = createDiv(iDN, taskName);
div.addEventListener('click', removeElm);
list.appendChild(div);
iDN += 1;
});
This way means the browser does not re-parse any HTML as it not use element.innerHTML, which is a dangerous property may destroy references etc (along with usually being a bit slower)
Helpful links
node.addEventListener
document.createElement
node.appendChild
I have created a grid (table) inside of an HTML doc based on user preference.
After appending the rows and cells to the table, it appears jQuery cannot access them properly? Its as if it doesn't consider them part of the DOM.
Essentially I will be handling everything inside of a multidimensional array for my data, such as:
[ ["","",""], ["","",""], ["","",""] ]
It is currently set up with appended rows named #row_0, #row_1, etc and td's added without an ID.
I need to be able to map the clicked TD to my jQuery so I can perform some functions and logic on it.
Is there any way to respond to my JS when a user clicks on a td with something like:
tr = [1]
td = [2]
Current code found here:
http://jsfiddle.net/HNjMR/4/
Here live example friend: http://jsfiddle.net/HNjMR/5/
$("#board").on('click', 'td', function(){
$("#board td").css("background","white");
$(this).css("background","red");
});
This happens because when you insert a new dynamic element on the page, ready jquery can not find it. The ready by default runs events only on existing elements at load time. While some functions like .on, solve this problem.
http://jsfiddle.net/898n2/1/
$( document ).on( "click", "td", function() {
alert(this.id);
});
This is using your current code
Notice I have used .on - this is a 'live' event using jquery
I have also modified your naming of each td to read col_1_2 - with 1 being col and 2 being row.
$("#row_"+i).append("<td id='col_"+j+"_"+i+"'> </td>");
when the td element click handler is added those target elements are not yet created so use event delegation and bind the click handler to the #board element with the target element selector as td as given below. Then use .index() to find the position of row and td(Add 1 to index as it is 0 based)
$("#board").on('click', 'td', function () {
var $td = $(this);
var td = $td.index() + 1,
row = $td.parent().index() + 1;
alert(td + '-' + row);
});
Demo: Fiddle
Also your create code is buggy, jQuery append does not work like string concatenation
$('#board tr').remove();
var size = parseInt($("#size").val());
for (var i = 0; i < size; i++) {
var $tr = $('<tr />', {
id: 'row_' + i
}).data('index', i + 1);
$("#board").append("<tr id='row_" + i + "'>");
for (var j = 0; j < size; j++) {
$('<td />', {
id: 'col_' + j
}).data('index', j + 1).appendTo($tr);
}
$("#board").append($tr);
}
Demo: Fiddle
Here the solution, remember that you should bind the eventclick when the elements was created, you souldn't use a generical $('td').click event, this does not work because some elements was not added in DOM yet.
$("#go").click(function(){
var size = $("#size").val();
if(size<3 || size>10){
alert("That is not a valid input. Please select 3-10");
return;
}
$('#board tr').remove();
// Check this bellow
var size = parseInt($("#size").val()),
tr = $("<tr/>"), td = $("<td/>"), tdc;
for(var i=0;i < size;i++){
for(var j=0;j<size;j++){
tdc = td.clone();
tdc.attr('id', 'col_'+j);
tr.append(tdc);
tdc.click(function(){
alert(this);
});
}
tr.attr('id','row_'+i);
$("#board").append(tr);
}
});
I am trying to generate dynamically the onclick event handlers of the cells of a flexigrid-generated table:
// ...
preProcess: function (data) {
var rows = data.rows;
for (var i = 0; i < rows.length; ++i) {
var row = rows[i];
// If and only if this condition is true, then
// row.cell[0] must be converted into a hyperlink.
if (row.cell[1] != '0') {
// I don't want to use the href attribute, because that would
// force me to define a non-anonymous function.
row.cell[0] = '<a href="javascript:void(0)" id="E'
+ i + '">' + row.cell[0] + '</a>';
// So I'm going to try assigning the onclick attribute.
$('#E' + i).click(function () {
window.open('doc.php?q=' + this.id, 'D' + this.id,
'menubar=0,toolbar=0,directories=0,location=0,status=0,' +
'resizable=0,scrollbars=0,width=600,height=300');
});
$('#E' + i).click().id = row.cell[4];
}
}
return data;
}
// ...
However, when I click on the generated hyperlinks, they don't work. What's the problem? My use of closures? The <a> tag doesn't accept the onclick attribute?
NOTE: Since I began using jQuery, my policy is all functions shall be anonymous functions. Please don't suggest me using an ordinary function.
Sounds like what you're looking for is live():
Attach a handler to the event for all elements which match the current selector, now and in the future
In effect, it allows you to create event handlers for elements that do not exist yet.
I get the feeling you only want to make minimal changes to your current code in order to make this work. In that case, live() is your best option since your code would only change from
$('#E' + i).click(function () { ...
to
$('#E' + i).live('click', function () { ...
Create the element using jQuery (or the browser's native dom functions) and attach an event handler:
$('<a href="#" id="E' + i + '"/>').html(row.cell[0]).click(function(e) {
e.preventDefault();
// your code
});
It looks like you're creating the <a> using raw string concatenation, and then assigning it... where? If the link isn't part of the DOM, then $('linkID') won't find anything, effectively assigning your click handler to nothing. jQuery selectors only search the DOM.
Firstly, it doesn't look like you're appending your with id='#E' + i.
So, I'd guess that when you call $('#E' + i), it's returning an empty jQuery object. You can check for this by alerting $('#E' + i).length. 0 means nothing was found.
Second, you don't need to the javascript:void(0) call. Just replace it with '#' and call event.preventDefault() in your anonymous function. You'll need to pass event as a parameter to the anonymous function, as well.
You are trying to hook up the onclick event on an element that doesn't exist yet. At the time, the element only exist as text in the array, as the code hasn't been added to the DOM, the selector can't find it.
If you want to use an anonymous function for the event handler, you have to wait to hook up the event until the element has been created so that it exists as an object.
Use jQuery's live event.
For ease of seeing what's going on, I'm also adding a class to the link because I'm assuming that there's other links on the page, .
function preProcess(data) {
...
row.cell[0] = '' + row.cell[0] + '';
}
jQuery("a.clickMe").live("click", function(event) {
event.preventDefault();
window.open('doc.php?q=' + this.id, 'D' + this.id, .....
});
Disclaimer: I've never used flexigrid, but from your other comments, it appears you are able to modify the content before flexigrid puts it in the DOM.
The live event lets up hook up a single handler (anonymous or not) before the element is added to the DOM.
See: jQuery live()
.live()
Attach a handler to the event
for all elements which match the
current selector, now and in the
future
I copied your code and, after a few minor corrections, I made it work. I assumed that data was referring to a table object. Here's my code together with dummy HTML.
<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.4.min.js"></script>
</head>
<body>
<table id='myTable'>
<tr>
<td>x</td><td>1</td><td>a</td><td>f</td><td>p</td>
</tr>
<tr>
<td>y</td><td>2</td><td>b</td><td>g</td><td>q</td>
</tr>
</table>
<script>
function preProcess(data) {
var rows = data.rows;
for (var i = 0; i < rows.length; ++i) {
var row = rows[i];
// If and only if this condition is true, then
// row.cell[0] must be converted into a hyperlink.
if (row.cells[1] != '0') {
// I don't want to use the href attribute, because that would
// force me to define a non-anonymous function.
row.cells[0].innerHTML = '<a href="javascript:void(0)" id="E' + i + '">'
+ row.cells[0].innerHTML + '</a>';
// So I'm going to try assigning the onclick attribute.
$('#E' + i).click(function () {
window.open('doc.php?q=' + this.id, 'D' + this.id,
'menubar=0,toolbar=0,directories=0,location=0,status=0,' +
'resizable=0,scrollbars=0,width=600,height=300');
});
//$('#' + id).click().id = row.cells[4];
}
}
return data;
}
$(document).ready(function() {
preProcess(document.getElementById('myTable'));
});
</script>
</body>
</html>
My corrections were the following (I think some might be due to transcription when you were copying the code for the post):
I replaced cell with cells
I added innerHTML after the cell index
I set the link to javascript:void instead of javascript.void
I commented out the line $('#' + id).click().id = row.cells[4];, because I had no idea what it did.
With those changes it worked like a charm.
I hope this helps.