20200918のJavaScriptに関する記事は20件です。

[html,js]ヒストリーバック リンク書き方

1つ前に戻る

<a href="javascript:history.back();>1つ前に戻る</a>

2つ前に戻る

<a href="javascript:history.go(-2);><2つ前に戻る</a>

windowオブジェクトが持っているメソッド臭い。いろんな書き方があるようだ。

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

Google Maps API キーをHTMLから隠す方法

Maps JavaScript API を使うとき、APIキーが心配

Google は、公式サイトで API キーをコードに直接埋め込まないでくださいと言っている割には、[公式サンプル]のコーディングが以下の様になっていて、堂々とHTMLの中にYOUR_API_KEYが登場している。
これでwebページ作ったら世界中にAPI キーを公開してしまうことになる。まるで、堂々とフリチンで公道を歩いている様な状態。公式サンプルは裸の王様か?

<script defer
  src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
</script>

一応 Google のドキュメント[API キーの制限を適用する]には「HTTP リファラー(ウェブサイト)」による制限が必須だと書いてはあるが、これだけでは非常に心許ない。
皆さんご存じの通り、ウエブページの名前なんてローカルにwebサーバー建てて hosts をいじれば簡単に詐称できるし・・・(私はそんなことやってませんよ。念のため)
もちろん、知らない間に課金されないように、[API 使用の上限設定]は実施しているが、どこかの誰かにAPIキーを勝手に使われて100万回とかアクセスされたらどうしよう?と思うと夜も眠れない・・・

APIキーは見えないところに隠そう

やっぱりAPIキーは、HTMLで公開したくない!!
ということで CGI を使って API キーをサーバーの中に隠しておくことにしました。

1.環境変数にセット

CGI にはAPIキーを環境変数にセットした状態で渡します。私の環境は nginx + fcgiwrap で CGI を動かしているので、/etc/nginx/fcgiwrap.conf の一番下に以下の様にセット。

fcgiwrap.conf
location /cgi-bin/ {
    ・・・・・・
    fastcgi_param GOOGLE_MAPS_API_KEY  YOUR_API_KEY; <=YOUR_API_KEYを自分のキーに置き換えてください
}

ここは、皆さんの環境(apacheなど)毎に設定方法が異なるので、ご自分の環境に合わせて設定してください。

2.CGI を用意

方針としては、公式サンプルの src="https://maps.googleapis.com/maps/api/js の部分を CGI で置き換える。
私の環境では python を使っているので、こんな感じになります。

getapijs.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import requests
import os
url = 'https://maps.googleapis.com/maps/api/js' # 公式サンプルの HTML が src= で読んでたurl
key = os.environ['GOOGLE_MAPS_API_KEY'] # 環境変数に入っている APIキーを取り出す
mysrc = url + "?key=" + key # url に APIキーを連結する
response = requests.get(mysrc) # google マップのサイトにアクセスして src を持ってくる
print("'Content-Type': 'text/javascript; charset=UTF-8'") # HTML に返してあげるためのヘッダー
print("")
print(response.text)

この部分も皆さんの環境毎に異なる部分なので、ご自分の使える言語で記述してください。
要は、サーバーの環境変数からAPIキーを持ってきて、url 指定して google のサイトから javascript を頂戴するということです。ヘッダーの部分は、'Content-Type': 'text/javascript; charset=UTF-8' にしましょう。

3.javascript の window.onload で CGI を呼ぶ

main.js
function initMap() {
    // 公式サンプルの内容と同じ
    // Initialize and add the map
    // The location of Uluru
    var uluru = {lat: -25.344, lng: 131.036};
    // The map, centered at Uluru
    var map = new google.maps.Map(document.getElementById('map'), {zoom: 4, center: uluru});
    // The marker, positioned at Uluru
    var marker = new google.maps.Marker({position: uluru, map: map});
}
window.onload = function() {
    // ページを表示した後に、実行したい処理を書く。ここからが今回のポイント!
    fetch("/cgi-bin/getapijs.py").then(res=>{
        // CGI 実行して、結果の TEXT だけを次にパスする
        return res.text();
    }).then(mytext => {
        // 受け取った javascript を EVAL で実行する。
        eval(mytext);
    }).then(() => {
        // 実行後の処理。公式サンプル HTML が &callback= でコールしていた部分
        initMap();
    }).catch(() =>{
        // お好きなエラー処理をどうぞ
    });
}

HTML には、ヘッダー部分に <script src="main.js"></script> を記載してます。この辺の流儀は人によって違うところなので、お好きなスタイルで良いと思います。
要は、HTML の要素を読み込んだ後(サンプルHTMLの defer の部分。私の例では window.onload)に、CGIを実行して javascript をTEXTで受け取りEVAL で実行する、という手順です。

これで安心して眠れるか?

APIキー は環境変数の中にしかないので、ソースをGITHUBで公開しても問題ないし、WEBサイトのユーザーが Chrome のデベロッパーツールで HTML や JS を見ても、APIキーは見つからない。
公式サンプルが堂々とフリチンで公道を歩いているのに比べれば、奥ゆかしい出来上がりになっています。
唯一、気持ち悪いところがあるとすると「邪悪な EVAL」を使っていることです。邪悪なwebページだと思われてしまうかもしれない・・・
EVAL が禁じ手として封じられている方は今回の手法は使えませんが、APIキーを公開の場に晒すのはEVAL使うよりも危険だと思います。止めましょう。会社でEVAL禁止されている人は諦めて JAVA で重いアプリ作ってください。私は軽くHTML + javascript + python で行きます。

ご参考

index.html
<!DOCTYPE html>
<html>
  <head>
    <style>
      /* Set the size of the div element that contains the map */
      #map {
        height: 400px;  /* The height is 400 pixels */
        width: 100%;  /* The width is the width of the web page */
      }
    </style>
    <title>Hello World</title>
    <script src="main.js"></script>
  </head>
  <body>
    <h3>My Google Maps Demo</h3>
    <!--The div element for the map -->
    <div id="map"></div>
  </body>
