Google Apps Script 事始め Webアプリ編

2016年12月08日
区分
advent-calender
報告者:
kadota

この記事は アクトインディ Advent Calendar 2016 8日目になります。 どうぞよろしくお願いします。

業務でかじっていたGoogle Apps Script (GAS) について軽くまとめました。意外と色々なことができるんですね。
今まで知らなかったWebアプリ化の部分に焦点をあててご紹介します。

概要

G Suite (旧名 Google Apps)を拡張するスクリプトです。Google Sheets, Docs, Forms等のサービスのカスタマイズや自動化、アドオン作成に利用されます。
https://developers.google.com/apps-script/

スクリプトエディタ

Google Apps Script (GAS)用のオンラインエディタです。
https://script.google.com/

Googleドライブからも作成できます。新規>その他>アプリを追加、から検索してGoogle Apps Scriptを追加。
スクリプトエディタはコードを書く環境であり、「プロジェクト」としてファイルをまとめたり各種オプションを設定する場所でもあります。
スクリプトは.gs拡張子でプロジェクトの中で管理されます。

スクリプトの種類

スタンドアローン

Googleドライブに置かれる独立したスクリプトです。後述するバインドスクリプトで無いものです。
https://developers.google.com/apps-script/guides/standalone

以下、最低限動くサンプルです。

コード.gs

function myFunction() {
  Logger.log('test');
}

Google Appsバインド

Sheets, Docs, Forms等のファイルにバインドされたスクリプトです。
https://developers.google.com/apps-script/guides/bound

データファイルのメニューから、ツール>スクリプトエディタを選ぶと出てきます。
getActiveSpreadsheet(), getActiveDocument(), getActiveForm()といった独自の関数が使えてファイルIDをいちいち調べる必要がない、シートにカスタム関数を定義できるなどの特徴があります。

またシンプルトリガーという単純なイベント(onOpen(), onEdit()など)を利用できます。
https://developers.google.com/apps-script/guides/triggers/

コード.gs (Google Sheetsに紐付いている場合)

function getCells() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  
  for (var i = 0; i < data.length; i++) {
    Logger.log('A列: ' + data[i][0]);
    Logger.log('B列: ' + data[i][1]);
  }
}

Webアプリ

スタンドアローンスクリプトも、バインドスクリプトも、Webアプリとして公開できます。
https://developers.google.com/apps-script/guides/web

公開するにあたって以下の条件があります。

  • doGet()doPost() 関数が定義されていること。
  • その関数が、 HTML service HtmlOutput オブジェクト または Content service TextOutput オブジェクト を返すこと。

Webアプリ公開時に、バージョン番号、スクリプトの実行時ユーザー(スクリプト作成者 or スクリプト実行者)、スクリプトを実行可能なユーザーを指定できます。
スクリプトエディタで「ウェブ アプリケーションとして導入…」から指定します。

コード.gs

function doGet(e) {
  var params = JSON.stringify(e); //入力を受けとり、
  return HtmlService.createHtmlOutput(params); //JSONで出力
}

GASのユーザーインターフェイス

バインドされたスクリプトにはカスタムメニュー、ダイアログ、サイドバーといったUI要素が使用可能です。 スタンドアローンスクリプトでも、画面をHTMLで作り込むことができます。
※どちらのスクリプトでも、用途によっては画面を作らずにスクリプトエディタを実行時インターフェイスとする、またはトリガーを設定する、でも十分な場合も。

UI用にHTMLを扱う場合は、HTML Serviceを利用します。※以前はUI生成用にUi Serviceというものもありましたが現在は廃止されています。
https://developers.google.com/apps-script/reference/html/

実行時モード

以前は、実行時のモード(サンドボックスモード)にNATIVE, EMULATEがありましたが、現在はIFRAMEモード一択です(速度改善、外部JS利用などの柔軟性)。

setSandboxMode(HtmlService.SandboxMode.IFRAME);

IFRAMEの条件:

  • リンクのtarget属性は _top_blank
  • 外部リソースの読み込みは http ではなく https で行う
  • IE9など古いブラウザは対象外。※HTML5のsandbox属性を利用するため

内部リソースの読み込み

スクリプトエディタのプロジェクトにHTMLファイルのみ追加可能です。
テンプレートなどで利用します。

スクリプトでテンプレート用HTMLを読み込む例

  t = HtmlService.createTemplateFromFile('index');

ではJSやCSSはどうしたらいいでしょうか?
この場合はそれぞれのJSやCSSをHTMLのパーツとして用意して、テンプレートにScriptletsというGAS用のテンプレート言語で読み込む形になります(ちょっと強引)。
HTMLの一部なので、中にはstyleタグやscriptタグを入れておく必要があります。

