I have this simple chrome extension that allows the user to create a table however I can't bind the remove Button.
options.html
<body>
<!-- would be created as the user clicks on new Row -->
<table id="rows">
<tr id="row0">
<td>
<input id="textBox" type="text">
<button id="remove">Remove</button>
</td>
</tr>
</table>
<!-- used as template -->
<table hidden>
<tbody>
<tr id="rowTemplate">
<td>
<input id="textBox" type="text">
<button id="remove">Remove</button>
</td>
</tr>
</tbody>
</table>
<button id="newRow">new Row</button>
<script src="options.js"></script>
options.js
function Row() {
var rows = document.getElementById('rows');
this.node = document.getElementById('rowTemplate').cloneNode(true);
this.node.id = 'row' + (Row.next_id++);
this.node.row = this;
rows.appendChild(this.node);
this.node.hidden = false;
var row = this;
this.getElement('remove').onclick = function () {
row.node.parentNode.removeChild(row.node);
}
}
Row.next_id = 0;
Row.prototype.getElement = function (name) {
return document.querySelector('#' + this.node.id + ' .' + name);
}
window.onload = function() {
document.getElementById('newRow').onclick = function() {
new Row();
};
}
The problem lies in the querySelector because whenever I click on 'new Row', I get an error Uncaught TypeError: Cannot set property 'onclick' of null pointing to the getElement('remove').onclick which calls getElement() where the querySelector returns null.
Related
I'm trying to insert HTML data dynamically to a table that is dynamically created, but when I try to attach an addEventListner for the button that is dynamically created the event is not firing. The solution would be appreciated.
const put = document.querySelector(".puthere");
const v1 = document.querySelector(".v1");
const v2 = document.querySelector(".v2");
const create = document.querySelector(".create");
const updateDelete = document.querySelector(".update-delete");
const update = document.querySelectorAll(".active-update");
const dynamic = document.querySelector(".dynamic");
//FUNCTIONS
const operations = function (v1, v2) {
const html = ` <tr class = "update-delete">
<td> <input type="${v1}"></td>
<td ><input type="${v2}"></td>
<td> <button class = "active-update" data-set=".active-update"> UPDATE</button></td>
<td> <button > ❌ </button></td>
</tr>`;
put.insertAdjacentHTML("beforeend", html);
};
//CREATE TABLE COLUMNS
create.addEventListener("click", function (e) {
operations(v1.value, v2.value);
});
//Ataching eventlistner to parent element
updateDelete.addEventListener("click", function (e) {
if(e.target.classList.contains('active-update'){
console.log('click)})
});
<div >
<table class = "puthere">
<tr class="dynamic">
<th>BOOKS</th>
<th class = "books">PRICE</th>
<th > <button class="create" > CREATE</button> </th>
</tr>
<tr class = "update-delete dynamic">
<td><input type="text" class="v1"></td>
<td><input type="text" class="v2"></td>
<td> <button class = "active-update" data-set=".active-update"> UPDATE </button></td>
<td> <button > ❌ </button></td>
</tr>
</table>
</div>
You should notice that you get updateDelete before you create another dynamic part. You could log updateDelete and you will find that it nerver changed.
So you could bind a click listener to higher level element, like this:
put.addEventListener('click', function (e) {
if (e.target.classList.contains('active-update')) {
console.log('click')
}
})
Or add click listener to every button you created dynamically.
I'm implementing autocomplete using webSocket. When I type in the input field which has the classname item-search-results, a bunch of result will show up search results screenshot. My problem is the result will is only attach to the very first item-search-results input field.
But I need the results show up under the right item-search-results input field.
For example, I type on the 4th item-search-results input field, but the results are attach to the first one results are attach to the first row screenshot
Here is my webSocket js, I use jQuery:
$(function () {
let webSocket = new WebSocket("ws://localhost:8000/autocomplete");
webSocket.onopen = (msgEvent) => {
console.log('connected to WebSocket!');
};
webSocket.onmessage = (msgEvent) => {
/* results shows {"results":{"results":[{..}]}}.
results.results shows {results:[{..}]}
results.results.results shows [{...}], which is an array */
let results = JSON.parse(msgEvent.data),
resultsArr = results.results.results;
console.log(resultsArr);
$.each(resultsArr, (index, value) => {
$('<div>' + value['name'] + '</div>').attr('id', 'item-search-results-' + index).appendTo($('.item-search-results'));
});
};
webSocket.onclose = (msgEvent) => {
console.log('disconnected');
};
webSocket.onerror = (msgEvent) => {
console.log('there\'s an error');
};
/**
* Request to display data
*/
$('.add-items-table').on('input', '.item-input', (e) => {
let value = e.target.value;
webSocket.send(value);
console.log(value);
})
})
Websockets are irrelevant here, and I don't understand why you are doing an append inside loop. When trying to do something like this, you need to use the $(this) selector which will be the element you actually click.
At the top of your script create a global variable or namespace.
var Item = {};
/* In this chunk of code, create an object which you can use as a reference */
$('.add-items-table').on('input', '.item-input', (e) => {
/* this will be the correct .item-search-results div */
var Item.list = $(this).siblings('.item-search-results');
let value = e.target.value;
webSocket.send(value);
console.log(value);
})
Finally, append to the correct <div>.
.appendTo(Item.list);
Not sure about Websocket But here is how you will approach.Get last Row of table and append data after that particular row.
var placeHolder=` <tr>
<td style="width: 16.66%">
<input type="text" name="quanty" value="">
</td>
<td class="item-search-container">
<input class="item-input" id="item-input-0" type="search" name="item" value="">
<div class="item-search-results"></div>
</td>
<td>
<input type="text" name="quanty" value="">
</td>
</tr>`;
function addItem(){
let totalElem=document.getElementsByClassName('tr').length;
let lastElem=document.getElementsByClassName('tr')[totalElem-1];
$.get( "https://jsonplaceholder.typicode.com/posts/1", function( data ) {
$.each(data, function(i, item) {
$(lastElem).after(placeHolder);
});
});
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<section class="item-form">
<table class="add-items-table table table-hover ">
<thead>
<tr>
<th>Quanty</th>
<th>Item</th>
<th>Comment</th>
<th>Remove</th>
</tr>
</thead>
<tbody class="item-row">
<tr class="tr">
<td style="width: 16.66%">
<input type="text" name="quanty" value="">
</td>
<td class="item-search-container">
<input class="item-input" id="item-input-0" type="search" name="item" value="">
<div class="item-search-results"></div>
</td>
<td>
<input type="text" name="quanty" value="">
</td>
<td>
<button class="remove-item-button btn-block btn-outline-danger" type="reset" name="remove">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
</td>
</tr>
</tbody>
</table>
<button class="add-item" onClick="addItem()">Add Another Item</button>
</section>
The problem here is that you are appending it to $('.item-search-results') that is a class and is returned as an array. When you run .appendTo($('.item-search-results')) it will append to the first item of the array.
You should use a more specific Id for each roll and append by Id.
I think #manikant 's idea of creating a template is great, but I would introduce a variable that you can change for each new row you create and give a different Id to each .item-search-results div. But you would have to send this to the websocket and receive it back to call the right Id to append.
In my code, I need to make it so when button 1 is pressed it sets a number to be "1" and if button 2 is pressed, it sets the number to be 2.
<!DOCTYPE html>
<html>
<body>
<table style="width:65%;height:100px">
<tr><td align="center" id="blockOne">
<p><button onclick="addGraph()" id="buttonOne">Create Graph/Chart</button></p>
<tr><td align="center" id="blockTwo">
<p><button onclick="addGraph()"id ="buttonTwo">Create Graph/Chart</button></p>
</table>
</body>
<script>
var button1 = document.getElementById('buttonOne');
var button2 = document.getElementById('buttonTwo');
var container = "0";
// When the user clicks the button, open the modal
function addGraph() {
{
if (button1.onclick === true) {
container = "1";
}
if (button2.onclick === true) {
container = "2";
}
}
console.log(container);
}
</script>
</html>
You can simply pass a parameter in your addGraph function which can be assigned to container as button1.onclick is not a valid way that gives you a boolean result.
var button1 = document.getElementById('buttonOne');
var button2 = document.getElementById('buttonTwo');
var container = "0";
function addGraph(value) {
container = value;
console.log(container);
}
<table border='1' style="width:65%;height:100px">
<tr><td align="center" id="blockOne">
<p><button onclick="addGraph(1)" id="buttonOne">Create Graph/Chart</button></p></tr>
<tr><td align="center" id="blockTwo">
<p><button onclick="addGraph(2)"id ="buttonTwo">Create Graph/Chart</button></p></tr>
</table>
You can check which button was pressed by passing this as your function parameter.
Then, simply check your element's id and edit container with :
container = elem.id == 'buttonOne' ? "1" : "2";
var container = "0";
// When the user clicks the button, open the modal
function addGraph(elem) {
container = elem.id == 'buttonOne' ? "1" : "2";
console.log(container);
}
<table style="width:65%;height:100px">
<tr>
<td align="center" id="blockOne">
<p><button onclick="addGraph(this)" id="buttonOne">Create Graph/Chart</button></p>
<tr>
<td align="center" id="blockTwo">
<p><button onclick="addGraph(this)" id="buttonTwo">Create Graph/Chart</button></p>
</table>
You can define the function to accept the number as input, and make the onclick function called filled with the dedicated number.
<button onclick="addGraph(1)">Some text</button>
In this case you don't even need to define the button id, if you don't need to manipulate the buttons.
Notice that in your Html you should open and close all the html elements.
In your JavaScript logic you can select multiple elements by their ids with document.querySelectorAll('#buttonOne, #buttonTwo')
Than just iterate over all them with Array.prototype.forEach() to dynamically attach the click event listener and handle the container value like this container = (index + 1).toString();
Code:
let container = '0';
document
.querySelectorAll('#buttonOne, #buttonTwo')
.forEach((el, index) => el.addEventListener('click', () => {
container = (index + 1).toString();
console.log(container);
}));
<table style="width:65%;height:100px">
<tr>
<td align="center">
<p>
<button id="buttonOne">Create Graph/Chart</button>
</p>
</td>
</tr>
<tr>
<td align="center">
<p>
<button id="buttonTwo">Create Graph/Chart</button>
</p>
</td>
</tr>
</table>
I did an example about replacing the input value when the row is deleted but is not working (this is not a static example).
<script src="./edit_files/prototype.js" type="text/javascript"></script>
<script src="./edit_files/application.js" type="text/javascript"></script>
<div class="contact">
<table border="0">
<tr>
<td><select class="position_id" id="obj_client_contact_attributes__position_id" name="obj_client[contact_attributes][][position_id]"><option value="1" selected="selected">INTELIGENT</option><option value="2">OTHER</option></select></td>
<td><input class="should_change_value" id="obj_client_contact_attributes__phone_mail" name="obj_client[contact_attributes][][phone_mail]" type="text" value="cristianoronaldo#realmadrid.com"/></td>
<td>
DELETE
<input id="obj_client_contact_attributes__id" name="obj_client[contact_attributes][][id]" type="hidden" value="16594"/>
<input class="should_destroy" id="obj_client_contact_attributes__should_destroy" name="obj_client[contact_attributes][][should_destroy]" type="hidden"/>
</td>
</tr>
</table>
</div>
<div class="contact">
<table border="0">
<tr>
<td><select class="position_id" id="obj_client_contact_attributes__position_id" name="obj_client[contact_attributes][][position_id]"><option value="1" selected="selected">INTELIGENT</option><option value="2">OTHER</option></select></td>
<td><input class="should_change_value" id="obj_client_contact_attributes__phone_mail" name="obj_client[contact_attributes][][phone_mail]" type="text" value="ONLY THE INPUT WILL BE test#hotmail.com IF I CLICK ON DELETE"/></td>
<td>
DELETE
<input id="obj_client_contact_attributes__id" name="obj_client[contact_attributes][][id]" type="hidden" value="16594"/>
<input class="should_destroy" id="obj_client_contact_attributes__should_destroy" name="obj_client[contact_attributes][][should_destroy]" type="hidden"/>
</td>
</tr>
</table>
</div>
Here is the application.js file:
function mark_for_destroy_contact(element,should_destroy,should_change_value) {
var element_text = $(element).up('.contact').down('.position_id',0);
element_text.className = 'position_id';
element_text.value = '';
if (should_destroy) {
$(element).next('.should_destroy').value = 1;
}
$(element).up('.contact').hide();
}
I tried this code but only works if I remove the first row.
function mark_for_destroy_contact(element,should_destroy,should_change_value) {
var element_text = $(element).up('.contact').down('.position_id',0);
element_text.className = 'position_id';
element_text.value = '';
$('should_change_value').update("test#hotmail.com");
if (should_destroy) {
$(element).next('.should_destroy').value = 1;
}
$(element).up('.contact').hide();
}
Here is the live example in jsfiddle
Here is the example download on Github but is not replacing the input value correctly
Ok I got it, you want to change the input value when the row is deleted, so do this:
function mark_for_destroy_contact(element,should_destroy,should_change_value) {
var element_text = $(element).up('.contact').down('.position_id',0);
element_text.className = 'position_id';
element_text.value = '';
var element_text2 = $(element).up('.contact').down('.should_change_value',0);
element_text2.className = 'should_change_value';
element_text2.value = 'test#hotmail.com';
if (should_destroy) { $(element).next('.should_destroy').value = 1;}
$(element).up('.contact').hide();
}
I just want to understand why does Knockout.js does not let me access part of the model data. Is it because I am binding the model to the div containing all the submodels (Form in this example) or I am thinking it wrong?
For example, in this jsfiddle http://jsfiddle.net/zv6hauft/1/
I want to save just fare information but leave out bus information. So even if i just pass "fare_lines" in the example, it shows me all the models in the console.
<!doctype html>
<script src="/javascript/knockout-3.3.0.js"></script>
<form id="transport_form" method="post">
<table class="table table-condensed required" data-bind='visible: bus_lines().length > 0'>
<thead>
<tr>
<th>Bus</th>
</tr>
</thead>
<tbody data-bind='foreach: bus_lines'>
<tr>
<td>
<input name="bus_date" type="text" class="bus_date" value=" " data-bind='value: bus_desc' required/>
</td>
<td><a href='#' id="bus_remove" data-bind='click: $parent.removeBusLine'>Delete</a>
</td>
</tr>
</tbody>
</table>
<div class="controls">
<button id="bus_button" data-bind='click: addBusLine'>Add Bus</button>
</div>
<div id="fare_info_table">
<table data-bind='visible: fare_lines().length > 0'>
<thead>
<tr>
<th>Fare</th>
</tr>
</thead>
<tbody class="table required" data-bind='foreach: fare_lines'>
<tr>
<td>
<input id="fare_amnt" data-bind='value: fare_desc' required />
</td>
<td><a href='#' id="fare_remove" data-bind='click:$parent.remove_fare_line'>Delete</a>
</td>
</tr>
</tbody>
</table>
<div class="controls">
<button id="fare_button" data-bind='click: add_fare_line'>Add fare</button>
</div>
</div>
</br>
</br>
<div class="control-group">
<div class="controls">
<button type='submit' data-bind="click: save_record">Submit</button>
</div>
</div>
</form>
</html>
<script>
//Bus Model
var Bus_model = function () {
var self = this;
self.bus_desc = ko.observable();
};
var fare_model = function () {
var self = this;
self.fare_desc = ko.observable();
}
var operations_bus_fare = function () {
var self = this;
//Study Schedule Operations
self.bus_lines = ko.observableArray([new Bus_model()]);
self.addBusLine = function () {
self.bus_lines.push(new Bus_model())
};
self.removeBusLine = function (Bus_line) {
self.bus_lines.remove(Bus_line)
};
//Fare operations
self.fare_lines = ko.observableArray([new fare_model()]);
self.add_fare_line = function () {
self.fare_lines.push(new fare_model())
};
self.remove_fare_line = function (fare_line) {
self.fare_lines.remove(fare_line)
};
self.save_record = function (fare_lines) {
var saveData2 = ko.toJSON(fare_lines);
console.log(saveData2);
alert(saveData2);
};
};
ko.applyBindings(new operations_bus_fare(), document.getElementById("transport_form"));
</script>
You can access part of ViewModel my doing like this
ViewModel:
self.save_record = function (data) { // we get entire vm here as param
var saveData2 = ko.toJSON(data.fare_lines); // access required part
console.log(saveData2);
alert(saveData2);
};
Working fiddle here