InputManJSのコメントコンポーネントで「ピン留め」機能を使う

便利で快適な入力フォーム開発に特化したJavaScriptライブラリ「InputManJS(インプットマンJS)」の最新バージョン「V5.1J」では、チャットやフォーラム、会話アプリなどで見られる会話機能のUIが構築できる「コメントコンポーネント(GcComment)」の新機能として、特定のコメントを画面上部にピン留めして表示できるようになりました。

コメントをピン留め

今回は以下の記事作成したコメントコンポーネントとPythonのWebフレームワーク「FastAPI」を使用したアプリケーションをベースに、ピン留め機能を追加する方法をご紹介します。
※ 本記事の最後で今回作成するサンプルをダウンロード可能です。

開発環境

今回は開発環境として以下を使用します。

バックエンド(Web API)をピン留め機能に対応する

Web APIと連携したコメントコンポーネントでピン留めを実行すると、リクエストに含まれるstickというフィールドの値に「true」が設定されて送信されます。

さらに、ピン留め機能が追加されたV5.1J以降のコメントコンポーネントでWeb APIから取得したコメント情報を画面に表示する場合、通常のコメント取得のリクエストのほか、ピン留めするべきコメントの情報を取得するために以下のようなクエリパラメータ(type=sticked)を持つリクエストを別途実行するようになりました。

(APIのURL)/comments?type=sticked

コメントコンポーネントはこのリクエストのレスポンスのコメントをピン留めして表示します。こちらを実現するには、バックエンドのWeb API側に以下のような機能を追加します。

  • ピン留めされた(stick=true)コメントに対して、ピン留めされたコメントであることを示すフラグを設定する
  • その際、ピン留めされたコメントが既に存在する場合は、そのコメントに設定されているフラグを解除する(ピン留めされているコメントが2つ以上存在しないようにする)
  • type=stickedのクエリパラメータが設定されたGETリクエストに対して、ピン留めされたコメントの情報を返却する

Web API(FastAPI)の修正

まずはSQLAlchemyのモデル定義を記載している「models.py」を以下のように追記し、ピン留めされたコメントであることを示すフラグのフィールドを追加します。今回はstickedという名前で追加しました。

from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey
from .database import Base

class Comment(Base):
    __tablename__ = "comments"
    id = Column('id', Integer, primary_key=True)
    parentCommentId = Column('parentCommentId', Integer, ForeignKey("comments.id", ondelete="CASCADE"), nullable=True)
    content = Column('content', Text)
    userId = Column('userId', Integer)
    mentionInfo = Column('mentionInfo', Text, nullable=True)
    postTime = Column('postTime', DateTime)
    updateTime = Column('updateTime', DateTime)
    sticked = Column('sticked', Boolean, default=False)
・・・(中略)・・・

次に「schemas.py」に設定したPydanticのスキーマ定義にもstickedを追加します。

from pydantic import BaseModel
from typing import Union

class CommentIn(BaseModel):
    userId: int
    parentId: Union[int, str, None] = None
    content: str
    mentionInfo: Union[str, None] = None
    sticked: Union[bool, None] = False
・・・(中略)・・・

次にCRUD処理を行うアプリケーション本体の「main.py」を修正します。

まずはピン留めされたコメント情報を取得するためのヘルパー関数を作成します。

・・・(中略)・・・
# ピン留めされたコメント情報取得のヘルパー関数
def get_sticked_comment(db_session: Session):
    return db_session.query(models.Comment).filter(models.Comment.sticked == True).first()
・・・(中略)・・・

さらに/comments エンドポイントに対するGETリクエストの処理を以下のように書き換え、クエリパラメータ(type)の値に応じて、返却するコメント情報の内容を分岐します。

from fastapi import Depends, FastAPI, Form, HTTPException, status, Query
・・・(中略)・・・
# Commentを全件取得
@app.get("/comments")
def read_comments(db: Session = Depends(get_db), type : str = Query("NONE")):
    # type=stickの場合はピン留めするコメントの情報を返却
    if type == "sticked":
        sticked_comment = get_sticked_comment(db)
        if sticked_comment is not None:
            return {
                        "id": sticked_comment.id,
                        "parentCommentId": sticked_comment.parentCommentId,
                        "content": sticked_comment.content,
                        "sticked": sticked_comment.sticked,
                        "postTime": sticked_comment.postTime.strftime("%Y/%m/%d %H:%M:%S"),
                        "updateTime": sticked_comment.updateTime.strftime("%Y/%m/%d %H:%M:%S"),
                        "userId": sticked_comment.userId,
                        "mentionInfo": sticked_comment.mentionInfo,
            }
        else:
            return {"hasMore": False, "comments": []}
    else:    
        comments = db.query(models.Comment).all()
        return {
            "hasMore": False,
            "comments": [
                {
                    "id": comment.id,
                    "parentCommentId": comment.parentCommentId,
                    "content": comment.content,
                    "sticked": comment.sticked,
                    "postTime": comment.postTime.strftime("%Y/%m/%d %H:%M:%S"),
                    "updateTime": comment.updateTime.strftime("%Y/%m/%d %H:%M:%S"),
                    "userId": comment.userId,
                    "mentionInfo": comment.mentionInfo,
                }
                for comment in comments
            ],
        }