</html>

なお、こちらの記事を参考にさせて頂いています。
[私がよく使うJSからの外部JSの読み込み方法]

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

都道府県に紐づく市町村をドロップダウンで表示させる

備忘録として記録

完成形
スクリーンショット 2020-09-18 18.33.23.png

先にコードから記載

<%= form_with(model: @current_masseur, url: store_manager_business_trip_ranges_update_path(@current_masseur), method: :patch, local: true) do |f| %>
        <table class="prefecture table table-hover">
            <thead>
              <!--今は東京のみを表示しているがDBには全都道府県が入っている-->
              <%= collection_check_boxes :prefecture, :prefecture_ids, Prefecture.all.where(id: [12, 13, 14]), :id, :name, include_hidden: false do |prefecture| %>
              <tr>
                <th>
                  <div id="menu<%= prefecture.object.id %>">
                    <span class="check_box"><%= prefecture.check_box %></span>
                    <span class="text"><%= prefecture.text %></span>
                  </div>
                </th>
              </tr>
            </thead>
            <tbody>
              <tr>
                <td class="city-check-boxes">
                  <div id="city-list<%= prefecture.object.id %>">
                    <%= f.collection_check_boxes :city_ids, City.all, :id, :name, include_hidden: false do |city| %>
                      <% if prefecture.object.id == city.object.prefecture_id %>
                        <ul class="city-check-box">
                            <li class="city-check-box-item">
                            <div id="boxes"><%= city.check_box + city.text %></div>
                            </li>
                        </ul>
                      <% end %>
                    <% end %>
                  </div>
                </td>
              </tr>
              <% end %>
            </tbody>

        </table>
        <div class="actions text-center">
          <%= f.submit "登録する", class: "btn btn-primary mt-5 w-50" %>
        </div>
      <% end %
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>

<script>

$(function () {

  // メニュー領域外をクリックしたらメニューを閉じる
  $(function(){
    $("#menu12 .text").on('click', function(){
      $("#city-list12").slideToggle('fast');
    });
  });

  $(function(){
    $('#menu13 .text').on('click', function(){
      $("#city-list13").slideToggle('fast');
    });
  });

  $(function(){
    $('#menu14 .text').on('click', function(){
      $("#city-list14").slideToggle('fast');
    });
  });
});

</script>

ポイントはjqueryのメソッドslideToggleが肝。これを使用するだけでslideDownとslideUpの両方が実装できます。

また$("#menu12 .text")の#menu12。
viewファイルの

<div id="menu<%= prefecture.object.id %>">

から取得したものだが、同じID名がviewファイルに複数あった場合、最初の要素しか取得できない事に注意。
またslideToggleを使用するとドロップダウンはデフォルトで開いた状態になっているので変えたい場合はcssで変更する必要がある。

*eachで繰り返したID要素は最初の一つしか表示されません。

参考:
http://php.o0o0.jp/article/jquery-dropdown_list
http://www.hp-stylelink.com/news/2013/11/20131122.php
https://teratail.com/questions/21542

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

【Typescript】Nullish Coalescing と短絡評価

はじめに

今回はNullish Coalescing と短絡評価についてです。

Javascriptの場合

const inputData = "";

const storedData = inputData || "DEFAULT TEXT";
console.log(storedData);
const inputData = null;

const storedData = inputData || "DEFAULT TEXT";
console.log(storedData);
const inputData = undefined;

const storedData = inputData || "DEFAULT TEXT";
console.log(storedData);

いずれもinputDataはfalseと評価されて"DEFAULT TEXT"が出力されます。

Typescriptの場合

Nullish Coalescingを使って

const inputData = undefined;

const storedData = userInput ?? "DEFAULT";

console.log(storedData);

と書けます。違いは、

const inputData = "";

const storedData = userInput ?? "DEFAULT";

console.log(storedData);

の時には""がtrueと評価されて空文字が出力されることです。

最後に

また新しいことを覚えたらアウトプットしたいと思います。

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

Twitter投稿時のevent、callbackについて(JS)

とあるお客様から依頼があり、調査した時の備忘録です。
Twiiter投稿時、投稿完了後に〇〇がしたい
ということがありました。

こちらの記事では実装工数が掛からない想定で、htmlとJavascriptだけで解決を試みたときの調査内容となります。(Twitter Javascript APIのみの話)
調査過程を載せてから結果書いているので、結果だけ見たい方は下の方見に行ってください。

現状の実装と分析

React 16.8.6
react-share使用(詳しくはこちら)
ボタンデザインはオリジナルです。
Twitterの画面開くときに小ウィンドウを出しています。

依頼内容がreact-shareで実現できるのか、軽く調べたらTwiiterのシェアボタンでは投稿後のcallbackは実装されてなかったので、結局Twiiterのdocumentを見に行くことに。

document確認

公式
日本語版

見るとそれっぽい内容が書いてある

tweet
このイベントは、ユーザーがツイートウェブインテントを使ってツイートを投稿した時(新規投稿かリプライ)に発生します。

埋め込み型ツイートへのリプライツイートや、埋め込み型タイムライン内に表示されるツイートを含みます。

twttr.events.bind(
  'tweet',
  function (event) {
    // Do something there
  }
);

これ使えそう、というかこれではないか。

よく見る記事で実装だけしてみる

https://teratail.com/questions/141755
https://qiita.com/HieroglypH/items/23252479cda93f88c227
こちらの記事を読んで参考にしてReactに入れてみました。

