ブログ

2026/01/07
【プリザンター】 第328回)手計算ミスをなくす!プリザンターの数値項目に電卓を追加する実践カスタマイズ

「計算のための数値項目」、増えすぎていませんか?

こんにちは、シーイーシーカスタマサービスの新村です。

プリザンターでは、数値項目に計算式を設定することで、合計や差分、金額計算などを自動化できます。
この仕組み自体はとても便利で、業務アプリを作る上で欠かせない機能です。

一方で実際の現場では、

・計算させるためだけの数値項目を用意する
・計算用項目と入力用項目が増えて画面が複雑になる
・「この項目は触っていい/いけない」の説明が必要になる

といった状況が発生しがちです。

特に、「一時的に計算したいだけ」「最終結果だけを入力したい」といったケースでは、
わざわざ数値項目を用意して計算式を設定するほどではない、という場面も多いのではないでしょうか。

また、入力担当者側から見ると、

・別途電卓で計算 → 数値を転記
・小数点やマイナスの入力ミス
・計算途中のメモが残らない不安

など、地味ながらも負担の大きい作業が発生しています。
そこで本記事では、プリザンターの数値項目そのものに「計算するための電卓」を追加するというアプローチを紹介します。

✅既存の数値項目はそのまま
✅計算式用の項目を増やさない
✅現場は「入力欄をクリックして計算する」だけ

プリザンターの設計思想を崩さず「作り込みすぎない業務改善」 を実現するカスタマイズです。

数値入力をその場で計算できる「ポップアップ電卓」の実装

プリザンターでは、数値項目に対して計算式を設定することができますが、現場では次のような運用になっているケースも少なくありません。

・計算用の数値項目をいくつも用意している
・実際には 電卓で計算した結果を手入力している
・計算式が複雑になり、どの項目が何のための値か分かりづらい

このような状態は、入力の手間増加・転記ミス・保守性の低下につながります。

そこで今回は、数値項目をクリックすると電卓が表示され、そのまま計算結果を入力できるシンプルなカスタマイズを実装します。

🔶実装の概要
今回のカスタマイズでは、以下のような動きを実現しています。

✅数値項目を対象に電卓を表示
✅入力欄の近くにポップアップ表示
✅マウス操作のみで計算(現場端末を想定)
✅安全性を重視した計算処理を実装
✅計算結果をそのまま数値項目へ反映

プリザンター標準の画面構成を大きく変えず、入力体験だけを改善することを目的としています。

🔶対象となる項目について
本カスタマイズでは、プリザンターが数値項目に自動付与している以下の属性を利用します。

input[data-validate-number="1"]
これにより、

「数値項目の追加時にスクリプトを編集する必要が無い」
「既存の数値項目にもそのまま適用できる」

というメリットがあります。

