Target is null error in browser dev tools - dom

I am trying to insert newly created elements (rows and cells) into a table (via javascript) above a row that has an ID of "dynPop". When I attempt to do this, I get the following error
TypeError: target is null
Here is the HTML of the table...
<table>
<tr id="dynPop">
<td style="color:white">
blah blah blah blah blah
</td>
</tr>
</table>
And here is the code to insert the new row and cell before it...
$rownumber = 1;
var target = document.getElementById('dynPop');
//Populate member table with chapter image row
var $CHAP_IMAGE = $out[1];
var olr1 = document.createElement('tr');
var old1 = document.createElement('td');
var olt1 = document.createTextNode($CHAP_IMAGE);
old1.appendChild(olt1);
olr1.appendChild(old1);
$rowname = 'row' + $rownumber;
olr1.setAttribute('name', $rowname);
target.insertBefore(olr1, target);
$rownumber++;
Any thoughts as to why this javascript code is not working ?
Thanks!

I"m not sure without seeing the rest of your HTML, but I think the main problem is that your JavaScript is executing before the DOM loads.
You might try putting your JavaScript into a function that gets called on the body element's onload event:
<!DOCTYPE html>
<html>
<head>
<title>asdf</title>
<script type="text/javascript">
function doThing() {
$rownumber = 1;
var target = document.getElementById('dynPop');
var $CHAP_IMAGE = "$out[1]; ";
var olr1 = document.createElement('tr');
var old1 = document.createElement('td');
var olt1 = document.createTextNode($CHAP_IMAGE);
old1.appendChild(olt1);
olr1.appendChild(old1);
$rowname = 'row' + $rownumber;
olr1.setAttribute('name',$rowname);
target.insertBefore(target, olr1);
$rownumber++;
}
</script>
</head>
<body onload="doThing()">
<table>
<tr id="dynPop">
<td style="color:white">
blah blah blah blah blah
</td>
</tr>
</table>
</body>
</html>

Related

Attaching a table to an email on Google Apps script

I have a code that sends a summary of a sheet in an email. However, I want to add a summary table from another sheet to that email. The sheet that I want to add to the email looks like this: [![Sheet][1]][1]. This sheet is a filter from another sheet and it filters only the rows where the date is equal to the date in B1.
I would like to have a copy of this table that contains only the rows that are not empty and add it to the email.
I tried this code:
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var responses = ss.getSheetByName("cambios mail");
var mail = ss.getSheetByName("MAILS");
var active_range = responses.getActiveRange();
var cambio = responses.getRange(active_range.getRowIndex(), 5).getValue();
var nuevo = responses.getRange(3, 11).getValue();
var cancelados = responses.getRange(3, 12).getValue();
var fecha =responses.getRange(3, 8).getValue();
var date = Utilities.formatDate(fecha, "GMT+2", "dd/MM/YYYY")
var sheet = ss.getSheetByName('cambios drop');
var values = sheet.getRange("A2:I" + sheet.getLastRow()).getValues();
var tabla= JSON.stringify(values);
var subject = "CAMBIOS REFERENCIAS: Resumen refes canceladas/añadidas";
var body = "Los siguientes modelos fueron modificados en el Master Doc ayer fecha " +date +".\n\n" + "Refes añadidas:" + nuevo + "\n\nRefes canceladas:"+ cancelados+ "\n\nCualquier consulta podéis contestar a este mail."+"\n\nAdjunto una tabla con los cambios de drops de ayer. Si no hubo cambios, la tabla aparecerá vacía."+"\n\nTabla"+ tabla+ "\n\nArchivo: https://docs.google.com/spreadsheets/d/";
var mailCorrecto = mail.getRange(1,2).getValues()
GmailApp.sendEmail(mailCorrecto, subject, body);
And this is what the table looks like on the email:
[![email][2]][2]
Does anybody know how I can format the range to be able to see it as a table on the email?
Thank you in advance!
[1]: https://i.stack.imgur.com/Tt3b9.png
[2]: https://i.stack.imgur.com/pZMEd.png
A templated html file for sending a table to an email
html:
<!DOCTYPE html>
<html>
<head>
<title>My Title</title>
</head>
<body>
<div id="myimages">
<? for(var i = 0;i < filename.length; i++) { ?>
<br /><img src="cid:img<?= i ?>" /><br />File Name: <?= filename[i] ?><hr />
<? } ?>
</div>
This is the div for the table
<div id="tabledata">
<? var vs = getBullSheetData(); ?>
<table>
<? vs.forEach((r,i)=>{ ?>
<tr>
<? r.forEach((c,j)=>{ ?>
<? if(i == 0) { ?>
<th style="padding:2px 5px;font-weight:bold;border:1px solid black;"><?= c ?> </th>
<? } else { ?>
<td style="padding:2px 5px;border:1px solid black;"><?= vs[i][j] ?> </td>
<? } ?>
<? }); ?>
</tr>
<? }); ?>
</table>
</div>
This is the end
</body>
gs:
function getBullSheetData() {
Logger.log('entering getBullSheetData')
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet2');
const vs = sh.getDataRange().getValues();
return vs;
}
templated html

