Google Sheets Script: Get Value From Script Function Into Sidebar - google-apps-script

For an analysis spreadsheet, a function does a lot of calculations for each of ~240 rows of data. The function is slow, so I added code to check how long it's been running whilst looping.
Before 360 secs is up I exit - first saving the last row processed in Project Properties, and providing an alert. I can then rerun the function, which checks if the function did not complete, and asks if I want to continue from the last row processed. All good.
However, this can take up to half an hour to finally complete , so I've been looking online for ways to run multiple instances of the function simultaneously. I found a suggestion that uses the Sidebar.
The Sidebar is opening as expected, but despite some help, my script in the HTML is not setting the table cells with the initial "size". The server-side function c_Optimise_Stabilator_Allocation() is correctly returning an array like [87.0, 74.0, 62.0, 25.0], but the client-side function setSize(arrSize) is not working.
The GS code is:
// display sidebar in gs
function c_Optimise_Stabilator_Sidebar(){
var html = HtmlService.createHtmlOutputFromFile('StabSidebar').setTitle('Optimise Stabilator').setWidth(250);
SpreadsheetApp.getUi().showSidebar(html);
}
function c_Optimise_Stabilator_Allocation(tranche, operation){
var debug = true;
const ui = SpreadsheetApp.getUi();
// Get numRows
const sAngle = "Optimise_Stab_Angle";
const range = c_GetRangeObjByName(sAngle);
const numRows = range.getLastRow(); // s/b ~248 rows
// set different num rows per tranche, using percentage
const pct1 = 0.35, pct2 = 0.30, pct3 = 0.25;
const size1 = Math.round(numRows*pct1), startRow1 = 0;
const size2 = Math.round(numRows*pct2), startRow2 = size1;
const size3 = Math.round(numRows*pct3), startRow3 = size1+size2;
const size4 = numRows-size1-size2-size3, startRow4 = size1+size2+size3;
if (debug) { Logger.log ('T1: '+size1+'; T2: '+size2+'; T3: '+size3+'; T4: '+size4); }
if (operation=='size') {
var arrSize = [size1, size2, size3, size4];
if (debug) { Logger.log(arrSize); }
return arrSize;
} else if (operation=='run') {
switch(tranche) {
case 'tranche1' :
// c_Optimise_Stabilator(startRow1, size1);
break;
case 'tranche2' :
// c_Optimise_Stabilator(startRow2, size2);
break;
case 'tranche3' :
// c_Optimise_Stabilator(startRow3, size3);
break;
case 'tranche4' :
// c_Optimise_Stabilator(startRow4, size4);
break;
default :
break;
}
}
return;
}
The HTML code:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
</head>
<body>
<button onclick='run()'>Run Stabilator Optimisation</button><br>
<table>
<tr> <th>Tr</th> <th>Status</th> <th>Optimised</th> <th>Tot.Rows</th> <th>Time</th> </tr>
<tr> <td>1</td> <td id='status1'>Waiting...</td> <td id='opt1'>0</td> <td id='size1'>0</td> <td id='time1'>xxx</td> </tr>
<tr> <td>2</td> <td id='status2'>Waiting...</td> <td id='opt2'>0</td> <td id='size2'>0</td> <td id='time2'>xxx</td> </tr>
<tr> <td>3</td> <td id='status3'>Waiting...</td> <td id='opt3'>0</td> <td id='size3'>0</td> <td id='time3'>xxx</td> </tr>
<tr> <td>4</td> <td id='status4'>Waiting...</td> <td id='opt4'>0</td> <td id='size4'>0</td> <td id='time4'>xxx</td> </tr>
</table>
<script>
window.onload=function(){
console.log('onload');
google.script.run
.withSuccessHandler( function(arrSize) {
setSize(arrSize);
}
)
.c_Optimise_Stabilator_Allocation('all', 'size');
}
function setSize(arrSize){
console.log('setSize');
for(var i=0;i<vA.length;i++) {
document.getElementById('size' + (i+1)).innerHTML=arrSize[i];
}
}
function run() {
google.script.run.c_Optimise_Foil('tranche1', 'run'); document.getElementById('status1').innerHTML='Running...';
google.script.run.c_Optimise_Foil('tranche2', 'run'); document.getElementById('status2').innerHTML='Running...';
google.script.run.c_Optimise_Foil('tranche3', 'run'); document.getElementById('status3').innerHTML='Running...';
google.script.run.c_Optimise_Foil('tranche4', 'run'); document.getElementById('status4').innerHTML='Running...';
}
console.log("My Code");
</script>
</body>
</html>
I have cobbled together the code for the HTML as best I could from various sources, but can't find why the functions to setSize aren't working. Any suggestions would be welcome. MTIA

