css だけでドロップメニュー(横)

水平方向に並べたメニューのドロップを書いたので、
oboe2uran.hatenablog.com

では、縦にならべたメニューの横に表示するメニュー

f:id:posturan:20170329213707j:plain

サンプル

<ul class="dropmenu">
    <li><a href="#">menu</a>
        <ul>
           <li><a href="#">sub1</a></li>
           <li><a href="#">sub2</a></li>
           <li><a href="#">sub3</a></li>
           <li><a href="#">sub4</a></li>
        </ul>
    </li>
    <li><span>menu</span>
        <ul>
            <li><a href="#">sub11</a></li>
            <li><a href="#">sub12</a></li>
            <li><a href="#">sub13</a></li>
        </ul>
    </li>
    <li><span class="drop">menu</span>
        <ul>
            <li><a href="#">sub21</a></li>
            <li><a href="#">sub22</a></li>
            <li><a href="#">sub23</a></li>
        </ul>
    </li>
    <li>&nbsp;</li>
</ul>

前と違って CSS は、
.dropmenu ul に zindex 属性を付けない。
.dropmenu li ul li は、top position を li 負の高さを指定
float属性は li ul li で指定

.dropmenu{
	padding: 0;
	white-space: nowrap;
	font-size: 14px;
}
.dropmenu:after{ content: ""; display: block; clear: both; }
ul.dropmenu{  margin: 0; padding: 0; }
.dropmenu ul{
	position: absolute;
	margin: 0;
	padding: 0;
 }