webapp form value to spreadsheet

I have a webapp form and I'm looking for a way to push the value of the form into google spreadsheet.
My current attempt is to assign a name to each form input (a1,a2,a3...) then attempt to iterate the form values into an array like this:
Code.gs
function doGet(e) {
return HtmlService
.createTemplateFromFile('Index')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
}
function writeForm(form) {
var ss =
SpreadsheetApp.openById('1bKrGjBV*****');
var sheet = ss.getSheets()[0];
var data = ss.getDataRange().getValues();
var input = [''];
var ndata = form
for(var j=0;j<data.length;j++){
var value = form.a+j
input.push(value)
}
for(var k=0;k<data.length;k++){
sheet.getRange(k+1,4).setValue(ndata[k]);
}
var range = sheet.getRange(1, 5);
}
function getData(){
return SpreadsheetApp
.openById('1bKrGjBV*****')
.getSheets()[0]
.getDataRange()
.getValues();
}
Index.html
<body>
<center><h1>Produce Inventory Form </h1></center>
<style>
table,th,td{border:1px solid black;}
</style>
<? var data = getData(); ?>
<form id="invform">
<input type = "submit" value="Submit" onclick
="google.script.run.writeForm(this.parentNode)">
<table align="center">
<tr><th>Code</th><th>Name</th><th>ChName</th><th>On Hand</th></tr>
<? for(var i=0;i<data.length;i++){ ?>
<tr>
<th><?= data[i][0] ?></th>
<th><?= data[i][1] ?></th>
<th><?= data[i][2] ?></th>
<th><input type = "text" style="width:40px" min="0" maxlength="3" name=a<?
=i ?>>
</tr>
<? } ?>
</table>
</body>
With this method, the problem I'm getting into is
var value = form.a+j
Doesn't do what I was hoping for, which is to assign a variable to form.a1, form.a2, form.a3, ... then push it into an array.
I'm pretty sure there's a better way but I have yet to find a solution. Apologize for the messy code, but I was focused on getting the result.
How about a following modification?
Modification points :
For Index.html, </form> is missing.
About form.a+j, you can retrieve the values using form["a" + j].
ndata is JSON like {a1: "value", a2: "value"}. So when ndata is imported to cells, it becomes undefined.
When several values are imported to cells, the importing efficiency becomes higher by using setValues().
When these are reflected to your script, the modified script is as follows. The values inputted to forms are imported to "D1:D" of spreadsheet.
Modified script :
Code.gs
function doGet(e) {
return HtmlService
.createTemplateFromFile('Index')
.evaluate()
.setSandboxMode(HtmlService.SandboxMode.NATIVE)
}
function writeForm(form) {
var ss = SpreadsheetApp.openById('1bKrGjBV*****');
var sheet = ss.getSheets()[0];
var data = ss.getDataRange().getValues();
var input = []; // Modified
// var ndata = form
for(var j=0;j<data.length;j++){
var value = form["a" + j]; // Modified
input.push([value]) // Modified
}
sheet.getRange(1, 4, input.length, input[0].length).setValues(input); // Added
// for(var k=0;k<data.length;k++) {
// sheet.getRange(k+1,4).setValue(ndata[k]);
// }
// var range = sheet.getRange(1, 5);
}
function getData(){
return SpreadsheetApp
.openById('1bKrGjBV*****')
.getSheets()[0]
.getDataRange()
.getValues();
}
Index.html
<body>
<center><h1>Produce Inventory Form </h1></center>
<style>
table,th,td{border:1px solid black;}
</style>
<? var data = getData(); ?>
<form id="invform">
<input type = "submit" value="Submit" onclick="google.script.run.writeForm(this.parentNode)">
<table align="center">
<tr><th>Code</th><th>Name</th><th>ChName</th><th>On Hand</th></tr>
<? for(var i=0;i<data.length;i++){ ?>
<tr>
<th><?= data[i][0] ?></th>
<th><?= data[i][1] ?></th>
<th><?= data[i][2] ?></th>
<th><input type = "text" style="width:40px" min="0" maxlength="3" name=a<?= i ?>>
</tr>
<? } ?>
</table>
</form> <!-- Added -->
</body>
If I misunderstand your question, I'm sorry.

