20200406のHTMLに関する記事は4件です。

タグの中身だけ取り出したい正規表現

たとえば、

<div>aaaaa</div>

このdivの中身だけ取り出したい場合に使う正規表現は

/(?<=<div>).*?(?=<\/div>)/

(?<=

これは、肯定的後読みという
わかりやすく書くと、

(?<= aa )
直前にaaがあるということ

(?=<

これは肯定的先読みという
わかりやすく書くと

(?=< aa )
直後にaaがあるやつ

なので(?<=<div>).*?(?=<\/div>)
<div>の直後で、</div>の前にあるやつってかんじ 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スプレッドシートをDB代わりにHTMLで更新してみた

はじめに

GoogleスプレッドシートをGAS(GooglAppsScript)を使って
ACCESSのようなデータベース代わりに使えるよう考えてみた。
インターフェースはHTMLベースだが、これもGASにて提供されている。
よって、自分で用意するサーバはない。
自分用の住所録にするもいいと思うし、
ブログするのは嫌な人は、趣味のことを日記風にまとめてもいいんじゃないかと思う。
また、会社での案件管理をエクセルで行って行っているのであれば
こちらに移行するのも手ではないかと思う。
以下お読みになっていただければと思う。
セキュリティ範囲は自己責任でお願いしたく。

用意するもの

・Googleアカウント(スプレッドシートが使えること)

ここに記載しないこと

・GASからHTMLを表示する際に初めに知っておかなければならないこと
 こちらを参考にしていただきたく:Google Apps Script でHTMLファイルを作って表示
・排他制御の説明
 こちらを参考にしていただきたく:排他制御でGoogle Apps Scriptを安全に実行
・ソートの説明:
 こちらを参考にしていただきたく:Google Apps Script試行錯誤Blog
・CSS周りは考慮しない。(見た目は気にしない)(気が向いたら次にやる)

ゴール

・データベース内容は吉野家の(一部の)店舗一覧とする。
 →理由:日々お世話になっているから。
 →データの内容については、検証のために入力している内容であって実際とはなんの関係もございません。
・一覧表示ができること
 →ただし、全件出てきても意味がないので、最新更新の10件を表示するものとする。
 →全項目を表示すると、項目が増えた時に見た目が悪いので、スプレッドシートの左から6列を表示するものとする。
 一覧画面はこんな感じ↓
image.png

・検索できること(すべてのデータから文言検索)
・新規登録ができること(「新規はこちら」から遷移)
・編集登録ができること(「編集」から遷移)
・上記画面を遷移できること

その他考慮したこと

・項目が増えた時の対応
 →項目を追加したいことはよくあることで、スプレッドシートに追加し、コード.gsにも例にならって追加すれば対応できるようにはした。(※スプレッドシートに記載すれば増えていくようにしたかったが、力が足らず実現できなかった。)

やってはいけないこと

・スプレッドシートの行削除
 →いらないからといって行を削除してしまうとうまく動作しなかった。
  行に書き込むと、そこまで書かれていたことを記憶しているようで
  見た目のMAX行と「MAX行教えて~」と問い合わせた時の行が異なる。
  削除したいなら、異なるデータで上書き保存すること。

データ

どうやって記載したらいいのかわからないので、とりあえずcsv形式で張っておきます。
どうにかしてスプレッドシートに張り付けてください。
私は下記をコピーして、スプレッドシートのA1を選択、右クリックして、特殊貼り付け、値のみでできました。
タイムスタンプの表示がおかしくなるので、B列を選択、メニューバーの「123▼」をクリックし、日付を選択してできました。

データ
リンク,タイムスタンプ,店舗名,郵便番号,県,住所,電話番号,駐車場,駐輪場,感想
2,2020/02/05 14:57:55,吉野家 市ヶ谷1号店,102-00763,東京都,千代田区神田1丁目,,あるかも,,うまかった6
3,2020/02/06 14:57:55,吉野家 秋葉原1号店,101-0021,東京都,千代田区外神田1丁目,,あるような,,おいしかった
4,2020/02/07 14:57:55,吉野家 有楽町1号店,100-0006,東京都,千代田区有楽町2丁目,,,,
5,2020/02/08 14:57:55,吉野家 有楽町2号店,100-0006,東京都,千代田区有楽町2丁目,,,,
6,2020/02/09 14:57:55,吉野家 永田町1号店,100-0014,東京都,千代田区永田町1丁目,,,,
7,2020/02/10 14:57:55,吉野家 水道橋1号店,101-0061,東京都,千代田区神田三崎町2丁目,,,,
8,2020/02/11 14:57:55,吉野家 小川町1号店,101-0052,東京都,千代田区神田小川町2丁目,,,,
9,2020/02/12 14:57:55,吉野家 秋葉原1号店,101-0023,東京都,千代田区神田松永町,,,,
10,2020/02/13 14:57:55,吉野家 神保町1号店,101-0051,東京都,千代田区神田神保町2丁目,,,,
11,2020/02/14 14:57:55,吉野家 淡路町1号店,101-0041,東京都,千代田区神田須田町1丁目,,,,
12,2020/02/15 14:57:55,吉野家 お茶の水1号店,101-0062,東京都,千代田区神田駿河台2丁目,,,,
13,2020/02/16 14:57:55,吉野家 神田1号店,101-0044,東京都,千代田区鍛冶町2丁目,,,,
14,2020/02/17 14:57:55,吉野家 霞ヶ関1号店,100-8918,東京都,千代田区霞が関,,,,
15,2020/02/18 14:57:55,吉野家 霞が関2号店,100-0013,東京都,千代田区霞が関,,,,
16,2020/02/19 14:57:55,吉野家 帯広1号店,080-0011,北海道,帯広市西1条南,,,,
17,2020/02/20 14:57:55,吉野家 旭川1号店,078-8231,北海道,旭川市豊岡1条,,,,
18,2020/02/21 14:57:55,吉野家 旭川2号店,070-0034,北海道,旭川市4条通2丁目,,,,
19,2020/02/22 14:57:55,吉野家 岩見沢1号店,068-0825,北海道,岩見沢市日の出町,,,,
20,2020/02/23 14:57:55,吉野家 新千歳1号店,006-0012,北海道,千歳市美々 新千歳空港,,,,
21,2020/02/24 14:57:55,吉野家 千歳2号店,066-0036,北海道,千歳市北栄2丁目,,,,
22,2020/02/25 14:57:55,吉野家 1厚別東1号店,004-0004,北海道,札幌市厚別区厚別東4条3丁目,,,,
23,2020/02/26 14:57:55,吉野家 苫小牧新開町店,053-0052,北海道,苫小牧市新開町2丁目,,,,
24,2020/02/27 14:57:55,吉野家 羊ヶ丘通清田店,004-0842,北海道,札幌市清田区清田2条3丁目,,,,
25,2020/02/28 14:57:55,吉野家 札幌伏古店,007-0871,北海道,札幌市東区伏古11条4丁目,,,,
26,2020/02/29 14:57:55,吉野家 南郷通6丁目店,003-0023,北海道,札幌市白石区南郷通6丁目北,,,,
27,2020/03/01 14:57:55,吉野家 環状通美園店,062-0004,北海道,札幌市豊平区美園4条6丁目,,,,
28,2020/03/02 14:57:55,吉野家 36号線苫小牧店,053-0021,北海道,苫小牧市若草町3丁目,,,,
29,2020/03/03 14:57:55,吉野家 環状通東店,065-0015,北海道,札幌市東区北15条東17丁目,,,,
30,2020/03/04 14:57:55,吉野家 231号線篠路店,002-8022,北海道,札幌市北区篠路2条1丁目,,,,
31,2020/03/05 14:57:55,吉野家 札幌エスタ店,060-0005,北海道,札幌市中央区北5条西2丁目,,,,
32,2020/03/06 14:57:55,吉野家 札幌狸小路店,060-0063,北海道,札幌市中央区南3条西4丁目,,,,
33,2020/03/07 14:57:55,吉野家 札幌駅APIA店,060-0005,北海道,札幌市中央区北5条西4丁目,,,,
34,2020/03/08 14:57:55,吉野家 札幌麻生店,001-0040,北海道,札幌市北区北40条西5丁目,,,,
35,2020/03/09 14:57:55,吉野家 大通西10丁目店,060-0042,北海道,札幌市中央区大通西10丁目,,,,
36,2020/04/03 16:44:33,吉野家 春吉店,810-0003,福岡県,福岡市中央区春吉3丁目,,,,神奈川と姉妹店?
37,2020/04/03 13:27:54,吉野家 天神サザン通り店,810-0001,福岡県,福岡市中央区天神2丁目,,,,サザンだぜ
38,2020/03/12 14:57:55,吉野家 博多祇園店,812-0038,福岡県,福岡市博多区祇園町,,,,
39,2020/03/13 14:57:55,吉野家 築港本町店,812-0022,福岡県,福岡市博多区神屋町,,,,
40,2020/03/14 14:57:55,吉野家 博多駅地下街店,812-0012,福岡県,福岡市博多区博多駅中央街,,,,
41,2020/03/15 14:57:55,吉野家 博多デイトスアネックス店,812-0012,福岡県,福岡市博多区博多駅中央街,,,,
42,2020/03/16 14:57:55,吉野家 港町店,810-0075,福岡県,福岡市中央区港2丁目,,,,
43,2020/03/17 14:57:55,吉野家 東光寺店,812-0896,福岡県,福岡市博多区東光寺町1丁目,,,,
44,2020/03/18 14:57:55,吉野家 3号線箱崎店,812-0053,福岡県,福岡市東区箱崎2丁目,,,,
45,2020/04/03 13:28:29,吉野家 福岡空港国際線ターミナル店,812-0851,福岡県,福岡市博多区青木739 福岡空港,,,,空港だってうまいぜ
46,2020/03/20 14:57:55,吉野家 大橋店,815-0033,福岡県,福岡市南区大橋1丁目,,,,
47,2020/03/21 14:57:55,吉野家 早良街道西新店,814-0004,福岡県,福岡市早良区曙1丁目,,,,
48,2020/03/22 14:57:55,吉野家 大池通り長丘店,815-0075,福岡県,福岡市南区長丘5丁目,,,,
49,2020/03/23 14:57:55,吉野家 福岡原田店,812-0063,福岡県,福岡市東区原田3丁目,,,,
50,2020/04/03 13:55:45,吉野家 粕屋仲原店,811-2304,福岡県,糟屋郡 粕屋町仲原,,,,やすい
51,2020/04/03 13:48:19,吉野家 202号線バイパス原店,814-0022,福岡県,福岡市早良区原4丁目,,,,バイパス横も最高
52,2020/04/03 13:16:15,吉野家 千早東店,813-0036,福岡県,福岡市東区若宮5丁目,,,,ここもうまい
53,2020/03/27 14:57:55,吉野家 385号線那珂川店,811-1204,福岡県,那珂川市片縄東1丁目,,,,福岡はいい!
54,2020/04/03 16:05:57,吉野家 宇都宮細谷町店,320-0074,栃木県,宇都宮市細谷町,99999999999,,,いいよ
55,2020/04/03 14:06:45,吉野家 宇都宮競輪場通り店,320-0013,栃木県,宇都宮市上大曽町,,,,gggg
56,2020/04/03 14:09:36,吉野家 宇都宮城東店,321-0935,栃木県,宇都宮市城東1,,,,hhhhh
57,2020/04/06 13:56:41,吉野家 広島本通店,730-0035,広島県,広島市 中区 本通8,1234567890,,,うまい
58,2020/04/06 14:15:03,吉野家 カインズホーム木更津店,292-0008,千葉県,木更津市金田東3丁目,,,,
59,2020/04/03 17:20:54,吉野家 厚木関口店,243-0804,神奈川県,厚木市関口,111111111,あり,,
60,2020/04/06 14:08:28,吉野家 松山竹原店,790-0053,愛媛県,松山市竹原2丁目,222334455,●かな,●かな,うまい
61,2020/04/06 13:59:59,吉野家 広島宇品店,734-0014,広島県,広島市南区宇品西3丁目,12345678,あったかな,あると思う,うまーい
62,2020/04/06 13:57:54,吉野家 広島吉島店,730-0823,広島県,広島市中区吉島西2丁目,,,,
63,2020/04/06 14:13:03,そば処吉野家 弘前中央通り店,036-8012,青森県,弘前市北瓦ヶ町7-1,,,,そばもあるの

それではコード

お恥ずかしいコードですが、公開します。
全部で4ファイルです。
コード.gs :スプレッドシートからスクリプトエディタを選択した時にできるファイル
index.html :一覧表示、検索一覧表示
result.html :編集画面
newPage.html :新規登録画面

コード.gs
//スプレッドシートの項目が増えた場合、以下の関数を修正してください。
function getParamet(ary1, e) {
  ary1.push(e.parameters.d_item2);
  ary1.push(e.parameters.d_item3);
  ary1.push(e.parameters.d_item4);
  ary1.push(e.parameters.d_item5);
  ary1.push(e.parameters.d_item6); //G列
  // -----------------------------------------------------------------------
  //項目が増えたら増やす
  ary1.push(e.parameters.d_item7); //H列
  ary1.push(e.parameters.d_item8); //I列
  ary1.push(e.parameters.d_item9); //J列


  // -----------------------------------------------------------------------
  return ary1;
}

function doGet(e) {
  if (e.parameter.mode == 'list' || e.parameter.mode == null) {
    var template = HtmlService.createTemplateFromFile('index');
    var ItemNameList = getItemNameList(6);

    Logger.log(ItemNameList);

    template.itemName = ItemNameList;
    tmp1 = getNewDataList();
    //Logger.log(tmp1);
    var res3 = [];
    for(var i = 0; i < tmp1.length; i++){
      res3.push(getCellValue(tmp1[i][0], 6));
    }
    template.res = res3;

  } else if (e.parameter.mode == 'edit') {
    var template = HtmlService.createTemplateFromFile('result');
    var ItemNameList = getItemNameList(-1);
    template.itemName = ItemNameList;
    var editRow = e.parameter.row;
    template.row = editRow;
    template.res = getCellValue(editRow, -1);
    Logger.log(editRow);
  }
  if (e.parameter.mode == 'new') {
    var template = HtmlService.createTemplateFromFile('newPage');
    var ItemNameList = getItemNameList(-1);
    template.itemName = ItemNameList;
  } 
  return template.evaluate();
}

function doPost(e) {
  Logger.log('ここから');
  Logger.log(e.parameter.mode);
  Logger.log('ここまで');
  var spread = SpreadsheetApp.getActiveSpreadsheet() ;
  var sheet = spread.getSheetByName('database');
  var date = new Date();
  // 今日の日付を表示
  date1 = Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');

  var template = HtmlService.createTemplateFromFile('index');
  var ItemNameList = getItemNameList(6);
  template.itemName = ItemNameList;  

  if (e.parameter.mode == 'edit') {
    //編集時
    var ary1 = [];
    var d_row = e.parameters.d_item0;
    ary1.push(d_row);
    ary1.push(date1);
    ary1 = getParamet(ary1, e);
    var ary2 = [];
    ary2.push(ary1);
    dataNewAdd(sheet, d_row,1,1,ary2[0].length, ary2);
    //sheet.getRange(d_row,1,1,ary2[0].length).setValues(ary2); 


    tmp1 = getNewDataList();
    var res3 = [];
    for(var i = 0; i < tmp1.length; i++){
      res3.push(getCellValue(tmp1[i][0], 6));
    }
    template.res = res3;


  }else if (e.parameter.mode == 'new') {
    //新規作成時
    var ary1 = [];
    ary1.push(sheet.getLastRow()+1);
    ary1.push(date1);
    ary1 = getParamet(ary1, e);

    var ary2 = [];
    ary2.push(ary1);

    dataNewAdd(sheet, sheet.getLastRow()+1, 1, 1, ary2[0].length, ary2);
    //sheet.getRange(sheet.getLastRow()+1,1,1,ary2[0].length).setValues(ary2); 


    tmp1 = getNewDataList();
    var res3 = [];
    for(var i = 0; i < tmp1.length; i++){
      res3.push(getCellValue(tmp1[i][0], 6));
    }
    template.res = res3;

  } else {
    //検索時   
    var res2 = rowSearch(e.parameter.search);
    var res3 = [];
    for(var i = 0; i < res2.length; i++){
      res3.push(getCellValue(res2[i], 6));
    }
    //<?= res ?> がHTMLにあること
    template.res = res3;
  }  
  return template.evaluate();
}

//更新や新規登録を行う
//排他制御を掛けるので更新処理を一か所にまとめた
function dataNewAdd(sheet, row1, col1, row2, col2, data){
  //UIを取得する
  //var ui = SpreadsheetApp.getUi();
  var msg = "";

  //ドキュメントロックを使用する
  var lock = LockService.getDocumentLock();

  //30秒間のロックを取得
  try {
    //ロックを実施する
    lock.waitLock(30000);

    //ここにメインルーチンを記述する
    sheet.getRange(row1, col1, row2, col2).setValues(data);

    //メッセージを格納
    msg = "保存完了";

  } catch (e) {
    //ロック取得できなかった時の処理等を記述する
    var checkword = "ロックのタイムアウト: 別のプロセスがロックを保持している時間が長すぎました。";

    //通常のエラーとロックエラーを区別する
    if(e.message == checkword){
      //ロックエラーの場合
      msg = "更新処理中でした";
    }else{
      //ソレ以外のエラーの場合
      msg = e.message;
    }    

  } finally {
    //ロックを開放する
    lock.releaseLock();

    //メッセージを表示する
    //ui.alert(msg);
  }
}

//エラーメッセージを保存


//項目名称を取得する
function getItemNameList(col){
  var res = [];
  var spread = SpreadsheetApp.getActiveSpreadsheet() ;
  var sheet = spread.getSheetByName('database');
  if (col == -1) {
    var values = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
  } else {
    var values = sheet.getRange(1, 1, 1, col).getValues();
  }
  Logger.log('values getItemNameList');
  Logger.log(values);
  return values;
}

//タイムスタンプの新しい10件を取得する。[行番号,日付]の二次元配列で時間で降順
//スプレッドシートの左から6列までとする
function getNewDataList(){
  var res = [];
  var spread = SpreadsheetApp.getActiveSpreadsheet() ;
  var sheet = spread.getSheetByName('database');
  var values = sheet.getRange(2, 1, sheet.getLastRow()-1, 2).getValues();
  //Logger.log('values before');
  //Logger.log(values);
  //ソート sorting_asc sorting_desc
  values.sort(sorting_desc);
  Logger.log('values after');
  Logger.log(values);
  for (var i = 0; i < 10; i++){
    res.push(values[i]);
  }
  return res;
}

//スプレッドシート内を文言で検索し行番号を返す(同じ行内に複数出てくると抽出結果も重複する
function rowSearch(str){
  var res = [];
  var spread = SpreadsheetApp.getActiveSpreadsheet() ;
  var sheet = spread.getSheetByName('database');
  var textFinder = sheet.createTextFinder(str);
  var ranges = textFinder.findAll();
  for(var i = 0; i < ranges.length; i++){
    var range = sheet.getRange(ranges[i].getA1Notation());
    res.push(range.getRow());
  }
  var res2 = uniqueArray(res);

  return res2;
}

//行番号からセル値を取得
function getCellValue(row, col){
  // 現在アクティブなスプレッドシートを取得
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getSheetByName('database');
  // そのシートにある (1, 1) のセルから3行目までのセル範囲を取得
  if (col == -1 ) {
    var range = sheet.getRange(row, 1, 1, sheet.getLastColumn());
  } else {
    var range = sheet.getRange(row, 1, 1, col);
  }
  // そのセル範囲の値を取得
  var values = range.getValues(); 
  values[0][0] = row;
  values[0][1] = Utilities.formatDate( values[0][1], 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');

  return values[0];
}

//一次配列から重複を排除する
function uniqueArray(ary){
  var res = [];
  res = ary.filter(function(value, index, self){ 
                       return self.indexOf(value) === index;
                   });
  return res;
}
//ソート昇順
function sorting_asc(a, b){
  if(a[1] < b[1]){
    return -1;
  }else if(a[1] > b[1] ){
    return 1;
  }else{
   return 0;
  }
}
//ソート降順
function sorting_desc(a, b){
  if(a[1] > b[1]){
    return -1;
  }else if(a[1] < b[1] ){
    return 1;
  }else{
   return 0;
  }
}
index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action='https://script.google.com/macros/ここに公開時のURLを記載/exec'>
      <input type=hidden name=mode value='list' />mode: list
      <h2>吉野家一覧画面</h2>
      初期表示は、スプレッドシートの2列目のタイムスタンプで最新の10行、6列を表示します。<br>
      そのため、一覧表示したいものは左から6項目内にするようにします。<br>
      ただし、左の2列はそのままにしてください。よって、残りの4項目になります。(※ソースを変更すれば項目数は調整できます)<br>
      検索時は、スプレッドシート全体から対象文言を検索し、ヒットした行すべてを表示します。<br>
      スプレッドシートの項目を追加(1行目の項目名を追加)する場合は、「コード.gs」のgetParamet関数の<br>
      「//項目が増えたら増やす」の下にある書き方にならって増やしてください。<br>

      <br>
      新規は<a href='https://script.google.com/macros/ここに公開時のURLを記載/exec?mode=new'>こちら</a><br>
      検索:<input type='text' name=search />
      <input type='submit' value='検索' /><br><br>

      <table border=1>
        <!-- 項目名を取得し、表示する   -->
        <? for (var g = 0; g < itemName.length; g++) { ?>
        <tr>
          <? for (var h = 0; h < itemName[g].length; h++) { ?>
              <td><?= itemName[g][h] ?></td>
          <? } ?>
        </tr>
        <? } ?>
        <!-- データを取得し、表示する   -->
        <? for (var i = 0; i < res.length; i++) { ?>
        <tr>
          <? for (var j = 0; j < res[i].length; j++) { ?>
            <? if (j == 0) {   ?>
              <td><a href='https://script.google.com/macros/ここに公開時のURLを記載/exec?mode=edit&row=<?= res[i][j] ?>'>編集</a></td>
            <? } else { ?>
              <td><?= res[i][j] ?></td>
            <? } ?>
          <? } ?>
        </tr>
        <? } ?>
      </table>
    </form>
  </body>
</html>

更新画面↓

result.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action='https://script.google.com/macros/ここに公開時のURLを記載/exec'>
      <input type=hidden name=mode value='edit' />mode: edit
      <h2>吉野家 編集画面</h2>
      <br>
      <table border=1> 
        <!-- 項目名を取得し、表示する   -->

        <? for (var h = 0; h < itemName[0].length; h++) { ?>
        <tr> 
          <td><?= itemName[0][h] ?></td>
          <? if ( h == 0 ) { ?>
            <td><input type=hidden name=d_item<?= h ?> value='<?= res[h] ?>' /><?= res[h] ?></td>

          <? } else if (h == 1) { ?>
            <td><input type=hidden name=d_item<?= h ?> /><?= res[h] ?></td>

          <? } else { ?>
            <td><input type=text name=d_item<?= h ?> value='<?= res[h] ?>' /></td>

          <? } ?>
        </tr>
        <? } ?>
      </table>
      <input type='submit' value='更新' /><br>
      <a href='https://script.google.com/macros/ここに公開時のURLを記載/exec?mode=list'>検索ページへ</a>
    </form>
  </body>
</html>

新規登録画面↓

newPage.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    <form method="post" action='https://script.google.com/macros/ここに公開時のURLを記載/exec'>
      <input type=hidden name=mode value='new' />mode: new<br>
      <h2>吉野家 登録画面</h2>
      <br>
      <table border=1> 
        <!-- 項目名を取得し、表示する   -->

        <? for (var h = 0; h < itemName[0].length; h++) { ?>
        <tr> 
          <td><?= itemName[0][h] ?></td>
          <? if ( h == 0 ) { ?>
            <td><input type=hidden name=d_item<?= h ?> /></td>
          <? } else if (h == 1) { ?>
            <td><input type=hidden name=d_item<?= h ?> /></td>   
          <? } else { ?>
            <td><input type=text name=d_item<?= h ?> /></td>   
          <? } ?>
        </tr>
        <? } ?>
      </table>
      <input type='submit' value='登録' /><br>
      <a href='https://script.google.com/macros/ここに公開時のURLを記載/exec?mode=list'>検索ページへ</a>
    </form>
  </body>
</html>

以上、誰かのお役に立てれば幸いです。

でわでわ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

storybook for htmlを使って簡単にコンポーネントのカタログを作成する

対象読者

  • React.js,Vue.jsを使っていないプロジェクトでstorybookを導入したいけどやり方がわからない人
  • マークアップ言語 + JavaScriptちょっとわかる人
  • storybookの勉強はしたいけど、React.js,Vue.jsの勉強まではやりたくない人

成果物

URL

https://miwa-storybook-html.netlify.com/

GitHub

現在作成中です。

https://github.com/miwashutaro0611/html-storybook

対応環境

言語

  • HTML
  • SCSS

cssで行いたい場合、「既存のscssファイルの読み込み」の部分などのscsscssに変えたり、「storybook上でscssファイルが読み込まれるように設定」の部分を飛ばすなどで対応することができると思います。(未検証)

バージョン

  • node: v12.16.1
  • @storybook/html: "^5.3.18"
  • node-sass: "^4.13.1"

セットアップ

参考: Storybook for HTML

自動セットアップ

$ npx -p @storybook/cli sb init --type html

ゼロから作成する場合

ゼロから設定を行う方法を見る

1. package.json作成。必要なパッケージのインストール

$ npm init
$ npm i -D @storybook/html babel-loader @babel/core

パッケージ一覧

2. npm scriptの追加

package.json
{
    "scripts": {
      "storybook": "start-storybook"
    }
}

3. .storybook/main.jsの作成

$ mkdir .storybook
$ touch .storybook/main.js

.storybook/main.jsに記載する内容

module.exports = {
    stories: ['../src/**/*.stories.[tj]s'],
};

上記にように記載した場合、以下のような感じで読み込みが行われる

├── .storybook
│   └── main.js
└── src 
    ├── index.stories.js # 対象ファイル
    └── components # 対象ディレクトリ
        └── index.stories.js # 対象ファイル

4. ストーリーファイルの作成

export default { title: 'Button' }

export const withText = () => '<button class="btn">Hello World</button>' // HTMLで書く場合

export const withEmoji = () => {
  const button = document.createElement('button') // button要素作成
  button.innerText = '? ? ? ?' // button要素のテキスト
  return button
};

storybook上でscssファイルが読み込まれるように設定

参考: Custom Webpack Config

必要なパッケージのインストール

$ npm i -D style-loader css-loader sass-loader

パッケージ一覧

.storybook/main.jsにscss読み込み設定を記載

const { resolve } = require('path')

module.exports = {
  webpackFinal: async(config, {configType}) => {
    config.module.rules.push({
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        'sass-loader'
      ],
      include: resolve(__dirname, '../'),
    })
    return config
  }
}

storybookに読み込みを行うコンポーネントを作成

.htmlを使ってHTMLの記載を行いたい場合

├── _index.scss # スタイル用ファイル
├── index.html # テンプレート用ファイル
└── index.stories.js # storybook用ファイル

_index.scss

.c-title {
  font-size: 40px;
  span {
      font-size: 60px;
  }
}

index.html

<p class='c-text'>Hello <span>World!!</span></p>

index.stories.js

import Templete from './index.html'
import './_index.scss'

export default {
  title: 'Heading',
}

export const HeadingTem = () =>  {
  return Templete
}

テンプレート内に入れる

├── _index.scss # スタイル用ファイル
├── index.js # テンプレート用ファイル
└── index.stories.js # storybook用ファイル

_index.scss

.c-title {
  font-size: 40px;
  span {
      font-size: 60px;
  }
}

index.js

export const Template = () => {
  return `
    <p class='c-text'>Hello <span>World!!</span></p>
  `
}

index.stories.js

import Templete from './index.js'
import './_index.scss'

export default {
  title: 'Heading',
}

export const HeadingTem = () =>  {
  return Templete
}

既存のscssファイルの読み込み

参考: Default Config

  • reset.cssなどの読み込み
  • 上記のようなコンポーネントごとにscssファイルを記載したくない時
  • プロジェクトのscssをそのまま使いたい時

の時に.storybook/config.jsに読み込みを行うことで既存のscssファイルを読み込むこともできる。

.storybook/config.jsにscss読み込み設定を記載

import '../scss/style.scss' // 読み込みを行いたいscssファイル

アドオン一覧

アドオン参考リンク

スクリーンショット 2020-04-05 3.35.50.png

※2020年4月5日時点

今回使ったアドオン一覧

@pickra/copy-code-block

HTMLのソースコードを表示・コピーすることができるアドオン

インストール

$ npm i @pickra/copy-code-block

使い方

コンポーネントファイル

import copyCodeBlock from '@pickra/copy-code-block'
import Templete from './index.html'

export const TempleteCode = () =>  {
  return (Templete + copyCodeBlock(Templete)) // テンプレートの内容 + コードブロックを表示
}

@storybook/addon-knobs

テンプレート内のテキストや状態を動的に変更を行うことができるアドオン

インストール

$ npm i @storybook/addon-knobs

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-knobs/register']
}

コンポーネントファイル

import { withKnobs, text } from '@storybook/addon-knobs'

export default {
  decorators: [withKnobs]
}

export const Template = () => {
  const tmpText = text('Text', 'Hello World!!')
  return `
    <p class='c-text'>${tmpText}</p>
  `
}

@storybook/addon-a11y

コンポーエント内のアクセシビリティをチェックするアドオン

インストール

$ npm i @storybook/addon-a11y

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-a11y/register']
}

