【React.js】React.jsとRailsでPocketみたいなのを作る①
ReactとRailsの勉強がてらPocketみたいなのを作っています。
Pocketは知ってる人多いのではないでしょうか?
サイトをブックマークするサービスです。
はてブと近いと思います。
作りたい仕様
- ブログやサイトのURLを入力し送信すると、そのURLのタイトルもしくはOGPタイトルを取得してくる
- データが更新されるとReactでViewを更新する
- 削除機能
- タグの追加・ソート機能
- ユーザーログイン機能
ざっとこんな感じで作ろうかと思っています。
今回は1.2のところまで。
準備
rails newした前提で。
Reactはgemの「react-rails」を使いました。
gem 'react-rails'
Railsでスクレイピングする
Railsでは「Nokogiri」というWebサイトの情報をとってくれる便利なgemがあります。
Gemfileに追記
gem 'nokogiri'
インストールします。
bundle install
コントローラ、モデルは作った上で、以下を書いてます。
[controllers/articles_controller.rb]
class ArticlesController < ApplicationController def index articles = Article.all render json: articles end def create article = Article.new article.url = params['articlelink'] p article.get_title if article.save render json: article, status: :created else render json: article, status: :unprocessable_entity end end private def create_params params.permit(:url) end end
[models/article.rb]
require 'open-uri' class Article < ActiveRecord::Base def get_title charset = nil html = open(self.url) do |f| charset = f.charset f.read # htmlを読み込んで変数htmlに渡す end # ノコギリを使ってhtmlを解析 doc = Nokogiri::HTML.parse(html, nil, charset) if doc.css('//meta[property="og:title"]/@content').empty? self.title = doc.title.to_s else self.title = doc.css('//meta[property="og:title"]/@content').to_s end end end
React.js
Reactはファイルを分けて書いてみました。
[main.js.jsx]
$(function() { ReactDOM.render( <Stock url="/articles" />, document.getElementById('content') ); });
[javascripts/components/stock.js.jsx]
var Stock = React.createClass({ getInitialState: function(){ return { articles: [] }; }, componentDidMount: function() { $.ajax({ url: this.props.url, dataType: 'json', cache: false, success: function(article) { this.setState({articles: article}); }.bind(this), error: function(_xhr, status, err) { console.error(this.props.url, status, err.toString()); }.bind(this) }); }, handleArticleSubmit: function(article){ $.ajax({ url: this.props.url, dataType: 'json', type: 'POST', data: article, success: function(article) { var newArticle = this.state.articles.concat(article); this.setState({articles: newArticle}); }.bind(this), error: function(_xhr, status, err) { console.error(this.props.articlelink, status, err.toString()); }.bind(this) }); }, render: function() { var stockarticles = this.state.articles.map(function(article) { return ( <StockItems key={article.id} article={article} /> ) }); return( <div> <StockForm onArticleSubmit={this.handleArticleSubmit} /> <ul> {stockarticles} </ul> </div> ); } });
[components/stock_items.js.jsx]
var StockItems = React.createClass({ render: function(){ return ( <li> <a href={this.props.article.url} target="_blank"> <h2 className="article-ttl">{this.props.article.title}</h2> </a> </li> ) } });
[components/stock_form.js.jsx]
var StockForm = React.createClass({ addStock: function(e){ e.preventDefault(); var url = this.refs.url.value.trim(); if(!url) { return; } this.props.onArticleSubmit({ articlelink: url}); this.refs.url.value = ""; }, render: function(){ return ( <form onSubmit={this.addStock}> <input type="text" placeholder="http://" ref="url" /> <button>送信</button> </form> ); } });
実際の動き
こんな感じになりました。
URLを入れて送信すると、タイトルを取得して表示してくれます。
このブログで以前書いた以下の記事を取得しています。
さいごに
まだまだ作りかけなので、更新したらまた記事にしようと思います。
書いてる内容も雑なので、ある程度完成したあたりで、一から作り方をまとめるつもりです。