const TwitterShareButton = memo((props) => {
  const { url, via, hashtags } = props

  const onClick = (e) => {
    console.log('Twiiterまで移動した!!!', e)
  }

  const onShare = useCallback((e) => {
    console.log('shared!!!',e)
  }, [])

  useEffect(() => {
    twttr.widgets.load()
    twttr.ready(function(twttr) {
      twttr.events.bind('click', onClick);
      twttr.events.bind('tweet', onShare);
    })
  }, [])

  return (
    <a href='https://twitter.com/intent/tweet' className='twitterShareButton' data-url='https://twitter.com/' target="_black">
      { props.children }
    </a>
  )
})

html上に

window.twttr = (function (d,s,id) {
 var t, js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return; js=d.createElement(s); js.id=id;
js.src="https://platform.twitter.com/widgets.js"; fjs.parentNode.insertBefore(js, fjs);
return window.twttr || (t = { _e: [], ready: function(f){ t._e.push(f) } });
}(document, "script", "twitter-wjs"));

こちらをscriptタグに追加しています。

target='_black'にしているのは、現実装で使っているwindow.open()ですとeventが取得できないためです。

結果

結果的に以下のような動きを起こしました。
私が取ったアクションとしては

Twitterシェアボタン(オリジナルボタン)をクリック
┗ twttr.events.bind('click', onClick);が動く想定

別タブが開き、Twitter画面になり、シェアテキストが入力できるようになる

ツイートする
┗ twttr.events.bind('tweet', onShare);が動く想定

というアクションを取っていきましたが、

Twitter画面に行く時点で、
Twiiterシェア.jpg
このようなlogを吐き出すこととなりました。
つまりtwttr.events.bind('tweet', onShare);は理想通りの動きをしてなかったということになります。

何故機能しないか

Twitterの公式フォーラムでは2015年に以下のような修正が入ったと書いてます。
https://twittercommunity.com/t/forthcoming-change-to-web-intent-events/54718

以前はTwitterシェアの投稿ボタンを押し、投稿された時点でcallbackが返ってきていたそうですが、今はindentが呼び出された時点でcallbackするように変更されていたようです。
日本語訳で抜粋すると

TwitterをWebサイトで使用する場合。アクションが完了した後ではなく、ユーザーがページでアクションを呼び出したときにトリガーされるようになりました。

この変更を行っているのは、過去数年の間にモバイルへの移行により、これらのイベントがページへのやり取りを伝えるための信頼性の低い代表的な手段になったためです。

2015年のフォーラムなので情報が古いだけか?と感じてますが、以降フォーラムで理想としている挙動と出会うことはありませんでしたので、実装はできないということなのでしょう。

終わりに

そしたらTwitterのdocument直してくれないかな~~~~って少し思ってしまいました。
紛らわしいなと。。

何か間違いがあればご指摘お願いしたいです。
読んで頂きありがとうございました。

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

【Typescript】オブジェクトの要素チェック

はじめに

最近Typescriptの勉強を始めました。ちょこちょこ便利だなと思った書き方を覚書していきます。今回はOptional Chainingについてです。

Javascriptの場合

const fetchedPostData = {
  id: "p1",
  author: { name: "MIKE", age: "22" },
};

こんな感じのオブジェクトの要素を取り出したいなーと思った時、エラーを回避するために

if (fetchedPostData) {
  if (fetchedPostData.author) {
    console.log(fetchedPostData.author.name);
  }
}

とか、もう少しスマートに書くなら

if (fetchedPostData && fetchedPostData.author)
  console.log(fetchedPostData.author.name);

と書きます。

Typescriptの場合

?を使ってさらに潔にかけます。

console.log(fetchedPostData?.author?.name);

最後に

美しい・・・

また新しいこと覚えたら書きます。

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

Javascriptの基本の書き方②

はじめに

前回に引き続いてjavascriptの基本の書き方を備忘録として残します。

繰り返し文

while文

while(条件式){
  処理;
}

for文

for(変数の定義;条件式;変数の更新){
  処理;
}

配列

[1,2,3]

配列も一つの値としてみなされるので定数への代入が可能

配列内の特定の値の取り出し

配列名[取得したい値のインデックス]

オブジェクト

オブジェクトも一つの値としてみなされるので定数への代入が可能

{プロパティ値1:1,プロパティ値2:2}

オブジェクトの取り出し

以下の方法で可能

オブジェクト名.プロパティ値

オブジェクトを要素にもつ配列

const 変数名 = [
  {プロパティ値1: 1, プロパティ値2: 2},
  {プロパティ値1: 1, プロパティ値2: 2}
];

取り出すときは以下のように取り出す

1.配列の中のオブジェクトを取り出す時

変数名[インデックス]

2.配列の中のオブジェクト内のプロパティ値に対応する値を取り出す時

変数名[インデックス].プロパティ値
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webpack4 splitChunks の対象を一部に絞る

webpack4で新たに追加されたoptimization.splitChunksの基本的な使い方はこちらの記事に譲るとして、
splitChunksの細かいところについて。

よくある書き方

module.exports = {
  ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: /node_modules/,
          name: 'vendor',
          chunks: 'initial',
        }
      }
    }
  }
};

こうすることでnode_modules配下をvendor.bundle.jsというかたちで分けることができる。

対象を絞る

const moduleList = ["@babel/polyfill", "react", "react-dom"];

module.exports = {
  ...
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendor: {
          test: new RegExp(
            `[\\/]node_modules[\\/](${moduleList.join("|")})[\\/]`
          ),
          name: 'vendor',
          chunks: 'initial',
        }
      }
    }
  }
};

こうすることで、splitしたいmoduleを一部に絞ることができる。

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

【Nuxt.js】Nuxt文法編:component③動的コンポーネント

? この記事はWP専用です
https://wp.me/pc9NHC-GF

前置き

Frame 6.png

liを使いまわして親のulによって
中のテキストを変えたりしたい!
というような場合に便利なkey属性をご紹介?
component②の続きです?‍♀️

