Flashcard App
フラッシュカードアプリを作成しました。英単語を覚えるツールとしてレンタルサーバで動作するWebアプリとしました。iPhoneからアクセスして勉強しています。単語登録はMySQLへ直接登録する必要があります。
Screen shots
利用方法は橙色の面に表示された単語を見て日本語訳をイメージします。そしてタップすると裏側の緑色の面が表示されるので答え合わせをします。さらにタップすると次の問題が橙色の面に表示されます。設定は左上のメニューをタップして出題の種類と橙色の面に表示する言語を選択します。
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;
}