焼け石に肉

新米プログラマの学習記録日記です。Scalaに興味があるので、ScalaとPlay Frameworkの勉強メモを残していこうと思います。

【Play Framework】Play FrameworkとReactで何か作ってみる Part3【React】

前回の続きです。

ログイン画面を作成し、ログイン処理まで実装しました。
このログイン画面のフォームをReactで生成してみました。

ログイン画面イメージ

f:id:julius_hs:20180330003727j:plain


ボタンやテキスト間の幅が狭いですが、細かいデザインは一旦無視しています。
メールアドレスとパスワードの入力と、ログイン保持のチェックボックスを配置しています。
ログインボタン下のテキストとリンクは、Reactでは生成していません。

login.scala.html

@import b3.vertical.fieldConstructor

@(loginForm: Form[Login], rememberme: Form[Boolean])(implicit messages: Messages, flash: Flash, request: RequestHeader)

@main(Messages("Login"), None) {
<div class="centeritems mdl-grid">
    <div class="mdl-layout-spacer"></div>
        <div class="mdl-cell mdl-cell--4-col">
            <h1>@Messages("LogIn")</h1>

            @loginForm.globalError.map { error =>
                <div class="alert alert-warning" role="alert">
                    @error.message
                </div>
            }

            <!-- ここにフォームを描画 -->
            <div id="signIn"></div>

            <div>
                <p>@Messages("AreYouNewUser") <a href="@routes.SignUpController.index()">@Messages("SignUp")</a></p>
            </div>
        </div>
    <div class="mdl-layout-spacer"></div>
</div>

<script type="text/javascript">
    $('#loginSubmit').click(function(){
        var label = $('label.mdl-js-checkbox');
        var hasClass = label.hasClass('is-checked')

        if(hasClass){
            $('input.mdl-checkbox__input').val(true)
        } else {
            $('input.mdl-checkbox__input').val(false)
        }

    });
</script>

<script type="text/jsx">
var FormApp = React.createClass({
    getInitialState: function () {
        return {
            data: {
                email: '',
                password: '',
                rememberMe: ''
            }
        };
    },

    handleChange: function (event) {
        var data = this.state.data;

        switch (event.target.name) {
            case 'email':
            data.email = event.target.value;
            break;
            case 'password':
            data.password = event.target.value;
            break;
            case 'rememberMe':
            data.password = event.target.value;
            break;
        }

        this.setState({
            data: data
        });
    },

    render: function () {
        return (
            @b3.formCSRF(routes.AuthController.login()) {
                <div>
                    <div className="mdl-textfield mdl-js-textfield">
                        <input name="@loginForm("email").id" className="mdl-textfield__input" type="text" value={this.state.email} onChange={this.handleChange} />
                        <label className="mdl-textfield__label" >@Messages("Email")</label>
                    </div>
                </div>
                <div>
                    <div className="mdl-textfield mdl-js-textfield">
                        <input name="@loginForm("password").id" className="mdl-textfield__input" type="password" value={this.state.password} onChange={this.handleChange} />
                        <label className="mdl-textfield__label" >@Messages("Password")</label>
                    </div>
                </div>
                <div>
                    <div>
                        <label className = "mdl-checkbox mdl-js-checkbox">
                            <input name="@rememberme("rememberme").id" type = "checkbox" value={this.state.password} onChange={this.handleChange} className = "mdl-checkbox__input" />
                            <span className = "mdl-checkbox__label">@Messages("RememberMe")</span>
                        </label>
                    </div>
                </div>

                <button id="loginSubmit" type="submit" className="mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
                    @Messages("LogIn")
                </button>

            }
        );
    }

});

React.render(<FormApp />, document.getElementById('signIn'));
</script>
}


Playのビューテンプレートの話は省略して、Reactを使用している部分を説明します。

script type="text/jsx" 以下で、フォームを作成し、フォームを描画しています。

Reactの書き方はものすごく汚い状態になってしまっているので改善の余地はありますが、とりあえず、Reactで生成することはできました。


次回も、Reactを使ったビューについて書きたいと思います。

【Play Framework】Play FrameworkとReactで何か作ってみる Part2【React】

前回の続きです。

前回は何を作るかは決めてなかったですが、
Play Framework、Scala、Reactで、TeraTailっぽいものを作ろうかと思います。

現状の画面

簡単なトップページとユーザー登録画面ができています。
フロントエンドは、Material Design Lite(MDL)を使用しています。
Bootstrapは業務で使用しまくりでさすがに飽きたので、MDLを採用しました。

トップページ

