フラッシュカードアプリ

Flashcard App

フラッシュカードアプリを作成しました。英単語を覚えるツールとしてレンタルサーバで動作するWebアプリとしました。iPhoneからアクセスして勉強しています。単語登録はMySQLへ直接登録する必要があります。

Screen shots

利用方法は橙色の面に表示された単語を見て日本語訳をイメージします。そしてタップすると裏側の緑色の面が表示されるので答え合わせをします。さらにタップすると次の問題が橙色の面に表示されます。設定は左上のメニューをタップして出題の種類と橙色の面に表示する言語を選択します。

screen shot

Condition

動作環境

項目 内容
サーバ さくらインターネット
契約プラン レンタルサーバ スタンダード
ライブラリ jquery
データベース MySQL
インターフェース cgi
cgi処理 python3.7

ファイル構成

Filename 内容
flashcard.html メインページ
flashcard.js jqueryでユーザーインターフェース処理
flashcard.cgi pythonでデータベース処理
flashcard.css 装飾

データベースレコード

データベースのレコード定義は何の工夫もなく下記としました。

CREATE TABLE IF NOT EXISTS `Flashcard` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `english_word` VARCHAR(128),
    `japanese_word` VARCHAR(128),
    `english_sample` TEXT,
    `japanese_sample` TEXT,
    PRIMARY KEY  (`id`)
) ;

Source code Sample

下記ソースコードは実際に運用しているものを少々改変しています。

flashcard.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>Flashcard</title>
        <link rel="stylesheet" href="../js/jquery.mobile-1.4.5.min.css" />
        <link rel="stylesheet" href="flashcard.css" />
        <script src="../js/jquery-1.12.4.min.js"></script>
        <script src="../js/jquery.mobile-1.4.5.min.js"></script>
        <script src="flashcard.js"></script>
    </head>
    <body>
        <div data-role="page" role="main" data-title="Flashcard">
            <div data-role="panel" id="menu-left" data-theme="b">
                <form data-mini="true">
                    <fieldset data-role="controlgroup">
                        <input type="checkbox" data-mini="true" name="levels" value="L6" id="L6">
                        <label for="L6">Level 6</label>
                        <input type="checkbox" data-mini="true" name="levels" value="L7" id="L7">
                        <label for="L7">Level 7</label>
                        <input type="checkbox" data-mini="true" name="levels" value="L8" id="L8">
                        <label for="L8">Level 8</label>
                        <input type="checkbox" data-mini="true" name="levels" value="L9" id="L9">
                        <label for="L9">Level 9</label>
                    </fieldset>
                    <select data-mini="true" id="select_question">
                        <option data-mini="true" value="jword">Japanese word</option>
                        <option data-mini="true" value="eword">English word</option>
                        <option data-mini="true" value="jphrase">Japanese phrase</option>
                        <option data-mini="true" value="ephrase">English phrase</option>
                    </select>
                </form>
            </div>
            <div data-role="header" id="header">
                <a href="#menu-left" data-role="button" data-icon="bars" data-iconpos="notext"> </a>
                <h1 id="title">Flashcard</h1>
            </div>
            <div class="ui-content" role="main" id="card">
                <div class="side-a" id="question"></div>
                <div class="side-b" id="answer"></div>
                <div id="temp"></div>
                <div id="env"><input type="hidden" name="range" value="L6L7L8L9" /></div>
                <div id="q_mode"><input type="hidden" name="mode" value="japanese_word" /></div>
            </div>
            <div data-role="footer" id="footer">
                <h3><small>yam.ktm@gmail.com</small></h3>
            </div>
        </div>
    </body>
</html>

flashcard.js

var QMode;
var RangeCode;
var CardIndex;
var JapaneseWord;
var EnglishWord;
var JapanesePhrase;
var EnglishPhrase;
var QText;
var AText;
var QPhrase;
var APhrase;

var MyStorage = function(app) {
    this.app = app;
    this.storage = localStorage;
    this.data = JSON.parse(this.storage[this.app] || '{}');
};

MyStorage.prototype = {
    getItem: function(key) {
        return this.data[key];
    },
    setItem: function(key, value) {
        this.data[key] = value;
    },
    save: function() {
        this.storage[this.app] = JSON.stringify(this.data);
    }
};

function make_range_code(levels) {
    var rcode = "";
    if(levels.length == 0) {
        rcode += "L6L7L8L9";
    } else {
        for(var i = 0; i < levels.length; i++) {
            rcode += levels[i];
        }
    }
    return rcode;
}