ul、ol、table、selectといった要素には
子要素にできるタグli、tr、optionが
決まっていますね。
これらを分けてコンポーネント化したい場合に
活躍するのがkeyです。

公式: https://jp.vuejs.org/v2/guide/components.html#DOM-テンプレートパース時の警告

keyに使う値については
こちらをご覧ください?

【Nuxt.js】Nuxt文法編:v-for

tableでいうならthは親、
trを子コンポーネントに分けると
管理がしやすいといったメリットもあります?

コード①

liタグに使うコンポーネントは
is属性で指定してあげます?

ListItem.vue
<template>
 <li>{{ text }}</li>
</template>

<script>
export default {
 props: ['text']
}
</script>
index.vue
<template>
 <div class="page">
   <ul>
     <li
       is="ListItem"
       v-for="(fruit, index) in fruits"
       :text="fruit"
       :key="index"
     >
       {{ fruit }}
     </li>
   </ul>
 </div>
</template>

<script>
import ListItem from '~/components/Lists/ListItem.vue'

export default {
 data () {
   return {
     fruits: ['りんご', 'みかん', 'ぶどう'],
   }
 },
 components: {
   ListItem,
 },
}
</script>

コード②

? 続きはWPでご覧ください?
https://wp.me/pc9NHC-GF

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

Javascriptの基本の書き方①

はじめに

自分の個人的勉強のまとめ
備忘録として残します

変数定義(代入まで)

var 変数名=;
let 変数名=;

varとletの違いは再代入が可能かどうか
(letが可能)

定数定義(代入まで)

const 定数名=;

定数と変数の違いは変数は値をあとで変更できるのに対して定数は値の変更ができない。

ターミナルでの出力方法

console.log();

テンプレートリテラル

以下のように${変数名}と記載することで変数の値を出力することが可能

let name="たけし";
console.log(`ぼくの名前は${name}です`);