ファイル全体に適応させたい場合、.storybook/config.jsに以下を記載

import { addDecorator } from '@storybook/html'
import { withA11y } from '@storybook/addon-a11y'

addDecorator(withA11y)

@storybook/addon-storysource

ストーリーのソースを表示するアドオン

インストール

$ npm i @storybook/addon-storysource

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-storysource'],
};

@storybook/addon-notes

テキストやマークダウン形式でストーリーの説明部などを追加することができるアドオン

インストール

$ npm i @storybook/addon-notes

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-notes/register'],
};

コンポーネントファイル

import README from './README.md' // コンポーネントと同じ階層にファイルを配置

export default {
  title: 'Heading',
  parameters: {
    notes: { README }
  }
}

@storybook/addon-viewport

インストール

$ npm i @storybook/addon-viewport

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-viewport/register']
}

サイズをカスタマイズしたい場合
既存だと「iphone5」などのサイズがないので、.storybook/config.jsをカスタマイズして、任意のサイズでの表示されるようにする。

import { addParameters } from '@storybook/html'

const customViewports = {
  iPhone5: {
    name: 'iPhone5',
    styles: {
      width: '320px',
      height: '568px',
    },
  },
  iPhone6: {
    name: 'iPhone6,7,8',
    styles: {
      width: '375px',
      height: '667px',
    },
  },
  iPhoneX: {
    name: 'iPhoneX',
    styles: {
      width: '375px',
      height: '812px',
    },
  },
  iPhoneXsMax: {
    name: 'iPhoneXsMax',
    styles: {
      width: '414px',
      height: '896px',
    },
  },
}