f:id:julius_hs:20180327212309j:plain

ユーザー登録ページ

f:id:julius_hs:20180327212348j:plain


Reactについては、チュートリアルで勉強中ですが、Reactについてもいい加減書きたいです。

今回は以上です。

【Play Framework】Play FrameworkとReactで何か作ってみる Part1【React】

久しぶりの更新になります。

Reactを勉強したいと思い立ったので、Play FrameworkとReactを合わせて何か作ってみようかと思います。
とりあえず、この記事ではHello World表示するまでやってみます。

環境

開発環境としては、以下の通りです。

作るもの

何も決まってないですが、とりあえず掲示板を作ってみます。
投稿、編集、削除がまで一通り実装してみます。
現時点でReactについては、名前しか知らない状態なので作る過程で少し変わってくるかもです。

準備

build.sbtやらapplication.confやらは置いておいてviewについて以下の通りにやっています

main.scala.html
@(title: String)(content: Html)

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>@title</title>
        <link rel="stylesheet" media="screen" href="@routes.Assets.versioned("stylesheets/main.css")">
        <link rel="shortcut icon" type="image/png" href="@routes.Assets.versioned("images/favicon.png")">
        <script src="http://fb.me/react-0.13.3.js"></script>
        <script src="http://fb.me/JSXTransformer-0.13.3.js"></script>
    </head>
    <body>

        @content

      <script src="@routes.Assets.versioned("javascripts/main.js")" type="text/javascript"></script>
    </body>
</html>
index.scala.html
@()

@main("Home") {
<div id="application"></div>
<script type="text/jsx">
  var HelloWorld = React.createClass({
    render: function() {
      return (
        <p>Hello!World!</p>
      );
    }
  });

  var m = React.render(<HelloWorld />, document.getElementById('application'));
</script>
}

実行

http://localhost:9000/にアクセスすると、Hello!World!が表示されます。

【Scala】Scala&Play Frameworkで掲示板を作ってみる Part3【Play Framework】

久しぶりの更新になります。

今回はModelについて書いていきます。

Message.scala

package models

import java.time.ZonedDateTime

import scalikejdbc._, jsr310._
import skinny.orm._

case class Message(id: Option[Long], body: String, title: String, createAt: ZonedDateTime, updateAt: ZonedDateTime)

object Message extends SkinnyCRUDMapper[Message] {

  override def tableName = "messages"

  override def defaultAlias: Alias[Message] = createAlias("m")

  override def extract(rs: WrappedResultSet, n: ResultName[Message]): Message =
    autoConstruct(rs, n)

  private def toNamedValues(record: Message): Seq[(Symbol, Any)] = Seq(
    'body     -> record.body,
    'title    -> record.title,
    'createAt -> record.createAt,
    'updateAt -> record.updateAt
  )

  def create(message: Message)(implicit session: DBSession): Long =
    createWithAttributes(toNamedValues(message): _*)

  def update(message: Message)(implicit session: DBSession): Int =
    updateById(message.id.get).withAttributes(toNamedValues(message): _*)
}

createとupdateメソッドは、ヘルパーメソッドです。

とりあえず今回はモデルだけの説明になります。
次回で完成させていきたいと思います。

【Scala】Scala&Play Frameworkで掲示板を作ってみる Part2【Play Framework】

前回の続きです。

今回はルーティングとコントローラーについて、書いていきます。

 

routes

GET         /                           controllers.Default.redirect(to = "/messages")
# メッセージ一覧画面の表示
GET         /messages                   controllers.MessageController.index
# メッセージ詳細画面の表示
GET         /messages/:id/get           controllers.MessageController.showDetail(id: Long)
# メッセージ作成画面の表示
GET         /messages/create            controllers.MessageController.showCreate
# メッセージ編集画面の表示
GET         /messages/:id/update        controllers.MessageController.showUpdate(id: Long)

# メッセージの作成
POST        /messages/create            controllers.MessageController.create
# メッセージの更新
POST        /messages/update            controllers.MessageController.update
# メッセージの削除
POST        /messages/:id/delete        controllers.MessageController.delete(id: Long)

GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

ルーティングについては、上記の通りです。
トップページは、
controllers.Default.redirect(to = "/messages")
によって、リダイレクト処理を行っています。
その他は、DBへの読み書きの処理についてのルーティングです。

MessageController

package controllers

import java.time.ZonedDateTime
import javax.inject.{ Inject, Singleton }

import forms.MessageForm
import models.Message
import play.api.i18n.{ I18nSupport, Messages, MessagesApi }
import play.api.mvc.{ Action, AnyContent, Controller }
import scalikejdbc.AutoSession