出力する値は`で囲む必要がある。

条件式の書き方

if (条件式){
  処理;
}

}の後ろに;はいらない

if...else文

if (条件式){
  処理;
}else{
  処理;
}

else if文

if (条件式){
  処理;
}else if (条件式){
  処理;
}else{
  処理;
}

Switch文

switch (条件の値) {
  case 1:
    1と条件の値が等しい時の処理;
    break;
  case 2:
    2と条件の値が等しい時の処理;
    break;
  case 3:
    3と条件の値が等しい時の処理;
    break;
  default:
    どれにも合致しなかった時の処理;
    break;
}

caseのブロックの最後に必ずbreakを入れることで処理を終了させることが大事

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

【JavaScript】小数点第{n}位で四捨五入・切上げ・切捨てる方法〜10^x倍して10^xで割る儀式すればよい

※確認ののち再公開します

前提

Math.round( ) は整数値を返す

小数点第{n}位で四捨五入・切上げ・切捨てたいと思ったことはありますか.

JavaScriptには小数点第●位を四捨五入する関数がありません.
Mathクラスのメソッドでは,全て小数点以下が省かれて整数が返ります.

//四捨五入 Math.round(<数値>)
Math.round(123.456) // 出力:123
Math.round(123.567) // 出力:124

//切り上げ Math.ceil(<数値>)
Math.ceil(123.456) // 出力:124
Math.ceil(123.567) // 出力:124

//切り捨て Math.floor(<数値>)
Math.floor(123.456) // 出力:123
Math.floor(123.567) // 出力:123

解決するには,10^x倍して10^xで割ればよいという話です.

ケーススタディ

【問い】小数点第2位を四捨五入するには?

シンプルなパターンです.

たとえば, 79.5714が与えられたとき,
どのように79.6 を出力してあげればよいでしょうか?

【答え】

Math.round(79.5714 *10 ) / 10 // 出力:79.6

10倍してMath処理,のちに10で割ればよいのです

補足(ひとこと)

slice と splice 間違えがち.
配列対象と文字列対象間違えがち.
int.toString()を toString(int)って書きがち.

参考 https://qiita.com/nagito25/items/0293bc317067d9e6c560

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

Developper Toolがなぜか使えない時のデバック方法

はじめに

コード書き終わった! リロード!

Error文

。。。。。

なんじゃこりゃ。

Vue.jsでフロントを開発している際にCromeブラウザ、Developper Toolの両方がエラーを起こして使えなくなってしまい、デバッグをしようにもできない状態になったのでその解決方法を手順に沿って共有しようと思います。

1.コミット履歴を遡りロールバックしてみる。 

まず、Gitを使用していることが前提になりますが、問題が起こったところの手前のコミット履歴からコードの差分を見てみましょう。

正常に動作していた時がコミット履歴から分かれば、原因の範囲を特定することができます。

細かい粒度でのコミットができていれば、ここでその原因が判明することもあります。

2.コメントアウト

コミット履歴を参考に、ここ原因だと思われるメソッドなどをコメントアウトしてみましょう。 

原因であるところを引き当てた場合、DevelopperToolが動作します。

3.console.log

その原因に関連するコードをひたすらにconsole.logして原因箇所を絞り込んでいきましょう。

しっかりと値が渡されているか、意図していない動きがでていないか確認していきましょう。

この段階で原因箇所が特定できると思います。

終わりに

私の場合はfor文による”無限ループ”が原因でした。

コミットの粒度や意義を考えずに適当にコミットしていたので、原因を特定するのにかなり時間がかかってしまいました。

適切な粒度でのコミットについて考えるいいきっかけになりました。

参考記事

デバックのコツ

デバッグに大活躍! JavaScriptのconsole.logで値を表示しよう

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

CORS設定を行いwebserver越しにRESTでYellowfinサーバーにアクセスする

image.png

今回の目的

題名のとおりなのですが、ちょっと案件で検証したこともあって、バージョンによって挙動が違うこともありなかなか苦戦したので備忘録です。
もともと、別サービスのサーバーにYellowfinのJavascriptAPIのversion3を組み込んで、ダッシュボードを表示させたいというのがやりたいことだったのですが、間にサーバーが2つあることもあり、設定もなかなか大変でした。。

結構基本的なことなのですが、少しずつ解説していきます。
※ちなみに、この方法はYellowfinのバージョン9.2.2で動作確認しており、9.2.1では動作しません。

SSOに関する設定

これは組み込み元のサーバーに配置するHTMLファイルの中のRequestBodyのJSONObjectの中に設定があります。

ポイントはSSO対応のためのRESTのBodyのオブジェクトの中のnoPasswordにtrueを設定することです。
このプロパティをTrueにすることでauthUserPassの値が何であっても実行できるようになります。
逆にFalseにすると、ID/Passどちらも必須になります。

リポジトリDBに以下のSQLを実行する

Yellowfinのリポジトリに対して、以下のSQLで設定を追加します。
これは簡易認証を許可する設定で、これがなければパスワードなしのSSO設定は不可能です。

forSSO.sql
INSERT INTO Configuration (IpOrg, ConfigTypeCode, ConfigCode, ConfigData) VALUES (1, 'SYSTEM', 'SIMPLE_AUTHENTICATION', 'TRUE');

アクセスするHTMLファイルのソース

ここでAPIのリクエストをフロントエンドwebサーバーに投げています。
RESTAPIの公式ドキュメントについてはこちら
https://developers.yellowfinbi.com/dev/api-docs/yf-api.html
ダッシュボードのロードについてはこちら
http://wiki.yellowfinbi.jp/display/yfcurrent/Base+API

rest.html
<html>
  <head>
    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
  </head>
<body>
  <div id="reportDiv"></div>
  <script>
    var authUserId = 'authUserId@auth.com';
    var authUserPass = 'dammy for noPass=true';
    var adminId = 'admin@yellowfin.com.au';
    var adminPassword = 'test';

    var body = {
      signOnUser: {
        userName: authUserId,
        password: authUserPass,
        clientOrgRef: ""
      },
      noPassword: true,
      adminUser: {
        userName: adminId,
        password: adminPassword
      }
    };
    //encode to JSON
    var json_text = JSON.stringify(body);

    $.ajax({
      type: 'POST',
      contentType: "application/json;charset=UTF-8",
      data: json_text,
      crossDomain: true,
      url: "http://frontend.web.server/api/rpc/login-tokens/create-sso-token",
      headers: {
        'Authorization': 'YELLOWFIN ts=' + new Date().getTime() + ', nonce=123',
        "Accept": 'application/vnd.yellowfin.api-v1+json'
      },
      success: function (res, textStatus, jqXHR) {
        securityToken = res.securityToken;
        let url = 'http://frontend.web.server/JsAPI/v3?token=' + securityToken;
        let tp = 'text/javascript';
        let sc = document.createElement('script');
        sc.src = url;
        sc.type = tp;
        document.body.appendChild(sc);

        setTimeout(function () {
          yellowfin.showLoginPrompt = false;
          yellowfin.init().then(() => {
            yellowfin.loadDashboard({
              dashboardUUID: '417a0ff3-6adf-4c92-8328-98d0eeb3fd6a',
              element: document.querySelector('div#reportDiv')
            });
          });
        }, 1000);
      },
      error: function (responseData, textStatus, errorThrown) {
        alert("error!");
      }
    });

  </script>
</body>
</html>

フロントエンドwebサーバーの設定(AJPでのアクセス設定用)

apacheのhttpd.confに以下を追加します。AJP経由のアクセスに変更する設定になります。
80番ポートで受け付けているアクセスをAJPの8009番ポートにリダイレクトする設定です。

proxy-ajp.conf
ProxyRequests Off
ProxyPass / ajp://yellowfin.server:8009/ secret=secretPass timeout=1860
ProxyPassReverse / ajp://yellowfin.server:8009/ secret=secretPass

Yellowfinサーバーの設定

AJPからのアクセス設定(AJPでのアクセス設定用)

先程のAJP設定を受ける設定をserver.xmlに記載します。ついでにデフォルトの8080や不要なこれまでの接続設定はコメントアウトします。

/usr/local/yellowfin/appserver/conf/server.xml
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
               maxThreads="500" minSpareThreads="25"
               enableLookups="false" acceptCount="100"
               connectionTimeout="20000"
               URIEncoding="UTF-8"
               maxPostSize="10485760" maxParameterCount="50000"
               address="0.0.0.0"
               secretRequired="true" secret="secretPass"
    />

<!-- comment out
     <Connector port="8080" protocol="HTTP/1.1" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true"
               URIEncoding="UTF-8" compression="on" compressionMinSize="512"
               noCompressionUserAgents="gozilla, traviata"
               compressibleMimeType="text/html,application/x-javascript,text/css,application/javascript,text/javascript,text/plain,text/xml,application/json,application/vnd.ms-fontobject,application/x-font-opentype,application/x-font-truetype,application/x-font-ttf,application/xml,font/eot,font/opentype,font/otf,image/svg+xml,image/vnd.microsoft.icon,image/svg,text/comma-separated-values,application/csv,application/vnd.yellowfin.api-v1+json"
               useSendfile="false" />
-->

YellowfinサーバーのTomcat(web.xml)に以下を追加する(CORS用設定)

これがCORS対応の肝となる部分ですね。アクセス元許可の設定・プリフライトのためのOPTIONメソッドの追加・後はYellowfinのAPIに投げるためのheaderを追加します。

/usr/local/yellowfin/appserver/conf/web.xml
<filter>
  <filter-name>CorsFilter</filter-name>
  <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
  <init-param>
    <param-name>cors.allowed.origins</param-name>
    <param-value>*</param-value>
  </init-param>
  <init-param>
    <param-name>cors.allowed.methods</param-name>
    <param-value>GET,POST,OPTIONS</param-value>
  </init-param>
  <init-param>
    <param-name>cors.allowed.headers</param-name>
    <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>CorsFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

システム構成の変更(CORS用設定)

システム構成→システムタブの許可するオリジンにブラウザでアクセスするドメインを登録する。また、マルチタブサポートをOffにする。
image.png
この一連のサーバー名の設定ですが、AJPの設定も、web.xmlの設定もドメイン名ならドメイン名・IPならIPでの表記を統一しないといけないそうです。それはそうか・・・・

結果

このような感じでダッシュボードの呼び出しが成功しました!これを使用すると、組み込みや外部サイトでの参照ができるようになりより利用の幅が広がりますね。
※ライセンス系統は要確認です・・
image.png

画像はダ鳥獣戯画さんからお借りしました。https://chojugiga.com/

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

【javascript】配列内の要素を指定INDEXの前(後)に移動

紆余曲折を経てこの形に落ち着きそう。

(追記)delete演算子を使うのはよろしくなかったかな?

引数解説

変数 説明
1 arr Array 処理対象の配列
2 idx Number 移動する要素のINDEX
3 mIdx Number 移動先のINDEX
4 after Boolean true : mIdxの後方に挿入
false / 省略 : 前方に挿入

コード

const moveElement = (arr, idx, mIdx, after=false) => {
    //事前処理は省略
    arr = [...arr];
    if (after) mIdx++;

    const elm = arr[idx];
    delete arr[idx];

    arr.splice(mIdx, 0, elm);
    return Object.values(arr);
};

//連番生成
const array = [...Array(10).keys()];

moveElement(array, 2, 8, true);     // [0, 1, 3, 4, 5, 6, 7, 8, 2, 9]
moveElement(array, 8, 2);           // [0, 1, 8, 2, 3, 4, 5, 6, 7, 9]

やっている事(追記)

  1. 移動する要素を取得
  2. 取得した要素の位置をdeleteでempty状態にする
  3. 配列の長さは変わらないので指定INDEXの前後へspliceで挿入
  4. Object.values()でempty状態の要素はスルーして配列を再構成

falsyな要素が消えても構わない場合は、deleteする代わりに空文字を代入。
Object.values()ではなくfilter(Boolean)でreturnするのもありでしょう。

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

【メモ】vue-routerを使ったアプリで画面が真っ白になる

問題

vue.jsの画面が真っ白になって表示されない。

前提

  1. vue-routerを使っている。
  2. サブディレクトリにデプロイしている

TL;DR

vue-routerにbaseプロパティでサブディレクトリ(ベースURL)を教える必要がある。

もう少し詳しく説明すると/var/www/html/subdirにデプロイすることで、URLがhttps://www.example.com/subdir/がアプリケーションのトップとなる場合に、ベースURLとして、/subdir/(前後のスラッシュ必須)をvue-routerに教える必要がある。

以下のように初期化処理でbaseプロパティで教えてあげる。

https://router.vuejs.org/ja/api/#mode

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

const routes = [
  {
    path: '/',
    component: xxxxxx,
    children: [
      {
        path: '',
        name: 'top',
        component: () => import(xxxxxxx)
      },
      {
        path: '/login',
        name: 'login',
        component: () => import(xxxxxxx)
      },
    ]
  }
]

export default new Router({
  mode: 'history',
  base: '/subdir/', <-これ
  routes
})

今回は、どこに設置するかわからなかったので、baseプロパティを環境変数で設定するようにした。

例えば、baseが以下のような場合は、ベースURLの判定ができずに、ルーティングできずに画面が真っ白になる。

  • /subdir :最後のスラッシュがない
  • // : スラッシュが2個ある。
  • https://example.com/subdir/ : URLになっている

環境変数で適当に設定するようにしちゃったので、書き方が統一されなくて画面が真っ白になっていました。

一度、設定すれば問題ないので、忘れちゃいます。
自戒をこめて記載しました。

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

element.tagName は readonly なので,HTML 要素のタグ名を変更する関数を作った

element.tagName は,readonly なので1,*someElement.tagName = 'h2' といったかんじにタグ名を変更することはできません2.しかし,たとえば <h1> タグを <h2> に一括して変えたいといったことは結構あります.そういったわけで,element.tagName を変更する関数を作ってみました3

コード

※ 公開後,戻り値を void から Element に変更しました.

TypeScript

replace-tag-name.ts
/**
 * @author JuthaDDA
 * @see [element.tagName は readonly なので,
 *     HTML 要素のタグ名を変更する関数を作った - Qiita](
 *     https://qiita.com/juthaDDA/items/974fda70945750e68120)
 */
const replaceTagName = ( target:Element, tagName:string ):Element => {
    if ( ! target.parentNode ) { return target; }

    const replacement = document.createElement( tagName );
    Array.from( target.attributes ).forEach( ( attribute ) => {
        const { nodeName, nodeValue } = attribute;
        if ( nodeValue ) {
            replacement.setAttribute( nodeName, nodeValue );
        }
    } );
    target.childNodes.forEach( ( node ) => replacement.appendChild( node ) );
    target.parentNode.replaceChild( replacement, target );
    return replacement;
};

JavaScript

replace-tag-name.js
/**
 * @author JuthaDDA
 * @see [element.tagName は readonly なので,
 *     HTML 要素のタグ名を変更する関数を作った - Qiita](
 *     https://qiita.com/juthaDDA/items/974fda70945750e68120)
 * @param {Element} target
 * @param {string} tagName
 * @return {Element}
 */
replaceTagName = ( target, tagName ) => {
    if ( ! target.parentNode ) { return target; }

    const replacement = document.createElement( tagName );
    Array.from( target.attributes ).forEach( ( attribute ) => {
        const { nodeName, nodeValue } = attribute;
        if ( nodeValue ) {
            replacement.setAttribute( nodeName, nodeValue );
        }
    } );
    target.childNodes.forEach( ( node ) => replacement.appendChild( node ) );
    target.parentNode.replaceChild( replacement, target );
    return replacement;
};

説明

第 1 引数にタグ名を変更したい Element,第 2 引数に変更後のタグ名を指定します.

Document.createElement() で生成した要素なんかは,target.parentNodenull ですが,想定しうる用途をからすると if ( ! target.parentNode ) { return; } はほとんどオマジナイみたいなものだと思います.

上述のとおり Element.tagNamereadonly なので,document.createElement(); で新しい要素を生成して,最後に元の target を新しく生成した replacement に置き換えています.replacementElement.tagName 以外とくに設定されていない要素なので,属性と内容を元の target からコピーしてくる必要があります.

まず属性ですが,Element.attributes もこれまた readonly なので,そのまま *replacement.attributes = target.attributes とか *Object.assign( replacement.attributes, target.attributes ) とか *replacement.attributes = { ...target.attributes } とかすることはできません.なので,target.attributes の各要素(のうち値をもつもの)ごとに,replacement.setAttribute() してやる必要があります.

内容については,replacement.innerHTML = target.innerHTML とか,replacement.insertAdjacentHTML( target.innerHTML ) 4とかでもおおむね問題ないですが,子要素がイベント・リスナーを持っていたり,mutationObserver 等のオブザーバー系 API の監視対象となっていた場合にも簡単に対応できるので,target.childeNodes.forEach() で回して,target.appendChild() してやっています.

【追記】戻り値は,タグ名の変更が成功した場合は replacement,失敗した場合は target です.以下のように書けば,成否を判定できます.

const h1Replacement = replaceTagName( h1, 'h2' )
const hasH1BeenReplaced = h1 !== h1Replacement;

補足

上では “HTML 要素” と書いていますが,厳密には,SVGElementMathMLElement といった,ほかの Element を継承する要素にも対応しているはずです(未検証).

また,target がイベント・リスナーを持っていたり,mutationObserver 等の監視対象となっていた場合は,それらを replacement に移してやることはできません5target.onClick 等のプロパティは,おそらくそのまま移すことが可能ですが,そこだけ対応するのも中途半端かなと思い,やっていません.

参考記事


  1. Cf. Document Object Model Core # Interface Element

  2. たとえば <div> から <p> への変換といった場合には,HTMLDivElement から HTMLParagraphElement へとインタフェースも変わるので,まあそう簡単には変更できないわなと思います. 

  3. <hx> タグのレベルを一括して変更する関数も作ったので,後日別途記事を立てる予定です. 

  4. document.innerHTML への代入と document.insertAdjacentHTML() の違いについては,innerHTML より insertAdjacentHTML を使う - Qiita が参考になります. 

  5. イベント・リスナーについては,GaurangTandon/checkEventAdded GitHub を使えば,うまく移せるようにできるかもしれません.おそらく EventTarget.prototype.addEventListener を呼び出される前に書き換えてやる必要があるので,複数のスクリプトを呼び出しているサイトだとそれなりにめんどくさそうですが.mutationObserver 等については,いまのところよさそうな方法が見当たりません. 

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

discord.jsでapiの情報を取得する

すえきゅーですどうも
今回は discord.jsでapiから情報を取得して出力する方法をここに書こうと思います

使うもの
Api
Node fetch
jsonを読む力

この3つです
次にNode fetch
こいつがないとダメです
$ npm install node-fetch
こいつをターミナルで実行しましょう

準備完了 コードをここに置きます

コード
const discord = require("discord.js");
const fetch = require("node-fetch");
const client = new discord.Client()

client.on('ready', async () => {

const msg = await client.channels.cache
    .get("送信したいチャンネルid") //チャンネルを取得
    .send("Status"); //送信

setInterval(async () => { //任意のミリ秒ごとに繰り返す
    const res = await fetch("Api"); //取得
    if (!res.ok) { //取得できなかったら
      console.log(await res.text()); //コンソールにメッセージを出力する
      return;
    }

const json = await res.json(); //json

msg.edit({
        embed: {
          color: 0x00ff00,
          title: "thinking",
          description:
            "Api test",
        footer: {
           text: "thinkingbot",
}, 

          fields: [
            {
              name: "fields",
              value: json.test.status, //Apiの情報を書く json.~~~~で行ける

            }
]
}
})

  }, 1500); //1.5秒ごとに繰り返す

こんな感じで出力出来ると思います
何か間違ってることや質問があるならこちらに参加して質問してください

最後まで見てくれてありがとうございました

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

gatsbyのブログテンプレートにMaterial-UI入れて見栄えを良くする -色の統一-

今回の目的

githubにあるgatsbyのブログテンプレートにMaterial-UIを入れて見栄えを良くしていきたいと思います。
まずは基本の色を決めていきましょう。

前提条件

nodeやgatsbyなどはインストールしておいてください。
Windows10の環境準備は以下で参照できます。

gatsby入門 チュートリアルをこなす 0.開発環境をセットアップする

ブログテンプレートをダウンロード

好きなディレクトリにて以下コマンドを実行します。

gatsby new my-blog※任意 https://github.com/gatsbyjs/gatsby-starter-blog

ダウンロード後に一度ブログを起動してみましょう
以下コマンドを実行します。

cd my-blog(ダウンロードすると上記で入力した任意の名前のディレクトリが出来ています。)
gatsby develop

http://localhost:8000
2020-09-17_23h22_48.jpg
スーパーシンブル!
これをカスタマイズしていきます。

Material-UIを導入

コマンドを使用して、カレントディレクトリ(ここでいうmy-blog)にて以下コマンドを実行します。

npm install -D @material-ui/core 
npm install -D gatsby-plugin-material-ui

gatsby-plugin-material-uiはMaterial-UIをGatsbyでいい感じに利用することが出来るプラグインと考えてください。
※build時にプラグインがないとエラーになるような事をどこかにありましたが、私は再現しませんでした。

インストール完了後、gatsby-config.jsに以下を追記します。
gatsby-config.jsはカレントディレクトリ直下にあります。

gatsby-config.js
    (中略)
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-plugin-typography`,
      options: {
        pathToConfigModule: `src/utils/typography`,
      },
    },
    `gatsby-plugin-material-ui`,←これを追記

