質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

4回答

3493閲覧

JSのToDoメモ制作について

退会済みユーザー

退会済みユーザー

総合スコア0

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2015/08/28 00:13

以下のコードはJavaScriptのToDoメモです。
書き込んだ内容を追加、保存はうまくいったのですが削除がうまくいきません。削除ボタンを押すとすべて消えてしまいます。
どうすれば解決するのでしょうか?

JavaScript

1<!doctype html> 2<html> 3<head> 4 <meta charset="utf-8"> 5 <title></title> 6</head> 7<body> 8 <input type="text" placeholder="add ToDo" id="txt_todo"><br> 9 <input type="button" value="add" onclick="add_todo();"><br> 10 <div id="view_todo"></div> 11 12<script> 13 function add_todo(){ 14 var txt_todo = document.getElementById('txt_todo').value; 15 var view_todo = document.getElementById('view_todo').innerHTML; 16 if (view_todo != ""){ 17 view_todo += "<br>"; 18 } 19 document.getElementById('view_todo').innerHTML = view_todo + txt_todo + "<input type='button' value='del' onclick='del_todo();'>"; 20 save(); 21 } 22 23 function del_todo(){ 24 document.getElementById('view_todo').innerHTML = ""; 25 } 26 27 window.onload = function(){ 28 var body_todo = localStorage.getItem('txt_todo'); 29 document.getElementById('view_todo').innerHTML = body_todo; 30 } 31 32 function save(){ 33 localStorage.setItem('txt_todo', document.getElementById('view_todo').innerHTML); 34 } 35 36 37</script> 38</body> 39 40</html> 41

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

修正を可能な限り少なくするならば、以下の様なコードとなります。
他の修正として、

  • localStorageに対応するデータが保存されていない(初回利用時の)場合が想定できていない
  • 項目を削除した時にデータが保存されていない

この2点も修正しました。

JavaScript