function register_data(data) {
    CardIndex = data['index'];
    EnglishWord = data['eword'];
    JapaneseWord = data['jword'];
    EnglishPhrase = data['ephrase'];
    JapanesePhrase = data['jphrase'];
}

var Storage = new MyStorage('Flashcard');

$(function() {
    $(document).ready(function(){
        QMode = Storage.getItem('qmode');
        RangeCode = Storage.getItem('rangecode');
        if(QMode == null) {
            QMode = "jword";
        }
        $("select option").prop("selected", false);
        $("#select_question").val(QMode).selectmenu("refresh");
        if(RangeCode == null) {
            RangeCode = "L6L7L8L9";
        }
        var regex;
        regex = /L6/;
        if(regex.test(RangeCode)) {
            $('input[value="L6"]').prop("checked", true).checkboxradio("refresh");
        }
        regex = /L7/;
        if(regex.test(RangeCode)) {
            $('input[value="L7"]').prop("checked", true).checkboxradio("refresh");
        }
        regex = /L8/;
        if(regex.test(RangeCode)) {
            $('input[value="L8"]').prop("checked", true).checkboxradio("refresh");
        }
        regex = /L9/;
        if(regex.test(RangeCode)) {
            $('input[value="L9"]').prop("checked", true).checkboxradio("refresh");
        }
        $.get("flashcard.cgi", {"range":RangeCode}, function(data) {
            register_data(JSON.parse(data));
            $("#answer").html("<p class='answer'>Click to start!</p>\n");
        });
        Storage.setItem('qmode', QMode);
        Storage.setItem('rangecode', RangeCode);
        Storage.save();
        var hsize = $(window).height();
        hsize -= 150;
        $("#card").css("height", hsize + "px");
        $("#question").css("height", "15em");
        $("#answer").css("height", "15em");
        $("#question").hide();
    });
    $('input[name="levels"]').change(function() {
        var levels = [];
        $('input[name="levels"]:checked').each(function() {
            levels.push($(this).val());
        });
        RangeCode = make_range_code(levels);
        $.get("flashcard.cgi", {"range":RangeCode}, function(data) {
            register_data(JSON.parse(data));
        });
        Storage.setItem('qmode', QMode);
        Storage.setItem('rangecode', RangeCode);
        Storage.save();
    });
    $("#select_question").change(function() {
        QMode = $("option:selected").val();
        Storage.setItem('qmode', QMode);
        Storage.setItem('rangecode', RangeCode);
        Storage.save();
    });
    $("#answer").click(function() {
        switch(QMode) {
            case "jword":
                if(JapaneseWord != "") {
                    QText = JapaneseWord;
                    AText = EnglishWord;
                    QPhrase = JapanesePhrase;
                    APhrase = EnglishPhrase;
                } else {
                    QText = JapanesePhrase;
                    AText = EnglishPhrase;
                    QPhrase = JapaneseWord;
                    APhrase = EnglishWord;
                }
                break;
            case "eword":
                if(EnglishWord != "") {
                    QText = EnglishWord;
                    AText = JapaneseWord;
                    QPhrase = EnglishPhrase;
                    APhrase = JapanesePhrase;
                } else {
                    QText = EnglishPhrase;
                    AText = JapanesePhrase;
                    QPhrase = EnglishWord;
                    APhrase = JapaneseWord;
                }
                break;
            case "jphrase":
                if(JapanesePhrase != "") {
                    QText = JapanesePhrase;
                    AText = EnglishPhrase;
                    QPhrase = JapaneseWord;
                    APhrase = EnglishWord;
                } else {
                    QText = JapaneseWord;
                    AText = EnglishWord;
                    QPhrase = JapanesePhrase;
                    APhrase = EnglishPhrase;
                }
                break;
            case "ephrase":
                if(EnglishPhrase != "") {
                    QText = EnglishPhrase;
                    AText = JapanesePhrase;
                    QPhrase = EnglishWord;
                    APhrase = JapaneseWord;
                } else {
                    QText = EnglishWord;
                    AText = JapaneseWord;
                    QPhrase = EnglishPhrase;
                    APhrase = JapanesePhrase;
                }
                break;
            default:
                if(JapaneseWord != "") {
                    QText = JapaneseWord;
                    AText = EnglishWord;
                    QPhrase = JapanesePhrase;
                    APhrase = EnglishPhrase;
                } else {
                    QText = JapanesePhrase;
                    AText = EnglishPhrase;
                    QPhrase = JapaneseWord;
                    APhrase = EnglishWord;
                }
                break;
        }
        var qes = "<p>" + QText + "</p>\n";
        $("#question").html(qes);
        $("#answer").hide();
        $("#question").show();
    });
    $("#question").click(function() {
        var ans = "\n"
        ans += "<p>" + QText + "</p>\n";
        ans += "<p class='answer'>" + AText + "</p>\n";
        ans += "<p class='phrase'>" + QPhrase + "</p>\n";
        ans += "<p class='phrase'>" + APhrase + "</p>\n";
        $("#answer").html(ans);
        $("#question").hide();
        $("#answer").show();
        $.get("flashcard.cgi", {"range":RangeCode}, function(data) {
            register_data(JSON.parse(data));
        });
    });
    $(window).resize(function () {
        hsize = $(window).height();
        $("section").css("height", hsize + "px");
    });
});