HTMLは スクリプトエディタの ファイル>新規作成>HTML ファイル から登録します。

読み込み用関数の定義

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}

HTMLにパーツを読み込む

<?!= include('index.css'); ?>
<?!= include('index.js'); ?>

外部リソースの読み込み

テンプレートHTMLにふつうにタグを記述するだけです。要HTTPS。

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

Webアプリのテンプレまとめ

以上をまとめてみるとこのようになります。

コード.gs

function doGet(request) {
  t = HtmlService.createTemplateFromFile('index'); //index.htmlをテンプレートに使う
  t.title = 'タイトルを設定します';
  Logger.log( t.getCode() ); //テンプレートの実際の出力コードを確認できる
  return t.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename)
      .getContent();
}

index.html

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <title><?= title ?></title>
    <?!= include('index.css'); ?>
  </head>
  <body>
  <h1><?= title ?></h1>
  
  <p id="content">Content ... </p>  
  
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <?!= include('index.js'); ?>
  </body>
</html>

index.css.html

<style>
body {
  background-color: #f0f0f0;
}
</style>

index.js.html

<script>
$(document).ready(function () {
  $('#content').text( 'Content ... Loaded.' );
});
</script>

文書をデータソースにしてWebアプリを作る

上記の雛形を、文書(例ではスプレッドシート)のバインドスクリプトとして適用する場合はこんなかんじになります。

  1. メニューから、ツール>スクリプトエディタを選ぶ
  2. プロジェクトにHTMLを登録する
  3. コード.gs を下記の例を参考に修正する
  4. テンプレートのどこか (index.js.html等) で data を受け取って処理する
  5. スクリプトエディタで「ウェブ アプリケーションとして導入…」を指定する

コード.gs

function doGet(e) {
  t = HtmlService.createTemplateFromFile('index');
  t.title = 'スプレッドシートをWebアプリ化';
  t.data = JSON.stringify( getCells() );
  return t.evaluate().setSandboxMode(HtmlService.SandboxMode.IFRAME);
}

function getCells() {
  //var sheet = SpreadsheetApp.getActiveSheet();
  var id = 'XXXXXXXXXXXXXXXXXXXX'; //Webアプリ化するとIDを自動で取れなかったので調べて記述しておきます
  var sheet = SpreadsheetApp.openById(id);
  var data = sheet.getDataRange().getValues();
  
  return getJSON(data);  
}

//このへん適当です…
function getJSON(data) {
  var json = [];
  
  for (var i = 0; i < data.length; i++) {
    json.push({"a": data[i][0], "b": data[i][1]});
  }
  
  return json;
}

データを受け取る例

  var foo = { data: JSON.parse(<?= data ?>) };
  // このあと受け取ったデータを処理する

後半ざっくりですが、以上です。

vim on tmux

2016年12月06日
区分
advent-calender
報告者:
endo

この記事はアクトインディ Advent Calendar 2016 6日目になります。 どうぞよろしくお願いします。

今回は私の開発環境の画面の使い方について説明します。

細かい開発環境の設定は割愛します。

最近は1画面でどのように操作するかを考え、画面の使い方を変更しました。

変更結果の紹介になります。

開発環境

前提条件の開発環境です。

  • editor
    • vim
  • terminal
    • iterm2
    • tmux

画面の使い方

開発環境の画面の使い方

上部画面をeditorにして、下部をターミナル作業にしています。

以前はeditorがメインの画面だったのですが、ターミナルのコンソールで値の確認や実行結果を見たいときがあります。

そんな時に下に画面を置いておけば、すぐに移動できます。

感想

作業環境を徐々に自分の使いやすい風に構築していくことが重要だ。

今後もより良い開発環境を構築できるようにしていきます。

最強のインフラ用スクリプト言語を探す旅にでます(其の一)

2016年12月05日
区分
advent-calender
報告者:
chiba

こんにちは!、chibaです!
この記事はアクトインディ Advent Calendar 2016 5日目です。

インフラまわりでシェルスクリプトを書いたりする日々ですが、このままシェルスクリプトを書いていて良いのだろうかという思いがつのっています。
もちろんシェルスクリプトは便利ですし、Unix作業の殆どのことはさらっと書けます。

しかし他のプログラミング言語のようにちょっと難しいことをやろうとした途端、ややこしいことになる印象が強いです。

