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
Related
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.
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();
}
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>
I have a module which is used to add banner images through admin panel. I have created the module successfully. My module edit page has two tabs. One for adding general informations of banner and second one for adding images. The general information tab is working fine now.
I need to have a button 'add banner image' in my second tab initially. When clicked on it, it should load an file type button.We can use this button for loading the image. We can use 'add banner image' in any number of times for loading the image. This is just similar to the add custom option for a product. I need that exact same functionality here.
I have searched a lot. But couldnt find how to add 'add banner image' button to my second tab. Please help me to solve this issue. Give me an idea of how can impliment this functionality. Sorry for my bad english.Thanks
I have created a similar functionality where I can add postcode ranges in my custom module using below code.
In my Module_name/Block/Adminhtml/Regions/Edit/Tab/Form.php I have added.
$fieldset->addField('postcodes', 'text', array(
'name'=>'postcodes',
'class'=>'requried-entry'
));
$form->getElement('postcodes')->setRenderer(
$this->getLayout()->createBlock('zones/adminhtml_regions_edit_tab_postcodes')
);
This will add postcodes field in my tab with renderer adminhtml_regions_edit_tab_postcodes
In my Module_name/Block/Adminhtml/Regions/Edit/Tab/Postcodes.php I have added the button with the code.
class Ripples_Zones_Block_Adminhtml_Regions_Edit_Tab_Postcodes
extends Mage_Adminhtml_Block_Catalog_Product_Edit_Tab_Price_Group_Abstract
{
public function __construct()
{
$this->setTemplate('zones/postcodes.phtml');
}
protected function _prepareLayout()
{
$button = $this->getLayout()->createBlock('adminhtml/widget_button')
->setData(array(
'label' => Mage::helper('zones')->__('Add Postcode Range'),
'onclick' => 'return addPostCodeRange.addItem()',
'class' => 'add'
));
$button->setName('add_postcode_range_button');
$this->setChild('add_button', $button);
return parent::_prepareLayout();
}
}
This will add a button on my tab with the onclick function addPostCodeRange.addItem().
After this I have added a phtml file in design/adminhtml/default/default/template/zones/postcodes.phtml
<?php $_htmlId = $this->getElement()->getHtmlId() ?>
<?php $_htmlName = 'postcodes';?>
<tr>
<td class="label">Postcode Range</td>
<td colspan="10" class="grid">
<table cellspacing="0" class="data border" id="postcodes_table">
<col width="50" />
<col width="50" />
<col width="1" />
<thead>
<tr class="headings">
<th><?php echo Mage::helper('zones')->__('From') ?></th>
<th><?php echo Mage::helper('zones')->__('To') ?></th>
<th class="last"><?php echo Mage::helper('catalog')->__('Action') ?></th>
</tr>
</thead>
<tbody id="<?php echo $_htmlId ?>_container"></tbody>
<tfoot>
<tr>
<td colspan="3" class="a-right"><?php echo $this->getAddButtonHtml() ?></td>
</tr>
</tfoot>
</table>
<script type="text/javascript">
//<![CDATA[
var tierPriceRowTemplate = '<tr>'
+ '<td><input type="text" value="{{from}}" class="custgroup required-entry" name="<?php echo $_htmlName ?>[{{index}}][from]" id="postcodes_row_{{index}}_from" />'
+ '</td>'
+ '<td><input type="text" value="{{to}}" class="custgroup required-entry" name="<?php echo $_htmlName ?>[{{index}}][to]" id="postcodes_row_{{index}}_to" />'
+ '</td>'
+ '<td class="last"><input type="hidden" name="<?php echo $_htmlName ?>[{{index}}][delete]" class="delete" value="" id="postcodes_row_{{index}}_delete" />'
+ '<button title="<?php echo Mage::helper('zones')->__("Delete Range") ?>" type="button" class="scalable delete icon-btn delete-product-option" id="postcodes_row_{{index}}_delete_button" onclick="return addPostCodeRange.deleteItem(event);">'
+ '<span><span><span><?php echo Mage::helper('zones')->__("Delete") ?></span></span></span></button></td>'
+ '</tr>';
var addPostCodeRange = {
template: new Template(tierPriceRowTemplate, new RegExp('(^|.|\\r|\\n)({{\\s*(\\w+)\\s*}})', "")),
itemsCount: 0,
addItem : function () {
var data = {
from: '',
to: '',
readOnly: false,
index: this.itemsCount++
};
if(arguments.length >= 2) {
data.from = arguments[0];
data.to = arguments[1];
}
if (arguments.length == 3) {
data.readOnly = arguments[2];
}
Element.insert($('<?php echo $_htmlId ?>_container'), {
bottom : this.template.evaluate(data)
});
$('postcodes_row_' + data.index + '_from').value = data.from;
$('postcodes_row_' + data.index + '_to').value = data.to;
if (data.readOnly == '1') {
['from', 'to', 'delete'].each(function(idx){
$('postcodes_row_'+data.index+'_'+idx).disabled = true;
});
$('postcodes_row_'+data.index+'_delete_button').hide();
}
},
disableElement: function(el) {
el.disabled = true;
el.addClassName('disabled');
},
deleteItem: function(event) {
var tr = Event.findElement(event, 'tr');
if (tr) {
Element.select(tr, '.delete').each(function(elem){elem.value='1'});
Element.hide(tr);
Element.addClassName(tr, 'no-display template');
}
return false;
}
};
<?php $collection = Mage::getModel('zones/regions')->getCollection()->addFieldToFilter('regions_id',array('eq' => $this->getRequest()->getParam('id'))); ?>
<?php foreach ($collection as $_item): ?>
<?php $postcodes = unserialize($_item['postcodes']); ?>
<?php foreach ($postcodes as $postcode): ?>
addPostCodeRange.addItem( '<?php echo $postcode['from'] ?>', '<?php echo $postcode['to'] ?>');
<?php endforeach; ?>
<?php endforeach; ?>
//]]>
</script>
</td></tr>
This file contains the function. I have added two textboxes on function call. You can add file field in this function.
Hope this helps.
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