addParameters({
  viewport: { viewports: customViewports }
})

@storybook/addon-docs

ストーリーをドキュメント形式で表示してくれるアドオン 

インストール

$ npm i @storybook/addon-docs

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-docs'],
};

@storybook/addon-backgrounds

storybookの背景色を変更することができるアドオン

インストール

$ npm i @storybook/addon-backgrounds

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['@storybook/addon-backgrounds/register']
};

.storybook/config.jsに以下を記載

import { addParameters } from '@storybook/html'

addParameters({
  backgrounds: [
    { name: 'white', value: '#fff', default: true }, // デフォルトの色を設定したい場合、「default: true」を記載。
    { name: 'black', value: '#333' },
    { name: 'facebook', value: '#3b5998' },
    { name: 'twitter', value: '#00aced' },
  ]
})

storybook-dark-mode/register

storybookのテーマカラーをダークモード・ライトモードで切り替えを行うことができるアドオン

インストール

$ npm i storybook-dark-mode

使い方

.storybook/main.jsに以下を記載

module.exports = {
  addons: ['storybook-dark-mode/register']
};

.storybook/config.jsに以下を記載

import { addParameters } from '@storybook/html'

addParameters({
  darkMode: {
    dark: { ...themes.dark, appBg: 'black' },
    light: { ...themes.normal, appBg: 'white' }
  }
})