弊社は、現在Rubyが主力言語ですので、Rubyに統一しても良いのかなとは思いますが、最強のスクリプティング言語を探す旅に出ることにしました。 そして、旅に出かけるドアを開けた直後に出会ったのがPerl6です。

昨年、ついに正式リリースとなったPerl6ですが、昨年は正式リリース前に書いていたスクリプトが結構動かなくなったりしていたので、何かを書くにはちょっと時期尚早かなあと思っていたのですが、もう1年経ったので多分大丈夫なことでしょう。

まずはダウンロード

まずは、Perl6自体を導入するところからです。
昨年ちょっといじったところでは、Rakudoの実装と、Pandaというパッケージマネージャーを使うというのが定番のようでした。
今現在でもこの組み合わせが定番のようですので、これを導入します。

導入手順はこちらに記載された通りに行いました。

https://perl6.org/downloads/

非常に親切ですが、 perl Configure.pl --gen-moar --prefix /opt/rakudo-star-2016.11 の箇所はルート権限が必要だったりするのに何度か引っ掛かりました。

とりあえず、ビルドが終了しmake installせよとなるので、実行します。

インストール終了のメッセージの最後には、

/opt/rakudo-star-2016.11/bin
/opt/rakudo-star-2016.11/share/perl6/site/bin

をパスに追加しておいてね、と書いてあるので、これに従います。

今回は、ちょっと奥ゆかしくパスの後ろに追加しておきましょう。

export PATH=$PATH:/opt/rakudo-star-2016.11/bin:/opt/rakudo-star-2016.11/share/perl6/site/bin

さて、perl6コマンドでperl6を起動です。

> say 8 x 4
8888

これで最強のスクリプト言語を書く準備ができました! 〜続く〜

ES2015の新記法

2016年12月03日
区分
advent-calender
報告者:
endo

この記事はアクトインディ Advent Calendar 2016 3日目になります。 どうぞよろしくお願いします。

ES2015の新記法について、まとめました。

ES2015の新記法

変数の宣言

let

  • 変数の重複を許可しない
  • ブロックスコープ({})を認識する
var x = "foo";

var x = "bar";

let y = "foo";

let y = "bar";
if (true) {
  var i = 5;
}

console.log(i);

if (true) {
  let x = 5;
}

console.log(x);

定数(const)

const TAX = 1.08;
let price = 100 * TAX;

データ型

分類 データ型 概要  
基本型 数値型(number)    
基本型 文字列型(string) シングル/ダブルクォートで囲まれた0個以上の文字の集合  
基本型 真偽型(boolean) true(真)/false(偽)  
基本型 シンボル型(symbol) シンボルを表す(ES2015の新記法)  
基本型 特殊型(null/undefined) 値が空、未定義であることを表す  
参照型 配列(array) データの集合(各要素にはインデックス番号でアクセス可能)  
参照型 オブジェクト(object) データの集合(各要素には名前でアクセス可能)  
参照型 関数 一連の処理(手続き)の集合 ### String型の不思議
let x = new String("foo");
let y = "foo";

x == y
=>true

let z = new String("foo");
x == z
=>false

x, zが一致しないのはnew Stringで生成したものは、objectになります。

参照型になっているからこの現象が起きます。

typeof(x);
=>"object"

x,yが比較して一致するのは、文字列リテラルを比較する場合は、一時的にString型にしているからです。

文字列リテラルを持つ値は、String オブジェクトのあらゆるメソッドを呼び出すことができます。JavaScript は自動的に文字列リテラルを一時的な String オブジェクトに変換し、メソッドを呼び出して、その後一時的に作られた String オブジェクトを破棄します。String.length プロパティを文字列リテラルで用いることもできます :

文法とデータ型

シンボル

  • Rubyのシンボルと違い、symbolで生成されたものはユニーク扱いになる
let x = Symbol('sym');
let y = Symbol('sym');

typeof(x);
=>symbol
x.toString();
=>Symbol(x)
x == y
=>false

Mapオブジェクト

  • 連想配列を管理するためのオブジェクト(今まではオブジェクトリテラルで管理)
  • データの検索が早い

伝統的な書き方

var x = { 'apple': 'りんご',
          'orange': 'みかん',
          'strawberry': 'いちご' }
console.time('associative_array');
x.apple
console.timeEnd('associative_array');

map

var x = new Map([['apple', 'りんご'],
                 ['orange', 'みかん'],
                 ['strawberry', 'いちご']]);
x.set('banana', 'バナナ')

console.time('map');
x.get('apple');
console.timeEnd('map');
x.keys();
x.size