I don't really want to debug your code for you but I did want to know if it would handle multiple callbacks so here's a simple example of it doing that:
HTML:
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<script>
window.onload=function(){
google.script.run
.withSuccessHandler(function(vA) {
updateSelect(vA);
})
.getSelectOptions();
}
function updateSelect(vA,id){//the id allows me to use it for other elements
var id=id || 'sel1';
var select = document.getElementById(id);
select.options.length = 0;
for(var i=0;i<vA.length;i++) {
select.options[i] = new Option(vA[i],vA[i]);
}
}
function sendRequest() {
const n=document.getElementById('sel1').value;
document.getElementById('div' + n).innerHTML='Initiating Request' + n;
google.script.run
.withSuccessHandler(function(v){
document.getElementById('div' + v).innerHTML='Received Request' + v;
})
.initRequest(n);
}
function sendAll() {
for(let i=1;i<5;i++) {
document.getElementById('div' + i).innerHTML="Iniating Request" + i;
google.script.run
.withSuccessHandler(function(v){
document.getElementById('div' + v).innerHTML='Received Request' + v;
})
.initRequest(i);
}
}
console.log("My Code");
</script>
</head>
<body>
<select id='sel1'></select>
<input type="button" value="Initiate" onClick="sendRequest();" />
<input type="button" value="All" onclick="sendAll();" />
<div id="div1"></div>
<div id="div2"></div>
<div id="div3"></div>
<div id="div4"></div>
</body>
</html>
CODE:
function getSelectOptions() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const rg=sh.getRange(1,1,sh.getLastRow());
const vs=rg.getValues();
return vs;
}
function launchDialog() {
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutputFromFile("ah3"),"Testing");
}
function initRequest(n) {
return n;
}
I don't expect any points for it. I just thought it might be helpful to know that it can work. Somebody will most likely vote it down and then I'll remove it.

Related

Splitting the random string into two steps And do the math on the final result (js -jquery)

