CSS調整

2018年9月27日

Spring Data RestのJson SchemaのEnumをJsonIgnoreする

SDRにはJsonSchemaを出力する機構があります。
要するにEntityを元にJsonSchemaを生成するConverterがあるってことですね。

普通にJsonSchemaを出力してみると


class Text {
  @Column
  var title: String = ""
  @Column
  var page: Int = 0
  @Column
  @Enumerated(EnumType.STRING)
  var category: CategoryType = CategoryType.Memo
  
  enum class CategoryType {
    Memo,
    Article,
    Log,
    Code
  }
}

上のEntityがあったとして、

RequestHeader ->
  Accept: application/schema+json

HttpRequest.GET ->
  /profile/texts

となげると

{
  title: "Text",
  type: "object",
  properties: {
    title: {
      title: "Title",
      type: "string"
    },
    page: {
      title: "Page",
      type: "integer"
    },
    category: {
      title: "Category",
      type: "string",
      enum: [
        "Memo",
        "Article",
        "Log",
        "Code"
      ]
    }
  }
}

といったものが返ってきます。(一部不要な部分は削ってます)


このEntityの一部にJsonIgnoreを指定してみます


class Text {
  @Column
  var title: String = ""
  
  @JsonIgnore
  @Column
  var page: Int = 0
  
  @Column
  @Enumerated(EnumType.STRING)
  var category: CategoryType = CategoryType.Memo
  
  enum class CategoryType {
    Memo,
    Article,
  
    @JsonIgnore Log,
    @JsonIgnore Code
  }
}

同じリクエストをなげた場合、返ってくるのは、

{
  title: "Text",
  type: "object",
  properties: {
    title: {
      title: "Title",
      type: "string"
    },
    category: {
      title: "Category",
      type: "string",
      enum: [
        "Memo",
        "Article",
        "Log",
        "Code"
      ]
    }
  }
}

となります。
pageプロパティは期待どおり出力されなくなりましたが、categoryプロパティのenum値は全て出力されてしまっています。


これをどうにかしたいのが、本題。


enum値のJsonSchemaはJsonSchema.EnumPropertyというクラスで実装されています。


標準のconverter相当の作り方をした場合


fun buildSchema(clazz: Class<Any>): JsonSchema.JsonSchemaProperty? {
  return when {
    clazz.isEnum -> buildEnumSchema(clazz)
    else -> null
  }
}
fun buildEnumSchema(clazz: Class<Any>): JsonSchema.JsonSchemaProperty {
  return JsonSchema.EnumProperty("name", "title", clazz, "description", false)
}

少し雑ですが、要するに、Classを渡して生成しています。

Classを渡した場合のコンストラクタは、

* 全Enumメンバーを取得
* toStringしてList化
* 別のコンストラクタへ渡す

という処理になっているので、JsonIgnore分を削ったListを用意して、その「別のコンストラクタ」をダイレクトに呼んであげれば良さそうです。


JsonIgnoreなメンバを省いたEnumPropertyを作る場合


上の処理を書き換えます。

fun buildEnumSchema(clazz: Class<Any>): JsonSchema.JsonSchemaProperty {
  clazz as Class<Enum<*>>
  return JsonSchema.EnumProperty("name", "title",
    clazz.enumConstants.filter { 
      clazz.fields[it.ordinal]
           .getAnnotationsByType(JsonIgnore::class.java)
           .isEmpty() 
    }.map { it.toString() }.toList(),
    "description", false)
}

これをConverterに組み込んで、JsonSchema取得のリクエストをなげてみると、

{
  title: "Text",
  type: "object",
  properties: {
    title: {
      title: "Title",
      type: "string"
    },
    category: {
      title: "Category",
      type: "string",
      enum: [
        "Memo",
        "Article"
      ]
    }
  }
}

categoryのenumの値もフィルタリングされました。


やっていることは単純で、enumメンバのうち、JsonIgnoreが貼られているものを除外してtoStringしたものを渡してEnumPropertyを作っているだけです。

ポイントは、ClassがEnumだと後続処理に教える為に未代入でキャストしているのと、enumConstantsを再度、自身のClassからFieldとして取得してAnnotationを見れるようにするところでしょうか。


最新のSDRならもしかしたら、わざわざやらなくてもいけるのかな?とか思いますが、まあ、メモ程度ってことで。

いじょ

2018年9月5日

html2canvasでgoogleのfeedbackみたいなの

html2canvasというライブラリが面白そうだったので触ってみました。

html2canvasとは?


htmlの要素を解析してcanvas化するライブラリです。

html2canvas - Screenshots with JavaScript

例えばとあるdivにcssでborderが指定されていて、子要素にtextNodeがあったならば、
divと同じ大きさのblockを作って、strokeでborder引いて、textで文字を書いて、同じ見た目のcanvas要素化してくれる、というかんじです。

これを実際にやろうと考えた場合、

タグは見た目にさして関係無いからいいとしても、全cssを変換する処理を用意するなんて、現実的でなくね?