これでMaterial-UIの導入は完了です。

色の設定を統一する

現時点はシンプルですが、今後機能が増えるにつれ色の設定がばらばらになる可能性があるのでサイトで使用する色を制限するためthemeを作成します。

色決め

下記サイトでいい感じに色が決めれます。
https://material.io/resources/color/#!/
私はこんな感じにしました。
2020-09-18_01h46_09.jpg

src/styles/theme.jsを作成

src/styles/theme.jsを作成し以下を追記します。

src/styles/theme.js
import { createMuiTheme } from '@material-ui/core/styles';

export const theme = createMuiTheme({
  palette: {
    primary: {
      light: '#b6ffff',
      main: '#81d4fa',
      dark: '#4ba3c7',
      contrastText: '#FFFFFF',
    },
    secondary: {
      light: '#ff93c1',
      main: '#ef6091',
      dark: '#b92a64',
      contrastText: '#000000',
    },
  },
});

これでこのサイトで使用する色が決まりました。
上記はまだまだカスタマイズ可能なようですがとりあえず簡単にいきましょう。
当然ですが、個別のスタイルで色を設定することをルール決めしないといけません。
複数人で作成している場合は、話し合いましょう。

色の設定を修正

それでは画面に反映していきます。

src/components/layout.jsを編集します。