How to fix the time formatting issue [duplicate]

This question already has an answer here:
using a forEach loop to build html table and date/time comes through as null
(1 answer)
Closed 2 years ago.
Trying to populate the sheet data in HTML web app, my data consists Time(24 hrs format) but in web apps, it's reflecting in a different format.
Attached Image for reference
function doGet() {
var t = HtmlService.createTemplateFromFile('index');
t.data = SpreadsheetApp
.openById('1ZPbCZWvEIJ7rpYxyfD4yiWrnC11RT9I_96kCndAJmdI')
.getActiveSheet()
.getDataRange()
.getValues();
return t.evaluate();
}
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<table>
<? for (var i = 0; i < data.length; i++) { ?>
<tr>
<? for (var j = 0; j < data[i].length; j++) { ?>
<td><?= data[i][j] ?></td>
<? } ?>
</tr>
<? } ?>
</table>
</body>
</html>
How about a following modification? getDisplayValues() was used for this situation.
From :
function doGet() {
var t = HtmlService.createTemplateFromFile('index');
t.data = SpreadsheetApp
.openById('1ZPbCZWvEIJ7rpYxyfD4yiWrnC11RT9I_96kCndAJmdI')
.getActiveSheet()
.getDataRange()
.getValues();
return t.evaluate();
}
To :
function doGet() {
var t = HtmlService.createTemplateFromFile('index');
t.data = SpreadsheetApp
.openById('1ZPbCZWvEIJ7rpYxyfD4yiWrnC11RT9I_96kCndAJmdI')
.getActiveSheet()
.getDataRange()
.getDisplayValues(); // Modified
return t.evaluate();
}

selecting an image inside nested tables

Need help in clicking an image that is with a td inside a table. The table is defined as
html is below:
`<html class=" myapp>
<head id="head1">
<body id="body1">
<div id = "container" class>
<div id = "Header" class="header">..</div>
<div id = "navbar" class="topnavbar">..</div>
<form name = "form1"....>
.
.
.
<div id = "Layout container" class>
<div id="appcontectinfo">...</div>
<div class="tabcontent">
<div id="allinfo">
<table id="tablename"> </table>
<table>
<tbody>
<tr>
<td>
<table id style = "height 100%">
<tbody>
<tr style ="height : 16">
<td class = "clsClassname1">
<table cellpadding = "0" cellspacing = "0">
<tbody>
<tr>
<td class = "clsMyexpClass1>
<input src = "https://www.mylinkhere.com/lookup.gif" name = "myimage1" type = "image" id = "imgLookup1">
</td>`
I tried the below:
image(:lookup1, :id => 'imgLookup1')
lookup1_element.click
I get the error message- >"Watir::Exception::UnknownObjectException: unable to locate element, using {:id=>"imgLookup1", :tag_name=>"img"}
I then tried
table(:lookuptab1) {div_element(:id =>'allinfo' ).table_element(:style => 'height: 100%; text-align: left;')}
and again the same error message. Please help!
The page-object-gem and watir treat input elements of type "image" (ie <input type="image">) as buttons.
The element should be located if you change your page object accessor to use button:
button(:lookup1, :id => 'imgLookup1')
Note that then you can click the input using lookup1 instead lookup1_element.click.
Here is a working example:
class MyPage
include PageObject
button(:lookup1, :id => 'imgLookup1')
end
browser = Watir::Browser.new
browser.goto("data:text/html,#{DATA.read}")
page = MyPage.new(browser)
page.lookup1
browser.close
__END__
<html>
<body>
<input src = "https://www.mylinkhere.com/lookup.gif" name = "myimage1" type = "image" id = "imgLookup1" onclick="alert();">
</body>
</html>