20200309233915.png

画像のようなエラーが出た場合、@storybook/themingのインストールを行う。

$ npm i @storybook/theming

.storybook/config.jsに「themes」を記載

import { addParameters } from '@storybook/html'
import { themes } from '@storybook/theming' // これを追記

addParameters({
  darkMode: {
    dark: { ...themes.dark, appBg: 'black' },
    light: { ...themes.normal, appBg: 'white' }
  }
})

アドオンを読みこんだ後の設定ファイルの中身

.storybook/main.js

const { resolve } = require('path')

module.exports = {
  stories: ['../components/**/*.stories.js'],
  addons: [
    '@storybook/addon-knobs/register',
    '@storybook/addon-a11y/register',
    '@storybook/addon-storysource/register',
    '@storybook/addon-notes/register',
    '@storybook/addon-viewport/register',
    '@storybook/addon-docs',
    '@storybook/addon-backgrounds/register',
    'storybook-dark-mode/register'
  ],
  webpackFinal: async(config, {configType}) => {
    config.module.rules.push({
      test: /\.scss$/,
      use: [
        'style-loader',
        'css-loader',
        'sass-loader'
      ],
      include: resolve(__dirname, '../'),
    })
    return config
  }
}

.storybook/config.js

import { configure, addParameters, addDecorator } from '@storybook/html'
import { withA11y } from '@storybook/addon-a11y'
import { themes } from '@storybook/theming'

import '../scss/style.scss'

addDecorator(withA11y)

const customViewports = {
  iPhone5: {
    name: 'iPhone5',
    styles: {
      width: '320px',
      height: '568px',
    },
  },
  iPhone6: {
    name: 'iPhone6,7,8',
    styles: {
      width: '375px',
      height: '667px',
    },
  },
  iPhoneX: {
    name: 'iPhoneX',
    styles: {
      width: '375px',
      height: '812px',
    },
  },
  iPhoneXsMax: {
    name: 'iPhoneXsMax',
    styles: {
      width: '414px',
      height: '896px',
    },
  },
}

addParameters({
  viewport: { viewports: customViewports },
  backgrounds: [
    { name: 'white', value: '#fff', default: true },
    { name: 'black', value: '#333' },
    { name: 'facebook', value: '#3b5998' },
    { name: 'twitter', value: '#00aced' },
  ],
  darkMode: {
    dark: { ...themes.dark, appBg: 'black' },
    light: { ...themes.normal, appBg: 'white' }
  }
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 282日目

100日チャレンジの282日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
282日目は、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む