.dropmenu li{
	list-style-type: none;
	position: relative;
	width: 100px;
	height: 27px;
	line-height: 27px;
	background: #0068b7;
	text-align: center;
}
.dropmenu li a{
	display: block;
	height: 27px;
	line-height: 27px;
	vertical-align: middle;
	color: #ffffff;
	text-decoration: none;
}
.dropmenu li span{
	display: table-cell;
	width: 100px;
	height: 27px;
	line-height: 27px;
	vertical-align: middle;
	background: #0068b7;
	color: #ffffff;
	text-decoration: none;
	cursor: pointer;
}
.dropmenu li ul li{
	float: left;
	position: relative;
	left: 100px;
	top: -27px;
}
.dropmenu li ul li a{
	height: 27px;
	line-height: 27px;
	vertical-align: middle;
	border-top: 1px solid #0068b7;
	background: #003f8e;
	text-align: center;
}
.dropmenu li:hover > a{ background: #003f8e; }
.dropmenu li a:hover, .dropmenu li span:hover{ background: #192f60;  }
.dropmenu li ul li{
	overflow: hidden;
	height: 0;
	transition: .2s;
}
.dropmenu li:hover ul li{
	overflow: visible;
	height: 27px;
}

css だけでドロップメニュー

html サンプル

<ul class="dropmenu">
    <li><a href="#">menu</a>
        <ul>
           <li><a href="#">sub1</a></li>
           <li><a href="#">sub2</a></li>
           <li><a href="#">sub3</a></li>
           <li><a href="#">sub4</a></li>
        </ul>
    </li>
    <li><span>menu</span>
        <ul>
            <li><a href="#">sub1</a></li>
            <li><a href="#">sub2</a></li>
            <li><a href="#">sub3</a></li>
        </ul>
    </li>
    <li><span class="drop">menu</span>
        <ul>
            <li><a href="#">sub1</a></li>
            <li><a href="#">sub2</a></li>
            <li><a href="#">sub3</a></li>
        </ul>
    </li>
    <li>&nbsp;</li>
</ul>

CSS

.dropmenu{
   padding: 0;
   white-space: nowrap;
   font-size: 14px;
}
.dropmenu:after{ content: ""; display: block; clear: both; }
ul.dropmenu{  margin: 0; padding: 0; }
.dropmenu ul{
   position: absolute;
   z-index: 9999;
   margin: 0;
   padding: 0;
 }
.dropmenu li{
   list-style-type: none;
   position: relative;
   width: 100px;
   float: left;
   height: 27px;
   background: #0068b7;
   text-align: center;
}
.dropmenu li a{
   display: block;
   height: 27px;
   line-height: 27px;
   vertical-align: middle;
   color: #ffffff;
   text-decoration: none;
}
.dropmenu li span{
   display: table-cell;
   width: 100px;
   height: 27px;
   line-height: 27px;
   vertical-align: middle;
   background: #0068b7;
   color: #ffffff;
   text-decoration: none;
   cursor: pointer;
}
.dropmenu li ul li a{
   height: 27px;
   line-height: 27px;
   vertical-align: middle;
   border-top: 1px solid #0068b7;
   background: #003f8e;
   text-align: center;
}
.dropmenu li:hover > a{ background: #003f8e; }
.dropmenu li a:hover, .dropmenu li span:hover{ background: #192f60;  }
.dropmenu li ul li{
   overflow: hidden;
   height: 0;
   transition: .7s;
}
.dropmenu li:hover ul li{
   overflow: visible;
   height: 27px;
}

CSS のポイントは、
:after疑似要素で float解除
li a と li span 各々で、height と line-height 同じサイズで、vertical-align: middle;
これは、li の高さだけを指定すると、vertical-align: middle が効かないからだ。
li:hover > a で選択候補色
li ul li を
overflow: hidden;
height: 0;
transition: .7s; overflow: hidden;
で時間をかけてドロップメニューの開閉
.dropmenu li a:hover, .dropmenu li span:hover{ background: #192f60; } で選択色

f:id:posturan:20170328222744j:plain

css float属性の解除は、after疑似要素で。

今更。。。
css float属性の解除は、after疑似要素を使うのがいい。

<ul class="foo">
    <li>a</li>
    <li>b</li>
    <li>c</li>
</ul>
<ul>
    <li>d</li>
    <li>e</li>
    <li>f</li>
</ul>

に対して、

.foo:after{ content: ""; display: block; clear: both; }

.foo li{
   float: left;
   list-style: none;
   
   width: 50px;
   border: 1px solid #808080;
}

Wicket 8 FeedbackPanel サンプル

Wicket8 の AjaxButton onSubmit を使用して気がついたのだが、前の Wicket バージョンでは、onSubmit の中で任意に
error(Serializable) を呼ぶと、onError が走るという認識だったけど、Wicket 8 でそう動かない。Validator を
仕掛けておいて、バリデーションエラーの時に、onError が走る。
Validator の仕掛け以外で、onSubmit でエラーを判定してエラー処理をしたいことは多々あるわけで、
error(Serializable) で onError が走らないのは不便だ。

final TextField<String> nameField = new TextField<>("name", new Model<>());
queue(nameField);

// フィードバック
final FeedbackPanel feedback = new FeedbackPanel("feedback");
feedback.setOutputMarkupId(true);
final AtomicBoolean feedbackError = new AtomicBoolean(false);
feedback.setFilter(e->feedbackError.get());
queue(feedback);

queue(AjaxButton.onSubmit("send", (b, t)->{
   feedbackError.set(false);
   try{
      String name = Optional.ofNullable(nameField.getModelObject())
                  .orElseThrow(()->new RuntimeException("必須エラー"));

      // フィードバッククリア
      t.add(feedback);
      // 正常処理

   }catch(Exception e){
      error(e.getMessage());
      feedbackError.set(true);
      t.add(feedback);
   }
}));

こんな意見、指摘、
 「 Validator エラー判定を全て用意するのが正しい、そうでなければそれは設計ミス!」
ということを言う人がいるかもしれない。

でも、それには私は反対だ。

HTTPClient の ResponseHandler

HTTPClient といえば、
https://hc.apache.org/ の HttpClient
この中の ResponseHandler は、残念なのか仕方ないのか、@FunctionalInterface を持っているわけではない。
あの面倒くさいHTTPで受け取る処理をもう少しスマートに書きたくて、とりあえず以下の static メソッドを書いてみる。

static void executeHttp(HttpClient client, HttpUriRequest request, Consumer<HttpResponse> handler)
throws ClientProtocolException, IOException{
   client.execute(request, response ->{
      handler.accept(response);
      return null;
   });
}

そして、HttpGet してみる。

try(CloseableHttpClient httpclient = HttpClients.createDefault()){
   HttpGet httpget = new HttpGet("http://google.com");
   System.out.println("Executing request " + httpget.getRequestLine());
   executeHttp(httpclient, httpget, response->{
      int status = response.getStatusLine().getStatusCode();
      if (status >= 200 && status < 300){
         Optional.ofNullable(response.getEntity()).<Runnable>map(entity->()->{
            try{
               System.out.println("EntityUtils.toString(entity) = " +  EntityUtils.toString(entity) );
            }catch(ParseException | IOException e){
                e.printStackTrace();
            }
         }).orElse(()->{
             System.out.println("NULL receive!!");
         }).run();
      }else{
         System.out.println("Error! HTTP status = " + status );
      }
   });
}catch(Exception e){
   e.printStackTrace();
}finally{
}

なんだか、あまりスマートじゃない。

思い切って、HTTP 200 以外は全てエラーということで、、、
でも、HttpEntity を正常処理でわたさないとならないか悩んで、、

static void executeHttp(HttpClient client, HttpUriRequest request, Consumer<HttpEntity> receive, Consumer<HttpResponse> error) {
   try{
      client.execute(request, new ResponseHandler<String>(){
         @Override
         public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException{
            int status = response.getStatusLine().getStatusCode();
            if (status==200){
               Optional.ofNullable(response.getEntity()).<Runnable>map(entity->()->{
                  receive.accept(entity);
               }).orElse(()->{
                  error.accept(response);
               }).run();
            }else{
               error.accept(response);
            }
            return null;
         }
      });
   }catch(IOException e){
      error.accept(null);
   }
}
try(CloseableHttpClient httpclient = HttpClients.createDefault()){
   HttpGet httpget = new HttpGet("http://google.com");
   System.out.println("Executing request " + httpget.getRequestLine());
   executeHttp(httpclient, httpget
   , entity->{
      try{
         System.out.println("EntityUtils.toString(entity) = " +  EntityUtils.toString(entity) );
      }catch(IOException e1){
         e1.printStackTrace();
      }
   }
   , res->{
      // エラー処理
   });
}catch(Exception e){
   e.printStackTrace();
}finally{
}

それでもやはりスマートとは言えない。EntityUtils.toString か toByteArray かは、Consumer 実行の中で記述するから
こうなってしまうのか。。。

Wicket 8 で AJAX イベントビヘビアを Generic に

Wicket 8 で AJAX イベントビヘビアを
oboe2uran.hatenablog.com

と書いたものの、落ち着いてみれば、一般用に以下のとおり書けるし、その方がビヘビアを追加した先の
コンポーネントを参照したビヘビアが書ける。。

import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxEventBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;

/**
 * GenericAjaxEventBehavior
 */
public abstract class GenericAjaxEventBehavior extends AjaxEventBehavior{

   private GenericAjaxEventBehavior(String event){
      super(event);
   }

   public static GenericAjaxEventBehavior of(String eventName, SerializableBiConsumer<Component, AjaxRequestTarget> consumer){
      return new GenericAjaxEventBehavior(eventName){
         private static final long serialVersionUID = 1L;
         @Override
         protected void onEvent(AjaxRequestTarget target){
            consumer.accept(getComponent(), target);
         }
      };
   }
}

xxx というコンポーネントに add する AJAX のビヘビアは、、

xxx.add(GenericAjaxEventBehavior.of("click", (c, t)->{
    // クリックしたコンポーネント = c を参照
    // AjaxRequestTarget = t を処理
});

AJAXDownload ビヘビア、

Wicket8 が前提の話だが、ビヘビアをラムダで。。を考え出すと、次から次へと出てくる。。。
AJAX の振る舞いで動かすダウンロード、以前、
Wicket 6.x→7.x でファイルダウンロード時のファイル名の注意 - Oboe吹きプログラマの黙示録
この中で、public abstract class AJAXDownload を書いた。
でもこれは、以下のように、インスタンス生成の staticメソッド-ラムダを用意すれば、
もう少しスッキリする。

import java.io.IOException;
import java.io.OutputStream;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.AbstractAjaxBehavior;
import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
import org.apache.wicket.request.resource.ContentDisposition;
import org.apache.wicket.util.resource.AbstractResourceStreamWriter;
import org.apache.wicket.util.resource.IResourceStream;
import org.danekja.java.util.function.serializable.SerializableConsumer;
import org.danekja.java.util.function.serializable.SerializableSupplier;
/**
 * AJAX Download.
 */
public abstract class AJAXDownload extends AbstractAjaxBehavior{
   protected abstract IResourceStream getResourceStream();

   public void callBackDownload(AjaxRequestTarget target){
      target.appendJavaScript("setTimeout(\"window.location.href='" + getCallbackUrl().toString() + "'\", 100);");
   }

   @Override
   public void onRequest(){
      try{
      ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(getResourceStream(), getFileName());
         handler.setContentDisposition(ContentDisposition.ATTACHMENT);
         getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
      }catch(Exception e){
         throw new RuntimeException(e.getMessage(), e);
      }
   }
   protected String getFileName(){
      return null;
   }

   public static AJAXDownload of(SerializableConsumer<OutputStream> write
   , SerializableSupplier<String> getConteType, SerializableSupplier<String> getName){
      return new AJAXDownload(){
         @Override
         protected IResourceStream getResourceStream(){
            return new AbstractResourceStreamWriter(){
               @Override
               public void write(OutputStream out) throws IOException{
                  write.accept(out);
                  out.close();
               }
               @Override
               public String getContentType(){
                  return getConteType.get();
               }
            };
         }
         @Override
         protected String getFileName(){
            return getName.get();
         }
      };
   }
}

とすれば、、

final AJAXDownload download = AJAXDownload.of(out->{
   // out への出力
}, ()->"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", ()->"foo.xlsx");
compoment.add(download);
queue(AjaxLink.onClick("download", t->{
   // 出力準備の後で、、
   download.callBackDownload(t);
}).add(download));

OutputStream の close の書き忘れを気にしなくてよい。