@Singleton
class MessageController @Inject()(val messagesApi: MessagesApi)
    extends Controller
    with I18nSupport
    with MessageControllerSupport {

  def index: Action[AnyContent] = Action { implicit request =>
    val result = Message.findAll()
    Ok(views.html.index(result))
  }

  def showDetail(messageId: Long): Action[AnyContent] = Action { implicit request =>
    val message = Message.findById(messageId).get
    Ok(views.html.show(message))
  }

  def showCreate: Action[AnyContent] = Action { implicit request =>
    Ok(views.html.create(form))
  }

  def showUpdate(messageId: Long): Action[AnyContent] = Action { implicit request =>
    val result     = Message.findById(messageId).get
    val filledForm = form.fill(MessageForm(result.id, result.body, result.title))
    Ok(views.html.edit(filledForm))
  }

  def create: Action[AnyContent] = Action { implicit request =>
    form
      .bindFromRequest()
      .fold(
        formWithErrors => BadRequest(views.html.create(formWithErrors)), { model =>
          implicit val session = AutoSession
          val now              = ZonedDateTime.now()
          val message          = Message(None, model.body, model.title, now, now)
          val result           = Message.create(message)
          if (result > 0) {
            Redirect(routes.MessageController.index())
          } else {
            InternalServerError(Messages("CreateMessageError"))
          }
        }
      )
  }

  def update: Action[AnyContent] = Action { implicit request =>
    form
      .bindFromRequest()
      .fold(
        formWithErrors => BadRequest(views.html.edit(formWithErrors)), { model =>
          implicit val session = AutoSession
          val result = Message
            .updateById(model.id.get)
            .withAttributes(
              'body     -> model.body,
              'title    -> model.title,
              'updateAt -> ZonedDateTime.now()
            )
          if (result > 0)
            Redirect(routes.MessageController.index())
          else
            InternalServerError(Messages("UpdateMessageError"))
        }
      )
  }

  def delete(messageId: Long): Action[AnyContent] = Action {
    implicit val session = AutoSession
    val result           = Message.deleteById(messageId)
    if (result > 0) {
      Redirect(routes.MessageController.index())
    } else {
      InternalServerError(Messages("DeleteMessageError"))
    }
  }

}

このコントローラーの中で、下記をミックスインしています。

MessageControllerSupport

package controllers

import forms.MessageForm
import play.api.data.Forms._
import play.api.data._
import play.api.mvc.Controller

trait MessageControllerSupport { this: Controller =>

  protected val form = Form(
    mapping(
      "id"    -> optional(longNumber),
      "body"  -> nonEmptyText,
      "title" -> nonEmptyText
    )(MessageForm.apply)(MessageForm.unapply)
  )

}

上記によって、フォームのマッピング処理を独立させています。


次回はモデルについて書いていきたいです。

【Scala】Scala&Play Frameworkで掲示板を作ってみる Part1【Play Framework】

タイトルどおりです。
掲示板アプリを作って、ScalaとPlay Frameworkの勉強です。

環境

Scala:2.11.11

Play:2.5

MySQL:6.0.6

概要

簡単な掲示板アプリです。
メッセージの作成、編集、削除ができます。
DBへの書き込みであったり、データの抽出といったところを学習したいと思います。

f:id:julius_hs:20180123215858j:plain

準備

用意したテーブルは、以下のとおりです。

messagesテーブル
+----+---------+---------------+---------------+---------------+
| id | body    | title         | create_at     | update_at     |
+----+---------+---------------+---------------+---------------+
build.sbt

MySQL用の依存関係を追加していきます。

libraryDependencies ++= Seq(
  "org.scalikejdbc"        %% "scalikejdbc"                  % "2.5.2",
  "org.scalikejdbc"        %% "scalikejdbc-config"           % "2.5.2",
  "org.scalikejdbc"        %% "scalikejdbc-test"             % "2.5.2" % Test,
  "org.skinny-framework"   %% "skinny-orm"                   % "2.3.7",
  "org.scalikejdbc"        %% "scalikejdbc-play-initializer" % "2.5.+",
  "ch.qos.logback"         % "logback-classic"               % "1.2.3",
  "org.scalikejdbc"        %% "scalikejdbc-jsr310"           % "2.5.2",
  "mysql"                  % "mysql-connector-java"          % "6.0.6",
  "com.adrianhurt"         %% "play-bootstrap"               % "1.1-P25-B3"
)
dev.conf