とかなると思いますが、そのものズバリ、html2canvasのgithubを見てもらうとわかりますが、cssごとの変換処理が書かれていたりします。とんでもないですね。

html2canvasを使ってみる


何も考えずに使う場合、使い方はシンプルです。

<script type="text/javascript" src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>

<script type="text/javascript">
    var content = document.getElementById("content");
    html2canvas(content).then(canvas => {
        ...
    });
</script>

これだけで渡した要素がcanvas化されたものが返ってきます。

せっかくなので、Googleのフィードバック送信時の処理
(表示中の画面にマーキングしてお問い合せする機構
ここの「フィードバックを送信」でできるやつ)

みたいなのをやってみます。

ui


適当にベタhtmlで

・画面キャプチャするボタン
・画面キャプチャしたものを入れる要素
・マークした結果をキャプチャするボタン
・マークした結果を表示する要素

を置きます。

<button id="execButton" class="exec-btn" onclick="h2c();"><img src="camera.svg"/></button>
<div id="screenshot" class="screenshot" style="display:none;"></div>
<button id="okButton" class="ok-btn" onclick="c2i();" style="display:none;"><img src="done.svg"/></button>
<div id="resultBg" class="result-bg" onclick="iClose();" style="display:none;"><img id="result" class="result"/></div>


画面をキャプチャする処理


画面をhtml2canvasにお願いしてcanvas化してもらいます。
要素の表示/非表示の切り替えに手抜き感が伺えますが、気にしない。

function h2c() {
    var content = document.getElementById("content");
    html2canvas(content).then(canvas => {
        var ss = document.getElementById("screenshot");
        ss.appendChild(canvas);
        initSelector(canvas);
        
        ss.style.display = "";
        content.style.display = "none";
        document.getElementById("execButton").style.display = "none";
        document.getElementById("okButton").style.display = "";
    });
}


canvasにマークする処理


所謂、自由選択ツールのようなことをします。
イベントの貼り方に手抜き感が伺えますが、良い子はマネしないでね!

function initSelector(screenshot){
    var selector = document.createElement("canvas");
    with (selector) {
        width = screenshot.width;
        height = screenshot.height;
        className = "selector";
        style.zIndex = (screenshot.style.zIndex || 0) + 1;
    }
    
    buildSelector(selector).selectEnd = function(r) {
        with (screenshot.getContext("2d")) {
            fillStyle = "#ffeb3ba1";
            fillRect(r.sx, r.sy, r.ex, r.ey);
        }
    }
    
    screenshot.parentElement.appendChild(selector);
}
function buildSelector(canvas) {
    var context = canvas.getContext("2d");
    with (context) {
        setLineDash([6, 3]);
        strokeStyle = "#3e3e3ecf";
    }
    
    context.rect = {
        sx: 0, sy: 0, ex: 0, ey: 0, dirty: false
    };
    context.selectStarted = false;
    context.clear = function(dirty) {
        this.rect.dirty = dirty;
        this.clearRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
    }
    context.select = function() {
        this.clear(true);
        this.strokeRect(this.rect.sx, this.rect.sy, this.rect.ex, this.rect.ey);
    }
    context.selectEnd = null;

    canvas.addEventListener("mousedown", function (e) {
        context.rect.sx = e.pageX;
        context.rect.sy = e.pageY;
        context.selectStarted = true;
    }, false);
    canvas.addEventListener("mousemove", function (e) {
        if (!context.selectStarted) return;
        context.rect.ex = e.pageX - context.rect.sx;
        context.rect.ey = e.pageY - context.rect.sy;
        context.select();
    }, false);
    canvas.addEventListener("mouseup", function onMouseUp (e) {
        if (context.rect.dirty) {
            context.clear(false);
            if (context.selectEnd) context.selectEnd(context.rect);
        }
        context.selectStarted = false;
    }, false);
    
    return context;
}


マークした結果をキャプチャする処理


canvasをインラインイメージ化してimgタグにぶち込みます。
それ以外でやってることは例によって手抜きですね。

ついでに、プレビューを閉じて最初に戻る処理も書いておきます。

function c2i() {
    var ss = document.getElementById("screenshot");
    var canvas = ss.firstChild;
    var selector = ss.lastChild;
    document.getElementById("result").src = canvas.toDataURL();
    
    ss.removeChild(canvas);
    ss.removeChild(selector);
    ss.style.display = "none";
    document.getElementById("okButton").style.display = "none";
    document.getElementById("resultBg").style.display = "";
}
function iClose() {
    document.getElementById("resultBg").style.display = "none";
    document.getElementById("execButton").style.display = "";
    document.getElementById("content").style.display = "";
}


当たり前といえば当たり前ですが、画面のキャプチャなので、それっぽい画面がなければ意味がない・・・
でも、出来上がっている画面を用意するのって地味に面倒なんですよね。。。というわけで、github pageのデフォルトを利用します。

で、出来上がったのが以下。

Welcome to GitHub Pages

完全版のソースはこれ

なんとも簡単にそれっぽくなりました。

使い所は限られるとは思いますが、アイデア次第では色々なことができそうです。

いじょ