🔶コード全体について
以下が、今回実装した ポップアップ電卓のスクリプトです。
出力先は「新規作成」「編集」とし、テーブルの管理画面の「スクリプト」に設定してください。
$p.events.on_editor_load = function () {

/*********************************************************
* 現在操作中の入力欄を保持
*********************************************************/
let $currentInput = null;

/*********************************************************
* 電卓HTML
*********************************************************/
const calcHtml = `
<div id="calculator" style="
display:none;
position:absolute;
background:#fff;
border:1px solid #ccc;
padding:10px;
z-index:99999;
width:220px;
">
<!-- 表示部 -->
<input id="calc-display" readonly style="
width:100%;
height:36px;
margin-bottom:8px;
font-size:16px;
text-align:right;
">

<!-- ボタン部 -->
<div class="calc-grid">
<button data-val="7">7</button>
<button data-val="8">8</button>
<button data-val="9">9</button>
<button data-val="+">+</button>

<button data-val="4">4</button>
<button data-val="5">5</button>
<button data-val="6">6</button>
<button data-val="-">-</button>

<button data-val="1">1</button>
<button data-val="2">2</button>
<button data-val="3">3</button>
<button data-val="*">×</button>

<button id="calc-clear">C</button>
<button data-val="0">0</button>
<button id="calc-back">⌫</button>
<button data-val="/">÷</button>

<button id="calc-eq" style="grid-column: span 4;">=</button>
</div>
</div>
`;

/*********************************************************
* スタイル
*********************************************************/
const calcStyle = `
<style>
#calculator .calc-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
}
#calculator button {
height: 42px;
font-size: 16px;
cursor: pointer;
}
#calc-eq {
background:#007bff;
color:#fff;
}
#calc-clear {
background:#ccc;
}
</style>
`;

/*********************************************************
* 安全な計算関数(eval不使用)
*********************************************************/
function safeCalculate(expression) {

const exp = expression.trim();
if (!exp) throw new Error();

// 許可文字のみ
if (!/^[0-9+\-*/().\s]+$/.test(exp)) {
throw new Error();
}

// 演算子連続防止
if (/[\+\*\/]{2,}/.test(exp)) {
throw new Error();
}

// ゼロ除算防止
if (/\/0+(?![\.\d])/.test(exp)) {
throw new Error();
}

return Function(`"use strict"; return (${exp});`)();
}

/*********************************************************
* 初期化
*********************************************************/
if ($('#calculator').length === 0) {
$('head').append(calcStyle);
$('body').append(calcHtml);
}

// 数値項目に電卓対象クラス付与
$('input[data-validate-number="1"]')
.not('[readonly]')
.addClass('calc-target');

const $calc = $('#calculator');
const $display = $('#calc-display');

/*********************************************************
* 電卓表示
*********************************************************/
$(document).on('focus click', '.calc-target', function (e) {
e.stopPropagation();

$currentInput = $(this);

const offset = $currentInput.offset();
$calc.css({
top: offset.top + $currentInput.outerHeight() + 5,
left: offset.left
}).show();

$display.val($currentInput.val());
});

/*********************************************************
* 入力処理(ボタンのみ)
*********************************************************/
function appendValue(val) {

let current = $display.val();
const lastChar = current.slice(-1);

// ===== 数字 =====
if (/\d/.test(val)) {
$display.val(current + val);
return;
}

// ===== 小数点 =====
if (val === '.') {
const lastNumber = current.split(/[\+\-\*\/]/).pop();
if (lastNumber.includes('.')) return;

if (current === '' || /[\+\-\*\/]$/.test(current)) {
$display.val(current + '0.');
} else {
$display.val(current + '.');
}
return;
}

// ===== マイナス(符号 or 減算) =====
if (val === '-') {

// 先頭 or 演算子直後 → 符号
if (current === '' || /[\+\-\*\/]$/.test(current)) {
$display.val(current + '-');
return;
}

// 数字の直後 → 減算演算子
if (/\d$/.test(current)) {
$display.val(current + '-');
return;
}

return;
}

// ===== + * / 演算子 =====
if (/[\+\*\/]/.test(val)) {
if (current === '' || /[\+\-\*\/]$/.test(current)) return;
$display.val(current + val);
}
}

/*********************************************************
* ボタン入力
*********************************************************/
$(document).on('click', '#calculator button[data-val]', function (e) {
e.stopPropagation();
appendValue($(this).data('val'));
});

/*********************************************************
* クリア
*********************************************************/
$(document).on('click', '#calc-clear', function (e) {
e.stopPropagation();
$display.val('');
});

/*********************************************************
* バックスペース
*********************************************************/
$(document).on('click', '#calc-back', function (e) {
e.stopPropagation();
$display.val($display.val().slice(0, -1));
});

/*********************************************************
* 計算確定
*********************************************************/
$(document).on('click', '#calc-eq', function (e) {
e.stopPropagation();
try {
const result = safeCalculate($display.val());
$currentInput.val(result).trigger('change');
$calc.hide();
} catch {
alert('計算式が正しくありません');
}
});

/*********************************************************
* 外側クリックで閉じる
*********************************************************/
$(document).on('click', function () {
$calc.hide();
});
};
🔶実装時のポイント
✅安全な計算処理(eval を使わない実装)
プリザンターでは CSP(Content Security Policy)が有効になっている環境も多く、eval という関数を使うと警告が出る場合があります。
※eval: 文字列をコードとして実行する関数で、セキュリティリスクがあるため推奨されません

本実装では、

・許可文字のチェック
・演算子の連続チェック
・ゼロ除算防止

を行ったうえで、安全に計算を実行しています。

✅現場操作を想定した入力制御
・小数点の多重入力防止
・マイナス符号と減算の判別
・バックスペース対応

「自由に入力できる」よりも「間違えにくい操作」を優先しています

🔶このカスタマイズが向いているケース
・見積金額・工数・数量などを手計算して入力している
・計算のためだけに数値項目を増やしている
・現場ユーザーに複雑な計算式を見せたくない

こうしたケースでは、シンプルな入力支援だけでも大きな業務改善効果があります。

🔶動作画面イメージ
数値項目からの値の取得と入力が電卓のUIを介して行われていることが確認いただけます。

まとめ

今回紹介したポップアップ電卓は、プリザンターの数値項目に対して 入力体験を補助するだけのシンプルなカスタマイズです。
計算用の項目を増やしたり、複雑な計算式を設定したりすることなく、「入力時のちょっとした不便」を解消することを目的としています。

最後に、導入時の注意点と、発展的なカスタマイズ案を整理します。

🔶導入時の注意点
【あくまで「入力支援」である点】
本カスタマイズは、プリザンターの計算式項目を置き換えるものではありません。

✅計算結果は数値項目に直接入力される
✅保存後は通常の数値として扱われる

そのため、計算ロジックの正しさを担保する用途や後続処理で再計算が必要なケースでは、従来どおり計算式項目を併用する方が適しています。

🔶今後のカスタマイズ案
今回の仕組みは汎用性が高く、現場の運用に合わせてさまざまな拡張が可能です。
例えば、

・電卓を表示する項目を限定する
・固定計算(例:税計算・倍率計算)など、業務に特化したボタンを電卓に追加する
・キーボードやテンキー操作に対応する

といったカスタマイズも行えます。

プリザンターは「標準機能だけで完結させようとすると、かえって複雑になる」という場面が少なくありません。
今回のように、「標準機能はそのまま活かす」「不便な部分だけを軽く補う」というカスタマイズは、現場の負担を増やさずに改善効果を出しやすいアプローチです。

同じように「数値入力が少し面倒だな」と感じている場合は、ぜひ一度試してみてください。

弊社では、プリザンター導入・活用に関して以下のサービスをご提供しています。
年間サポート
各種書籍
帳票出力(Excel/PDF)支援パック

プリザンターの導入から開発・運用をあらゆる角度から全力サポートいたします。
ぜひお気軽にご相談ください!

☆☆☆
サービスの説明などをご希望の方は【 問い合わせフォーム 】よりお気軽にお問い合わせください。
☆☆☆

お問い合わせ
PAGE TOP