オブジェクトリテラルとの違い

  1. 任意の型でキーを設定できる
    • オブジェクト/NaNでもキーになりうる
  2. マップのサイズを取得できる
    • sizeメソッドで要素数を取得できる
  3. クリーンなマップを作成できる
    • オブジェクトリテラルは、Objectそのもの 配下にはObjectが標準で用意しているプロパティが最初から存在している

キーに関わる3つの注意点

  1. キーは「===」演算子で比較される
  2. 特別なNaNは特別ではない
var x = new Map();
x.set(NaN, 'foo');
x.get(NaN);
  1. オブジェクトの比較には注意
var x = new Map();
x.set({}, "foo");
x.get({});

Setオブジェクト

  • 重複しない値の集合を操作する(初めての概念自体)
let i = new Set([1, 5, 100, 10, 5]);
for (var int of i.values()) {
    console.log(int);
}
  • オブジェクトの比較はMapと同じ

書き方

代入

分割代入

  • 端数は切り捨てられる
  • オブジェクトの場合は、変数の並び順は考慮しなくても大丈夫

配列

let [x, y, z] = [1, 2];

連想配列(オブジェクト)

let associative_array = { x: 1,y: 2, z: 3 }
let { x, z, y } = associative_array;

関数

アロー関数

  • 関数リテラルのfunctionを記入しなくて良い
  • 1行で書く場合は、returnを書く必要がない

関数リテラル

let getSquare = function(num) {
    return num * num;
}

アロー関数

let getSquare = (num) => {
    retun num * num;
}

let getSquare = (num) => num * num;

変数展開

テンプレート文字列

let name = "Tom";
let greet = `Hello ${name}`;

引数

引数のデフォルト値

  • 今までは引数にデフォルト値を渡すことはできなかった
function getSquare(num = 2) {
    return num * num;
}

let getSquare = (num = 2) => num * num;

getSquare(5);

名前付き引数

  • {}を使用することで名前付き引数になる
  • 使用する場合に{}で指定する
let getSquare = ({num = 2}) => num * num;

getSquare({num: 5});

let getSquare = ({num}) => num * num;

getSquare({num: 5});

可変長引数の定義

  • ...(スプレッド演算子)で可変長引数になる
function sum(...nums) {
  let result = 0;
  for (let num of nums) {
      result += num;
  }
  return result;
}

「…」演算子による引数の展開

  • 「…」演算子は、実引数で利用することで、配列(正確にはfor..ofブロックで処理できる)をここの値に展開する
Math.max(1, 3, 5, 4);
let x = [1, 3, 5, 4]
Math.max(x);
=> NaN
Math.max(...x);
=> 5

引数の戻り値

複数の戻り値を個別の変数に代入する

function getMaxMin(...nums) {
    return[Math.max(...nums), Math.min(...nums)];
}

nums = [1, 4, 100, 5, 904580259, 40]
let x = getMaxMin(...nums);

let [max, min] = getMaxMin(...nums);

クラス

定義方法

  • constructorで初期値を定義する
class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  getName() {
      return this.firstName + this.lastName;
  }
}

  • 関数として呼び出すことは不可能
  • 定義前のクラスは呼び出せない
let m = Member("foo", "bar");

class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getName() {
    return this.firstName + this.lastName;
  }
}

静的メソッド(特異メソッド)を定義する

  • staticで定義する

class Member { constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  static greet() {
    return "hello"
  }
}

継承する

  • extendsで継承
class Actindi extends Member {
  static service() {
    return "iko-yo";
  }
}

基底クラスのメソッド/コンストラクターを呼び出す(super)

  • superで基底クラスを継承 or 上書き

書き方

// construcor
super(args)

// method
super.method();
class Actindi extends Member {
  constructor(firstName, lastName, post) {
    super(firstName, lastName);
    this.post = post;
  }

  getName() {
    return this.post + super.getName();
  }

}

モジュール

  • exportでアクセスできるようにする

utility.js ```javascript export class Member { … }

export class Actindi { … } ```

  • importで取り込む
import { Member, Actindi } from './utility'

let m = new Member("foo", "bar");

importの記法

  • モジュール全体をまとめてインポート
    • asでモジュールの別名を指定しておく
import * as app from './utility'

let m = new app.Member("foo", "bar");
  • モジュール内の個々の要素に別名を付与する
import { Member as MyMember, Actindi as Act } from './utility'

let m = new MyMember("foo", "bar");

メソッド編

配列

for..of(配列を取り出す)

過去 ```javascript let nums = [1, 2, 3];

for(var i = 0, len = nums.length; i < len; i++){ console.log(nums[i]); } ```