I want to split random strings generated by my code,
But here I just use static string for example|
i can do it manual ,but its boring to this in the long time and large scale code
i want function , to do this easily
here is my codes :
function stringSplitter(myString, chunkSize) {
var splitString = [];
for (var i = 0; i < myString.length; i = i + chunkSize) {
splitString.push(myString.slice(i, i + chunkSize));
}
return splitString;
}
var randnumber = 'va2b1881ff21GSD1SSSS18aav1v112H1tkdpkmmnnwqkkq118188493';
var get4 = stringSplitter(randnumber, 4);
var str1 = $("#get").html(get4);
var str2 = $("#get2").html(get4[0]);
var str3 = $("#get3").html(get4[1]);
var str4 = $("#get4").html(get4[2]);
//.................
var get2of4_1 = stringSplitter(get4[0], 1);
var str5 = $("#td1").html(get2of4_1[0]);
var str6 = $("#td2").html(get2of4_1[1]);
var str7 = $("#td3").html(get2of4_1[2]);
var str8 = $("#td4").html(get2of4_1[3]);
var get2of4_2 = stringSplitter(get4[1], 1);
var str9 = $("#td5").html(get2of4_2[0]);
var str10 = $("#td6").html(get2of4_2[1]);
var str11 = $("#td7").html(get2of4_2[2]);
var str12 = $("#td8").html(get2of4_2[3]);
var get2of4_3 = stringSplitter(get4[2], 1);
var str13 = $("#td9").html(get2of4_3[0]);
var str14 = $("#td10").html(get2of4_3[1]);
var str15 = $("#td11").html(get2of4_3[2]);
var str16 = $("#td12").html(get2of4_3[3]);
//for example :
var sum = str5 + str6 + str7 / 100
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="../!Needs/jquery.min.js"></script>
</head>
<body>
<hr>
<span id="get">
...........
</span>
<hr>
<br>
<span id="get2"></span>
<br>
<span id="get3"></span>
<br>
<span id="get4"></span>
<br>
<span id="get5"></span>
<br><br>
<table border="1">
<tr>
<td id="td1"></td>
<td id="td2"></td>
<td id="td3"></td>
<td id="td4"></td>
</tr>
<tr>
<td id="td5"></td>
<td id="td6"></td>
<td id="td7"></td>
<td id="td8"></td>
</tr>
<tr>
<td id="td9"></td>
<td id="td10"></td>
<td id="td11"></td>
<td id="td12"></td>
</tr>
</table>
</body>
</html>
i split my string twice .
first i got 4 characters , then i splite 4 characters to 1
for Example : abcdefghijklmnopq 4 => (abcd) 1 => (a)(b)(c)(d)
then i want to do some math actions ,
but my problem is . is there and function or simple way to do this, faster and easily ?
i dont want to write several times
and i dont know how to use loop or functions , to make this :
1 => Split random strings into 4 characters ,
2 => Split again that 4 characters into 1 characters
3 => doing some action at final step
i need a function or loop to use,
thank you :)
Consider the following.
$(function() {
function stringSplitter(myString, chunkSize) {
var splitString = [];
for (var i = 0; i < myString.length; i += chunkSize) {
splitString.push(myString.slice(i, i + chunkSize));
}
return splitString;
}
var randnumber = 'va2b1881ff21GSD1SSSS18aav1v112H1tkdpkmmnnwqkkq118188493';
var get4 = stringSplitter(randnumber, 4);
$("#get").html(get4);
$("#output-1").html(get4.join("<br />"));
var j = 0;
$.each(get4, function(i, el) {
var row = $("<tr>").appendTo("#output-2");
var cells = stringSplitter(el, 1);
$.each(cells, function(c, val) {
$("<td>", {
id: "str-" + (++j)
}).html(val).appendTo(row);
});
});
var sum = parseInt($("#str-5").text()) + parseInt($("#str-6").text()) + parseInt($("#str-7").text()) / 100;
console.log(sum);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="../!Needs/jquery.min.js"></script>
</head>
<body>
<hr>
<span id="get">
...........
</span>
<hr>
<div id="output-1"></div>
<table id="output-2" border="1">
</table>
</body>
</html>
This uses nested ForEach loops to iterate each of the arrays.

ng-repeat is not working to refresh the display results based on filter search

// My ng-repeat is displaying the results fine, but after typing in the search box it is not filtering results as per search. please help. Also, I'm getting data from http.get
// I'm new to angular js can you help how to repeat search results and display nothing when you don't enter anything in search box.
// I used ng-show this is helping initially to stop showing
topic and response are my column headers in sheet
<html>
<head>
<script src="menu.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
// Json file : https://spreadsheets.google.com/feeds/worksheets/1OJX_UfZ7KQ-NMKcpXmYT8Ml1OfzerPnmUyEcSoMqeZc/public/basic?alt=json
// code for displaying the data in tabs
// Json file : https://spreadsheets.google.com/feeds/worksheets/1OJX_UfZ7KQ-NMKcpXmYT8Ml1OfzerPnmUyEcSoMqeZc/public/basic?alt=json
function openPage(pageName,elmnt) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablink");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].style.color = "#969595";
}
document.getElementById(pageName).style.display = "block";
elmnt.style.color = "white";
}
//angular js code to display search in JSON URL data
var app= angular.module('sample', []);
app.controller('sampleController', function ($scope, $http) {
var url = "https://spreadsheets.google.com/feeds/list/153Obe1TdWlIPyveZoNxEw53rdrghHsiWU9l-WgGwCrE/od6/public/values?alt=json";
//var url1 = "https://spreadsheets.google.com/feeds/list/1OJX_UfZ7KQ-NMKcpXmYT8Ml1OfzerPnmUyEcSoMqeZc/2/public/values?alt=json";
//var url2 = "https://spreadsheets.google.com/feeds/list/1OJX_UfZ7KQ-NMKcpXmYT8Ml1OfzerPnmUyEcSoMqeZc/3/public/values?alt=json";
//var url = "https://spreadsheets.google.com/feeds/cells/1OJX_UfZ7KQ-NMKcpXmYT8Ml1OfzerPnmUyEcSoMqeZc/1/public/values?alt=json"; // url for sheet1
$http.get(url)
.success(function(data, status, headers, config) {
$scope.users = data.feed.entry;
//console.log($scope.users);
})
.error(function(error, status, headers, config) {
console.log(status);
console.log("Error occured");
});
app.filter('highlightFilter', $sce =>
function (element, searchInput) {
element = element.replace(new RegExp(`(${searchInput})`, 'gi'),
'<span class="highlighted">$&</span>');
return $sce.trustAsHtml(element);
});
$scope.search='';
$scope.searchFilter=function(item){
if(item.gsx$topic.$t.toLowerCase().indexOf($scope.search.toLowerCase()) != -1 || item.gsx$response.$t.toLowerCase().indexOf($scope.search.toLowerCase()) != -1){
// code to highlight
// code to highlight
return true;
}
return false;
}
});
</script>
</head>
<body>
<div ng-app="sample" ng-controller="sampleController">
<input type="text" name="search" ng-model="search" placeholder="Search in all sheets" ></input>
<br>
<br>
<table style="border: 1px solid black ;" >
<tbody>
<tr>
<td style="color:blue; font-size:14px;"><center><b>Question</b></center></td>
<td style="color:blue; font-size:14px;"><center><b>Response</b></center></td>
</tr>
<tr ng-repeat="user in users | filter:searchFilter">
<td style="border: 1px solid black ; width:30%;white-space: pre-wrap;">{{user.gsx$topic.$t}}</td>
<td style="border: 1px solid black ; width:70%;white-space: pre-wrap;">{{user.gsx$response.$t}}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
If you want to add search filter on single field from $scope.users then you have to specify that property from json
<div ng-repeat="user in users | filter: { gsx$topic.$t : search}">
also please update input for modal as :
<input type="text" name="search" ng-model="search" placeholder="search" ng-click="didSelectLanguage()"></input>

