webapp form value to spreadsheet - forms

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.

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

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();
}

dropdown will accept a value that is not part of the list as well in Knockout

When entering an E-mail address, the user will have to select the E-mail
domain from the pre-defined list (e.g., gmail.com; outlook.com; hotmail.com).
The Domain dropdown will accept a value that is not part of the list as well.
I should have both select and input feature.
HTML:
<!-- Mulitple array of emails -->
<div>
<table class="table table-bordered">
<tbody data-bind='foreach: billDeliveryEmails'>
<tr>
<td><input class='required' data-bind='value: userName' /></td>
<td><select data-bind="options: $root.availableEmailDomains(), value: domainName, optionsText: 'domainName', optionsValue: 'domainName'"></select></td>
<td><a data-bind="click:'removeDeliveryEmailAddress'">Delete</a></td>
</tr>
</tbody>
</table>
<a class="atm-bloque-link" data-bind="click:'addDeliveryEmailAddress'">Agregue otra direccion de email</a>
</div>
VM:
// Domain class
function Domain(domainName) {
var self = this;
self.domainName = domainName;
}
billDeliveryEmails : [],
availableEmailDomains : ko.observableArray([
new Domain("hotmail.com"),
new Domain("yahoo.com")
])
addDeliveryEmailAddress: function ($element, data, context, bindingContext, event) {
bindingContext.$root.billDeliveryEmails.push({
userName: "",
domainName: this.viewModel.get('availableEmailDomains')[0]
});
event.preventDefault();
},
removeDeliveryEmailAddress: function ($element, data, context, bindingContext, event) {
bindingContext.$root.billDeliveryEmails.splice(0, 1)
event.preventDefault();
}
Based on the link in your comment, here's an example:
function Domain(domainName) {
var self = this;
self.domainName = domainName;
}
var vm = function () {
var self = this;
self.browser = ko.observable();
self.browserList = ko.observableArray([
new Domain('yahoo.com'),
new Domain('hotmail.com')
])
}();
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<label>Choose a browser from this list:
<input list="browsers" name="myBrowser" data-bind="value: browser" /></label>
<datalist id="browsers" data-bind="foreach: browserList">
<option data-bind="text: domainName">
</datalist>
<br />
Your browser is: <span data-bind="text: browser"></span>
An observable array is used to populate the list and a regular observable tracks the selected value.

Mystery of "this" in ReactJS