flashcard.cgi

#!/usr/bin/env -S LD_LIBRARY_PATH=/home/username/.pyenv/shims /home/username/.pyenv/shims/python
#-*- coding:utf-8 -*-

"""
CREATE TABLE IF NOT EXISTS `Flashcard` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `english_word` VARCHAR(128),
    `japanese_word` VARCHAR(128),
    `english_sample` TEXT,
    `japanese_sample` TEXT,
    PRIMARY KEY  (`id`)
) ;
"""
import os, sys, io, re
import random
import cgi
import json
import mysql.connector
from urllib.parse import urlparse

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

url = urlparse('mysql://username:mysql@mysql.server/dbname')

# setting of database
connector = mysql.connector.connect(
    host = url.hostname or 'localhost',
    port = url.port or 3306,
    user = url.username or 'root',
    password = url.password or '',
    database = url.path[1:],
)
REC_NAME = "Flashcard"

#Create instance of FieldStorage
form = cgi.FieldStorage()

if "range" in form:
    rcode = form["range"].value
else:
    rcode = "L6L7L8L9"

ids = []
if rcode.find("L6") >= 0:
    ids += list(range(1, 600))

if rcode.find("L7") >= 0:
    ids += list(range(601, 700))

if rcode.find("L8") >= 0:
    ids += list(range(701, 800))

if rcode.find("L9") >= 0:
    ids += list(range(801, 900))

idx = random.choice(ids)

# Get data from fields
cursor = connector.cursor()

sql = "select * from %s where id=%d" % (REC_NAME, idx)
cursor.execute(sql)
result = cursor.fetchall()

for row in result:
    index = int(row[0])
    english_word = row[1]
    japanese_word = row[2]
    english_phrase = row[3]
    japanese_phrase = row[4]

cursor.close()
connector.close()

japanese_word = japanese_word.replace("cadv", "[接続副詞]")
japanese_word = japanese_word.replace("adv", "[副]")
japanese_word = japanese_word.replace("pp", "[過去分詞]")
japanese_word = japanese_word.replace("a", "[形]")
japanese_word = japanese_word.replace("p", "[前]")
japanese_word = japanese_word.replace("c", "[接]")
japanese_word = japanese_word.replace("n", "[名]")
japanese_word = japanese_word.replace("v", "[動]")
japanese_word = japanese_word.replace("[", "<small> [")
japanese_word = japanese_word.replace("]", "] </small>")

data = {
    "index":index,
    "eword":english_word,
    "jword":japanese_word,
    "ephrase":english_phrase,
    "jphrase":japanese_phrase
}

print("Content-Type: text/html")
print()
print(json.dumps(data))

flashcard.css

p {
    line-height: 1em;
    margin: 0.5em 0.5em;
    padding: 0.2em 0.2em;
    font-size: medium;
}
p.answer {
    font-size: large;
}
.side-a {
    background-color: #c85d05;
    color: #fff;
    margin: 0.5em 1em;
    padding: 0.5em 1em;
    position: relative;
}
.side-a::after {
    content: '';
    border-color: #3e8504 #fff #fff #3e8504;
    border-style: solid;
    border-width: 0 0 24px 24px;
    bottom: 0;
    position: absolute;
    right: 0;
}
.side-b {
    background-color: #469704;
    color: #fff;
    margin: 0.5em 1em;
    padding: 0.5em 1em;
    position: relative;
}
.side-b::after {
    content: '';
    border-color: #853e04 #fff #fff #853e04;
    border-style: solid;
    border-width: 0 0 24px 24px;
    bottom: 0;
    position: absolute;
    right: 0;
}

コメントを残す

メールアドレスが公開されることはありません。