現在 ```javascript let nums = [1, 2, 3];

for (num of nums) { console.log(num); } ```

オブジェクト

assign

  • 同名のプロパティは後のもので上書きされる
let x = { name: "foo",
          email: "foo@foo.com",
          login: "var" }

let y = { name: "bar" }
let z = { title: "baz" }
Object.assign(x, y, z)
x
=>Object {name: "bar", email: "foo@foo.com", login: "var", title: "baz"}

処理

Strictモード

  • 「仕様として存在するが、安全性や効率面で利用すべきでない構文」を通知する
分類 Strictモードによる制限型
変数 var命令の省略禁止
変数 将来的に追加予定のキーワードを予約語に追加
変数 引数/プロパティ名の重複を禁止
変数 undefined/nullへの代入を禁止
命令 with命令の利用禁止
命令 arguments.calleeプロパティへのアクセスを禁止
命令 eval命令で宣言された変数を、周囲のスコープに拡散しない
その他 関数配下のthisはグローバルオブジェクトを参照しない(undefinedとなる)
その他 「0〜」の8進数表記は禁止

bitriseで起こった問題と解決方法

2016年12月01日
区分
advent-calender
報告者:
honda

この記事はアクトインディ Advent Calendar 2016 1日目になります。 どうぞよろしくお願いします。

アクトインディのAndroid開発ではCIにbitriseを使用しています。 今回はbitriseで起こった問題と解決方法をご紹介します。

発生した問題

2016/11/07頃から突然bitirseでビルドエラーが発生しました。 最初は実装ミスか?と思っていたのですが、ログ調査すると以下のようなログが出力されていました。

File /root/.android/repositories.cfg could not be loaded.
Preparing "Install Solver for ConstraintLayout 1.0.0-alpha8".
"Install Solver for ConstraintLayout 1.0.0-alpha8" ready.
Finishing "Install Solver for ConstraintLayout 1.0.0-alpha8"
Installing Solver for ConstraintLayout 1.0.0-alpha8 in /opt/android-sdk-linux/extras/m2repository/com/android/support/constraint/constraint-layout-solver/1.0.0-alpha8
"Install Solver for ConstraintLayout 1.0.0-alpha8" failed.
Preparing "Install ConstraintLayout for Android 1.0.0-alpha8".
"Install ConstraintLayout for Android 1.0.0-alpha8" ready.
Finishing "Install ConstraintLayout for Android 1.0.0-alpha8"
Installing ConstraintLayout for Android 1.0.0-alpha8 in /opt/android-sdk-linux/extras/m2repository/com/android/support/constraint/constraint-layout/1.0.0-alpha8
"Install ConstraintLayout for Android 1.0.0-alpha8" failed.

ビルドしていたプロジェクトではConstraintLayoutを使用していて、 そのConstraintLayoutのインストールに失敗しているようです。

ConstraintLayoutとは

Android開発で使用する新しく登場したUIレイアウトです。 プロジェクトが依存するライブラリとしてgradleで定義しないといけないものです。 詳しくはこちらを御覧ください。

解決方法

今回の問題を解決した方法が以下になります。

./gradlew dependencies || true ←※このコマンドを追加
./gradlew "--build-file" "build.gradle" "assembleDebug" "--stacktrace"

ビルドコマンドの前に上記の※のコマンドを追加するだけです。

解決方法の解説

ビルドエラーの原因はConstraintLayoutのインストールが失敗したことです。 が、実はConstraintLayoutのインストールは完了していました。 この現象はAndroid StudioからSDK Managerを立ち上げてインストールした場合でも発生します。 以下の画像が発生した時のメッセージです。

91f1b5ac095035d49ff8d19cd2ccc355.png (308.5 kB)

「Finish」を押して再度SDK Managerを立ち上げてインストール状況を確認するとインストールしたことになっています。

cba047f278c1949bd38a989088b4f8a2.png (27.6 kB)

なので

./gradlew dependencies

このコマンドで依存関係にあるライブラリを先にインストールされます。 インストールは完了していますが結果として異常終了となるので

./gradlew dependencies || true

とすることで正常終了となり、CIが止まることなくビルドすることが出来ます。

以上です。

 | 

技師部隊からの
お知らせ

【求人】エンジニア募集しています。

本頁の来客数
八十七万千百七十六名以上(計測停止中)

メンバー一覧

アクトインディ技師部隊員名簿

アクトインディ技師部元隊員

アクトインディへ

カテゴリー

アクトインディ

aaaa