回答編集履歴

2

案作成

2024/03/28 14:58

投稿

Refrain
Refrain

スコア537

test CHANGED
@@ -14,3 +14,242 @@
14
14
  ```
15
15
 
16
16
  `parentNode`を連結して要素にアクセスしようとするよりも、何らかの変数に予め代入しておくと良いのではないでしょうか。
17
+
18
+ # 自分が実装するなら
19
+
20
+ ※dpi周りの処理がNaNになるようですので、完全な動作テストは済んでいません。
21
+ ※画面上の処理の簡略化は確認済みです。
22
+
23
+ ```html
24
+ <!DOCTYPE html>
25
+ <html lang="ja">
26
+ <head>
27
+ <meta charset="UTF-8">
28
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
29
+ <title>JPEG to PDFコンバータ</title>
30
+ <link href="style.css" rel="styesheet">
31
+ </head>
32
+ <body>
33
+ <table id="imageTable">
34
+ <thead>
35
+ <tr>
36
+ <th>ページ数</th>
37
+ <th>JPEGアップロード</th>
38
+ <th>削除</th>
39
+ </tr>
40
+ </thead>
41
+ <tbody></tbody>
42
+ </table>
43
+ <button id="add-row">列追加</button>
44
+ <button id="to-pdf">変換</button>
45
+ <form id="dpiForm">
46
+ <label for="dpi">DPI:</label>
47
+ <input type="number" id="dpi" name="dpi" min="1" step="1" required>
48
+ <button type="button" id="auto-fill-dpi">DPI自動入力</button>
49
+ </form>
50
+ <div class="overlayL" id="overlayL">
51
+ <div class="loaderL">
52
+ <img src="loading.svg" alt="処理中..." width="52px">
53
+ </div>
54
+ </div>
55
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.11.338/pdf.min.js"></script>
56
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.3.2/html2canvas.min.js"></script>
57
+ <script src="script.js"></script>
58
+ </body>
59
+ </html>
60
+ ```
61
+
62
+ ```css
63
+ .overlayL {
64
+ position: fixed;
65
+ width: 100%;
66
+ height: 100%;
67
+ top: 0;
68
+ left: 0;
69
+ background-color: rgba(0, 0, 0, 0.5);
70
+ display: none;
71
+ justify-content: center;
72
+ align-items: center;
73
+ }
74
+ .loaderL {
75
+ display: flex;
76
+ justify-content: center;
77
+ align-items: center;
78
+ }
79
+ ```
80
+
81
+ ```javascript
82
+ 'use strict';
83
+ class FormRows {
84
+ // 内部で持っておきたいデータ
85
+ __rows = []; // FormRowの一覧
86
+ __table = null; // 操作する<table>
87
+ __handler = _ => {}; // コールバック
88
+
89
+ constructor () {}
90
+
91
+ // FormRowを追加
92
+ addRow () {
93
+ const row = new FormRow(this);
94
+ row.setIndex(this.__rows.length + 1);
95
+ this.__rows.push(row);
96
+ this.__table.appendChild(row.element);
97
+ }
98
+
99
+ // FormRowを取得
100
+ getRow (index) {
101
+ return this.__rows[index];
102
+ }
103
+
104
+ // コールバックの登録
105
+ setOnImageSelected (handler) {
106
+ this.__handler = handler;
107
+ }
108
+
109
+ // 内部データ登録
110
+ bind (table) {
111
+ this.__table = table;
112
+ }
113
+
114
+ requireDelete (index) {
115
+ // 例外処理してfalseも返す(= requireの失敗)
116
+ this.__rows.splice(index, 1);
117
+ this.__rows.filter(row => row.index > index)
118
+ .forEach(row => row.setIndex(row.index - 1));
119
+ return true;
120
+ }
121
+
122
+ // ファイルが選ばれたと教えられたら、コールバックを実行する
123
+ onImageSelected () {
124
+ const dpi = this.__dpi;
125
+ this.__handler(dpi);
126
+ }
127
+
128
+ // 最小DPI取得
129
+ updateDpi (dpi) {
130
+ this.__dpi = Math.min(this.__dpi, dpi);
131
+ }
132
+
133
+ // pdfに変換する
134
+ toPdf () {
135
+ const dpi = this.__dpi;
136
+ const canvasPromises = [];
137
+ for (let i = 0; i < this.__rows.length; i++) {
138
+ const input = this.__rows[i].picker;
139
+ // 以下略
140
+ }
141
+ // asyncしたら括弧が要ります
142
+ Promise.all(canvasPromises).then(async (canvases) => {
143
+ const pdf = new jsPDF({
144
+ orientation: 'p',
145
+ unit: 'in',
146
+ format: [8.5, 11] // ここ:Letterサイズならこうするが、A4,B5,A5,フリーサイズにするにはどうすればいいのか
147
+ // →A4サイズの幅と高さ指定で良いのでは?
148
+ });
149
+ // 以下略
150
+ const pdfDataUri = pdf.output('datauristring');
151
+ this.downloadPdf(pdfDataUri);
152
+ }).catch(error => {
153
+ console.error('Error converting images to PDF:', error);
154
+ }).finally(_ => {
155
+ });
156
+ }
157
+
158
+ // 持っているFormRowのdpiで小さいものを返す
159
+ adjustDpi () {
160
+ return this.__rows.map(row => row.dpi).sort((a, b) => a - b)[0];
161
+ }
162
+
163
+ downloadPdf () {
164
+ // 以下略
165
+ }
166
+ }
167
+ class FormRow {
168
+ // 内部で持っておきたいデータ
169
+ __index;
170
+ __parent = null;
171
+ __tr = null;
172
+ __td1 = null;
173
+ __td2 = null;
174
+ __td3 = null;
175
+ __dpi = 0;
176
+
177
+ // 内部データを取得する
178
+ get dpi () { return this.__dpi; }
179
+ get element () { return this.__tr; }
180
+ get index () { return this.__index; }
181
+ get picker () { return this.__picker; }
182
+
183
+ // <table>に追加する<tr>の作成と内部データ登録
184
+ constructor (parent) {
185
+ const tr = document.createElement('tr');
186
+ const td1 = document.createElement('td');
187
+ tr.appendChild(td1);
188
+ const td2 = document.createElement('td');
189
+ const picker = document.createElement('input');
190
+ picker.accept = 'image/jpeg';
191
+ picker.onchange = _ => this.onImageSelected(picker.files[0]);
192
+ picker.type = 'file';
193
+ td2.appendChild(picker);
194
+ tr.appendChild(td2);
195
+ const td3 = document.createElement('td');
196
+ const button = document.createElement('button');
197
+ button.onclick = _ => this.onDeleteSelf();
198
+ button.textContent = '列削除';
199
+ td3.appendChild(button);
200
+ tr.appendChild(td3);
201
+ this.__td1 = td1;
202
+ this.__td2 = td2;
203
+ this.__td3 = td3;
204
+ this.__tr = tr;
205
+ this.__picker = picker;
206
+ this.__parent = parent;
207
+ }
208
+
209
+ // <button>のクリック時に実行され、自信を削除する
210
+ onDeleteSelf () {
211
+ if (this.__parent.requireDelete(this.__index)) this.__tr.remove();
212
+ }
213
+
214
+ // <button>のクリック時に実行され、FormRows側に変更を教える
215
+ onImageSelected (file) {
216
+ const image = new Image();
217
+ const reader = new FileReader();
218
+ reader.onload = event => {
219
+ image.src = reader.result;
220
+ image.onload = _ => {
221
+ // ここ、NaNになっていますね
222
+ // これでdpiが取得出来るのでしょうか?
223
+ const dpi = (image.width / file.width) * 72;
224
+ this.__dpi = dpi;
225
+ this.__parent.updateDpi(dpi);
226
+ // FormRows側にファイルが選ばれたことを教える
227
+ this.__parent.onImageSelected();
228
+ };
229
+ };
230
+ reader.readAsDataURL(file);
231
+ }
232
+
233
+ // ページ数のテキスト変更など
234
+ setIndex (index) {
235
+ this.__index = index;
236
+ this.__td1.textContent = index;
237
+ }
238
+ }
239
+
240
+ // FormRows/FormRowをクラス化してメインのコードの簡略化
241
+ const rows = new FormRows();
242
+ const table = document.getElementById('imageTable');
243
+ rows.setOnImageSelected(dpi => {
244
+ document.getElementById('dpi').value = dpi;
245
+ });
246
+ rows.bind(table);
247
+ rows.addRow();
248
+ rows.addRow();
249
+ rows.addRow();
250
+ rows.addRow();
251
+ rows.addRow();
252
+ document.getElementById('add-row').onclick = _ => rows.addRow();
253
+ document.getElementById('to-pdf').onclick = _ => rows.toPdf();
254
+ document.getElementById('auto-fill-dpi').onclick = _ => document.getElementById('dpi').value = rows.adjustDpi();
255
+ ```

1

動作確認結果の追記

2024/03/28 12:27

投稿

Refrain
Refrain

スコア537

test CHANGED
@@ -1,3 +1,16 @@
1
1
  エラーメッセージというよりは警告ではないですか?
2
2
  フォーム要素に`<label>`が振られていなかったり、`placeholder`が指定されていため、ユーザーに優しくないよ?ってニュアンスですね。
3
+
3
- 動作しないいうは、具体的にどこ動作しないのでしょうか?
4
+ また、動作させるコンソール上に以下エラーメッセージ表示されます。
5
+
6
+ ```
7
+ Uncaught TypeError: Cannot set properties of null (setting 'value')
8
+ ```
9
+
10
+ 確認してみると以下の箇所で発生しており、HTMLの構造的に存在しない要素を操作しようとしているため発生しているようです。
11
+
12
+ ```javascript
13
+ input.parentNode.parentNode.querySelector('input[type="number"]').value = dpi;
14
+ ```
15
+
16
+ `parentNode`を連結して要素にアクセスしようとするよりも、何らかの変数に予め代入しておくと良いのではないでしょうか。