001 : //
002 : // WeeklyAllViewer
003 : //
004 : // Created by M.Miyazaki [2013.11.26]
005 : // Modified by M.Miyazaki [2014.01.16] - 実装の方針を転換(Ajaxの利用)
006 : // Modified by M.Miyazaki [2014.01.18] - 不具合の修正とキーワード欄増減対応
007 : // Modified by M.Miyazaki [2014.05.31] - 作業タイトル押下によるトグル表示、ボタン配置変更、検索結果のリスト表示対応
008 : // Modified by M.Miyazaki [2014.06.01] - 作業ナンバーのリンク化、否定検索対応
009 : //
010 :
011 : //==================
012 : // Settings
013 : //==================
014 :
015 : /**
016 : * 作業ログのファイル
017 : */
018 : var WORKLOG_FILE = "log_sample.txt";
019 :
020 :
021 : //===================
022 : // Constants
023 : //===================
024 :
025 : /**
026 : * 本文と見出しを表示する
027 : */
028 : var VISIBLE_DETAIL = 1;
029 :
030 : /**
031 : * 本文を表示せず、見出しのみ表示する
032 : */
033 : var INVISIBLE_DETAIL = 2;
034 :
035 : /**
036 : * 本文も見出しも表示しない(非表示の画面要素)
037 : */
038 : var INVISIBLE_LOG = 3;
039 :
040 : /**
041 : * リクエストの状態(一連の動作が完了した)
042 : * 4で固定。
043 : */
044 : var DONE = 4;
045 :
046 : /**
047 : * キーワード入力欄のIDの接頭辞
048 : */
049 : var KEYWORD_PREFIX = "keyword";
050 :
051 : /**
052 : * preタグのID接頭辞
053 : */
054 : var PRE_PREFIX = "pre";
055 :
056 : /**
057 : * liタグのID接頭辞
058 : */
059 : var LI_PREFIX = "li";
060 :
061 :
062 :
063 : //===================
064 : // Variables
065 : //===================
066 :
067 : /**
068 : * 作業ログの配列
069 : */
070 : var workLogs = null;
071 :
072 :
073 : //======================
074 : // Main Routine
075 : //======================
076 :
077 : /**
078 : * 作業ログを吸い出して、見出しのみ一覧表示する。
079 : *
080 : * Created by M.Miyazaki [2014.01.15]
081 : */
082 : function loadWorkLogs()
083 : {
084 : var handle = new XMLHttpRequest();
085 :
086 : handle.open("GET", WORKLOG_FILE);
087 :
088 : handle.onreadystatechange = function()
089 : {
090 : if (handle.readyState == DONE)
091 : {
092 : workLogs = getWorkLogs(handle.responseText);
093 : showLogs(workLogs, INVISIBLE_DETAIL);
094 : }
095 : }
096 :
097 : handle.send();
098 :
099 : return;
100 : }
101 :
102 : /**
103 : * 検索を行って結果を表示する。
104 : *
105 : * Created by M.Miyazaki [2013.11.27]
106 : * Modified by M.Miyazaki [2014.01.15]
107 : * Modified by M.Miyazaki [2014.06.01]
108 : */
109 : function searchWorkLogs()
110 : {
111 : searchWorkLogsWithVisibleStatus(VISIBLE_DETAIL);
112 :
113 : return;
114 : }
115 :
116 : /**
117 : * 検索を行って結果のリストを表示する。
118 : *
119 : * Created by M.Miyazaki [2014.06.01]
120 : */
121 : function searchWorkLogsList()
122 : {
123 : searchWorkLogsWithVisibleStatus(INVISIBLE_DETAIL);
124 :
125 : return;
126 : }
127 :
128 :
129 : //======================
130 : // Sub Routines
131 : //======================
132 :
133 : /**
134 : * 検索を行って結果を表示する。
135 : *
136 : * Created by M.Miyazaki [2014.06.01]
137 : *
138 : * @param visibleStatus 作業内容を表示するか否か
139 : */
140 : function searchWorkLogsWithVisibleStatus(visibleStatus)
141 : {
142 : if (workLogs == null) { return; }
143 :
144 : resetVisibleStatus(workLogs);
145 :
146 : var keywords = getKeywords();
147 :
148 : var resultLogs = search(workLogs, keywords);
149 :
150 : showLogs(resultLogs, visibleStatus);
151 :
152 : return;
153 : }
154 :
155 : /**
156 : * 作業ログのテキストを配列で応答する。
157 : *
158 : * Created by M.Miyazaki [2013.11.27]
159 : * Modified by M.Miyazaki [2013.11.28]
160 : *
161 : * @param logTextString 作業ログのテキスト
162 : * @return 作業ログの配列
163 : */
164 : function getWorkLogs(logTextString)
165 : {
166 : if (logTextString == null) { return null; }
167 :
168 : var logArray = logTextString.split(/(■No\.[0-9]+ - [0-9]+\.[0-9]+\.[0-9]+\([^\)]+\)[=]{10,})/);
169 :
170 : var workLogs = new Array();
171 :
172 : var numberOfLogs = (logArray.length - 1) / 2;
173 :
174 : for (var index = 0; index < numberOfLogs; index++)
175 : {
176 : var logString = logArray[index*2 + 1] + "" + logArray[index*2 + 2];
177 : workLogs[index] = new WorkLog(logString);
178 : }
179 :
180 : return workLogs;
181 : }
182 :
183 : /**
184 : * 検索キーワードの配列を応答する。
185 : *
186 : * Created by M.Miyazaki [2014.01.18]
187 : *
188 : * @return 検索キーワードの配列
189 : */
190 : function getKeywords()
191 : {
192 : var keywordElement;
193 : var index = 0;
194 : var keywords = new Array();
195 :
196 : while ((keywordElement = document.getElementById(KEYWORD_PREFIX + (index + 1))) != null)
197 : {
198 : var keywordText = keywordElement.value;
199 : var isNegative = ("negativeKeywordField" == keywordElement.className) ? true : false;
200 : var keyword = new Keyword(keywordText, isNegative);
201 :
202 : keywords.push(keyword);
203 : index++;
204 : }
205 :
206 : return keywords;
207 : }
208 :
209 : /**
210 : * 作業ログの可視性をリセットする。
211 : *
212 : * Created by M.Miyazaki [2014.06.01]
213 : *
214 : * @param workLogs 可視性をリセットする作業ログの配列
215 : */
216 : function resetVisibleStatus(workLogs)
217 : {
218 : for (var index = 0; index < workLogs.length; index++)
219 : {
220 : workLogs[index].isVisible = true;
221 : }
222 :
223 : return;
224 : }
225 :
226 : /**
227 : * 検索条件で絞り込む。
228 : *
229 : * Created by M.Miyazaki [2013.11.27]
230 : * Modified by M.Miyazaki [2014.01.16]
231 : * Modified by M.Miyazaki [2014.06.01] - 否定検索処理追加
232 : *
233 : * @param workLogs 検索対象となる作業ログの配列
234 : * @param keywords 検索キーワードの配列
235 : * @return 検索で絞り込んだ作業ログの配列
236 : */
237 : function search(workLogs, keywords)
238 : {
239 : if (keywords.length == 0) { return workLogs; }
240 :
241 : var resultLogs = new Array();
242 : var keyword = keywords.shift();
243 :
244 : var keywordText = keyword.getKeywordText();
245 : var isNegative = keyword.isNegative();
246 :
247 : if (keywordText != "")
248 : {
249 : var regularExpression = new RegExp(keywordText, "g");
250 :
251 : for (var index = 0; index < workLogs.length; index++)
252 : {
253 : if (true == workLogs[index].isVisible)
254 : {
255 : var wordArray = workLogs[index].getWorkLogText().match(regularExpression);
256 :
257 : if (isNegative == false)
258 : {
259 : workLogs[index].isVisible = ((wordArray != null) ? true : false);
260 : }
261 : else
262 : {
263 : workLogs[index].isVisible = ((wordArray == null) ? true : false);
264 : }
265 : }
266 :
267 : resultLogs.push(workLogs[index]);
268 : }
269 : }
270 : else
271 : {
272 : resultLogs = workLogs;
273 : }
274 :
275 : return search(resultLogs, keywords);
276 : }
277 :
278 : /**
279 : * 作業ログを一覧表示する。
280 : *
281 : * Created by M.Miyazaki [2013.11.27]
282 : * Modified by M.Miyazaki [2013.11.29]
283 : * Modified by M.Miyazaki [2014.01.16]
284 : * Modified by M.Miyazaki [2014.05.31]
285 : * Modified by M.Miyazaki [2014.06.01]
286 : *
287 : * @param resultLogs 表示する作業ログの配列
288 : * @param visibleStatus 作業内容を表示するか否か(VISIBLE_DETAIL or INVISIBLE_DETAIL)
289 : */
290 : function showLogs(resultLogs, visibleStatus)
291 : {
292 : if (visibleStatus == INVISIBLE_LOG)
293 : {
294 : // INVISIBLE_LOG is unexpected visibleStatus!
295 : visibleStatus = VISIBLE_DETAIL;
296 : }
297 :
298 : var htmlString = "";
299 :
300 : htmlString += "<ul>\n";
301 :
302 : for (var index = 0; index < resultLogs.length; index++)
303 : {
304 : var log = resultLogs[index];
305 : var workLogId = 'WorkLogNo.' + sanitize(log.getWorkNumber());
306 :
307 : if (log.isVisible == true)
308 : {
309 : htmlString += "<li id='" + LI_PREFIX + workLogId + "' style='display:;'>";
310 : }
311 : else
312 : {
313 : htmlString += "<li id='" + LI_PREFIX + workLogId + "' style='display:none;'>";
314 : }
315 :
316 : htmlString += "<a name='" + workLogId + "' onclick='toggleLog(\"" + workLogId + "\");'>";
317 : htmlString += "No." + sanitize(log.getWorkNumber()) + ":" + sanitize(log.getWorkTitle());
318 : htmlString += "</a>";
319 :
320 : if (visibleStatus == VISIBLE_DETAIL)
321 : {
322 : htmlString += "<pre id='" + PRE_PREFIX + workLogId + "' style='display:;'>";
323 : }
324 : else
325 : {
326 : htmlString += "<pre id='" + PRE_PREFIX + workLogId + "' style='display:none;'>";
327 : }
328 :
329 : var workDetail = sanitize(log.getWorkDetail()).replace(/\r\n|\r|\n/g, "<br>");
330 : workDetail = convertNumberToLink(workDetail);
331 :
332 : htmlString += workDetail;
333 : htmlString += "</pre>";
334 : htmlString += "</li>\n";
335 : }
336 :
337 : htmlString += "</ul>\n";
338 :
339 : document.getElementById("searchResult").innerHTML = htmlString;
340 :
341 : return;
342 : }
343 :
344 : /**
345 : *
346 : * 作業ログの表示形式(タイトルのみor詳細)をトグルする。
347 : *
348 : * Created by M.Miyazaki [2014.05.31]
349 : *
350 : * @param workLogId トグル対象の作業ログのID
351 : */
352 : function toggleLog(workLogId)
353 : {
354 : var isVisible = ("" == document.getElementById(PRE_PREFIX + workLogId).style.display);
355 : var visibleStatus = isVisible ? INVISIBLE_DETAIL : VISIBLE_DETAIL;
356 :
357 : setVisibleStatus(workLogId, visibleStatus);
358 :
359 : return;
360 : }
361 :
362 : /**
363 : *
364 : * 作業ログの表示形式(タイトルのみor詳細or非表示)を設定する。
365 : *
366 : * Created by M.Miyazaki [2014.05.31]
367 : * Modified by M.Miyazaki [2014.06.01]
368 : *
369 : * @param workLogId 対象の作業ログのID
370 : * @param visibleStatus 作業ログの表示形式(VISIBLE_DETAIL or INVISIBLE_DETAIL or INVISIBLE_LOG)
371 : */
372 : function setVisibleStatus(workLogId, visibleStatus)
373 : {
374 : if (visibleStatus == VISIBLE_DETAIL)
375 : {
376 : document.getElementById(LI_PREFIX + workLogId).style.display = "";
377 : document.getElementById(PRE_PREFIX + workLogId).style.display = "";
378 : }
379 : else if (visibleStatus == INVISIBLE_DETAIL)
380 : {
381 : document.getElementById(LI_PREFIX + workLogId).style.display = "";
382 : document.getElementById(PRE_PREFIX + workLogId).style.display = "none";
383 : }
384 : else if (visibleStatus == INVISIBLE_LOG)
385 : {
386 : document.getElementById(LI_PREFIX + workLogId).style.display = "none";
387 : }
388 : else
389 : {
390 : // Unexpected visibleStatus...
391 : }
392 :
393 : return;
394 : }
395 :
396 : /**
397 : * 文字列をサニタイズ(無害化)する。
398 : *
399 : * Created by M.Miyazaki [2014.01.16]
400 : *
401 : * @param inputString サニタイズ対象の文字列
402 : * @return サニタイズ後の文字列
403 : */
404 : function sanitize(inputString)
405 : {
406 : if (null == inputString) { return ""; }
407 :
408 : return inputString.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
409 : }
410 :
411 : /**
412 : * 作業ログのナンバリング「No.XXX」をリンク化する。
413 : *
414 : * Created by M.Miyazaki [2014.06.01]
415 : *
416 : * @param inputString リンク化対象の文字列
417 : * @return リンク化した文字列
418 : */
419 : function convertNumberToLink(inputString)
420 : {
421 : if (null == inputString) { return ""; }
422 :
423 : var regularExpression = new RegExp("(No\.[0-9]+)", "g");
424 : var replacement = "<a href='#WorkLog$1' onclick='setVisibleStatus(\"WorkLog$1\", VISIBLE_DETAIL);'>$1</a>";
425 : var outputString = inputString.replace(regularExpression, replacement);
426 :
427 : return outputString;
428 : }
429 :
430 : /**
431 : * キーワード種別(肯定or否定)をトグルする。
432 : *
433 : * Created by M.Miyazaki [2014.06.01]
434 : *
435 : * @param keywordId 種別変更対象のキーワード欄のID
436 : * @return リンク化した文字列
437 : */
438 : function toggleKeywordType(keywordId)
439 : {
440 : var keywordField = document.getElementById(keywordId);
441 :
442 : if ("negativeKeywordField" == keywordField.className)
443 : {
444 : keywordField.className = "keywordField";
445 : }
446 : else
447 : {
448 : keywordField.className = "negativeKeywordField";
449 : }
450 :
451 : return;
452 : }
453 :
454 : /**
455 : * 検索キーワード入力欄を増やす。
456 : *
457 : * Created by M.Miyazaki [2014.01.18]
458 : * Modified by M.Miyazaki [2014.05.20]
459 : * Modified by M.Miyazaki [2014.05.31]
460 : * Modified by M.Miyazaki [2014.06.01]
461 : */
462 : function addKeywordField()
463 : {
464 : var index = 2;
465 : while (document.getElementById(KEYWORD_PREFIX + index) != null) { index++; }
466 :
467 : //if (index == 2)
468 : //{
469 : // document.getElementById("deleteKeywordFieldButton").style.display = ""; // visible!
470 : //}
471 :
472 : var bottomOfKeywords = document.getElementById("bottomOfKeywords");
473 :
474 : var newBreak = document.createElement("br");
475 : newBreak.id = KEYWORD_PREFIX + "Br" + index;
476 : bottomOfKeywords.parentNode.insertBefore(newBreak, bottomOfKeywords);
477 :
478 : var newField = document.getElementById(KEYWORD_PREFIX + "1").cloneNode();
479 : newField.id = KEYWORD_PREFIX + index;
480 : newField.ondblclick = new Function("toggleKeywordType('" + KEYWORD_PREFIX + index + "');");
481 : bottomOfKeywords.parentNode.insertBefore(newField, bottomOfKeywords);
482 :
483 : // Initialize textfield
484 : newField.value = "";
485 : newField.className = "keywordField";
486 :
487 : return;
488 : }
489 :
490 : /**
491 : * 検索キーワード入力欄を減らす。
492 : *
493 : * Created by M.Miyazaki [2014.01.18]
494 : * Modified by M.Miyazaki [2014.05.31]
495 : */
496 : function deleteKeywordField()
497 : {
498 : var index = 2;
499 : while (document.getElementById(KEYWORD_PREFIX + index) != null) { index++; }
500 :
501 : index--;
502 :
503 : if (index == 1) { return; }
504 : //if (index == 2)
505 : //{
506 : // document.getElementById("deleteKeywordFieldButton").style.display = "none"; // invisible!
507 : //}
508 :
509 : var oldBreak = document.getElementById(KEYWORD_PREFIX + "Br" + index);
510 : oldBreak.parentNode.removeChild(oldBreak);
511 :
512 : var oldField = document.getElementById(KEYWORD_PREFIX + index);
513 : oldField.parentNode.removeChild(oldField);
514 :
515 : return;
516 : }
517 :
518 :
519 : //=================
520 : // Objects
521 : //=================
522 :
523 : /**
524 : * 作業ログのオブジェクト。
525 : *
526 : * Created by M.Miyazaki [2013.11.27]
527 : * Modified by M.Miyazaki [2013.11.28]
528 : * Modified by M.Miyazaki [2014.06.01]
529 : *
530 : * @param logText 原本データ(作業ログ)
531 : */
532 : function WorkLog(logText)
533 : {
534 :
535 : //===== Properties =====
536 :
537 : /**
538 : * 作業ナンバー
539 : */
540 : this.workNumber = null;
541 :
542 : /**
543 : * 日付
544 : */
545 : this.workDate = null;
546 :
547 : /**
548 : * 作業タイトル
549 : */
550 : this.workTitle = null;
551 :
552 : /**
553 : * 作業内容
554 : */
555 : this.workDetail = null;
556 :
557 : /**
558 : * 表示対象か否か
559 : */
560 : this.isVisible = true;
561 :
562 : /**
563 : * 原本データ(作業ログ)
564 : */
565 : this.workLogText = logText;
566 :
567 :
568 : //===== Methods =====
569 :
570 : /**
571 : * 原本データに含まれる各プロパティの値を解析する。
572 : */
573 : this.parse = function()
574 : {
575 : var regularExpression = /^■No\.([0-9]+) - ([0-9]+)\.([0-9]+)\.([0-9]+)\([^\)]+\)[=]{10,}(?:\r\n|\n){2}【([^】]*)】(?:\r\n|\n)([\d\D]*)$/;
576 :
577 : var resultArray = this.workLogText.match(regularExpression);
578 : if (resultArray == null) return;
579 :
580 : // 作業ナンバー
581 : this.workNumber = resultArray[1];
582 :
583 : // 日付
584 : var year = resultArray[2];
585 : var month = resultArray[3];
586 : var day = resultArray[4];
587 : this.workDate = new Date(year, month - 1, day);
588 :
589 : // 作業タイトル
590 : this.workTitle = resultArray[5];
591 :
592 : // 作業内容
593 : this.workDetail = resultArray[6].trim();
594 :
595 : return;
596 : }
597 :
598 : /**
599 : * 自分自身を文字列で表現して応答する。
600 : */
601 : this.toString = function()
602 : {
603 : var aString = "";
604 :
605 : aString += "WorkLog(";
606 : aString += this.workNumber;
607 : aString += ",'";
608 : aString += this.workTitle;
609 : aString += "')";
610 :
611 : return aString;
612 : }
613 :
614 :
615 : //===== Accessor =====
616 :
617 : /**
618 : * 作業ナンバーを応答する。
619 : *
620 : * @return 作業ナンバー
621 : */
622 : this.getWorkNumber = function()
623 : {
624 : if (this.workNumber == null) this.parse();
625 :
626 : return this.workNumber;
627 : }
628 :
629 : /**
630 : * 日付を応答する。
631 : *
632 : * @return 日付
633 : */
634 : this.getWorkDate = function()
635 : {
636 : if (this.workDate == null) this.parse();
637 :
638 : return this.workDate;
639 : }
640 :
641 : /**
642 : * 作業タイトルを応答する。
643 : *
644 : * @return 作業タイトル
645 : */
646 : this.getWorkTitle = function()
647 : {
648 : if (this.workTitle == null) this.parse();
649 :
650 : return this.workTitle;
651 : }
652 :
653 : /**
654 : * 作業内容を応答する。
655 : *
656 : * @return 作業内容
657 : */
658 : this.getWorkDetail = function()
659 : {
660 : if (this.workDetail == null) this.parse();
661 :
662 : return this.workDetail;
663 : }
664 :
665 : /**
666 : * 原本データ(作業ログ)を応答する。
667 : *
668 : * @return 作業内容
669 : */
670 : this.getWorkLogText = function()
671 : {
672 : return this.workLogText;
673 : }
674 :
675 : }
676 :
677 : /**
678 : * 検索キーワードのオブジェクト。
679 : *
680 : * Created by M.Miyazaki [2014.06.01]
681 : *
682 : * @param keywordText 検索キーワード
683 : * @param isNegative 否定検索キーワードか否かの真偽値
684 : */
685 : function Keyword(keywordText, isNegative)
686 : {
687 :
688 : //===== Properties =====
689 :
690 : /**
691 : * 検索キーワード
692 : */
693 : this.keywordText = keywordText;
694 :
695 : /**
696 : * 否定検索キーワードか否かの真偽値
697 : */
698 : this.isNegativeKeyword = isNegative;
699 :
700 :
701 : //===== Methods =====
702 :
703 : /**
704 : * 自分自身を文字列で表現して応答する。
705 : */
706 : this.toString = function()
707 : {
708 : var aString = "";
709 :
710 : aString += "Keyword('";
711 : aString += this.keywordText;
712 : aString += "',";
713 : aString += (this.isNegativeKeyword) ? "negative=true" : "negative=false";
714 : aString += ")";
715 :
716 : return aString;
717 : }
718 :
719 :
720 : //===== Accessor =====
721 :
722 : /**
723 : * 検索キーワードを応答する。
724 : *
725 : * @return 検索キーワード
726 : */
727 : this.getKeywordText = function()
728 : {
729 : return this.keywordText;
730 : }
731 :
732 : /**
733 : * 否定検索キーワードか否かの真偽値を応答する。
734 : *
735 : * @return 否定検索キーワードか否かの真偽値
736 : */
737 : this.isNegative = function()
738 : {
739 : return this.isNegativeKeyword;
740 : }
741 :
742 : }
743 :
744 :
745 : //====================
746 : // Extensions
747 : //====================
748 :
749 : /**
750 : * 文字列にtrimメソッドを追加する。
751 : *
752 : * Quoted from https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/trim
753 : */
754 : if (!String.prototype.trim)
755 : {
756 : String.prototype.trim = function()
757 : {
758 : return this.replace(/^\s+|\s+$/g, "");
759 : }
760 : }
761 :
762 : /**
763 : * 非同期通信用オブジェクトを設定する。(Windowsへの対応)
764 : *
765 : * Quoted from http://itpro.nikkeibp.co.jp/article/COLUMN/20070613/274683/
766 : */
767 : if (window.ActiveXObject)
768 : {
769 : window.XMLHttpRequest = function()
770 : {
771 : try
772 : {
773 : return (new ActiveXObject("Microsoft.XMLHTTP"));
774 : }
775 : catch (ignore) {}
776 :
777 : return null;
778 : }
779 : }
780 :
This document was generated by NanigashiBiyori on 2014/06/02 at 00:25:56.