Grails One to Many form

I am currently working on a grails project and am trying to create a one to many form. I have been using the tutorial at the link below to get started:
http://omarello.com/2010/08/grails-one-to-many-dynamic-forms/
The solution has a form with two field of data that are static and one dynamic fiwld where the user is allowed to add as many variables as they want and then save them. below you can see all the various files for this functionality:
Products.Groovy Domain Class
import org.apache.commons.collections.list.LazyList;
import org.apache.commons.collections.FactoryUtils;
/**
* Products
* A domain class describes the data object and it's mapping to the database
*/
class Products {
String productType
String product
List vars = new ArrayList()
//This represents a product belonging to a single department
static belongsTo = [dept:Depts]
static hasMany = [ vars:Dynam ]
static mapping = {
vars cascade:"all-delete-orphan"
}
def getVarsList() {
return LazyList.decorate(
vars,
FactoryUtils.instantiateFactory(Dynam.class))
}
static constraints = {
productType blank: false
product blank:false, size: 1..160
}
}
Dynam.groovy
class Dynam {
public enum VarType{
T("Testimonial"),
D("Dimentions"),
N("Networking")
final String value;
VarType(String value) {
this.value = value;
}
String toString(){
value;
}
String getKey(){
name()
}
static list() {
[T, D, N]
}
}
static constraints = {
index(blank:false, min:0)
data(blank:false)
type(blank:false, inList:VarType.list(), minSize:1, maxSize:1)
}
int index
String data
VarType type
boolean deleted
static transients = [ 'deleted' ]
static belongsTo = [ product:Products ]
def String toString() {
return "($index) ${data} - ${type.value}"
}
}
ProductsController.Groovy
Creation Function
def createDynProduct(){
def productsInstance = new Products()
productsInstance.properties = params
//This is used in order to create a new User Object for the current User logged in
def userObjects = springSecurityService.currentUser
//This passes the 2 models to the view one being Products and the other a User Department
[productsInstance: productsInstance, dept: userObjects.dept]
}
Save Function
def save() {
def productInfo = "dynam"
def userObjects = springSecurityService.currentUser
def dept = Depts.findByName(params.dept.name)
def product = new Products(product:productInfo, productType:params.productType, dept: dept, vars:params.varsList)
def _toBeRemoved = product.vars.findAll {!it}
// if there are vars to be removed
if (_toBeRemoved) {
product.vars.removeAll(_toBeRemoved)
}
//update my indexes
product.vars.eachWithIndex(){phn, i ->
if(phn)
phn.index = i
}
//If the the save is not successful do this
if (!product.save(flush: true)) {
render(view: "create", model: [productsInstance: product, dept: userObjects.dept])
return
}
redirect(action: "show", id: product.id)
}
createDynProduct.gsp
<%# page import="com.tool.Products" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name='layout' content='springSecurityUI'/>
<g:set var="entityName" value="${message(code: 'messages.label', default: 'Products')}" />
<title><g:message code="default.create.label" args="[entityName]" /></title>
</head>
<body>
<g:renderErrors bean="${productsInstance}" />
<g:form action='save' name='ProductForm' >
<br/>
<!-- Render the product template (_dynam.gsp) here -->
<g:render template="dynam" model="['productsInstance':productsInstance]"/>
<!-- Render the product template (_dynam.gsp) here -->
<div class="buttons">
<g:submitButton name="create" class="save" value="${message(code: 'default.button.create.label', default: 'Create')}" />
</div>
</g:form>
<!-- Render the var template (_var.gsp) hidden so we can clone it -->
<g:render template='var' model="['var':null,'i':'_clone','hidden':true]"/>
<!-- Render the var template (_var.gsp) hidden so we can clone it -->
</body>
</html>
_dynam.gsp
<div class="dialog">
<table>
<tbody>
<tr class="prop">
<td valign="top" class="name">
<label for="productType">Product Type</label>
</td>
<td>
<g:textField name='productType' bean="${productsInstance}" value="${productsInstance.productType}"
size='40'/>
</td>
</tr>
<tr class="prop">
<td valign="top" class="name">
<label for="Vars">Vars</label>
</td>
<!-- Render the vars template (_vars.gsp) here -->
<g:render template="vars" model="['productsInstance':productsInstance]" />
<!-- Render the vars template (_vars.gsp) here -->
</tr>
<tr class="prop">
<td valign="top" class="name">
<label for="Department">Department</label>
</td>
<td>
<g:textField name='dept.name' readonly="yes" value="${dept.name}" size='40'/>
</td>
</tr>
</tbody>
</table>
</div>
_vars.gsp
<script type="text/javascript">
var childCount = ${productsInstance?.vars.size()} + 0;
function addVar(){
var clone = $("#var_clone").clone()
var htmlId = 'varsList['+childCount+'].';
var varInput = clone.find("input[id$=number]");
clone.find("input[id$=id]")
.attr('id',htmlId + 'id')
.attr('name',htmlId + 'id');
clone.find("input[id$=deleted]")
.attr('id',htmlId + 'deleted')
.attr('name',htmlId + 'deleted');
clone.find("input[id$=new]")
.attr('id',htmlId + 'new')
.attr('name',htmlId + 'new')
.attr('value', 'true');
varInput.attr('id',htmlId + 'data')
.attr('name',htmlId + 'data');
clone.find("select[id$=type]")
.attr('id',htmlId + 'type')
.attr('name',htmlId + 'type');
clone.attr('id', 'var'+childCount);
$("#childList").append(clone);
clone.show();
varInput.focus();
childCount++;
}
//bind click event on delete buttons using jquery live
$('.del-var').live('click', function() {
//find the parent div
var prnt = $(this).parents(".var-div");
//find the deleted hidden input
var delInput = prnt.find("input[id$=deleted]");
//check if this is still not persisted
var newValue = prnt.find("input[id$=new]").attr('value');
//if it is new then i can safely remove from dom
if(newValue == 'true'){
prnt.remove();
}else{
//set the deletedFlag to true
delInput.attr('value','true');
//hide the div
prnt.hide();
}
});
</script>
<div id="childList">
<g:each var="var" in="${productsInstance.vars}" status="i">
<!-- Render the var template (_var.gsp) here -->
<g:render template='var' model="['var':var,'i':i,'hidden':false]"/>
<!-- Render the var template (_var.gsp) here -->
</g:each>
</div>
<input type="button" value="Add Var" onclick="addVar();" />
_var.gsp
<div id="var${i}" class="var-div" <g:if test="${hidden}">style="display:none;"</g:if>>
<g:hiddenField name='varsList[${i}].id' value='${var?.id}'/>
<g:hiddenField name='varsList[${i}].deleted' value='false'/>
<g:hiddenField name='varsList[${i}].new' value="${var?.id == null?'true':'false'}"/>
<g:textField name='varsList[${i}].number' value='${var?.data}' />
<g:select name="varsList[${i}].type"
from="${com.tool.Dynam.VarType.values()}"
optionKey="key"
optionValue="value"
value = "${var?.type?.key}"/>
<span class="del-var">
<img src="${resource(dir:'images/skin', file:'icon_delete.png')}"
style="vertical-align:middle;"/>
</span>
</div>
The solution works in part so you can go to the products page and it loads with the fields and the dynamic fields are added to the view fine and I can enter the data. However when I click save the data for the static fields is persisted but the dynamic vars are not saved to the domain.
I don’t get any errors but I believe it has something to do with the save function line below and the vars:params.varsList in particular.
def product = new Products(product:productInfo, productType:params.productType, dept: dept, vars:params.varsList)
I have checked the varsList data using println and it returns null, can someone please help with this?
Thanks in advance