• React Nativeでカメラロールを一覧表示して、選択した画像をアップロードする
    React Nativeでカメラロールを一覧表示して、選択した画像をアップロードする
    No Comments on React Nativeでカメラロールを一覧表示して、選択した画像をアップロードする

    はじめに React Nativeでカメラロールをいじる方法がわからなかったので調べました。 ついでに、一覧表示した画像から、複数ファイルを選択し、axiosでアップロードするところまでを紹介します。 サーバーサイドはRailsで受け取るソースをのっけておきます。 最後に全ソースを貼り付けます。 説明は部分部分かいつまんで行います。 CameraRollから画像一覧取得 カメラロールをインポートして、 [bash] import { CameraRoll, } from “react-native” [/bash] componentDidMountで一覧を取得し、Stateにセットします。 imageSelは画像が選択されたかどうかのフラグです。 [bash] componentDidMount() { let svThis = this CameraRoll.getPhotos({first:25}) .then(function(obj){ console.log(obj) svThis.storeImages(obj.edges, svThis) }) } storeImages(edges,svThis) { const images = edges.map((asset) => { //Alert.alert(asset.node.image.uri) return asset.node.image }) let imgSel = [] images.map((img) => { imgSel.push({uri: img.uri, selected: false}) }) svThis.setState({images: images, imageSel: imgSel}) } [/bash] 画像一覧表示 renderメソッドで一覧表示します。 画像をTouchableHighlightタグの中にセットします。画像が選択されているか否かを selViewという変数にセットして、 選択中の画像にチェックがつくようにします。 [bash] render() { let imgs = [] imgs = this.state.images.map((img) => { let selTrue = false let selView = null this.state.imageSel.map((sel) => { if (sel.uri == img.uri && sel.selected == true){ selTrue = true } }) if (selTrue){ selView = ( ) } return ( this.handleClick(img)}> {selView} ) }) return ( {this.state.selPhoto} this.handleUpload()} title=”Upload” /> {imgs} ) } [/bash] 画像が選択されたら、チェックマークをつける 画像がクリックされたら、ステートのフラグをたてかえて、チェックマークをつけます。 [bash] handleClick(img) { let imgSel = this.state.imageSel.map((sel) => { if (sel.uri == img.uri){ sel.selected = !sel.selected } return sel }) this.setState({imageSel: imgSel, selPhoto: img.uri}) } [/bash] 選択された画像をaxiosでアップロード 注意すべき点は、data.appendするときに uriと name と typeの3つをセットすることです。 [bash] handleUpload(){ let data = new FormData() let cnt = 1 this.state.imageSel.map((sel) => { if (sel.selected){ data.append(“file_” + cnt, {uri: sel.uri ,name: “photo_” + cnt + “.jpg”,type:”image/jpg”}) cnt += 1 } }) let REQ_URI = “http://192.168.11.3:3000/upload_image_from_native” axios.post(REQ_URI,data) .then(function(res){ Alert.alert(“good”) }) .catch(function(err){ console.log(err) }) } [/bash] アップロードされた画像を保存するサーバーサイド(Rails)のソース [bash] def upload_image_from_native image_path = Rails.root.to_s + “/public/upload_images/reactnative” FileUtils.mkdir_p(image_path) unless FileTest.exist?(image_path) pp “************** params *******************” pp params cnt = 1 while true do break if params[“file_” + cnt.to_s].blank? file = params[“file_” + cnt.to_s] fname = file.original_filename File.open(image_path + “/#{fname}”, ‘wb’) { |f| f.write(file.read) } cnt += 1 end end [/bash] 全ソース 以下に全ソースを貼り付けておきます。 [bash] import React from “react” import { StyleSheet, Text, View, ScrollView, Image, CameraRoll, Alert, TouchableHighlight, Button, } from “react-native” import axios from “axios” class App extends React.Component { constructor(props){ super(props) this.state = {images: [], imageSel: [], selPhoto: “”} } componentDidMount() {…

    Read more
  • Webpackで React(ES6)+SASS(SCSS) をコンパイルして開発する最小限の環境を作る
    Webpackで React(ES6)+SASS(SCSS) をコンパイルして開発する最小限の環境を作る
    No Comments on Webpackで React(ES6)+SASS(SCSS) をコンパイルして開発する最小限の環境を作る

    はじめに ReactでSPAを作るようになりました。 Webpackで開発環境を作るわけですが、Reactの ES6(JSX)をコンパイルして、 デザイナーさんから上がってきたSASS(SCSS)をコンパイルしたい。 Webで探してもぴったりくる情報が少なかったので、両方のコンパイルが出来て、 テストできる最小限の環境を作る方法を記載してみます。 出来上がりのファイルツリーのイメージ だいたいこんなイメージで・・・ 成果物がdist配下に配置される感じですね。 作業フォルダ作成 [bash] $ mkdir sass_react $ cd sass_react/ $ npm init -y [/bash] パッケージのインストール npmで必要なパッケージをインストールします。 [bash] $ npm install –save react react-dom $ npm install –save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react webpack webpack-dev-server $ npm install –save-dev webpack extract-text-webpack-plugin css-loader sass-loader style-loader node-sass file-loader [/bash] エントリーポイントとなるJavaScript作成 scssをインポートするところがポイントです。 index.js [bash] import React from “react” import {render} from “react-dom” import App from “./components/App” //scssのコンパイル import “./sass/style.scss” document.write(“ “); render ( , document.getElementById(“root”) ) [/bash] アプリ画面のトップページ作成 components/App.js [bash] import React from “react” const App = () => { return ( This is React Scss App This is Menu ) } export default App [/bash] エントリーポイントとなるHTML ./main.css や ./main.bundle.js というような相対パスではなく、絶対パスで書くことがポイント! (react routerなどで、パラメーターを渡したパスを起動するときに、エラーが出てハマりますので・・・) dist/index.html [bash] React Scssコンパイル [/bash] SASSのファイル sass/style.scss [bash] div.contents{ margin-top: 30px; div.menus { padding: 20px; label { background-color: #45cc77; } } } [/bash] webpack.config.js webpack.config.js [bash] var path = require(‘path’); var webpack = require(‘webpack’); const ExtractTextPlugin = require(‘extract-text-webpack-plugin’); module.exports = { entry: { main: ‘./index.js’, }, output: { path: path.join(__dirname, ‘dist’), filename: “[name].bundle.js” }, module: { loaders: [ { test: /\.js[x]?$/, loader: ‘babel-loader’, exclude: /node_modules/, query: { presets: [‘es2015’, ‘react’] } }, { test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$/, loader: “file-loader?name=images/[name].[ext]” }, { test: /\.scss$/, loader: ExtractTextPlugin.extract(‘css-loader!sass-loader’), }, ] }, plugins: [ new ExtractTextPlugin({ filename: ‘[name].css’, allChunks: true }), ], resolve: { extensions: [‘.js’, ‘.jsx’] }, devServer: { inline: true, hot: true } } [/bash] ES6、SCSSコンパイル [bash] $ ./node_modules/.bin/webpack [/bash] distフォルダ配下に main.bundle.jsとmain.cssが出来ていれば成功です。 package.jsonの調整 scriptsのところに startとbuildを追加 package.json [bash] { “name”: “sass_react”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo \”Error: no test specified\” && exit 1″, “start”: “webpack-dev-server –content-base dist/ –hot –inline –progress –colors”, “build”: “webpack –progress –colors” }, [/bash] 動作検証 [bash] $ npm start [/bash] ブラウザーで “http://localhost:8080” で、以下のようなページが表示されれば、ES6のコンパイルとSCSSのコンパイルは成功です。

    Read more
  • dockerを使って RailsとReact(node)の開発環境を構築する
    dockerを使って RailsとReact(node)の開発環境を構築する
    No Comments on dockerを使って RailsとReact(node)の開発環境を構築する

    はじめに dockerを使って、Rails(サーバーサイド)の環境を1つ、React(node)(クライアントサイド)の環境を1つ作る。 両環境ではChromeとAtomが動かせて、開発できるようになるところまでが目標です。 docker 自体触るのが初めてだったので、基本的なことから記載しておきます。 加えて初心者なので、間違えがあったらごめんなさい。 docker自体のインストールはあちこちに記載されているので、そちらを参照ください。 docker イメージのダウンロード まず、ubuntuのイメージを検索します。 スター(STARS)が多い、イメージをダウンロード(PULL)すればOKでしょう。 [bash] $ docker search “ubuntu 16” NAME DESCRIPTION STARS OFFICIAL AUTOMATED ubuntu Ubuntu is a Debian-based Linux operating s… 5919 [OK] 1and1internet/ubuntu-16-nginx-php-phpmyadmin-mysql-5 ubuntu-16-nginx-php-phpmyadmin-mysql-5 12 [OK] 1and1internet/ubuntu-16-apache ubuntu-16-apache 2 [OK] $ docker pull ubuntu [/bash] dockerの基本的な操作 dockerを起動し、bashシェルを起動します。 [bash] $ docker run -ti ubuntu bash [/bash] dockerの仮想環境でbashシェルが走ります。 ホストOSから dockerを閲覧・操作する ■起動中のコンテナの確認 [bash] $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 561fabf4163e ubuntu “bash” 9 minutes ago Up 9 minutes adxx_eadd [/bash] ■停止しているコンテナの確認 [bash] $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8541933cccdc hello-world “/hello” 3 seconds ago Exited (0) 2 seconds ago sick_euclid [/bash] ■コンテナの削除 [bash] docker rm [CONTAINER ID] [/bash] ■dockerイメージ一覧 [bash] $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest e7e0c0b1785f About an hour ago 163.1 MB [/bash] ■コンソールを複数立ち上げたい場合 docker ps で起動中のコンテナ(CONTAINER ID)を確認し、そのNAMEで 別のコンソールを立ち上げる [bash] $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS cac1b80e535d ubuntu “bash” 13 minutes ago Exited (0) 9 seconds ago [/bash] [bash] $ docker exec -ti cac1 bash [/bash] ■dockerイメージの保存 ホストPC側で 起動中のdocker を 新しいリポジトリ名で commit します。 [bash] $ docker commit cac1 ubuntu/test1 sha256:ea0b9db5551f4e5a8cd5959f16b54943deda0b6d481fce39092f6b6af5333ff3 [/bash] ■dockerコンテナをむやみに増やさない方法 docker run するたびに新しいコンテナが出来ていきます。 これでは管理がややこしくなるので、docker run するときに –rm オプションをつけて docker から exitしたら、コンテナを削除するようにしたほうが便利です。 docker で作業したイメージを保存したい場合は、上に記述した docker commitをすればOKですので。 [bash] $ docker run –rm -ti ubuntu bash [/bash] Atom(エディタ)とChrome(ブラウザー)を動かせるようにする docker環境で、AtomとChromeをインストールしてみましょう。 最初は 最低限必要そうなパッケージを入れてみます。 [bash] # apt-get update [/bash] wget,net-tools コマンドのインストール [bash] # apt-get install wget net-tools nano [/bash] docker の X Windowシステムを使えるようにする。 dockerはたぶんコマンドラインで出来るサーバー環境を構築することをするのがメインなのかと思いますが・・・ 今回は開発環境をdockerで作りたいので、 dockerのX Windowシステムを使えるようにしてやる必要があります。 いきなりAtomやChromeをインストールするのは重いので、 x11-appsをインストールして動作確認してみます。 [bash] # apt-get install sudo # apt-get install x11-apps [/bash] ■dockerイメージの保存 ホストPC側で 起動中のdocker を 新しいリポジトリ名で commit します。 [bash] $ docker commit cac1 ubuntu/x11 sha256:ea0b9db5551f4e5a8cd5959f16b54943deda0b6d481fce39092f6b6af5333ff3 [/bash] 一旦 docker(仮想環境)から exitして再度、X Window を利用できるようなパラメーターをつけて、dockerを起動します。 [bash] # exit [/bash] dockerのX Windowシステムをホスト側で使えるようにして、起動する [bash] $ docker run -ti –privileged –rm -e DISPLAY=$DISPLAY -v /tmp/.X11-unix/:/tmp/.X11-unix [IMAGE ID] bash [/bash] ユーザーの作成・xclockを起動 root ユーザーではX Window を利用できないようなので、一般ユーザーを作ります。 そしてその一般ユーザーに sudo権限を与えます。…

    Read more
  • Webpackを使って、Sassをコンパイルする方法 2017年4月版
    Webpackを使って、Sassをコンパイルする方法 2017年4月版
    No Comments on Webpackを使って、Sassをコンパイルする方法 2017年4月版

    はじめに Webpackを使って、Sassをコンパイルする方法です。 Webにそれなりにやり方が載ってますが、2017年4月末現在では、動く例が少なかったので、 実際に動いた例を示しておきます。 末尾にReactなんかの開発で ES6をノーマルなJavascriptに変換するときとの合わせ技の方法も記載しておきます。 コンパイル環境構築 [bash] $ mkdir test-saas $ cd test-saas $ npm init -y $ npm install webpack extract-text-webpack-plugin css-loader sass-loader style-loader node-sass –save-dev [/bash] こんな感じのフォルダツリーにsassがあると仮定します。 webpack.configの調整 ルートフォルダに webpack.config.jsを以下のような内容で作成します。 目標は sassフォルダにある app.scssとsub.scssを dist/cssフォルダにapp.cssとsub.cssとして出力する。 [bash] var path = require(‘path’); var webpack = require(‘webpack’); var ExtractTextPlugin = require(‘extract-text-webpack-plugin’); module.exports = { devtool: ‘source-map’, entry: { app: ‘./sass/app.scss’, sub: ‘./sass/sub.scss’ }, output: { path: path.join(__dirname, ‘./dist/css’), filename: ‘[name].css’ }, module: { loaders: [ { test: /\.scss$/, loader: ExtractTextPlugin.extract(‘css-loader!sass-loader’) } ] }, plugins: [ new ExtractTextPlugin(‘[name].css’) ], } [/bash] 動作環境(各モジュールのバージョン) 2017年4月現在で動いたのは以下のバージョンです。 [bash] “devDependencies”: { “css-loader”: “^0.28.0”, “extract-text-webpack-plugin”: “^2.1.0”, “node-sass”: “^4.5.2”, “sass-loader”: “^6.0.3”, “style-loader”: “^0.16.1”, “webpack”: “^2.4.1”, } [/bash] コンパイル [bash] ./node_modules/.bin/webpack [/bash] こんな感じにファイルができていれば成功です。 あとがき sassだけをコンパイルしたいケースは少ないと思いますが、Reactなんかでアプリを作り 同時にsassもコンパイルしたいケースなんかに webpack.config.js をどう設定するかという ことで悩んだりしますよね。 ES6とかとのコンパイルと組み合わせたい場合は、 [bash] module.exports = { ごにょごにょ } [/bash] の部分を配列定義に変えてやって、 [bash] module.exports = [ { ごにょごにょ }, { ごにょごにょ }, ] [/bash] としてやってください。 Reactで ES6をノーマルなJavaScriptに変換するときの webpack.config.jsの設定は こちらの記事を参考にしてみてください。

    Read more
  • React Native デバイスの縦向き(PORTRAIT)、横向き(LANDSCAPE)の変化を捉える(イベント)
    React Native デバイスの縦向き(PORTRAIT)、横向き(LANDSCAPE)の変化を捉える(イベント)
    No Comments on React Native デバイスの縦向き(PORTRAIT)、横向き(LANDSCAPE)の変化を捉える(イベント)

    はじめに React Nativeを初めて10日ほどが立ちました。画面デザインが難しいですね。 flex layoutを使うと、それなりの画面は出来ますが・・・ ヘッダー(Navigation)とフッター(Menu or Tab)のサイズが 曖昧になり、 時には隠れてしまうケースもあります。 で、どうすりゃいいんだと考えて、今、思いついたのは、 flex layoutを諦めて、固定ピッチで画面デザインをしてやること。 固定ピッチと言っても、デバイスの画面サイズを判定して、 ヘッダーはこの高さ、メインのコンテンツはこの高さ、フッターはこの高さ というのを計算で求めてやるのが、一番スマートかなという気がしています。 そこで、デバイスの向きが縦向きから横向きに変化、その逆、両方に対応して どんなロジックを書けばいいか考えてみました。 画面を構成するコンポーネント だいたいこんなイメージで・・・ index.android.js や index.ios.js で これらを含んだ Viewを1つレンダリングしてやる ようなイメージです。 デバイスの向きの変化を捉える これ、 Viewに onLayoutというイベントがありますので、こいつを使います。 こいつと、Dimensionsから求められるデバイスの画面サイズを組み合わせて、 ヘッダー・フッター・メインコンテンツの高さを計算します。 それを setStateなんかを使って、設定してあげて、画面を再描画すればOKです。 日本語で書くとあっさりしてますが、実際実装するとなると それなりにコードを書かなきゃなりません。 index.android.js の例 [bash] import React from “react” import {View, Dimensions, Alert} from “react-native” 〜 中略 〜 handleLayoutChange(event) { let {height, width} = Dimensions.get(“window”) if (height > width) { Alert.alert(“縦向き PORTRAIT”) } else{ Alert.alert(“横向き LANDSCAPE”) } } render() { { this.handleLayoutChange(event) }}> } 〜 中略 〜 [/bash] 動作イメージ 実際にうまく計算できると、こんな感じになります。 あとがき React Native以外でスマホ向けのアプリを作ったことがないので、 実際にこんなロジックを実装しないといけないのかどうかよく分かっておりません。。 なんで、フッターを画面の下にはりつけることぐらい出来ないんだよ・・・ と思いますが、今 軽く調べた感じでは、方法はなさそうなので、 ゴリゴリ計算して求めちゃう方法が、一番手っ取り早い気がします。 もっとスマートな方法があれば、じゃんじゃんコメントください。

    Read more
  • Reactで react-dropzone と axiosを使ってファイルアップロードを実装してみる(バックエンドはRails)
    Reactで react-dropzone と axiosを使ってファイルアップロードを実装してみる(バックエンドはRails)
    No Comments on Reactで react-dropzone と axiosを使ってファイルアップロードを実装してみる(バックエンドはRails)

    はじめに Reactでファイルアップロードを実装してみたくて、どのような方法があるのか調べてみた。 あまり深く調べてはないが、1つめに良さそうだと感じた react-dropzone を使ってみたら、 これがかなりいい!! あまりに気に入ってしまったので、react-dropzoneでアップロード対象のファイルを選んで、 axiosでPOSTして、サーバーサイドのRailsでファイルを保存するところまでをざっと紹介します。 Reactのことは分かっている前提ですすめますので、あしからず。 出来上がりのイメージはこんな感じ 画面左上に ファイルをドロップする場所があり、その右側にドロップされたファイルを列挙する場所がある。 で、画面下部にアップロード対象ファイルのプレビュー表示。 npm で必要なものをインストール [bash] $ npm install –save react-dropzone axios [/bash] アップロード画面実装 一気に貼り付けちゃいましたので、ざっと説明しておきます。 DropZone タグで、ドラッグドロップでファイルをアップロードできる機能が できちゃいます。acceptプロパティで受け付ける画像の種類(ファイルの種類)を指定し、 onDropでドラッグドロップされた時の処理を実装します。 今回はonDropで受付可能なファイル、不可能なファイルを引数で受け取り、 ステートを変えて、ドロップ領域の右側にそれらのファイル名を列挙します。 と同時に、画面下部に受付可能なファイルのプレビューを表示しちゃいます。 これがいとも簡単なんです。びっくりですよ。 アップロードボタンがクリックされたら、FormData オブジェクトを作り、 そこにアップロード対象のファイルを詰め込んで、axiosでPOSTします。 たったこれだけなんですよ、ホント便利ですね。 何個のファイルがアップロードされるかは不定なので、 file_1、file_2、file_3のような連番の名前をつけておきます。 [bash] import React from “react” import DropZone from “react-dropzone” import axios from “axios” import {HOST_NAME} from “../constants/index” class Upload extends React.Component { constructor(props){ super(props) this.state = {acceptedfiles: [],rejectedfiles: []} } handleDrop(accepted, rejected) { this.setState({acceptedfiles: accepted, rejectedfiles: rejected}) } handleOpenClick() { this.dropzone.open() } handleUpload() { let data = new FormData() data.append(‘name’, ‘Tom’) data.append(‘age’, ’28’) let cnt = 1 this.state.acceptedfiles.map(file =>{ data.append(‘file_’ + cnt, file) cnt+=1 }) axios.post(HOST_NAME + ‘/upload_image’, data) } render(){ let prev = [] this.state.acceptedfiles.map(f => prev.push( ) ) return ( Hello Upload… this.dropzone = node} accept=”image/jpg,image/png” onDrop={(accepted, rejected)=> this.handleDrop(accepted, rejected)} > ここにファイルをドロップしてください。 this.handleOpenClick(e)}> Open Dropzone this.handleUpload(e)}> Upload実行 Accepted files… {this.state.acceptedfiles.map(f => {f.name} – {f.size} )} Rejected files… {this.state.rejectedfiles.map(f => {f.name} – {f.size} )} Preview {prev} ) } } export default Upload [/bash] サーバーサイドの実装 Railsの実装になります。 まず最初に渡ってきたパラメーター(:name)に従ってフォルダを作ります。 今回の例では Tomさん固定です。 で、file_1、file_2、file_3と連番でアップロードされてきたファイルを そのフォルダに保存します。 ね、簡単でしょ。 [bash] def upload_image image_path = Rails.root.to_s + “/public/upload_images/” + params[:name] FileUtils.mkdir_p(image_path) unless FileTest.exist?(image_path) cnt = 1 while true do break if params[“file_” + cnt.to_s].blank? file = params[“file_” + cnt.to_s] fname = file.original_filename File.open(image_path + “/#{fname}”, ‘wb’) { |f| f.write(file.read) } cnt += 1 end end [/bash] デザインCSS とりあえず、適当ですが、こんな感じで。 [bash] div.uploadFile{ float: left; width: 260px; } div.preview { float: left; padding: 10px; border: 1px solid #cccccc; } br.clear { clear: both; } [/bash] あとがき 出来上がってみれば、本当に簡単に出来てしまったように見えるのですが・・・ 画像をPOSTした時 Rails側で – 422 unprocessable entity – Invalid form authenticity token というエラーが出て悩まされました。CSRFについては分かってるつもりだったのですが、 分かってなかったようです。 ReactでSPAを作る時なんかは JSをサーブするサーバーと、APIをサーブするサーバーが 別になると思うので、 gem rack-cors を導入して、信用できるドメインを絞り込んで APIを受け付ける。 で、CSRFは ApplicationControllerで – protect_from_forgery with: :null_session として、とりあえず何でも受け付けるようにする。という感じでしょうか。

    Read more
  • ReactでSPAを作り、Twitter認証(OAuth)でログインする。バックエンドはRails
    ReactでSPAを作り、Twitter認証(OAuth)でログインする。バックエンドはRails
    No Comments on ReactでSPAを作り、Twitter認証(OAuth)でログインする。バックエンドはRails

    はじめに ReactでSPAを作ることになって、ログイン認証にメール&パスワードの認証に加えて、 GoogleやTwitter認証も加えたくなった時、どうすればよいかわからなかった。 バックエンドはRailsだが、あくまでAPIサーバー的な立場なので メジャーなomniauth なんかは使えそうにない。。。 前回のGoogle編に続き、今回はTwitter編。 あれこれ悩みながらなんとか動くところまでたどり着いたので、やり方を公開。 React+Reduxの環境なんですが、そこまで立ち入ると話がややこしくなるので、 実装のエキス部分だけを記載してみます。 Twitter Developerにアプリ登録 Twitter Developer これについては上記のURLにアクセスしてアプリを登録してください。 アプリ名とコールバックURLを登録したら、Consumer Key (API Key)とConsumer Secret (API Secret)がもらえます。 詳しくは書きません、ググればこれはたくさん書いてあります。 動作イメージ デザインはしょぼいですが、こんな感じのログイン画面。 GoogleかTwitterで認証してログインしてね、というもの。 今回はTwitterについてのみ取り上げます。 Twitterでログインをクリックすると、Twitterが用意しているOAuth用の画面が表示されて、 Twitterのユーザー名とパスワードを入れて、最後に今回作るアプリ(SPA)へのアクセス許可画面が表示され、 許可すると、SPAアプリにログイン出来るというもの。 ステップ1(Javascript、Ajax) まずは まずはTwitterのOAuth用URLの取得のためのRails API(自作)呼び出し。 Reactの書き方なので、functionらしくないですが・・・ただのfunctionだと思ってソースを見てください。 API呼び出しの結果、OAuth用のURLが得られるので、window.openする感じです。 ポップアップというか別タブでTwitterのOAuth画面が開きます。 で、window.openしたあとは setTimeoutで1秒おきにウィンドウが閉じたかどうかをチェックします。 [bash] onLoginTwitter: (history) => { axios.get(“http://localhost:3000/get_twitter_oauth_url”, { withCredentials: true, params: {} }) .then(function (response) { windowLogin = window.open(response.data.oauth_url) setTimeout(function(){CheckLoginStatusTwitter(history)}, 1000) windowLogin.focus() }) .catch(function (response) { console.log(response); }); } [/bash] ステップ1.5(Rails) ステップ1から呼び出されるRailsのソース。 CONSUMER_IDとCONSUMER_SECRETを用いて、OAuth用のURLを取得し、JSONで結果を戻します。 [bash] require ‘oauth’ def get_twitter_oauth_url consumer = OAuth::Consumer.new( Oauth::TWITTER_CONSUMER_ID, Oauth::TWITTER_CONSUMER_SECRET, { :site => “https://api.twitter.com” } ) request_token = consumer.get_request_token( :oauth_callback => “http://localhost:3000/oauth_twitter” ) cookies[:request_token] = request_token.token cookies[:request_token_secret] = request_token.secret rtn = {} rtn[“status”] = true rtn[“oauth_url”] = request_token.authorize_url render json: rtn end [/bash] ステップ2(Javascript) ステップ1のソースのWindow呼び出しのところだけを抜粋。 RailsのAPIで戻ってきた認証用URLを引数にして window.open。 [bash] windowLogin = window.open(response.data.oauth_url) [/bash] ステップ3 ここはプログラムする必要なしです。TwitterのOAuthの画面で 純粋に認証&許可をしてもらいます。 ステップ4(Rails) Twitter側の認証と許可の処理が終わると、ステップ1.5で指定したコールバックURLに制御が戻ってきます。 テスト環境ですのでRailsのデフォルト http://localhost:3000 でサーバーが立ち上がっていて、 コールバックURLは http://localhost:3000/oauth_twitter です。 では、Railsの方の実装を見てみましょう。 最初にCONSUMER_IDとCONSUMER_SECRETを用いて、トークンを作成します。 さらに、パラメーターで渡ってきた、 oauth_tokenとoauth_verifierを使って アクセストークンを得ます。 最後に得られたアクセストークンを使ってAPIアクセスし、ユーザー情報を得ます。 で、cookiesに得られた情報をセットして、このメソッドを抜けます。 cookies[:oauth_token] = params[:oauth_token] cookies[:twitter_uid] = user_info[“id”] cookies[:screen_name] = user_info[“screen_name”] [bash] class OauthController < ApplicationController #URL /oauth_twitter への処理 def twitter consumer = OAuth::Consumer.new( Oauth::TWITTER_CONSUMER_ID, Oauth::TWITTER_CONSUMER_SECRET, { :site => “https://api.twitter.com” } ) request_token = OAuth::RequestToken.new( consumer, cookies[:request_token], cookies[:request_token_secret] ) access_token = request_token.get_access_token( {}, :oauth_token => params[:oauth_token], :oauth_verifier => params[:oauth_verifier] ) response = consumer.request( :get, ‘/1.1/account/verify_credentials.json’, access_token, { :scheme => :query_string } ) rtn = {} case response when Net::HTTPSuccess user_info = JSON.parse(response.body) if user_info[“screen_name”] cookies[:oauth_token] = params[:oauth_token] cookies[:twitter_uid] = user_info[“id”] cookies[:screen_name] = user_info[“screen_name”] else #”Authentication failed” end else #”Failed to get user info via OAuth” end end end [/bash] RailsのGemfileに以下の3つのgemを追加します。 [bash] gem ‘json’ gem ‘oauth’ gem ‘rack-cors’, :require => ‘rack/cors’ [/bash] SPAをサーブしているサーバーとAPIをサーブしているサーバーが別物になるので、 クロスドメインなAPIアクセスとなるため、API側でそれを許可する必要があります。 gem rack-cors でそれに対応できます。で、対応するためにapplication.rbを 以下のように書き換えます。 本番ではoriginsは「*」ではなく、ちゃんと特定のドメイン名を入れるべし。 config/application.rb [bash] class Application < Rails::Application config.middleware.insert_before ActionDispatch::Static, Rack::Cors do allow do origins '*' resource '*', :headers => :any, :methods => [:get, :post, :delete] end end end [/bash] ステップ5(RailsのJavascript) OauthControllerのtwitterメソッドで処理をしたので、普通に処理が終われば、ポップアップ画面には そのメソッドに対応するビューが表示されます。 なので、対応するビューは作るのですが、あくまでダミーで結構です。 そもそも、ポップアップ(別タブ)でTwitter認証が開いていて、認証が終わればポップアップは閉じてほしい はずですので・・・ で、assets/javascripts 配下に以下のようなJavaScript(CoffeeScriptではない)を作ります。 ファイル名はなんでもいい。 で、ここでする処理は、ダミーのビューが表示された時にそのhrefをチェックして、 認証終了のダミービューだと判定できたら、window.closeしてやるというものです。 [bash] $(function(){ if (window.location.href.indexOf(“oauth_twitter”) >0){ window.close(); }…

    Read more
  • ReactでSPAを作り、Google認証(OAuth)でログインする。バックエンドはRails
    ReactでSPAを作り、Google認証(OAuth)でログインする。バックエンドはRails
    No Comments on ReactでSPAを作り、Google認証(OAuth)でログインする。バックエンドはRails

    はじめに ReactでSPAを作ることになって、ログイン認証にメール&パスワードの認証に加えて、 GoogleやTwitter認証も加えたくなった時、どうすればよいかわからなかった。 バックエンドはRailsだが、あくまでAPIサーバー的な立場なので メジャーなomniauth なんかは使えそうにない。。。 あれこれ悩みながらなんとか動くところまでたどり着いたので、やり方を公開。 React+Reduxの環境なんですが、そこまで立ち入ると話がややこしくなるので、 実装のエキス部分だけを記載してみます。 Google Developer Consoleにアプリ登録 Google Developers Console これについては上記のURLにアクセスしてアプリを登録してください。 アプリ名とコールバックURLを登録したら、クライアントIDとクライアントシークレットがもらえます。 詳しくは書きません、ググればこれはたくさん書いてあります。 動作イメージ デザインはしょぼいですが、こんな感じのログイン画面。 GoogleかTwitterで認証してログインしてね、というもの。 今回はGoogleについてのみ取り上げます。 Google+でログインをクリックすると、Googleが用意しているOAuth用の画面が表示されて、 Googleのユーザー名とパスワードを入れて、最後に今回作るアプリ(SPA)へのアクセス許可画面が表示され、 許可すると、SPAアプリにログイン出来るというもの。 ステップ1(Javascript) まずはGoogleのOAuth画面の呼び出し。 Reactの書き方なので、functionらしくないですが・・・ただのfunctionだと思ってソースを見てください。 https://accounts.google.com/o/oauth2/auth に必要なパラメーターをざーっとつけて window.openする感じです。 ポップアップというか別タブでGoogleのOAuth画面が開きます。 で、window.openしたあとは setTimeoutで1秒おきにウィンドウが閉じたかどうかをチェックします。 [bash] onLoginGoogle: (history) => { const GOOGLE_CLIENT_ID = “52630050245845-i8xt563tc215pv9mxjy13e2ewm6mrewwl.apps.googleusercontent.com” let state_token = getRndStr() Cookies.set(“mystate”, state_token) let url = “https://accounts.google.com/o/oauth2/auth?” url += “client_id=” + GOOGLE_CLIENT_ID + “&” url += “response_type=code&” url += “scope=email%20profile&” url += “redirect_uri=http://localhost:3000/oauth_google&” url += “state=” + state_token windowLogin = window.open(url) setTimeout(function(){CheckLoginStatusGoogle(history)}, 1000) windowLogin.focus() }, const getRndStr = () => { ランダムな文字列を生成するメソッドです。 } [/bash] ステップ2(Rails) ステップ1でウィンドウを開くときのURLにコールバック用のURLを設定していますので、Google側の認証が終われば、 コールバックURL(自分で実装するアプリ)に制御が戻ってきます。 テスト環境ですのでRailsのデフォルト http://localhost:3000 でサーバーが立ち上がっていて、 コールバックURLは http://localhost:3000/oauth_google です。 では、Railsの方の実装を見てみましょう。 最初にステップ1でGoogleの認証画面を呼び出した時のステート(ランダム文字列)と Googleさんが戻してきたステートが一致するか確認します。 OKならば、Developer Consoleで登録した、CLIENT_IDとCLIENT_SECRETを使い、 トークンを取得しに行きます。 最後に得られたトークンを使って、API呼び出しをしてGoogleに登録されている情報を読み出します。 で、cookiesに得られた情報をセットして、このメソッドを抜けます。 cookies[:google_code] = params[:code] cookies[:email] = email cookies[:google_uid] = api_result[“user_id”] [bash] class OauthController < ApplicationController #URL /oauth_google への処理 def google cookies[:google_code] = params[:code] if cookies[:mystate].present? && cookies[:mystate] == params[:state] && params[:code].present? postData = { code: params[:code], client_id: Oauth::GOOGLE_CLIENT_ID, client_secret: Oauth::GOOGLE_CLIENT_SECRET, redirect_uri: "http://localhost:3000/oauth_google", grant_type: "authorization_code" } response = Net::HTTP::post_form(URI.parse("https://accounts.google.com/o/oauth2/token"),postData) result = JSON.parse(response.body) if result["expires_in"] > 0 && result[“id_token”].present? api_response = Net::HTTP.get URI.parse(“https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=#{result[‘id_token’]}”) api_result = JSON.parse(api_response) if api_result[“issuer”] == “accounts.google.com” && api_result[“issued_to”] == Oauth::GOOGLE_CLIENT_ID && api_result[“audience”] == Oauth::GOOGLE_CLIENT_ID && api_result[“email_verified”] email = api_result[“email”] puts “Signin: success Google OAuth ‘#{email}’ / #{api_result[‘user_id’]}” cookies[:email] = email cookies[:google_uid] = api_result[“user_id”] end end end end end [/bash] ステップ3(RailsのJavascript) OauthControllerのgoogleメソッドで処理をしたので、普通に処理が終われば、ポップアップ画面には そのメソッドに対応するビューが表示されます。 なので、対応するビューは作るのですが、あくまでダミーで結構です。 そもそも、ポップアップ(別タブ)でGoogle認証が開いていて、認証が終わればポップアップは閉じてほしい はずですので・・・ で、assets/javascripts 配下に以下のようなJavaScript(CoffeeScriptではない)を作ります。 ファイル名はなんでもいい。 で、ここでする処理は、ダミーのビューが表示された時にそのhrefをチェックして、 認証終了のダミービューだと判定できたら、window.closeしてやるというものです。 [bash] $(function(){ if (window.location.href.indexOf(“oauth_google”) >0){ window.close(); } }); [/bash] ステップ4(SPAのJavascript) 最後にクライアントサイドです。window.openの時にsetTimeoutで呼ばれるメソッドです。 windowLogin.closedの状態を1秒間隔でチェックし、Googleの認証画面が閉じたら alertに認証で得られた情報をクッキーから表示します。(別に表示する必要はないのですが・・・) で、認証が通ったと判定できたら、アプリのトップページなどへジャンプしてやります。 あとはSPAなのでAPIサーバーへアクセスするたびにおそらくユーザー認証も必ずすることになると思うので、 APIを呼ぶたびに、ここで得られた情報を付与してやるイメージでしょうか・・・ 本題とはそれますが、setTimeoutで引数をつけたfunctionを呼びたいときは 無名関数とする のがルールのようです。 [bash] const CheckLoginStatusGoogle = (history) => { if (windowLogin.closed) { alert(Cookies.get(‘mystate’) + “\n” + Cookies.get(‘google_code’) + “\n” + Cookies.get(’email’) + “\n” + Cookies.get(‘google_uid’)) if (Cookies.get(‘google_code’) != undefined && Cookies.get(’email’) != undefined){ history.push(“/counter”) } else{ alert(“認証に失敗しました”) } } else{ setTimeout(function(){CheckLoginStatusGoogle(history)}, 1000) } } [/bash] あとがき あっさりと説明しちゃいましたが、理屈がわかるまで結構時間がかかりました。 SPAでSNS認証するならどうすりゃいいんだという情報が、以外になくて・・・ あちこち読み漁って、なんとか形になりました。 何かの参考になればと・・・・

    Read more
  • React-Router(react-router-dom)とReduxとRailsを組み合わせる
    React-Router(react-router-dom)とReduxとRailsを組み合わせる
    No Comments on React-Router(react-router-dom)とReduxとRailsを組み合わせる

    はじめに React+Redux+RailsでSPAアプリを作ることになったので、あれこれ調べたのでメモです。 SPAというと、当然ですが、1つのページであれこれできちゃうものを作らなきゃいけないわけで・・・ そうなるとコンテンツ制御の仕組みが必要です。そこで登場するのがReact-Router-Dom。 で、これ単体では使い方簡単なんですが、RailsとReduxを絡めて動作させる方法が分からなかったので あれこれ調べて、なんとか動くところまでたどり着いたので、メモを公開します。 React-Routerは古いようで、React-Router-Domにしてみました。。 ドキュメントが超少なくてまいりました。 完成図はこんな感じ。 青いところがメニューで、クリックすると、下のコンテンツがReact-Routerで入れ替わります。 ソースコードはGitHubで公開しています。利用方法は、記事の末尾を参照ください。 Railsプロジェクト作成 [bash] $ rails new react_router_with_redux $ cd react_router_with_redux/ [/bash] Gemを調整して、bundle install [bash] $ nano Gemfile [/bash] [bash] gem ‘therubyracer’, platforms: :ruby gem ‘react-rails’ gem ‘browserify-rails’ [/bash] [bash] $ bundle install [/bash] application.rbを編集 作った ES6(JavaScriptのバージョン)のソースをES5に自動変換するための設定を追記 config.browserify_rails の行を追加 config/application.rb [bash] require_relative ‘boot’ require ‘rails/all’ # Require the gems listed in Gemfile, including any gems # you’ve limited to :test, :development, or :production. Bundler.require(*Rails.groups) module RailsRedux class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. + config.browserify_rails.commandline_options = '-t babelify' end end [/bash] Rails に Reactインストール [bash] $ rails g react:install [/bash] application.jsの編集 app/assets/javascripts/application.js ・Reactは npmでインストールしたものを使うのでカット。 ・require_tree . もカット 変更前 [bash] //= require jquery //= require jquery_ujs //= require turbolinks //= require react //= require react_ujs //= require components //= require_tree . [/bash] 変更後 [bash] //= require jquery //= require jquery_ujs //= require turbolinks //= require react_ujs //= require components [/bash] React、Redux、etc…の準備(node_modules) React、Redux、React-Routerその他諸々を npm を使ってインストールします。 [bash] $ npm init -y $ npm install –save-dev browserify browserify-incremental babelify babel-preset-es2015 babel-preset-react $ npm install –save react react-dom react-redux redux redux-thunk react-router-dom [/bash] .babelrc ファイルをRailsのルートフォルダに作成 [bash] { “presets”: [“es2015”, “react”] } [/bash] Reduxの標準的なフォルダを作成 [bash] $ cd app/assets/javascripts/ $ mkdir actions containers reducers [/bash] components.js編集 app/assets/javascripts/components.js アプリ名は 「MyApp」とします。 myapp.jsはアプリのエントリーポイントとなるJSファイルです。ここに定義したクラス名(const宣言)を windowの下にセット(変数として公開)してやる。 変更前 [bash] //= require_tree ./components [/bash] 変更後 [bash] window.React = require(‘react’); window.ReactDOM = require(‘react-dom’); window.MyApp = require(‘./myapp.js’); [/bash] Reduxでカウンターアプリ、+静的ページをひたすら作る ここ以降、しばらくは、Reduxでカウンターのインクリメント、デクリメントできるアプリを作ります。 加えて、静的ページもいくつか作ります。 何も考えずにソースをコピーしていってやってください・・・ app/assets/javascripts/actions/index.js [bash] export const counter_increment = () => { return { type: “INCREMENT” } } export const counter_decrement = () => { return { type: “DECREMENT” } } [/bash] app/assets/javascripts/components/About.js [bash] import React from “react” const About = () => { return ( This is About ) } export…

    Read more
  • React、Redux入門 公式サイトとはちょっと違う角度から
    React、Redux入門 公式サイトとはちょっと違う角度から
    No Comments on React、Redux入門 公式サイトとはちょっと違う角度から

    はじめに ここ3週間ほど死ぬほどReactとReduxやって、あれこれ分かるようになったので、これからReact+Reduxで開発を始めたい人向けに記事を書いてみます。 公式サイトの英語記事や、それを翻訳してくれているようなサイトを見てもそれなりに分かるのですが・・・ なんだかんだ言って難しい。 Reactの理解はなんとか出来ても、Reduxが特に難しい。 欲張りなんですが、とにかく両方をサーッと理解して、さっさとアプリ組みたいよという方の助けになれればと・・・ 考えていく手順 画面イメージ 画面を構築するためのコンポーネント階層図 ステート イベント 基本的にこの4つを考えればOKです。 既に別の記事で書いている RailsとReactとReduxを組み合わせた掲示板アプリの画面を例にとって説明します。 まずは作るアプリの画面イメージを頭の中で構築してください。紙に絵を書いてみたりするといいですね。 この例の場合は、画面は至ってシンプル。画面左側に掲示板一覧がリストアップされていて、その掲示板のどれかを選択すると、画面右側に掲示板に投稿されたコメント一覧が列挙される。 画面右上では、掲示板に新たにコメントを投稿できる。こんなもの。 この画面を構成するパーツをReact風に考えてみると、ざっとこんな感じ。 これが「画面を構築するためのコンポーネント階層図」。 次、ステート。 この画面イメージを見て、変動しそうなものを考える。 ざっと考えると掲示板一覧と、コメント一覧、アクティブな掲示板、ユーザー一覧 ぐらい。 ユーザー一覧がなんで必要かというと、このアプリでは説明を簡単にするために、ログイン処理など設けずに、投稿する際に「自分は誰々ですよ」と選択して、コメントを投稿するために必要です。 で、上記の4つが本当にステートか考える。 よく考えてみると、掲示板一覧は追加できる機能がないので、初期値固定のまま。ユーザー一覧も初期値固定のまま。 よってこの2つはステートとは考えない。 じゃあ、ステートとして残るのは コメント一覧 アクティブな掲示板 の2つだけ。 次、イベント。 これはすぐ解ると思います。 掲示板一覧からどれかを選択する 掲示板にコメントを投稿する この2つだけ。 どうやってアプリを組み上げていくか 画面イメージ 画面を構築するためのコンポーネント階層図 ステート イベント 考える手順でこれはできたので、どこからか手を付けていきます。 画面から先に作りたくなるかもしれませんが、あれこれややこしくなるので アプリのトップになるApp.js ぐらいだけ作って、そこにHelloWorldぐらいを出すだけにして、 先にデータ構造とイベントの部分を考えましょう。たぶんこれがRedux風な考え方。 データ構造とはつまりステートのこと。データ構造が変化するときはどんな時かというとイベントが発生した時。 となるので、まずイベント一覧(ActionCreator)を作りましょう。 actions/index.js [bash] export const board_select = (id) => { return { type: “BOARD_SELECT”, id: id } } export const comment_read = (comments) => { return { type: “COMMENT_READ”, comments: comments } } [/bash] ここでは2つのfunctionを作って、そのconst(function)をexport してます。 functionの戻り値が actionオブジェクト。 ちょっと難しくなってきましたね。とりあえず、actionオブジェクトにはTypeプロパティを 必ず作り、そこにどんなイベントか識別できる文字列をセットしておきます。 そして、type以外のプロパティはイベントで発生したあとに変化する値を列挙します。 BOARD_SELECT つまり、掲示板が選択されたら、アクティブな掲示板IDが変わるので、 id をセットしておくというような感じ。 次、イベントが発生したあと、実際にデータ(public変数みたいなイメージ)を変更するところを作ります。 その場所がreducerです。 reducers/sel_board.js [bash] const sel_board = (state=-1, action)=> { switch (action.type) { case “BOARD_SELECT”: return action.id default: return state } } export default sel_board [/bash] これも1つ const(function)を定義したもの。functionの引数の1つめは state、2つめが action。 これはほぼ固定でOK。で、stateには 初期値をセットしておく。 掲示板アプリでは起動直後、アクティブな掲示板がないので、初期値は「-1」。 で、functionの中身はというと、 action.typeにセットされた文字列が、自分が反応したいイベントか判断して、 もしそうであれば、イベント後に変更したい値を作って返すというもの。 今回はアクティブな掲示板が変更された というイベントが来るので、当然新しく選ばれた掲示板のIDを返してあげる というものです。 ちょっと難しくなりますが、 reducerでは、stateに入っている現在の値は参照するのみとし、新しい結果を returnしてやるのがお約束です。 次に画面を構築するコンポーネントを見ていきましょう。 掲示板一覧のコンポーネント「BoardList」の例です。 初期化の引数に {datas, selb, onBoardClick} が来ています。 datas は掲示板一覧のデータの配列。 selbはアクティブな掲示板のID。 onBoardClickは掲示板を選択した時に反応してほしい functionがセットされています。 イベントの処理はここでは書かないのがRedux風。 components/BoardList.js [bash] import React from “react” import BoardLine from “./BoardLine” const BoardList = ({datas, selb, onBoardClick}) => { let list = [] datas.map((b) => { let active = (b.id == selb) list.push( onBoardClick(b.id)} />) }) return ( {list} ) } export default BoardList [/bash] ここが一番難しい、コンポーネントのラッパー(container)クラス。 BoardList.js をラップする CBoardList.js containers/CBoardList.js [bash] import React from “react” import {connect} from “react-redux” import {board_select, comment_read} from “../actions/index” import BoardList from “../components/BoardList” const mapStateToProps = (state, ownProps) =>{ return { datas: ownProps.boards, selb: state.sel_board } } const mapDispatchToProps = (dispatch) => { return { onBoardClick: (id) => { dispatch(board_select(id)) $.ajax({ type: “GET”, url: “/board/readComments/” + id, async: false, dataType: “json”, data: “name=John&location=Boston”, success: function(data, dataType){ if (data.status == true){ //alert(data.rows.length); if (data.rows.length > 0){ dispatch(comment_read(data.rows)) } else{ dispatch(comment_read([])) } } }, error :function(XMLHttpRequest, textStatus, errorThrown){ } }); } } } const CBoardList = connect(mapStateToProps,mapDispatchToProps)(BoardList) export default CBoardList [/bash] とりあえず、以下の3つをおさえればよい。 const mapStateToProps = (state, ownProps) =>{ const mapDispatchToProps = (dispatch) => { const ラッパーコンポーネント = connect(mapStateToProps,mapDispatchToProps)(画面を構築する実態コンポーネント) 1つ1つ説明していくと。 mapStateToProps では、storeのstateが直接参照できるので、実態コンポーネントのプロパティにセットしてあげたい…

    Read more

Back to Top