// 本日をYYYYMMDDにする
var dateToday = function () {
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
return year + month + day;
};
2015年5月11日月曜日
2015年4月10日金曜日
kintone@日付と日時を比較する方法
●背景
日付と日時を比較した時に問題は起きた。
まずは下図のように日付と日時を並べてみるのである。

そして、両データをJavaScriptでアラート表示させてみるのである。

なんでじゃー。3/31よ、君はどっからきたー。
というわけなのである。
日付の方は入力された通りの正しい日付が入っているように見える、が、
日時の方は一日前の、とんちんかんな時間を指している、ように見える。
●原因
日時はタイムゾーンの影響を受けるようである。
つまり、日時だけは世界標準時で設定されるわけだ。
日本であれば「+9時間の時差がある」とみなされて、
「4/1の0時」からシステムサイドで-9時間の処理を行い、
「3/31の15時」というアラートが出てしまうわけだ。
はー、使いにくい話である。
●対策
「日時の比較」であれば「日付」を-9時間すれば良い。
「日付フォームの値を送るとその地域の時差に合わせて日時が取得できるコード」
をサンプルコードとして作った。
●サンプルコード
/***************************************************************************************************
* function getDateTime
* @param:date:「event['record']['日付']['value']」等をそのまま引き渡す
***************************************************************************************************/
function getDateTime(date) {
// ログインユーザのタイムゾーン名を取得する。日本なら「Asia/Tokyo」と取得
var timeZone = kintone.getLoginUser().timezone;
// タイムゾーン名から時差を取得
var timeZoneOffset = getTimeZoneOffset(timeZone);
// 年月日を抽出
var dateFromYYYY = event['record']['日付']['value'].substring(0, 4);
var dateFromMM = event['record']['日付']['value'].substring(5, 7);
var dateFromDD = event['record']['日付']['value'].substring(8, 10);
// 月(0~11)調整しながら、時差を加味したDateオブジェクトを生成
var dateFromYMDT = new Date(dateFromYYYY, dateFromMM - 1, dateFromDD, timeZoneOffset, 0);
// Dateオブジェクトから「日時フォーム」形式に整形する
var dateFromYYYY2 = dateFromYMDT.getFullYear();
var dateFromMM2 = ('00' + String(dateFromYMDT.getMonth() + 1)).slice(-2);
var dateFromDD2 = ('00' + dateFromYMDT.getDate()).slice(-2);
var dateFromHH2 = ('00' + dateFromYMDT.getHours()).slice(-2);
var dateFromMI2 = ('00' + dateFromYMDT.getMinutes()).slice(-2);
var dateFromYMDT2 = dateFromYYYY2 + '-' + dateFromMM2 + '-' + dateFromDD2
+ 'T' + dateFromHH2 + ':' + dateFromMI2 + ':' + '00Z';
return dateFromYMDT2;
}
/***************************************************************************************************
* function getTimeZoneOffset
* @param:timeZoneName:タイムゾーン名
***************************************************************************************************/
function getTimeZoneOffset(timeZoneName) {
var ret = -9;
var timeZoneOffsetObj = new Object();
timeZoneOffsetObj['Etc/GMT+12'] = 12;
timeZoneOffsetObj['Etc/GMT+11'] = 11;
timeZoneOffsetObj['Pacific/Honolulu'] = 10;
timeZoneOffsetObj['America/Anchorage'] = 9;
timeZoneOffsetObj['America/Santa_Isabel'] = 8;
timeZoneOffsetObj['America/Los_Angeles'] = 8;
timeZoneOffsetObj['America/Chihuahua'] = 7;
timeZoneOffsetObj['America/Phoenix'] = 7;
timeZoneOffsetObj['America/Denver'] = 7;
timeZoneOffsetObj['America/Guatemala'] = 6;
timeZoneOffsetObj['America/Chicago'] = 6;
timeZoneOffsetObj['America/Regina'] = 6;
timeZoneOffsetObj['America/Mexico_City'] = 6;
timeZoneOffsetObj['America/Bogota'] = 5;
timeZoneOffsetObj['America/Indiana/Indianapolis'] = 5;
timeZoneOffsetObj['America/New_York'] = 5;
timeZoneOffsetObj['America/Caracas'] = 4.5;
timeZoneOffsetObj['America/Halifax'] = 4;
timeZoneOffsetObj['America/Asuncion'] = 4;
timeZoneOffsetObj['America/La_Paz'] = 4;
timeZoneOffsetObj['America/Cuiaba'] = 4;
timeZoneOffsetObj['America/Santiago'] = 4;
timeZoneOffsetObj['America/St_Johns'] = 3.5;
timeZoneOffsetObj['America/Sao_Paulo'] = 3;
timeZoneOffsetObj['America/Godthab'] = 3;
timeZoneOffsetObj['America/Cayenne'] = 3;
timeZoneOffsetObj['America/Argentina/Buenos_Aires'] = 3;
timeZoneOffsetObj['America/Montevideo'] = 3;
timeZoneOffsetObj['Etc/GMT2'] = 2;
timeZoneOffsetObj['Atlantic/Cape_Verde'] = 1;
timeZoneOffsetObj['Atlantic/Azores'] = 1;
timeZoneOffsetObj['Africa/Casablanca'] = -0;
timeZoneOffsetObj['Atlantic/Reykjavik'] = -0;
timeZoneOffsetObj['Europe/London'] = -0;
timeZoneOffsetObj['Etc/GMT'] = -0;
timeZoneOffsetObj['Europe/Berlin'] = -1;
timeZoneOffsetObj['Europe/Paris'] = -1;
timeZoneOffsetObj['Africa/Lagos'] = -1;
timeZoneOffsetObj['Europe/Budapest'] = -1;
timeZoneOffsetObj['Europe/Warsaw'] = -1;
timeZoneOffsetObj['Africa/Windhoek'] = -1;
timeZoneOffsetObj['Europe/Istanbul'] = -2;
timeZoneOffsetObj['Europe/Kiev'] = -2;
timeZoneOffsetObj['Africa/Cairo'] = -2;
timeZoneOffsetObj['Asia/Damascus'] = -2;
timeZoneOffsetObj['Asia/Amman'] = -2;
timeZoneOffsetObj['Africa/Johannesburg'] = -2;
timeZoneOffsetObj['Asia/Jerusalem'] = -2;
timeZoneOffsetObj['Asia/Beirut'] = -2;
timeZoneOffsetObj['Asia/Baghdad'] = -3;
timeZoneOffsetObj['Europe/Minsk'] = -3;
timeZoneOffsetObj['Asia/Riyadh'] = -3;
timeZoneOffsetObj['Africa/Nairobi'] = -3;
timeZoneOffsetObj['Asia/Tehran'] = -3.5;
timeZoneOffsetObj['Europe/Moscow'] = -4;
timeZoneOffsetObj['Asia/Tbilisi'] = -4;
timeZoneOffsetObj['Asia/Yerevan'] = -4;
timeZoneOffsetObj['Asia/Dubai'] = -4;
timeZoneOffsetObj['Asia/Baku'] = -4;
timeZoneOffsetObj['Indian/Mauritius'] = -4;
timeZoneOffsetObj['Asia/Kabul'] = -4.5;
timeZoneOffsetObj['Asia/Tashkent'] = -5;
timeZoneOffsetObj['Asia/Karachi'] = -5;
timeZoneOffsetObj['Asia/Colombo'] = -5.5;
timeZoneOffsetObj['Asia/Kolkata'] = -5.5;
timeZoneOffsetObj['Asia/Kathmandu'] = -5.75;
timeZoneOffsetObj['Asia/Almaty'] = -6;
timeZoneOffsetObj['Asia/Dhaka'] = -6;
timeZoneOffsetObj['Asia/Yekaterinburg'] = -6;
timeZoneOffsetObj['Asia/Rangoon'] = -6.5;
timeZoneOffsetObj['Asia/Bangkok'] = -7;
timeZoneOffsetObj['Asia/Novosibirsk'] = -7;
timeZoneOffsetObj['Asia/Krasnoyarsk'] = -8;
timeZoneOffsetObj['Asia/Ulaanbaatar'] = -8;
timeZoneOffsetObj['Asia/Shanghai'] = -8;
timeZoneOffsetObj['Australia/Perth'] = -8;
timeZoneOffsetObj['Asia/Singapore'] = -8;
timeZoneOffsetObj['Asia/Taipei'] = -8;
timeZoneOffsetObj['Asia/Irkutsk'] = -9;
timeZoneOffsetObj['Asia/Seoul'] = -9;
timeZoneOffsetObj['Asia/Tokyo'] = -9;
timeZoneOffsetObj['Australia/Darwin'] = -9.5;
timeZoneOffsetObj['Australia/Adelaide'] = -9.5;
timeZoneOffsetObj['Australia/Hobart'] = -10;
timeZoneOffsetObj['Asia/Yakutsk'] = -10;
timeZoneOffsetObj['Australia/Brisbane'] = -10;
timeZoneOffsetObj['Pacific/Port_Moresby'] = -10;
timeZoneOffsetObj['Australia/Sydney'] = -10;
timeZoneOffsetObj['Asia/Vladivostok'] = -11;
timeZoneOffsetObj['Pacific/Guadalcanal'] = -11;
timeZoneOffsetObj['Etc/GMT-12'] = -12;
timeZoneOffsetObj['Pacific/Fiji'] = -12;
timeZoneOffsetObj['Asia/Magadan'] = -12;
timeZoneOffsetObj['Pacific/Auckland'] = -12;
timeZoneOffsetObj['Pacific/Tongatapu'] = -13;
timeZoneOffsetObj['Pacific/Apia'] = -13;
for (var key in timeZoneOffsetObj) {
if (key == timeZoneName) {
ret = timeZoneOffsetObj[key];
break;
}
}
return ret;
}
日付と日時を比較した時に問題は起きた。
まずは下図のように日付と日時を並べてみるのである。
そして、両データをJavaScriptでアラート表示させてみるのである。
なんでじゃー。3/31よ、君はどっからきたー。
というわけなのである。
日付の方は入力された通りの正しい日付が入っているように見える、が、
日時の方は一日前の、とんちんかんな時間を指している、ように見える。
●原因
日時はタイムゾーンの影響を受けるようである。
つまり、日時だけは世界標準時で設定されるわけだ。
日本であれば「+9時間の時差がある」とみなされて、
「4/1の0時」からシステムサイドで-9時間の処理を行い、
「3/31の15時」というアラートが出てしまうわけだ。
はー、使いにくい話である。
●対策
「日時の比較」であれば「日付」を-9時間すれば良い。
「日付フォームの値を送るとその地域の時差に合わせて日時が取得できるコード」
をサンプルコードとして作った。
●サンプルコード
/***************************************************************************************************
* function getDateTime
* @param:date:「event['record']['日付']['value']」等をそのまま引き渡す
***************************************************************************************************/
function getDateTime(date) {
// ログインユーザのタイムゾーン名を取得する。日本なら「Asia/Tokyo」と取得
var timeZone = kintone.getLoginUser().timezone;
// タイムゾーン名から時差を取得
var timeZoneOffset = getTimeZoneOffset(timeZone);
// 年月日を抽出
var dateFromYYYY = event['record']['日付']['value'].substring(0, 4);
var dateFromMM = event['record']['日付']['value'].substring(5, 7);
var dateFromDD = event['record']['日付']['value'].substring(8, 10);
// 月(0~11)調整しながら、時差を加味したDateオブジェクトを生成
var dateFromYMDT = new Date(dateFromYYYY, dateFromMM - 1, dateFromDD, timeZoneOffset, 0);
// Dateオブジェクトから「日時フォーム」形式に整形する
var dateFromYYYY2 = dateFromYMDT.getFullYear();
var dateFromMM2 = ('00' + String(dateFromYMDT.getMonth() + 1)).slice(-2);
var dateFromDD2 = ('00' + dateFromYMDT.getDate()).slice(-2);
var dateFromHH2 = ('00' + dateFromYMDT.getHours()).slice(-2);
var dateFromMI2 = ('00' + dateFromYMDT.getMinutes()).slice(-2);
var dateFromYMDT2 = dateFromYYYY2 + '-' + dateFromMM2 + '-' + dateFromDD2
+ 'T' + dateFromHH2 + ':' + dateFromMI2 + ':' + '00Z';
return dateFromYMDT2;
}
/***************************************************************************************************
* function getTimeZoneOffset
* @param:timeZoneName:タイムゾーン名
***************************************************************************************************/
function getTimeZoneOffset(timeZoneName) {
var ret = -9;
var timeZoneOffsetObj = new Object();
timeZoneOffsetObj['Etc/GMT+12'] = 12;
timeZoneOffsetObj['Etc/GMT+11'] = 11;
timeZoneOffsetObj['Pacific/Honolulu'] = 10;
timeZoneOffsetObj['America/Anchorage'] = 9;
timeZoneOffsetObj['America/Santa_Isabel'] = 8;
timeZoneOffsetObj['America/Los_Angeles'] = 8;
timeZoneOffsetObj['America/Chihuahua'] = 7;
timeZoneOffsetObj['America/Phoenix'] = 7;
timeZoneOffsetObj['America/Denver'] = 7;
timeZoneOffsetObj['America/Guatemala'] = 6;
timeZoneOffsetObj['America/Chicago'] = 6;
timeZoneOffsetObj['America/Regina'] = 6;
timeZoneOffsetObj['America/Mexico_City'] = 6;
timeZoneOffsetObj['America/Bogota'] = 5;
timeZoneOffsetObj['America/Indiana/Indianapolis'] = 5;
timeZoneOffsetObj['America/New_York'] = 5;
timeZoneOffsetObj['America/Caracas'] = 4.5;
timeZoneOffsetObj['America/Halifax'] = 4;
timeZoneOffsetObj['America/Asuncion'] = 4;
timeZoneOffsetObj['America/La_Paz'] = 4;
timeZoneOffsetObj['America/Cuiaba'] = 4;
timeZoneOffsetObj['America/Santiago'] = 4;
timeZoneOffsetObj['America/St_Johns'] = 3.5;
timeZoneOffsetObj['America/Sao_Paulo'] = 3;
timeZoneOffsetObj['America/Godthab'] = 3;
timeZoneOffsetObj['America/Cayenne'] = 3;
timeZoneOffsetObj['America/Argentina/Buenos_Aires'] = 3;
timeZoneOffsetObj['America/Montevideo'] = 3;
timeZoneOffsetObj['Etc/GMT2'] = 2;
timeZoneOffsetObj['Atlantic/Cape_Verde'] = 1;
timeZoneOffsetObj['Atlantic/Azores'] = 1;
timeZoneOffsetObj['Africa/Casablanca'] = -0;
timeZoneOffsetObj['Atlantic/Reykjavik'] = -0;
timeZoneOffsetObj['Europe/London'] = -0;
timeZoneOffsetObj['Etc/GMT'] = -0;
timeZoneOffsetObj['Europe/Berlin'] = -1;
timeZoneOffsetObj['Europe/Paris'] = -1;
timeZoneOffsetObj['Africa/Lagos'] = -1;
timeZoneOffsetObj['Europe/Budapest'] = -1;
timeZoneOffsetObj['Europe/Warsaw'] = -1;
timeZoneOffsetObj['Africa/Windhoek'] = -1;
timeZoneOffsetObj['Europe/Istanbul'] = -2;
timeZoneOffsetObj['Europe/Kiev'] = -2;
timeZoneOffsetObj['Africa/Cairo'] = -2;
timeZoneOffsetObj['Asia/Damascus'] = -2;
timeZoneOffsetObj['Asia/Amman'] = -2;
timeZoneOffsetObj['Africa/Johannesburg'] = -2;
timeZoneOffsetObj['Asia/Jerusalem'] = -2;
timeZoneOffsetObj['Asia/Beirut'] = -2;
timeZoneOffsetObj['Asia/Baghdad'] = -3;
timeZoneOffsetObj['Europe/Minsk'] = -3;
timeZoneOffsetObj['Asia/Riyadh'] = -3;
timeZoneOffsetObj['Africa/Nairobi'] = -3;
timeZoneOffsetObj['Asia/Tehran'] = -3.5;
timeZoneOffsetObj['Europe/Moscow'] = -4;
timeZoneOffsetObj['Asia/Tbilisi'] = -4;
timeZoneOffsetObj['Asia/Yerevan'] = -4;
timeZoneOffsetObj['Asia/Dubai'] = -4;
timeZoneOffsetObj['Asia/Baku'] = -4;
timeZoneOffsetObj['Indian/Mauritius'] = -4;
timeZoneOffsetObj['Asia/Kabul'] = -4.5;
timeZoneOffsetObj['Asia/Tashkent'] = -5;
timeZoneOffsetObj['Asia/Karachi'] = -5;
timeZoneOffsetObj['Asia/Colombo'] = -5.5;
timeZoneOffsetObj['Asia/Kolkata'] = -5.5;
timeZoneOffsetObj['Asia/Kathmandu'] = -5.75;
timeZoneOffsetObj['Asia/Almaty'] = -6;
timeZoneOffsetObj['Asia/Dhaka'] = -6;
timeZoneOffsetObj['Asia/Yekaterinburg'] = -6;
timeZoneOffsetObj['Asia/Rangoon'] = -6.5;
timeZoneOffsetObj['Asia/Bangkok'] = -7;
timeZoneOffsetObj['Asia/Novosibirsk'] = -7;
timeZoneOffsetObj['Asia/Krasnoyarsk'] = -8;
timeZoneOffsetObj['Asia/Ulaanbaatar'] = -8;
timeZoneOffsetObj['Asia/Shanghai'] = -8;
timeZoneOffsetObj['Australia/Perth'] = -8;
timeZoneOffsetObj['Asia/Singapore'] = -8;
timeZoneOffsetObj['Asia/Taipei'] = -8;
timeZoneOffsetObj['Asia/Irkutsk'] = -9;
timeZoneOffsetObj['Asia/Seoul'] = -9;
timeZoneOffsetObj['Asia/Tokyo'] = -9;
timeZoneOffsetObj['Australia/Darwin'] = -9.5;
timeZoneOffsetObj['Australia/Adelaide'] = -9.5;
timeZoneOffsetObj['Australia/Hobart'] = -10;
timeZoneOffsetObj['Asia/Yakutsk'] = -10;
timeZoneOffsetObj['Australia/Brisbane'] = -10;
timeZoneOffsetObj['Pacific/Port_Moresby'] = -10;
timeZoneOffsetObj['Australia/Sydney'] = -10;
timeZoneOffsetObj['Asia/Vladivostok'] = -11;
timeZoneOffsetObj['Pacific/Guadalcanal'] = -11;
timeZoneOffsetObj['Etc/GMT-12'] = -12;
timeZoneOffsetObj['Pacific/Fiji'] = -12;
timeZoneOffsetObj['Asia/Magadan'] = -12;
timeZoneOffsetObj['Pacific/Auckland'] = -12;
timeZoneOffsetObj['Pacific/Tongatapu'] = -13;
timeZoneOffsetObj['Pacific/Apia'] = -13;
for (var key in timeZoneOffsetObj) {
if (key == timeZoneName) {
ret = timeZoneOffsetObj[key];
break;
}
}
return ret;
}
2015年4月8日水曜日
kintone@REST API@レコードを登録(INSERT)する
●背景
「つべこべ言わずに、動くソースをくれよ」
パート2である。
・指定したアプリに1レコード登録する。
・データは当該アプリで画面入力されている名前と年齢
(自アプリにも、指定先アプリにも名前、年齢のテキストボックスが必要)
・登録できたらレコード番号を、できなかったら-1を返却する
●サンプルコード
・SELECTでやった解説は除外します。
・param
appとrecordで構成されるよ。詳しくは「レコードの登録(POST)」
・xmlHttp.open('POST', appUrl, false);
GETじゃなくてPOSTになる。
・xmlHttp.send(JSON.stringify(param));
今回は取得ではなく登録なので、
nullではなくて、JSON形式のデータを送る。
・var respdata = JSON.parse(xmlHttp.responseText);
登録結果のレスポンス内容を受け取る。
・lastRID = respdata["id"];
レスポンスの中からレコード番号を取得。
・今回は1レコードなので'/k/v1/record'を指定した。
・複数レコードの場合は以下の内容が変わる
→'/k/v1/record'が'/k/v1/records'になる
→paramの"record"が"records"になる
→paramの"records"の右辺(:の右側)が配列になる
「つべこべ言わずに、動くソースをくれよ」
パート2である。
・指定したアプリに1レコード登録する。
・データは当該アプリで画面入力されている名前と年齢
(自アプリにも、指定先アプリにも名前、年齢のテキストボックスが必要)
・登録できたらレコード番号を、できなかったら-1を返却する
●サンプルコード
/***************************************************************************************************
* function insertRec
* 登録できた場合はレコード番号を返却する。
* 登録できなかった場合は-1を返却する。
***************************************************************************************************/
function insertRec(event, appID) {
// 返却値
var lastRID = -1;
var param =
{
"app": appID,
"record":
{
"名前": { "value": event['record']['名前']['value'] },
"年齢": { "value": event['record']['年齢']['value'] },
}
}
// CSRFトークンの取得
var token = kintone.getRequestToken();
param["__REQUEST_TOKEN__"] = token;
// 同期リクエストを行う
var appUrl = kintone.api.url('/k/v1/record');
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('POST', appUrl, false);
xmlHttp.setRequestHeader('Content-Type', 'application/json');
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(JSON.stringify(param));
if (xmlHttp.status == 200) {
var respdata = JSON.parse(xmlHttp.responseText);
lastRID = respdata["id"];
}
return lastRID;
}
●解説* function insertRec
* 登録できた場合はレコード番号を返却する。
* 登録できなかった場合は-1を返却する。
***************************************************************************************************/
function insertRec(event, appID) {
// 返却値
var lastRID = -1;
var param =
{
"app": appID,
"record":
{
"名前": { "value": event['record']['名前']['value'] },
"年齢": { "value": event['record']['年齢']['value'] },
}
}
// CSRFトークンの取得
var token = kintone.getRequestToken();
param["__REQUEST_TOKEN__"] = token;
// 同期リクエストを行う
var appUrl = kintone.api.url('/k/v1/record');
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('POST', appUrl, false);
xmlHttp.setRequestHeader('Content-Type', 'application/json');
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(JSON.stringify(param));
if (xmlHttp.status == 200) {
var respdata = JSON.parse(xmlHttp.responseText);
lastRID = respdata["id"];
}
return lastRID;
}
・SELECTでやった解説は除外します。
・param
appとrecordで構成されるよ。詳しくは「レコードの登録(POST)」
・xmlHttp.open('POST', appUrl, false);
GETじゃなくてPOSTになる。
・xmlHttp.send(JSON.stringify(param));
今回は取得ではなく登録なので、
nullではなくて、JSON形式のデータを送る。
・var respdata = JSON.parse(xmlHttp.responseText);
登録結果のレスポンス内容を受け取る。
・lastRID = respdata["id"];
レスポンスの中からレコード番号を取得。
・今回は1レコードなので'/k/v1/record'を指定した。
・複数レコードの場合は以下の内容が変わる
→'/k/v1/record'が'/k/v1/records'になる
→paramの"record"が"records"になる
→paramの"records"の右辺(:の右側)が配列になる
2015年4月3日金曜日
kintone@データセットを作ってしまう
●背景
DBがない。1アプリ1データセットのkintone思想が
「くれぐれもコーディングするなよ!」と念を押してくるようだ。
でもやる。
●対応
appIDカラムと、枝IDカラム1~5、value値あたりまでを持った
コードマスタ的なアプリを作り、
閲覧はEveryone、登録+編集+削除は制限してしまえばいい。
DBがない。1アプリ1データセットのkintone思想が
「くれぐれもコーディングするなよ!」と念を押してくるようだ。
でもやる。
●対応
appIDカラムと、枝IDカラム1~5、value値あたりまでを持った
コードマスタ的なアプリを作り、
閲覧はEveryone、登録+編集+削除は制限してしまえばいい。
2015年4月2日木曜日
kintone@プロセス管理(ステータス変更時)のイベント
●背景
1)承認済みに変更された日付を更新したい
2)承認済みに変更できる作業者を制限したい
●APIの記載事項
・eventオブジェクトを return することでレコード情報を更新できます。
(参考:フィールドの値を書き換える)※レコード編集権限が必要です。
・false を return した場合アクションがキャンセルされます。
・eventオブジェクトに error プロパティを設定して return した場合、
error に設定した文字列でアラートが表示され、アクションがキャンセルされます。
・不正な値を return した場合エラーが表示されてアクションがキャンセルされます。
・何も return しない場合ステータスのみが更新されます。
●サンプルコード
1)承認済みに変更された日付を更新したい
2)承認済みに変更できる作業者を制限したい
●APIの記載事項
・eventオブジェクトを return することでレコード情報を更新できます。
(参考:フィールドの値を書き換える)※レコード編集権限が必要です。
・false を return した場合アクションがキャンセルされます。
・eventオブジェクトに error プロパティを設定して return した場合、
error に設定した文字列でアラートが表示され、アクションがキャンセルされます。
・不正な値を return した場合エラーが表示されてアクションがキャンセルされます。
・何も return しない場合ステータスのみが更新されます。
●サンプルコード
// プロセス管理アクション実行時
kintone.events.on(["app.record.detail.process.proceed"], function(event){
var record = event.record;
var nStatus = event.nextStatus.value;
// ステータスが「承認済み」の場合、承認日と承認者を設定する
switch(nStatus){
case "承認済み":
var user = kintone.getLoginUser();
if(user.code == "上長") {
record['承認日']['value'] = moment().format("YYYY-MM-DDTHH:mmZ");
record['承認者']['value'][0] = {code : user.code};
} else {
event.error = "上長以外は承認済みに設定できません";
}
break;
}
return event;
});
kintone.events.on(["app.record.detail.process.proceed"], function(event){
var record = event.record;
var nStatus = event.nextStatus.value;
// ステータスが「承認済み」の場合、承認日と承認者を設定する
switch(nStatus){
case "承認済み":
var user = kintone.getLoginUser();
if(user.code == "上長") {
record['承認日']['value'] = moment().format("YYYY-MM-DDTHH:mmZ");
record['承認者']['value'][0] = {code : user.code};
} else {
event.error = "上長以外は承認済みに設定できません";
}
break;
}
return event;
});
2015年4月1日水曜日
kintone@アプリAのINSERT時にレコード番号を取得したい
●背景
アプリAでレコードを登録する際の(app.record.create.submit)では
レコードがまだ作成されていないので「レコード番号」は未採番だ。
番号の予約も出来ていないので何番になるか分からない。
だがアプリAのINSERT時に、アプリBにも同時にINSERTしたい場面はあるだろう。
そしてアプリBには「アプリAのレコード番号」をカラムとして持っておき、
両データを関連付けたい、そんな場合、どうすれば良いかを考えた。
●解決策1
アプリAの最終レコード番号+1を自レコード番号とする方式
→よく目にする、一見して無駄のない処理方式である、
が、正常な挙動が望めない場合がある。
例えば「作成者がログインユーザのレコードのみ閲覧可能」のような、
ユーザに対してレコードの閲覧権限をいじっている場合がそうだ。
「JavaScriptでの閲覧権限」は「ログインユーザ」と同じレベルのため、
最終番号を保持したレコードが当該ログインユーザではない場合、
IDが参照できず、重複してしまう。
これ以外にも例えばアプリ操作者が複数いる場合、
ユーザ1が最新レコードを参照してる間に
ユーザ2も同じレコードを参照してしまい、
それぞれが+1した同じレコード番号を登録してしまう可能性がある。
上述の心配がまったくいらない業務では、以下のソースを使えば良い
・アプリCを作り、アプリAの新規登録時、まずアプリCにINSERTを行う。
INSERTしたレスポンスで取得できるIDをアプリAのレコード番号として使う方式。
→アプリCというアプリを作る必要があり、
無駄な採番が行われてしまいやすい等の欠点はあるが、
レコード番号の予約をほぼ完ぺきな形で行うことができる。
●サンプルコード2
アプリAでレコードを登録する際の(app.record.create.submit)では
レコードがまだ作成されていないので「レコード番号」は未採番だ。
番号の予約も出来ていないので何番になるか分からない。
だがアプリAのINSERT時に、アプリBにも同時にINSERTしたい場面はあるだろう。
そしてアプリBには「アプリAのレコード番号」をカラムとして持っておき、
両データを関連付けたい、そんな場合、どうすれば良いかを考えた。
●解決策1
アプリAの最終レコード番号+1を自レコード番号とする方式
→よく目にする、一見して無駄のない処理方式である、
が、正常な挙動が望めない場合がある。
例えば「作成者がログインユーザのレコードのみ閲覧可能」のような、
ユーザに対してレコードの閲覧権限をいじっている場合がそうだ。
「JavaScriptでの閲覧権限」は「ログインユーザ」と同じレベルのため、
最終番号を保持したレコードが当該ログインユーザではない場合、
IDが参照できず、重複してしまう。
これ以外にも例えばアプリ操作者が複数いる場合、
ユーザ1が最新レコードを参照してる間に
ユーザ2も同じレコードを参照してしまい、
それぞれが+1した同じレコード番号を登録してしまう可能性がある。
上述の心配がまったくいらない業務では、以下のソースを使えば良い
●サンプルコード1
/***************************************************************************************************
* 最新レコード番号を取得する1
* @param event イベント
* @param appID 取得対象のアプリID
* @return recID 現レコード番号の最大値+1。レコード非存在時は1。処理失敗時は-1。
***************************************************************************************************/
function getLastRecID1(event, appID) {
// 戻り値の設定(処理失敗時は-1)
var recID = -1;
// URLを設定する
var appUrl = kintone.api.url('/k/v1/records', true)
+ '?app=' + appID
+ '&query=' + encodeURI('order by レコード番号 desc limit 1');
// 最新レコード番号を取得
try {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", appUrl, false);
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(null);
if (xmlHttp.status == 200 && window.JSON) {
var obj = JSON.parse(xmlHttp.responseText);
if (obj.records[0] != null) {
// recID = 現レコード番号の最大値+1
recID = parseInt(obj.records[0][' id']['value']) + 1;
} else {
// recID = 1(レコード非存在時)
recID = 1;
}
}
} catch (e) {
}
return recID;
}
●解決策2/***************************************************************************************************
* 最新レコード番号を取得する1
* @param event イベント
* @param appID 取得対象のアプリID
* @return recID 現レコード番号の最大値+1。レコード非存在時は1。処理失敗時は-1。
***************************************************************************************************/
function getLastRecID1(event, appID) {
// 戻り値の設定(処理失敗時は-1)
var recID = -1;
// URLを設定する
var appUrl = kintone.api.url('/k/v1/records', true)
+ '?app=' + appID
+ '&query=' + encodeURI('order by レコード番号 desc limit 1');
// 最新レコード番号を取得
try {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", appUrl, false);
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(null);
if (xmlHttp.status == 200 && window.JSON) {
var obj = JSON.parse(xmlHttp.responseText);
if (obj.records[0] != null) {
// recID = 現レコード番号の最大値+1
recID = parseInt(obj.records[0][' id']['value']) + 1;
} else {
// recID = 1(レコード非存在時)
recID = 1;
}
}
} catch (e) {
}
return recID;
}
・アプリCを作り、アプリAの新規登録時、まずアプリCにINSERTを行う。
INSERTしたレスポンスで取得できるIDをアプリAのレコード番号として使う方式。
→アプリCというアプリを作る必要があり、
無駄な採番が行われてしまいやすい等の欠点はあるが、
レコード番号の予約をほぼ完ぺきな形で行うことができる。
●サンプルコード2
/***************************************************************************************************
* 最新レコード番号を取得する2
* @param event イベント
* @param appID_RecIDManager レコードID採番用アプリ(アプリC)のAppID
* @return recID 予約した最新レコード番号。処理失敗時は-1。
***************************************************************************************************/
function getLastRecID2(event, appID_RecIDManager) {
// 戻り値の設定(処理失敗時は-1)
var lastRecID = -1;
/*=========================================
* 「RecIDManager(アプリC)」に登録
=========================================*/
var param =
{
"app": appID_RecIDManager,
"record": {}
}
// CSRFトークンの取得
var token = kintone.getRequestToken();
param["__REQUEST_TOKEN__"] = token;
// 同期リクエストを行う
var appUrl = kintone.api.url('/k/v1/record');
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('POST', appUrl, false);
xmlHttp.setRequestHeader('Content-Type', 'application/json');
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(JSON.stringify(param));
if (xmlHttp.status == 200 && window.JSON) {
var respdata = JSON.parse(xmlHttp.responseText);
lastRecID = respdata["id"];
}
return lastRecID;
}
* 最新レコード番号を取得する2
* @param event イベント
* @param appID_RecIDManager レコードID採番用アプリ(アプリC)のAppID
* @return recID 予約した最新レコード番号。処理失敗時は-1。
***************************************************************************************************/
function getLastRecID2(event, appID_RecIDManager) {
// 戻り値の設定(処理失敗時は-1)
var lastRecID = -1;
/*=========================================
* 「RecIDManager(アプリC)」に登録
=========================================*/
var param =
{
"app": appID_RecIDManager,
"record": {}
}
// CSRFトークンの取得
var token = kintone.getRequestToken();
param["__REQUEST_TOKEN__"] = token;
// 同期リクエストを行う
var appUrl = kintone.api.url('/k/v1/record');
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('POST', appUrl, false);
xmlHttp.setRequestHeader('Content-Type', 'application/json');
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(JSON.stringify(param));
if (xmlHttp.status == 200 && window.JSON) {
var respdata = JSON.parse(xmlHttp.responseText);
lastRecID = respdata["id"];
}
return lastRecID;
}
kintone@一覧からの編集を制限する方法
●背景
「javascriptでの操作権限はユーザ権限と完全に等しいkintone世界」では、
「ユーザには隠して持っておきたい情報」を、
ユーザに完全に非表示にすることは事実上不可能だ。
レコードの新規登録時(app.record.create.showやedit)、
レコードの編集時(app.record.edit.showやedit)では
項目の表示/非表示や活性/非活性などの入力/表示制限が実装可能だが、
レコードの一覧更新時(app.record.index.edit.showやsubmit)では
上述の入力/表示制限が全く仕事してくれない。
「一覧:(すべて)」の前では非表示にしたいあらゆる項目がユーザに剥き出しだ。
現状では「見せてはやるが編集はさせない」を次善策として、
我慢して運用していくしかない。
とはいえ一覧では項目の表示/非表示はおろか、
活性/非活性さえ機能してくれない。
というわけで、一覧保存時('app.record.index.edit.submit')に
とにかくエラーを吐かせて編集させないこととした。
●サンプルコード
「javascriptでの操作権限はユーザ権限と完全に等しいkintone世界」では、
「ユーザには隠して持っておきたい情報」を、
ユーザに完全に非表示にすることは事実上不可能だ。
レコードの新規登録時(app.record.create.showやedit)、
レコードの編集時(app.record.edit.showやedit)では
項目の表示/非表示や活性/非活性などの入力/表示制限が実装可能だが、
レコードの一覧更新時(app.record.index.edit.showやsubmit)では
上述の入力/表示制限が全く仕事してくれない。
「一覧:(すべて)」の前では非表示にしたいあらゆる項目がユーザに剥き出しだ。
現状では「見せてはやるが編集はさせない」を次善策として、
我慢して運用していくしかない。
とはいえ一覧では項目の表示/非表示はおろか、
活性/非活性さえ機能してくれない。
というわけで、一覧保存時('app.record.index.edit.submit')に
とにかくエラーを吐かせて編集させないこととした。
●サンプルコード
/*******************************************************************************************
* 一覧画面 保存
********************************************************************************************/
kintone.events.on('app.record.index.edit.submit', function (event) {
event.error = "一覧画面からの編集機能は制限しております";
return event;
});
* 一覧画面 保存
********************************************************************************************/
kintone.events.on('app.record.index.edit.submit', function (event) {
event.error = "一覧画面からの編集機能は制限しております";
return event;
});
2015年3月26日木曜日
kintone@REST API@レコードを取得(SELECT)する
●背景
本格的にJavaScriptを触ってる人はともかく、
kintoneで初めてJavaScriptを始めるよって人には
「REST API」だの「kintone API」だの、まだるっこしいものである。
「つべこべ言わずに、動くソースをくれよ」
そんな意見があって、当然だと思う。
というわけで、
・「アプリID=11」のレコードを、「レコード番号の降順」で取得する
・取得結果をアラート表示させる
上述の簡単なサンプルコードをこさえた。
当該ソースはそのまま拡張子「js」で保存して
kintoneの「JavaScript / CSSでカスタマイズ」で
アップロードしてくれても正常に挙動する見込みである。
●サンプルコード
・最初の二行
何も考えずに記入する
・kintone.events.on(A,B);
イベントハンドラ。kintone上でAの操作が行われた時、
それをトリガーにして関数Bを動かしてね、という記述。
・'app.record.detail.show'
詳細画面表示時、というイベント指定。
他にもいろいろある。詳しくは「JavaScript API(イベント)」
・function (event) {処理}
関数。今回の場合は「詳細画面表示時」に動く実処理部。
・appUrl
アプリのURL。アプリIDとクエリ(WHERE句以降)を指定する。
複数レコードの操作なら'/k/v1/records'、1件の操作なら'/k/v1/record'を指定する。
kintoneアプリは「1アプリ=1テーブル」なので、
アプリのURLさえあればテーブルを指定したことになるんだね。
・XMLHttpRequest
URLからデータを読み出すためのオブジェクト。
私にとっては「同期処理のデータ送受信」を
叶えてくれる優秀なオブジェクト。
「え、今どき、非同期でしょ?処理早いし^^」
って言うかもしれないけどさ。
データ取得は同期処理じゃないとまったく意味が無いし、
データ更新だって「更新に成功したらこの処理、失敗したら…」
みたいな制御はいくらでもするでしょ?
ま、細かい話は専用のホームページを漁っておくれ、
私はよく知らんまま使っているがね。
・xmlHttp.open("GET", appUrl, false);
指定したデータ(appURL)の内容をGET(取得)したいよ、
なお、処理に失敗しても落ちないで次の処理に進んでね(false)
という約束の取り交わし。
・xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
通信時(HTTP要求時)に送るヘッダ。
'X-Requested-With'が名前で、'XMLHttpRequest'が値。
これを指定しないとブラウザによっては通信に失敗するらしい、
要は「盲目的に書いておくべきお約束文」。
・xmlHttp.send(null);
通信、はじめます。
・if (xmlHttp.status == 200 && window.JSON) {
HTTPステータスが200(正常)で
ちゃんとJSON形式だったら、
データを受け取っておきましょうかね、という、
これもお約束ごとのような一文。
elseで失敗時の挙動を書いておくとデバッグしやすいかも。
本格的にJavaScriptを触ってる人はともかく、
kintoneで初めてJavaScriptを始めるよって人には
「REST API」だの「kintone API」だの、まだるっこしいものである。
「つべこべ言わずに、動くソースをくれよ」
そんな意見があって、当然だと思う。
というわけで、
・「アプリID=11」のレコードを、「レコード番号の降順」で取得する
・取得結果をアラート表示させる
上述の簡単なサンプルコードをこさえた。
当該ソースはそのまま拡張子「js」で保存して
kintoneの「JavaScript / CSSでカスタマイズ」で
アップロードしてくれても正常に挙動する見込みである。
●サンプルコード
(function () {
"use strict";
// 適当なイベントから呼び出す
kintone.events.on('app.record.detail.show', function (event) {
//アプリID「11」のレコード内容をアラート表示する
alert(getRecords(event, 11));
return event;
});
//指定したappIDのレコードをレコード番号の降順で取得する
function getRecords(event, appID) {
// 戻り値の設定
var resp;
// URLを設定する
var appUrl = kintone.api.url('/k/v1/records', true)
+ '?app=' + appID
+ '&query=' + encodeURI('order by レコード番号 desc');
// レコードを取得
try {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", appUrl, false);
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(null);
if (xmlHttp.status == 200 && window.JSON) {
resp = JSON.parse(xmlHttp.responseText);
}
} catch (e) {
}
return resp;
}
})();
●解説"use strict";
// 適当なイベントから呼び出す
kintone.events.on('app.record.detail.show', function (event) {
//アプリID「11」のレコード内容をアラート表示する
alert(getRecords(event, 11));
return event;
});
//指定したappIDのレコードをレコード番号の降順で取得する
function getRecords(event, appID) {
// 戻り値の設定
var resp;
// URLを設定する
var appUrl = kintone.api.url('/k/v1/records', true)
+ '?app=' + appID
+ '&query=' + encodeURI('order by レコード番号 desc');
// レコードを取得
try {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", appUrl, false);
xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xmlHttp.send(null);
if (xmlHttp.status == 200 && window.JSON) {
resp = JSON.parse(xmlHttp.responseText);
}
} catch (e) {
}
return resp;
}
})();
・最初の二行
何も考えずに記入する
・kintone.events.on(A,B);
イベントハンドラ。kintone上でAの操作が行われた時、
それをトリガーにして関数Bを動かしてね、という記述。
・'app.record.detail.show'
詳細画面表示時、というイベント指定。
他にもいろいろある。詳しくは「JavaScript API(イベント)」
・function (event) {処理}
関数。今回の場合は「詳細画面表示時」に動く実処理部。
・appUrl
アプリのURL。アプリIDとクエリ(WHERE句以降)を指定する。
複数レコードの操作なら'/k/v1/records'、1件の操作なら'/k/v1/record'を指定する。
kintoneアプリは「1アプリ=1テーブル」なので、
アプリのURLさえあればテーブルを指定したことになるんだね。
・XMLHttpRequest
URLからデータを読み出すためのオブジェクト。
私にとっては「同期処理のデータ送受信」を
叶えてくれる優秀なオブジェクト。
「え、今どき、非同期でしょ?処理早いし^^」
って言うかもしれないけどさ。
データ取得は同期処理じゃないとまったく意味が無いし、
データ更新だって「更新に成功したらこの処理、失敗したら…」
みたいな制御はいくらでもするでしょ?
ま、細かい話は専用のホームページを漁っておくれ、
私はよく知らんまま使っているがね。
・xmlHttp.open("GET", appUrl, false);
指定したデータ(appURL)の内容をGET(取得)したいよ、
なお、処理に失敗しても落ちないで次の処理に進んでね(false)
という約束の取り交わし。
・xmlHttp.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
通信時(HTTP要求時)に送るヘッダ。
'X-Requested-With'が名前で、'XMLHttpRequest'が値。
これを指定しないとブラウザによっては通信に失敗するらしい、
要は「盲目的に書いておくべきお約束文」。
・xmlHttp.send(null);
通信、はじめます。
・if (xmlHttp.status == 200 && window.JSON) {
HTTPステータスが200(正常)で
ちゃんとJSON形式だったら、
データを受け取っておきましょうかね、という、
これもお約束ごとのような一文。
elseで失敗時の挙動を書いておくとデバッグしやすいかも。
Office@異なるバージョンをインストール時の挙動
●背景
Office2010Standardを利用している環境に、
最新Officeの試用版(1か月無料と謳われているもの)に含まれていると思われる
Accessをインストールする要件があった。
Accessの挙動さえ確認できればよかったのだがフルセット入ってしまうのだろうか…
まぁささっと確認してアンインストールすれば良いか、と軽く考えてインストーラを起動した。
ライセンス周りの確認が出現し、ああ、これはめんどくさいと断念。
「Officeの入っていない自宅PCで試そう」
と思ったが、時すでに遅かった。
●事象
・既に試用版Officeがインストールされライセンス認証さえすれば使用できる状態になっていた
・既存のエクセルファイル等はすべて最新の評価版にしか関連付けられており、
ダブルクリックによる実行は「ライセンス認証の門番」が。
・関連付けは「Excel(デスクトップ)」のようなラベル付けがされており、
2010のEXCEL.EXEを指定しても自動的に最新バージョンに関連付けなおされてしまう
・要は2010を起動してから「開く」を実行しない限り、既存のOfficeで文書を開けない状態になってしまった。
・とはいえOffice的にはインストールが完了している位置づけでもないようで
「プログラムと機能」には最新Officeが毛ほども表示されない
●解決策
・最新Officeのアンインストール
→ ×:アンインストールする手段がなかった
・最新Officeのフォルダをまるごとゴミ箱へ
→ ◎:無事、試用版最新Officeとお別れできた模様
●手順
1)Office2010のEXCELを起動。
タスクマネージャのプロセスタブから「EXCEL.EXE」を探し、
右クリック→「ファイルの場所を開く」で、旧バージョンのフォルダを特定
(ショートカットから右クリックではフォルダを特定できなかったため)
2)最新OfficeのEXCELを起動。
タスクマネージャのプロセスタブから「EXCEL.EXE」を探し、
右クリック→「ファイルの場所を開く」で、新バージョンのフォルダを特定
(こいつもショートカットから右クリックではフォルダを特定できなかったため)
Office2010Standardを利用している環境に、
最新Officeの試用版(1か月無料と謳われているもの)に含まれていると思われる
Accessをインストールする要件があった。
Accessの挙動さえ確認できればよかったのだがフルセット入ってしまうのだろうか…
まぁささっと確認してアンインストールすれば良いか、と軽く考えてインストーラを起動した。
ライセンス周りの確認が出現し、ああ、これはめんどくさいと断念。
「Officeの入っていない自宅PCで試そう」
と思ったが、時すでに遅かった。
●事象
・既に試用版Officeがインストールされライセンス認証さえすれば使用できる状態になっていた
・既存のエクセルファイル等はすべて最新の評価版にしか関連付けられており、
ダブルクリックによる実行は「ライセンス認証の門番」が。
・関連付けは「Excel(デスクトップ)」のようなラベル付けがされており、
2010のEXCEL.EXEを指定しても自動的に最新バージョンに関連付けなおされてしまう
・要は2010を起動してから「開く」を実行しない限り、既存のOfficeで文書を開けない状態になってしまった。
・とはいえOffice的にはインストールが完了している位置づけでもないようで
「プログラムと機能」には最新Officeが毛ほども表示されない
●解決策
・最新Officeのアンインストール
→ ×:アンインストールする手段がなかった
・最新Officeのフォルダをまるごとゴミ箱へ
→ ◎:無事、試用版最新Officeとお別れできた模様
●手順
1)Office2010のEXCELを起動。
タスクマネージャのプロセスタブから「EXCEL.EXE」を探し、
右クリック→「ファイルの場所を開く」で、旧バージョンのフォルダを特定
(ショートカットから右クリックではフォルダを特定できなかったため)
2)最新OfficeのEXCELを起動。
タスクマネージャのプロセスタブから「EXCEL.EXE」を探し、
右クリック→「ファイルの場所を開く」で、新バージョンのフォルダを特定
(こいつもショートカットから右クリックではフォルダを特定できなかったため)
3)最新の方のフォルダをzip圧縮で保存しておきつつ、全削除
●考察
余計な手間でした
登録:
投稿 (Atom)