src/components/layout.js
import React from "react"
import { Link } from "gatsby"

import { rhythm, scale } from "../utils/typography"

import {theme} from "../styles/theme";

const Layout = ({ location, title, children }) => {
  const rootPath = `${__PATH_PREFIX__}/`
  let header

  if (location.pathname === rootPath) {
    header = (
      <h1
        style={{
          ...scale(1.5),
          marginBottom: rhythm(1.5),
          marginTop: 0,
          backgroundColor: theme.palette.primary.main,←追記
        }}
      >
        <Link
          style={{
            boxShadow: `none`,
            color: theme.palette.primary.contrastText,←修正
          }}
          to={`/`}
        >
(中略)

2020-09-18_02h00_41.jpg
画面に色が付きました。
これで、色を付けたい場合はthemeから選ぶ(または背景色白の場合は何も指定しない。)

次回は

今回は以上です。
次回はMaterial-UIを使用してヘッダを追加しようと思います。

ありがとうございました。

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

テーブルタグからほしい情報を取り出す

新型コロナウイルス感染症に関する報道発表資料(発生状況、国内の患者発生、海外の状況、その他)
https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/0000121431_00086.html

データサンプル
https://www.mhlw.go.jp/stf/newpage_13640.html

const html = (上のテーブルをコピペする)

// カンマ区切りの数字のカンマ削除、タブを,に変換して CSVライクにする
const csv = html.replaceAll(',', '').replaceAll('\t', ',')
// 改行ごとに配列に変換
const arr = csv.split('\n')
// ほしいデータに整形
const data = arr.map(d => {
const [name, a,b ] = d.split(',')
return {name, a: parseInt(a, 10), b: parseInt(b, 10)}
})
// 感染者数の降順にする
data.sort((a, b) => -(a.a - b.a))

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

Automation Anywhere A2019でQRコードを生成してみる その2 ~ 文字化け対策

はじめに

【前回の記事】
Automation Anywhere A2019でQRコードを生成してみる - Qiita

現在のバージョンのQR Code Generatorは日本語未対応

前回はAutomation Anywhere A2019によるQRコード生成をご紹介しましたが、
実は現在のバージョンのQR Code Generatorアクションは、日本語などの文字コードに未対応となっています。
そのため、日本語を含む文字列をQRコード化し、読み込もうとすると以下の写真のように文字化けしてしまいます。
image.png

解決方法

このQRコード生成パッケージ自体を日本語化してくれたら話が早いんですが、
今回はこのままでも何とかする方法を考えてみます。
色々方法はあると思いますが、今回は文字列をURLエンコードして、QRコード生成を生成し、
読み取り後に複合し、日本語を含む文字列に戻す方法を取ろうと思います。
(同パッケージの日本語対応依頼も出してはいますが、対応時期は不明です。)

処理の流れ

  • 日本語を含む文字列を生成
  • JavaScriptで文字列をURLエンコードする
  • URLエンコードした文字列を元にQRコードを生成する
  • QRコードを読み取る
  • 読み取った文字列をJavaScriptでデコード(復号)する
  • 終了

実際に組んでみる

ということで、実際にやってみましょう。

JavaScript部分

エンコードに使うJavaScriptコード
function getEncodedText(a){
  return encodeURI(a);
}
デコードに使うJavaScriptコード
function getDecodedText(a){
  return decodeURI(a);
}

大枠はこんな感じ

image.png

Stepの中を展開するとこんな感じになってます

image.png

各アクションの設定値

※デコードする部分もほぼエンコード時と設定内容は一緒で、呼び出す関数名を変えるくらいです。
image.png
image.png

image.png

image.png

結果

image.png
上記をQRコード化し、それを読み取り、デコードした結果がこちら。
image.png

まとめ

ということで、日本語対応していないQRコード生成用アクションで日本語を扱ってしまうことが出来ました。
気を付けないといけないのは、QRコードで扱える最大文字数ですが、一般的な用途であれば大丈夫なんじゃないかと思っています。

参考ページ、記事

URLエンコードとは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典
URLエンコードについておさらいしてみた - Qiita

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