プロジェクト直下にenvディレクトリを作成し、その中にdev.confファイルを作成します。
そして、以下の通り記述します。

jdbcDriver = "com.mysql.cj.jdbc.Driver"
jdbcUrl = "jdbc:mysql://localhost:3306/【DB名】?autoReconnect=true&useSSL=false"
jdbcUserName = "【ユーザー】"
jdbcPassword = "【パスワード】"
application.conf

データベース接続情報を設定します。

include file("./env/dev.conf")

play.modules {
  enabled += "scalikejdbc.PlayModule"
}

db {
  default.driver=${jdbcDriver}
  default.url=${jdbcUrl}
  default.username=${jdbcUserName}
  default.password=${jdbcPassword}
}

scalikejdbc {
  global {
    loggingSQLAndTime.enabled = true
    loggingSQLAndTime.singleLineMode = true
    loggingSQLAndTime.logLevel = DEBUG
    loggingSQLAndTime.warningEnabled = true
    loggingSQLAndTime.warningThresholdMillis = 5
    loggingSQLAndTime.warningLogLevel = warn
  }
}

とりあえず、今回は以上です。
次回は、ルーティングとかを書いていきたいです。

【Scala】Scala&Play FrameworkでTwitter4jをいじってみる Part5【Twitter4j】

前回の続きです。今回で完成させていきます。

routes

GET     /                           controllers.HomeController.index
GET     /search                     controllers.SearchTweetsController.search

ルーティングは上記のとおりになっています。
HomeControllerは初期画面表示を行い、SearchTweetsControllerはTweetの検索処理と検索結果画面表示を行います。

Controllers

HomeController
package controllers

import javax.inject._

import forms.SearchTweets
import play.api.data.Form
import play.api.data.Forms._
import play.api.i18n.{ I18nSupport, MessagesApi }
import play.api.mvc._
import play.api.data.format.Formats._

@Singleton
class HomeController @Inject()(val messagesApi: MessagesApi) extends Controller with I18nSupport {

  private val searchTweetsForm: Form[SearchTweets] = Form {
    mapping(
      "lat"     -> of[Double],
      "lng"     -> of[Double],
      "range"   -> of[Double],
      "keyword" -> nonEmptyText
    )(SearchTweets.apply)(SearchTweets.unapply)
  }

  def index = Action { implicit request =>
    Ok(views.html.index(searchTweetsForm, Nil))
  }

}

こちらの、HomeControllerは、初期画面表示を行います。
searchTweetsFormをマッピングしています。
indexメソッドで、初期画面表示を行っています。

SearchTweetsController
package controllers

import javax.inject.{ Inject, Singleton }

import forms.SearchTweets
import models.Tweet
import play.api.data.Form
import play.api.data.Forms._
import play.api.i18n.{ I18nSupport, MessagesApi }
import play.api.mvc.{ Action, AnyContent, Controller }
import twitter4j._
import play.api.data.format.Formats._

@Singleton
class SearchTweetsController @Inject()(
    val messagesApi: MessagesApi,
) extends Controller
    with I18nSupport {

  private val searchTweetsForm: Form[SearchTweets] = Form {
    mapping(
      "lat"     -> of[Double],
      "lng"     -> of[Double],
      "range"   -> of[Double],
      "keyword" -> nonEmptyText
    )(SearchTweets.apply)(SearchTweets.unapply)
  }

  def search(): Action[AnyContent] = Action { implicit request =>
    val twitter = new TwitterFactory().getInstance()

    val query = new Query()

    val form = searchTweetsForm.bindFromRequest.get

    val geo = new GeoLocation(form.lat, form.lng)
    query.setGeoCode(geo, form.range, Query.KILOMETERS)

    query.setQuery(form.keyword)
    query.setCount(100)

    var result: QueryResult = null

    try {
      result = twitter.search(query)
    } catch {
      case e: TwitterException => println("error")
    }

    val itr = result.getTweets().iterator()

    var tweetList: List[Tweet] = Nil
    while (itr.hasNext()) {
      val status = itr.next()
      val tweet  = new Tweet(status.getText, status.getUser.getProfileImageURL)
      tweetList = tweetList.::(tweet)
    }

    Ok(views.html.index(searchTweetsForm, tweetList))
  }

}

こちらは、Tweetの検索処理を行います。
twitter4jの使い方などは、そこらじゅうに情報が転がっているので、説明はしません。

まとめ

とりあえず、ScalaとPlay Frameworkを使って、簡単なアプリを作ってみました。
さらなる学習のために、このアプリをより多機能にしたりしていきたいとおもいます。