1function add_todo() { 2 var txt_todo = document.getElementById('txt_todo').value; 3 4 /** 5 * 各項目をdiv要素で囲む。 6 * div要素は自動で改行するため、br要素の挿入処理を省略。 7 * 8 * また、del_todo関数の引数にthis(onXXX等のイベントプロパティ内では、これはイベントを設定した要素を示す)を指定し、 9 * クリックされたinput type=button要素を取得する。 10 */ 11 12 /* 13 var view_todo = document.getElementById('view_todo').innerHTML; 14 if (view_todo != '') { 15 view_todo += '<br>'; 16 } 17 document.getElementById('view_todo').innerHTML = view_todo + txt_todo + '<input type="button" value="del" onclick="del_todo()">'; 18 */ 19 document.getElementById('view_todo').innerHTML += '<div>' + txt_todo + '<input type="button" value="del" onclick="del_todo(this)">' + '</div>'; 20 save(); 21} 22 23/* 24function del_todo() { 25 document.getElementById('view_todo').innerHTML = ''; 26} 27*/ 28function del_todo(item_button_elem) { 29 // クリックされたbutton要素の親要素(=項目を囲むdiv要素)を取得 30 var item_elem = item_button_elem.parentNode; 31 // 項目のdiv要素を削除 32 item_elem.parentNode.removeChild(item_elem); 33 // 保存 34 save(); 35} 36 37window.onload = function () { 38 var body_todo = localStorage.getItem('txt_todo'); 39 // localStorage.getItemは対応するデータが無い場合にnullを返すため、 40 // これに対応 41 if (body_todo) { 42 document.getElementById('view_todo').innerHTML = body_todo; 43 } 44} 45 46function save() { 47 localStorage.setItem('txt_todo', document.getElementById('view_todo').innerHTML); 48}

(上記の修正はJavaScriptに対してのみ行いました。このため、上記のコードはJavaScriptのみとなります)

動作も確認しています。
ただし、このコードには以下に挙げる改善すべき点が数多くあります。

  • ToDoは項目リストなのだから、ulli要素を利用するべき
  • 項目の入力値が空、もしくはスペースのみの場合、追加を行わない方が利便性が向上する。
  • onXXX等の属性を利用してイベントを登録する方法は、イベントを重複して登録できない等の問題からあまり使用するべきではない。
    (このコードでは重複登録はないが、利用者のアドオン等がそのような実装になっている場合も。その場合、利用者のアドオンが正常に動作しない可能性が考えられる)
    addEventListener(Internet Explorer 8以前に対応するなら、attachEventも併用)を利用したほうが良い。
  • innerHTMLは重い。このため、document.createElement等でHTMLに対応するDOMを生成するか、insertAdjacentHTMLを利用した方が良い。
  • window.localStorageオブジェクトは全てのブラウザが対応しているわけではない。window.localStorageを利用する処理を行う前に、これに対応しているか検証するべき。
  • localStorageにはHTMLを直接保存するより、JavaScript側で配列を作成し、その配列に項目の情報を入れ、それをJSON.stringifyJSONに変換し、保存したほうが良い。これにより、利用者のハードディスクの使用量を軽減できる。
    なお、JSONから配列に戻す場合はJSON.parseを使用。

このため、私ならば以下のようにコードを書きます。
(下記コードはInternet Explorer 8以前には対応していません。対応する必要がある場合はコメントをお願いします。)

HTML

1<!doctype html> 2<html> 3<head> 4 <meta charset="utf-8"> 5 <title>teratail's answer on 15262</title> 6</head> 7<body> 8 <input type="text" placeholder="add ToDo" id="txt_todo"><br> 9 <input type="button" value="add" id="add-button"><br> 10 <ul id="view_todo"></ul> 11 <script> 12 /** 13 * localStorageに保存するToDoの情報を保持する配列 14 * @type {!Array.<!String>} 15 */ 16 var todo_items = []; 17 18 /** 19 * localStorageに対応しているか検証 20 * @const 21 * @type {boolean} 22 * @see https://github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js 23 */ 24 var IS_SUPPORT_LS = (function () { 25 var t; 26 27 if (window.hasOwnProperty('localStorage')) { 28 t = Math.random(); 29 try { 30 localStorage.setItem(t, t); 31 localStorage.removeItem(t); 32 return true; 33 } catch (e) { } 34 } 35 return false; 36 })(); 37 38 /** 39 * localStorageで使用するキー 40 * @const 41 * @type {string} 42 */ 43 var LS_KEY = 'txt_todo'; 44 45 /** 46 * ToDoに項目を追加する関数 47 */ 48 function add_todo() { 49 var value; 50 var item_value; 51 52 /** 53 * 入力欄の入力値を取得 54 */ 55 var value = document.getElementById('txt_todo').value; 56 57 /** 58 * 入力値が空、あるいはスペースのみの場合、項目を追加しない 59 */ 60 if (value.trim() !== '') { 61 /** 62 * 入力欄の入力値をStringオブジェクトに変換 63 * 64 * Note: ここでStringオブジェクトに変換しているのは、重複した値を持つ項目を識別するため 65 * プリミティブ値(普通の文字列)では同じ値の項目を区別できないが、 66 * オブジェクトは区別できる 67 */ 68 item_value = new String(value); 69 70 /** 71 * ToDoの情報を配列に追加 72 */ 73 todo_items.push(item_value); 74 75 /** 76 * HTML内に要素を追加する 77 */ 78 add_item_elem(item_value); 79 80 /** 81 * データを保存 82 */ 83 save(); 84 } 85 } 86 87 /** 88 * ToDoの項目をHTML内に挿入する関数 89 * @param {!String} item_value 挿入する項目の値 90 */ 91 function add_item_elem(item_value) { 92 var item_elem; 93 var item_del_button_elem; 94 95 /** 96 * この処理は、項目に対応する以下のHTMLを生成しています 97 * (厳密には異なります) 98 * 99 * <li> 100 * {item_value} 101 * <input type=button value=del onclick=del_todo> 102 * </li> 103 */ 104 item_elem = document.createElement('li'); 105 item_elem.appendChild(document.createTextNode(item_value + '')); 106 item_del_button_elem = document.createElement('input'); 107 item_del_button_elem.type = 'button'; 108 item_del_button_elem.value = 'del'; 109 item_del_button_elem.addEventListener('click', del_todo_create(item_value), false); 110 item_elem.appendChild(item_del_button_elem); 111 112 /** 113 * HTMLを挿入 114 */ 115 document.getElementById('view_todo').appendChild(item_elem); 116 } 117 118 /** 119 * 「HTML内のToDoの項目を削除する関数」を生成する関数 120 * 121 * Note: わざわざ「削除する関数を生成する関数」を定義しているのは、 122 * 削除に必要な情報である項目の値(item_value)を 123 * クロージャで保持するため 124 * 125 * @param {!String} item_value 削除する項目の値 126 * @return {function(Event)} HTML内のToDoの項目を削除する関数 127 */ 128 function del_todo_create(item_value) { 129 return function (event) { 130 var item_elem; 131 132 /** 133 * ToDoの情報を配列から削除 134 */ 135 todo_items = todo_items.filter(function (value) { 136 return value !== item_value; 137 }); 138 139 /** 140 * HTML内の項目を削除 141 */ 142 item_elem = event.currentTarget.parentNode; 143 item_elem.parentNode.removeChild(item_elem); 144 145 /** 146 * 保存 147 */ 148 save(); 149 }; 150 } 151 152 /** 153 * ページが読み込まれたら、localStorageから情報を取り出しHTMLに展開する関数 154 */ 155 function load() { 156 var todo_json_data; 157 var todo_saved_data; 158 159 if (IS_SUPPORT_LS) { 160 /** 161 * localStorageからデータを読み込む 162 */ 163 todo_json_data = localStorage.getItem(LS_KEY); 164 165 /** 166 * localStorageから正しく読み込めたか、 167 * またそのデータが存在するか判定 168 */ 169 if (todo_json_data) { 170 try { 171 /** 172 * localStorageから読み込んだJSONをJavaScriptオブジェクトに変換 173 */ 174 todo_saved_data = JSON.parse(todo_json_data); 175 176 /** 177 * 読み込んだデータが配列か、また配列の項目数が1以上か判定 178 */ 179 if (Array.isArray(todo_saved_data) && 0 < todo_saved_data.length) { 180 /** 181 * 読み込んだデータに合わせ、HTMLを挿入 182 * また、データ内の文字列をStringオブジェクトに変換 183 * 184 * Note: ここでStringオブジェクトに変換しているのは、重複した値を持つ項目を識別するため 185 * プリミティブ値(普通の文字列)では同じ値の項目を区別できないが、 186 * オブジェクトは区別できる 187 */ 188 todo_items = todo_saved_data.map(function (v) { 189 // 各項目に相当する文字列を、Stringオブジェクトに変換 190 var value = new String(v); 191 192 // 項目に対応するHTMLを挿入 193 add_item_elem(value); 194 195 // Stringオブジェクトを返す 196 return value; 197 }); 198 } 199 } catch (e) { } 200 } 201 } 202 } 203 204 /** 205 * localStorageに情報を保存する関数 206 */ 207 function save() { 208 var todo_json_data; 209 210 if (IS_SUPPORT_LS) { 211 todo_json_data = JSON.stringify(todo_items); 212 localStorage.setItem(LS_KEY, todo_json_data); 213 } 214 } 215 216 /** 217 * ページが読み込まれた際にload関数が実行されるよう設定 218 * Note: この処理は以下とほぼ等価: 219 * window.onload = load; 220 */ 221 window.addEventListener('load', load, false); 222 223 /** 224 * addボタンがクリックされた時、add_todo関数が実行されるよう設定 225 */ 226 document.getElementById('add-button').addEventListener('click', add_todo, false); 227 </script> 228</body> 229</html>

投稿2015/09/03 18:29

編集2015/09/03 18:53
sounisi5011

総合スコア697

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

jsdoit 上に作ってみました。(jquery を使って書いています)
http://jsdo.it/katoy/4cP3

html のエスケープ、削除時のアニメーション機能を追加しています。

  • todo のテキストに <s>abc</s> と入力して [add] した時に todo リスト上で 取り消し線が現れないようになっています。
  • [del] をクリックすると、アニメションしながら、その行が消えていきます。
  • todo のテキストが空の時に [add] をクリックしても、todo リストには追加されません。

投稿2015/09/03 14:59

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

pi-chanさんがおっしゃられている通り、document.getElementById('view_todo').innerHTML = "";とすると<div id="view_todo"></div>の中身(innerHTML)を空にしてしまうので、<div id="view_todo">内に追加したものは全て削除されてしまいます。


追加する時と削除するときで下記のような考え方にすれば意図しているように動作すると思います。
サンプルで載せているコードはクロスブラウザとかIEとか全く考慮していませんので、そのあたりは適時変更するなどして参考程度に考えてください。

追加する時
TODOを追加するときに、TODOごとのidを作成して
追加するTODOのHTMLに書かれているのonclick="del_toto()"の引数で作成したidを指定する
TODOをdivタグとかspanタグで囲ってid=""で作成したTODOのidを指定してあげる
(<br/> タグの有無を考えるとdivとかpのブロック要素の方が良さそうかも)

追加するTODOの例

html

1<div id="todo_1">TODO TEXT <input type="button" value="del" onclick="del_todo('todo_1');"></div>

追加する関数(add_todo)の例

javascript

1// 追加するTODOの連番 2var todo_no = 0; 3// TODOのidの接頭語 4var todo_prefix = 'todo'; 5 6function add_todo() { 7 var d = document; 8 var todo_elm; // 追加するTODOのエレメントを入れる変数 9 var input_elm = d.getElementById('txt_todo'); // TODOを追加するinputタグを取得 10 var txt_todo = input_elm.value; // inputに入力されている文字 11 // もし 入力されている文字が空でなければ 12 if(txt_todo !== "") { 13 // TODOの連番を+1する 14 todo_no += 1; 15 // 追加するTODOになる div 要素を作成 -> <div></div> 16 todo_elm = d.createElement('div'); 17 // 追加するTODOになる div 要素に todo1 の様な idをつける -> <div id="todo1"></div> 18 todo_elm.id = todo_prefix + todo_no; 19 // 追加するTODOになる div の中に inputで入力された文字列と 削除ボタンになるinput[type="button"]タグを追加 20 todo_elm.innerHTML = txt_todo + ' <input type="button" value="del" onclick="del_todo(\'' + todo_elm.id + '\');">'; 21 // 確認用のlogを出力 ※ 機能的には不要 22 console.log('+ create TODO', todo_elm); 23 // <div id="view_todo"></div> に作成した TODOのdivを追加 24 d.getElementById('view_todo').appendChild( todo_elm ); 25 save(); 26 // TODOを追加したらinputタグをクリアしてあげると使いやすくなりそうです 27 input_elm.value = ''; 28 } 29}

削除する時
funcion del_todo()で削除対象のTODOのidを受け取るようにする
対象のTODOを探してきてremoveChild()とかで対象の要素を削除する

削除する関数(del_toto)の例

javascript

1function del_todo(todo_id) { 2 // 確認用のタダのlog ※ 機能的には不要 3 console.log('>>> delete TODO ID is', todo_id); 4 // 引数のTODO IDから対象のTODOを取得 todo_id が 'todo1'なら <div id="todo1">を取得 5 var del_elm = document.getElementById(todo_id); 6 // <div id="view_todo">の中から del_elm で取得した div を削除 7 document.getElementById('view_todo').removeChild(del_elm); 8 // 消した後も保存してあげないとリロード時に消す前の状態になってしまう 9 save();

※ ただし、このままではリロード時にTODOのIDが0に戻ってしまうので同じIDが存在してしまう可能性ができます。
save時に今のtodo_noも保存して、ロード時にtodo_noも値があれば読み込んであげるとか
IDに時間を追加してあげるとか、(この場合 todo_no の必要性ほぼ無いですが)

javascript

1todo_elm.id = todo_prefix + todo_no + '-' + new Date().getTime();

リロード時にTODOのIDが被らないように工夫してあげる必要もあるでしょう。

投稿2015/08/29 04:33

編集2015/09/03 08:53
KiKiKi_KiKi

総合スコア596

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2015/09/03 08:43

返信遅れて申し訳ございません もう少し簡単なコードってないですかね? 少し難しくて理解できません><
退会済みユーザー

退会済みユーザー

2015/09/03 10:47

返信ありがとうございます 本当に申し訳ないのですが全体的に何回読んでも理解できません 自分のやるべきこととして ・書き込まれたメモにIDをつける ということは理解できたのですが どうやってIDをつけるかがわからないのです。 例えば innerHTML = "<div id="a">" + text + "</div>" としても同じIDが繰り返されるだけで同じIDは無効ですし・・・。
KiKiKi_KiKi

2015/09/03 12:21

> Furisukeさん ユニークなIDの付け方ですが、仰られているように innerHTML = "<div id="a">" + text + "</div>" と固定にしてしまうと同じIDのdivが作られてしまうので、意図したとおりに動作しません。 先の例では var todo_no という始まりが0の数字を保持しておく変数を用意しておきます。 この変数はTODOを作る関数add_todo()の外に置いてあるので、add_todo()が呼び出される度に0に初期化されることはありません。 add_todo()が呼び出されてTODOを作る度に todo_noを +1 します。 1回目はtodo_noは1になり、2回目はtodo_noは2になります。 この増えていく番号をIDに使うという記述が todo_elm.id = todo_prefix + todo_no; になります。HTMLのIDの先頭が数字から始まるのはダメなのでtodo_prefixという変数で指定した文字列 (todo) の後に 増えていく数字(todo_no)を繋いだものをIDとして設定しています。 1回目は <div id="todo1"> 2回目は <div id="todo2"> という異なるIDで追加していくことができます。 ここまでが、最初に書いたコードの部分の説明になります。 で、補足部分なのですが ロードの度にtodo_noは初期値の0から始まることになりますのでTODOが保存されている状態だと、また0から連番を始めるとIDが被ってしまう可能性が出てきます。 その回避方法として、下記の方法の例を示しています。 1. いま 何番までTODOを作成したか、つまりtodo_noの数字も保存しておいて、リロード時に、保存してある番号からスタートさせる方法 2. そもそもIDに new Date().getTime() でTODOを追加した時間の数字を付けてしまえば、被ることは殆どなくなるだろうという方法 と言った感じです。 --- chromeなどで見られているのであれば、一度コードをコピペしてメニューの 表示 > 開発/管理 > デベロッパーツール を選択して、コンソール(Console)のタブをクリックすると追加されるTODOのHTMLとかが表示されますので、解らない変数などがあれば console.log( todo_no ); の様に書いてあげれば、その時に何がその変数に入っているか確認することができますよ!
sounisi5011

2015/09/03 18:31

del_todo関数の引数にthisを指定することで、クリックされた要素を取得できます。 これを利用すれば、ユニークなIDを使用する必要は無いと考えますが…いかがでしょうか。
KiKiKi_KiKi

2015/09/04 03:47

> sounisi5011さん はい。単にこのクリックされた時に対象を消すという機能を実装するだけならIDは必ずしも必要は無いです。 質問者さん的にHTMLにはIDが付いているので、getElementByI()で何を取得しているかが見えるので理解しやすいかと思った点と、ここから機能を拡張していくのであればIDがあった方が色々楽だろうな。思う程度の問題です。
guest

0

.getElementById('view_todo') だと、文字通りview_todo全体になっちゃいますよね?
todoの項目を追加する時に、各項目毎に一意のIDを付加しておき、削除時にはまず対象エレメントもIDを取得してからそのIDで削除対象を指定すれば良いにではないでしょうか?


追記

#いま移動中で具体的なコードを示せませんが・・・

[add]する時
ハッシュ関数か何かで擬似的な乱数を生成し、それを挿入するhtmlのブロックに個別のIDとして付加できませんか?

[del]する時
削除対象を、マウスクリックか何かで指定する想定なのですよね?
例えば ココ の「■任意のエレメント上で、マウスオーバーしたか調べる」などを組み合わせ、削除対象のIDを取得できませんか?

投稿2015/08/28 00:28

編集2015/08/28 04:40
pi-chan

総合スコア5936

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2015/08/28 03:59

回答ありがとうございます 全体を指定していることは理解できるのですが >各項目毎に一意のIDを付加しておき、削除時にはまず対象エレメントもIDを取得してからそのIDで削除対象を指定すれば良い をいくら考えてもわかりません どのようにすれば付加や削除対象の指定ができるのでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問