DOM element in mdTab not updated with angular .css method

I got progress bar in my table where I show how many hours has been spend on project. When I click on some tab, currentTab method is fired to get data from database and after I promise is resolved my TimeService function is fired also to calculate hours for progress bar, but when angular .css() is reached it doesnt update my progress bar at all and I dont know why since it definitely 100% working code. Is there some feature in mdTabs which prevent this?
HTML code below, I deleted plenty of stuff to make it more readable
<md-tab label="Testing" md-on-select="currentTab('Testing')">
<md-content class="md-padding">
<table id="projects-active" class="table table-bordered table-striped">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Estimated time</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="project in projects.Data">
<td>{{project.Id}}</td>
<td>{{project.Name}}</td>
<td>{{project.Description}}</td>
<td>
<div class="progress-group">
<div class="progress sm">
<div class="progress-bar progress-bar-success progress-bar-striped" id="progress-bar-pId-{{project.Id}}">
</div>
</div>
</div>
</td>
<td>{{project.Status}}</td>
</tr>
</tbody>
</table>
<div class="box-footer clearfix">
<ul uib-pagination total-items="totalItems"
items-per-page="maxSize"
ng-model="currentPage"
max-size="maxSize"
class="pagination-sm"
boundary-links="true"
num-pages="pages"
ng-change="setPage(currentPage, 'Testing')"></ul>
</div>
</md-content>
Here is my function in TimeService modul
projectProgressArray: function (array) {
var items = array.Data;
angular.forEach(items, function (key, value) {
var id = key.Id;
var maxTime = key.EstimatedTime;
var currentTime = key.TimeSpend;
var percentageComplete = Math.round(((currentTime / maxTime) * PERCENTAGE) * 100) / 100;
if (!("#progress-bar-pId-" + id).length == 0) {
angular.element("#progress-bar-pId-" + id).css("width", percentageComplete + "%");
if (percentageComplete > 100) {
angular.element("#progress-bar-pId-" + id).removeClass("progress-bar-success");
angular.element("#progress-bar-pId-" + id).addClass("progress-bar-danger");
}
}
});
Here is my controller function where I fetch data to my data tables
var MAX_SIZE_PER_PAGE = 5;
$scope.currentPage = 1;
$scope.maxSize = MAX_SIZE_PER_PAGE;
$scope.pages = 0;
$scope.totalItems = 0;
$scope.setPage = function (pageNo, status) {
$scope.currentPage = pageNo;
$scope.projects = ProjectService.queryPaged({ pageSize: $scope.maxSize, pageNumber: $scope.currentPage, status: status });
$scope.projects.$promise.then(function (data) {
setTimeout(function () {
TimeService.projectProgressArray($scope.projects);
}, 0);
});
};
$scope.currentTab = function (status) {
$scope.projects = ProjectService.queryPaged({ pageSize: $scope.maxSize, pageNumber: 1, status: status });
$scope.projects.$promise.then(function (data) {
$scope.totalItems = data.TotalCount;
setTimeout(function () {
TimeService.projectProgressArray($scope.projects);
}, 0);
});
}
UPDATE: I added image where i copy progress bars outside of md-tab to show its working outside of md-tabs but not inside it.
So after some time of I found out that I cannot manipulate with css in Angular material using angular.element().css() method. I had to edit my TimeService method where I created array filled with information about single progress bar per project and send it back to my controller and use ng-style
to manipulate with my css inside ng-material DOM
projectProgressArray: function (array) {
var progressBarArray = [];
var items = array.Data;
angular.forEach(items, function (key, value) {
var progressBar = {
projectId: null,
percentage: null,
barClass: null
}
var id = key.Id;
var maxTime = key.EstimatedTime;
var currentTime = key.TimeSpend;
var percentageComplete = Math.round(((currentTime / maxTime) * PERCENTAGE) * 100) / 100;
progressBar.projectId = id;
progressBar.percentage = percentageComplete + "%";
if (!("#progress-bar-pId-" + id).length == 0) {
if (percentageComplete > 100) {
progressBar.barClass = "progress-bar-danger";
}
else {
progressBar.barClass = "progress-bar-success";
}
}
progressBarArray.push(progressBar);
});
return progressBarArray;
Reworked HTML binding
<div class="progress-group">
<div class="progress sm" ng-repeat="item in progressBars" ng-hide="item.projectId != project.Id">
<div ng-class="{'progress-bar-success': item.barClass == 'progress-bar-success',
'progress-bar-danger': item.barClass == 'progress-bar-danger' }"
class="progress-bar progress-bar-success progress-bar-striped" id="progress-bar-pId-{{project.Id}}"
ng-style="{'width':item.percentage}">
</div>
</div>

I need with parsing an array from google script to HTML

I have a date picker that I'd like to use to choose an event and then show details from a spread sheet.
HTML:
<html lang="en">
<head>
<meta charset="utf-8">
<title>jQuery UI Datepicker - Default functionality</title>
<link rel="stylesheet" href="//code.jquery.com/ui/1.11.0/themes/smoothness/jquery-ui.css">
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<script src="//code.jquery.com/ui/1.11.0/jquery-ui.js"></script>
<link rel="stylesheet" href="/resources/demos/style.css">
<script>
$(function() {
$( "#datepicker" ).datepicker({
onSelect: function(date) {
var stuff= updDate(date);
},
selectWeek: true,
inline: true,
startDate: '01/01/2000',
firstDay: 1,
});
});
</script>
<script>
function updDate(date){
google.script.run.updDate(date);
}
</script>
</head>
<body>
<p>Date: <input type="text" id="datepicker" onchange="updDate()"></p>
Hello, world!
<input type="button" value="Close"
onclick="google.script.host.close()" />
</body>
</html>
Google Script:
function updDate(date){
var searchString = date;
var data = SpreadsheetApp.getActiveSheet().getDataRange().getValues();
var s2 = SpreadsheetApp.openById("*************");
var row = new Array();
var k;
for (var i in data) {
//Logger.log("length is: "+data[i].length)
//var p = data[i].length
for (var j in data[i]) {
//Logger.log("We are at i: "+i) //Row
//Logger.log("We are at j: "+j) //Col
if (i !=0){
if(data[i][j] != ""){
if(j == 4){
//Logger.log("date from picker: " + date);
//Logger.log("date from Data: " + data[i][j]);
var ssDate = Utilities.formatDate(data[i][j], "GMT", "MM/dd/yyyy");
//Logger.log("date post Convert: " +ssDate);
if(date == ssDate){
k= i
var p = data[i].length
Logger.log("P is: " +p);
}
}
}
}
}
}
Logger.log("K is: "+k)
var q = 1
while (q <= p){
row[q] = data[k][q];
q++
}
Logger.log("Row: " +row);
return row;
}
Eventually I'd like to get the data read into a table but I've been hitting a wall when it comes to successfully getting the data read into a variable in the HTML.
Right now I get this error:
Uncaught ScriptError: The script completed but the returned value is not a supported return type.
Any help in returning the array "row"(in the google script) to the variable "stuff"(in the HTML) successfully or any pointers about how to better execute this task would be greatly appreciated.
Loren
Edit code:
function updDate(date){
var stuff = google.script.run.withSuccessHandler(myReturnFunction).updDate(date);
Console.log(stuff)
}
function myReturnFunction(){
window.myReturnFunction = function(whatGotReturned) {console.log(whatGotReturned);};
}
Sandy Good had it right in the comments above:
function updDate(date){
var junk = google.script.run.withSuccessHandler(myReturnFunction).updDate(date);
}
function myReturnFunction(whatGotReturned){
console.log(whatGotReturned);
}

How to save a Google maps overlay shape in the database?

I want to save a Google maps overlay shape in the database. This is my code. It works perfectly but I just need to save all_shapes array in the database.
<html>
<head>
<style type="text/css">
#map, html, body
{
padding: 0;
margin: 0;
height: 100%;
}
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true&libraries=drawing,geometry"></script>
<script>
var coordinates = [];
var all_shapes = [];
var selectedShape;
</script>
<script>
function draw_shape()
{
for(var i = 0; i < all_shapes.length; i++)
{
all_shapes[i].setMap(null);
}
for(var i = 0; i < all_shapes.length; i++)
{
all_shapes[i].setMap(map);
}
}
</script>
<script>
function clearSelection()
{
if(selectedShape)
{
selectedShape.setEditable(false);
selectedShape = null;
}
}
function setSelection(shape)
{
clearSelection();
selectedShape = shape;
shape.setEditable(true);
}
function deleteSelectedShape()
{
if (selectedShape)
{
selectedShape.setMap(null);
}
}
</script>
<script>
function save_coordinates_to_array(newShapeArg)
{
if(newShapeArg.type == google.maps.drawing.OverlayType.POLYGON)
{
var polygonBounds = newShapeArg.getPath();
for(var i = 0 ; i < polygonBounds.length ; i++)
{
coordinates.push(polygonBounds.getAt(i).lat(), polygonBounds.getAt(i).lng());
}
}
else
{
//alert("Not polygon");/////////////
}
}
</script>
<script>
var map;
function initialize()
{
map = new google.maps.Map(document.getElementById('map'), {zoom: 12, center: new google.maps.LatLng(32.344, 51.048)});
var drawingManager = new google.maps.drawing.DrawingManager();
drawingManager.setMap(map);
google.maps.event.addListener(drawingManager, 'overlaycomplete', function(e) {
var newShape = e.overlay;
newShape.type = e.type;
all_shapes.push(newShape);
setSelection(newShape);
save_coordinates_to_array(newShape);
google.maps.event.addListener(newShape, 'click', function() {setSelection(newShape)});
});
google.maps.event.addListener(map, 'click', function(e) {clearSelection();});
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<table border="1">
<tr>
<td>Name</td>
<td><input name="name" id="name" type="text"></td>
</tr>
<tr>
<td>Color</td>
<td>
<table border="1" width="100%">
<tr>
<td bgcolor="#FF0000"> </td>
<td bgcolor="#00FF00"> </td>
<td bgcolor="#0000FF"> </td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan="2"><input name="save" type="button" value="Save" onClick="draw_shape()"></td>
</tr>
<tr>
<td colspan="2"><input name="delete" type="button" value="Delete" onClick="deleteSelectedShape()"></td>
</tr>
</table>
<div id="map"></div>
</body>
</html>
Where and how can I save the created overlay shapes in the database. All shapes are saved in the var all_shapes = []; array. What kind of type I have to choose for the field in database? I mean for example int, char, etc.
I'm going to use MySQL and PHP.
When you simply want to store the shapes somehow, you may use a JSON-string, store it in e.g. a Text-column(char would be to small to store detailed polygons/polylines )
Note: when you create the JSON-string, you must convert the properties(e.g. to native arrays or objects), you cannot store for example LatLng's directly, because the prototype will be lost when saving it. Pathes of polylines/polygons may be stored encoded
Another approach:
use multiple columns, e.g.
a column(varchar) where you store the type(LatLng, Circle,Polyline,etc.)
a column(geometry) where you store the geometric features(LatLng,Polygon or Polyline)
a column(int) where you store a radius(used when you insert a circle)
optionally column(text) where you store the style-options(when needed)
The first suggestion would be sufficient when you simply want to store it.
When you must be able to select particular shapes, e.g for a given area, use the 2nd suggestion.
See http://dev.mysql.com/doc/refman/5.0/en/spatial-extensions.html for details of the spatial extensions
2 functions that either remove the circular references and create storable objects, or restore the overlays from these stored objects.
var IO={
//returns array with storable google.maps.Overlay-definitions
IN:function(arr,//array with google.maps.Overlays
encoded//boolean indicating if pathes should be stored encoded
){
var shapes = [],
goo=google.maps,
shape,tmp;
for(var i = 0; i < arr.length; i++)
{
shape=arr[i];
tmp={type:this.t_(shape.type),id:shape.id||null};
switch(tmp.type){
case 'CIRCLE':
tmp.radius=shape.getRadius();
tmp.geometry=this.p_(shape.getCenter());
break;
case 'MARKER':
tmp.geometry=this.p_(shape.getPosition());
break;
case 'RECTANGLE':
tmp.geometry=this.b_(shape.getBounds());
break;
case 'POLYLINE':
tmp.geometry=this.l_(shape.getPath(),encoded);
break;
case 'POLYGON':
tmp.geometry=this.m_(shape.getPaths(),encoded);
break;
}
shapes.push(tmp);
}
return shapes;
},
//returns array with google.maps.Overlays
OUT:function(arr,//array containg the stored shape-definitions
map//map where to draw the shapes
){
var shapes = [],
goo=google.maps,
map=map||null,
shape,tmp;
for(var i = 0; i < arr.length; i++)
{
shape=arr[i];
switch(shape.type){
case 'CIRCLE':
tmp=new goo.Circle({radius:Number(shape.radius),
center:this.pp_.apply(this,shape.geometry)});
break;
case 'MARKER':
tmp=new goo.Marker({position:this.pp_.apply(this,shape.geometry)});
break;
case 'RECTANGLE':
tmp=new goo.Rectangle({bounds:this.bb_.apply(this,shape.geometry)});
break;
case 'POLYLINE':
tmp=new goo.Polyline({path:this.ll_(shape.geometry)});
break;
case 'POLYGON':
tmp=new goo.Polygon({paths:this.mm_(shape.geometry)});
break;
}
tmp.setValues({map:map,id:shape.id})
shapes.push(tmp);
}
return shapes;
},
l_:function(path,e){
path=(path.getArray)?path.getArray():path;
if(e){
return google.maps.geometry.encoding.encodePath(path);
}else{
var r=[];
for(var i=0;i<path.length;++i){
r.push(this.p_(path[i]));
}
return r;
}
},
ll_:function(path){
if(typeof path==='string'){
return google.maps.geometry.encoding.decodePath(path);
}
else{
var r=[];
for(var i=0;i<path.length;++i){
r.push(this.pp_.apply(this,path[i]));
}
return r;
}
},
m_:function(paths,e){
var r=[];
paths=(paths.getArray)?paths.getArray():paths;
for(var i=0;i<paths.length;++i){
r.push(this.l_(paths[i],e));
}
return r;
},
mm_:function(paths){
var r=[];
for(var i=0;i<paths.length;++i){
r.push(this.ll_.call(this,paths[i]));
}
return r;
},
p_:function(latLng){
return([latLng.lat(),latLng.lng()]);
},
pp_:function(lat,lng){
return new google.maps.LatLng(lat,lng);
},
b_:function(bounds){
return([this.p_(bounds.getSouthWest()),
this.p_(bounds.getNorthEast())]);
},
bb_:function(sw,ne){
return new google.maps.LatLngBounds(this.pp_.apply(this,sw),
this.pp_.apply(this,ne));
},
t_:function(s){
var t=['CIRCLE','MARKER','RECTANGLE','POLYLINE','POLYGON'];
for(var i=0;i<t.length;++i){
if(s===google.maps.drawing.OverlayType[t[i]]){
return t[i];
}
}
}
}
The array returned by IO.IN may be sended to a serverside script. The serverside script should iterate over this array and INSERT a JSON-string into the table:
<?php
$mysqli = new mysqli(/*args*/);
$stmt = $mysqli->prepare('INSERT INTO `tableName`(`columnName`) VALUES (?)');
$stmt->bind_param('s', $json);
foreach($_POST['shapes'] as $value){
$json = json_encode($value);
$stmt->execute();
}
?>
to restore the shapes fetch them:
<?php
$json=array();
$res=$mysqli->query('SELECT `columnName` from `tableName`');
while ($row = $res->fetch_assoc()) {
$json[]=json_decode($row['columnName']);
}
$res->close();
$json=json_encode($json);
?>
and pass the result to IO.OUT():
IO.OUT(<?php echo $json;?>, someGoogleMapsInstance);
Demo: http://jsfiddle.net/doktormolle/EdZk4/show/
Simple GeoJson Editor is an example of drawing, editing, dropping and saving shapes as geoJson on google maps. The author ( a Google intern) described the project in this post.
The Javascript and HTML are not minified.
An even better opensource tool can be found at Geojson.io
Strange behavior I found in the code http://jsfiddle.net/doktormolle/EdZk4/show/
I added next code to IN function:
if (tmp.type != 'MARKER') {
tmp.strokeColor = shape.strokeColor;
tmp.strokeWeight = shape.strokeWeight;
tmp.fillColor = shape.fillColor;
tmp.fillOpacity = shape.fillOpacity;
tmp.editable = shape.getEditable();
if (tmp.type == 'POLYLINE' || tmp.type == 'POLYGON')
tmp.infoWindowContent = shape.infoWindow.content;
}
All shapes are editable, but only the last shows editable as true. For example, I add one editable polyline and it is editable in the result.
"[{"type":"POLYLINE","id":null,"draggable":false,"geometry":["gn_sFwt`eEvmd#ig|B"],
"strokeColor":"red","strokeWeight":3,"fillOpacity":0.35,"editable":true,
"infoWindowContent":"Polyline Length: 58.80 kms"}]"
I add second editable polyline, but in the result the first is uneditable and second editable.
"[{"type":"POLYLINE","id":null,"draggable":false,"geometry":["gn_sFwt`eEvmd#ig|B"],
"strokeColor":"red","strokeWeight":3,"fillOpacity":0.35,"editable":false,
"infoWindowContent":"Polyline Length: 58.80 kms"},
{"type":"POLYLINE","id":null,"draggable":false,"geometry":["qoiqFgvheEcsw#ygz#"],
"strokeColor":"red","strokeWeight":3,"fillOpacity":0.35,"editable":true,
"infoWindowContent":"Polyline Length: 41.41 kms"}]"
If you need to store the path just to restore it later on a map, you can also use Google Maps Encoding Utility. It is not as powerful as Dr.Molle's answer but can be useful for storing polygons and polylines.