I am fairly new to the Facebook's React world. Their documentation seems to be very good but there are a few areas where I need a little bit of clarity. This is one of them.
Src: http://tuts-javascript.appspot.com/reactjs-add-remove-table-row
var CompanyApp = React.createClass({
getInitialState: function() {
return {companylist:this.props.companies};
},
handleNewRowSubmit: function( newcompany ) {
this.setState( {companylist: this.state.companylist.concat([newcompany])} );
},
handleCompanyRemove: function( company ) {
var index = -1;
var clength = this.state.companylist.length;
for( var i = 0; i < clength; i++ ) {
if( this.state.companylist[i].cname === company.cname ) {
index = i;
break;
}
}
this.state.companylist.splice( index, 1 );
this.setState( {companylist: this.state.companylist} );
},
render: function() {
var tableStyle = {width: '100%'};
var leftTdStyle = {width: '50%',padding:'20px',verticalAlign: 'top'};
var rightTdStyle = {width: '50%',padding:'20px',verticalAlign: 'top'};
return (
<table style={tableStyle}>
<tr>
<td style={leftTdStyle}>
<CompanyList clist={this.state.companylist} onCompanyRemove={this.handleCompanyRemove}/>
</td>
<td style={rightTdStyle}>
<NewRow onRowSubmit={this.handleNewRowSubmit}/>
</td>
</tr>
</table>
);
}
});
var CompanyList = React.createClass({
handleCompanyRemove: function(company){
this.props.onCompanyRemove( company );
},
render: function() {
var companies = [];
var that = this; // TODO: Needs to find out why that = this made it work; Was getting error that onCompanyDelete is not undefined
this.props.clist.forEach(function(company) {
companies.push(<Company company={company} onCompanyDelete={that.handleCompanyRemove} /> );
});
return (
<div>
<h3>List of Companies</h3>
<table className="table table-striped">
<thead><tr><th>Company Name</th><th>Employees</th><th>Head Office</th><th>Action</th></tr></thead>
<tbody>{companies}</tbody>
</table>
</div>
);
}
});
var Company = React.createClass({
handleRemoveCompany: function() {
this.props.onCompanyDelete( this.props.company );
return false;
},
render: function() {
return (
<tr>
<td>{this.props.company.cname}</td>
<td>{this.props.company.ecount}</td>
<td>{this.props.company.hoffice}</td>
<td><input type="button" className="btn btn-primary" value="Remove" onClick={this.handleRemoveCompany}/></td>
</tr>
);
}
});
var NewRow = React.createClass({
handleSubmit: function() {
var cname = this.refs.cname.getDOMNode().value;
var ecount = this.refs.ecount.getDOMNode().value;
var hoffice = this.refs.hoffice.getDOMNode().value;
var newrow = {cname: cname, ecount: ecount, hoffice: hoffice };
this.props.onRowSubmit( newrow );
this.refs.cname.getDOMNode().value = '';
this.refs.ecount.getDOMNode().value = '';
this.refs.hoffice.getDOMNode().value = '';
return false;
},
render: function() {
var inputStyle = {padding:'12px'}
return (
<div className="well">
<h3>Add A Company</h3>
<form onSubmit={this.handleSubmit}>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Company Name" ref="cname"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Employee Count" ref="ecount"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="text" className="form-control col-md-8" placeholder="Headoffice" ref="hoffice"/>
</div>
<div className="input-group input-group-lg" style={inputStyle}>
<input type="submit" className="btn btn-primary" value="Add Company"/>
</div>
</form>
</div>
);
}
});
var defCompanies = [{cname:"Infosys Technologies",ecount:150000,hoffice:"Bangalore"},{cname:"TCS",ecount:140000,hoffice:"Mumbai"}];
React.renderComponent( <CompanyApp companies={defCompanies}/>, document.getElementById( "companyApp" ) );
This is a very good basic explanation of how ReactJS works. Thanks to the author.
But this comment,
var that = this; // TODO: Needs to find out why that = this made it work; Was getting error that onCompanyDelete is not undefined
Why is that necessary?
Is this the right way to do it? If not, what is?
Thanks in advance.
There's no mystery of "this" that is specific to ReactJS.
This is just a case of standard scoping issues that crop up with callbacks in JavaScript.
When you're in a react component, all methods on the base component will be scoped with the this being the current component, just like any other JavaScript "class".
In your snippet you have a render method which is a function on the base component and therefore this is equal to the component itself. However within that render method you're calling a callback with this.props.clist.forEach, any function callbacks inside the render method will need to be either bound to the correct this scope, or you can do var that = this (although this is an anti-pattern and should be discouraged)`.
Example, slightly simplified version of your snippet:
var MyComponent = React.createClass({
handleCompanyRemove: function(e) {
// ...
},
render: function() {
// this === MyComponent within this scope
this.props.someArray.forEach(function(item) {
// this !== MyComponent, therefore this.handleCompanyRemove cannot
// be called!
})
}
})
As you can see from the comments above, inside your callback for the .forEach you cannot use this directly without either defining a variable outside, or properly binding the function.
Other options to solve this are:
Binding the callback function to the correct this scope. Example:
this.props.someArray.forEach(function(item) {
// this === MyComponent within this scope too now!
// so you can call this.handleCompanyRemove with no problem
}.bind(this))
If you're using Babel/ES6 you can use the Fat Arrow function syntax which guarantees that this scope continues to the callback from the parent scope. Example:
this.props.someArray.forEach((item) => {
// this === MyComponent within this scope too now!
// so you can call this.handleCompanyRemove with no problem
})

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