・・・(中略)・・・

同じく/comments エンドポイントのPOSTリクエストの処理を以下のように追加し、ピン留めされたコメントであることを示すフラグのstickedを登録する項目に追加します。

・・・(中略)・・・
# Commentを登録
@app.post("/comments")
async def create_comment(
    userId: int = Form(...),
    parentId: Union[int, str, None] = Form(None),
    sticked: bool = Form(False),
    content: str = Form(...),
    mentionInfo: Union[str, None] = Form(None),
    db: Session = Depends(get_db)
):
    formdata = schemas.CommentIn(
        userId=userId,
        parentId=parentId,
        sticked=sticked,
        content=content,
        mentionInfo=mentionInfo,
    )

    comment = models.Comment(
        userId=formdata.userId,
        parentCommentId = None if formdata.parentId == 'undefined' else formdata.parentId,
        sticked=formdata.sticked,
        content=formdata.content,
        mentionInfo=formdata.mentionInfo,
        postTime=datetime.now(),
        updateTime=datetime.now()
    )

    db.add(comment)
    db.commit()
    db.refresh(comment)
    return comment
・・・(中略)・・・

最後に/comments エンドポイントのPUTリクエストの処理を以下のように書き換え、通常のコメント情報の更新処理に加え、ピン留めの更新リクエスト(stick=true)が送信された際のピン留め状態の更新の処理を追加します。その際、すでにピン留めしているコメントがある場合はそのコメントのstickedの値を「False」に更新します。

・・・(中略)・・・
# Commentを更新
@app.put("/comments")
async def update_comment(
    id: int = Form(...),
    userId: int = Form(...),
    parentCommentId: Union[int, str, None] = Form(None),
    stick: bool = Form(False),
    content: Union[str, None] = Form(None),
    newContent: Union[str, None] = Form(None),
    mentionInfo: Union[str, None] = Form(None),
    db: Session = Depends(get_db)
):
    formdata = schemas.CommentIn(
        userId=userId,
        parentId=parentCommentId,
        sticked=True if stick is True else False, # ピン留めの状態を更新
        content=newContent if newContent is not None else content,
        mentionInfo=mentionInfo,
    )

    comment = models.Comment(
        userId=formdata.userId,
        parentCommentId=formdata.parentId,
        sticked=formdata.sticked,
        content=formdata.content,
        mentionInfo=formdata.mentionInfo,
        updateTime=datetime.now()
    )
    try:
        sticked_comment = get_sticked_comment(db)
        if sticked_comment is not None and sticked_comment.id != id and comment.sticked:
            # 既にピン留めされているコメントがある場合は、ピン留めを解除
            sticked_comment.sticked = False

        db_comment = get_comment(id,db)
        if db_comment is None:
            raise HTTPException(status_code=404, detail="Comment not found")
        else:
            db_comment.userId = comment.userId
            db_comment.parentCommentId = None if comment.parentCommentId == 'undefined' else comment.parentCommentId
            db_comment.sticked = comment.sticked
            db_comment.content = comment.content
            db_comment.mentionInfo = comment.mentionInfo    
            db_comment.updateTime = comment.updateTime    
    
            db.commit()
            db.refresh(db_comment)
            return db_comment
    except Exception as e:
        db.rollback()  # エラーが発生したらすべての変更をロールバック
        raise HTTPException(status_code=500, detail=f"An error occurred: {e}")
    finally:
        db.close()
・・・(中略)・・・

ピン留めの動作確認

更新が完了したら「fastapi-comment-api」フォルダの直下で以下のコマンドを実行してAPIを起動します。

uvicorn app.main:app --reload

APIを起動したら、「inputmanjs-comment-db」の「index.html」をVisual Studio Code上で右クリックして、「Open with Live Server」を実行します。

Live Serverを実行

実行後、ブラウザ上にコメントコンポーネントが組み込まれたWebページが表示されます。

コメントコンポーネントをWebページに組み込み

いくつかコメントを登録し、ピン留めしたいコメントにカーソルをあわせると、横にアイコンが表示されるので、それをクリックするとコメントをピン留めできます。また、ピン留めしたコメントをクリックすると、そのコメントに移動できます。

すでにピン留めされているコメントがある状態で別のコメントをピン留めすると、ピン留めするコメントを入れ替えることができます。

また、ピン留めされたコメントのピン留めを解除することもできます。

今回作成したサンプルは以下よりダウンロード可能です。

さいごに

今回はWebアプリケーションにコメント機能を組み込むことができるInputManJSの「コメントコンポーネント(GcComment)」でコメントをピン留めする機能を実装する方法をご紹介しました。

なお、今回ご紹介したコメントコンポーネントの機能はほんの一部です。製品サイトでは、InputManJSのコメントコンポーネントの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /