React.js

ReactとRailsの勉強がてらPocketみたいなのを作っています。

Pocketは知ってる人多いのではないでしょうか?
サイトをブックマークするサービスです。

pocket

はてブと近いと思います。

作りたい仕様
  1. ブログやサイトのURLを入力し送信すると、そのURLのタイトルもしくはOGPタイトルを取得してくる
  2. データが更新されるとReactでViewを更新する
  3. 削除機能
  4. タグの追加・ソート機能
  5. ユーザーログイン機能

ざっとこんな感じで作ろうかと思っています。

今回は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を入れて送信すると、タイトルを取得して表示してくれます。

このブログで以前書いた以下の記事を取得しています。

男木島での合宿に参加してきました

さいごに

まだまだ作りかけなので、更新したらまた記事にしようと思います。

書いてる内容も雑なので、ある程度完成したあたりで、一から作り方をまとめるつもりです。

Tweet
このエントリーをはてなブックマークに追加
Pocket