準備
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> </parent> <properties> <java.version>10.0</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
コード
controller
import java.time.LocalDateTime; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.ModelAndView; @Controller public class FragmentAjaxSampleController { @GetMapping("/fragsample") public ModelAndView index(ModelAndView model) { model.addObject("now", LocalDateTime.now().toString() + " init"); model.setViewName("fragmentsajax/sample"); return model; } @GetMapping("/fragsample/frag") public String frag(Model model) { model.addAttribute("now", LocalDateTime.now().toString() + " frag"); return "fragmentsajax/sample :: sample-fragment"; } @GetMapping("/fragsample/frag-with-selector") public String frag002(Model model) { model.addAttribute("now", LocalDateTime.now().toString() + " frag-with-selector"); return "fragmentsajax/sample :: sample-fragment-with-selector//div[id='frag2-sub']"; } }
html
src/main/resources/templates/fragmentsajax/sample.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>fragments ajax sample</title> </head> <body> <div> <a href="#" onclick="frag001Click();">ajax fragments</a><br /> <a href="#" onclick="frag002Click();">ajax fragments-with-selector</a><br /> </div> <div id="frag1" th:fragment="sample-fragment"> <span th:text="${now}"></span> </div> <div id="frag2" th:fragment="sample-fragment-with-selector"> <span th:text="${now}"></span> non ajax <div id="frag2-sub" class="class1"> <span th:text="${now}"></span> </div> </div> <br /> </body> <script type="text/javascript"> function frag001Click() { var frag1 = document.querySelector('div#frag1'); fetch("/fragsample/frag", { method: "get" }).then(response => { return response.text(); }).then(body => { frag1.outerHTML = body; }); } function frag002Click() { var frag2sub = document.querySelector('div#frag2-sub'); fetch("/fragsample/frag-with-selector", { method: "get" }).then(response => { return response.text(); }).then(body => { frag2sub.outerHTML = body; }); } </script> </html>
説明
fragments + ajax
まず/fragsample
にアクセスすると上記のhtmlが表示される。
次にfrag001Click()
をすると/fragsample/frag
のコントローラーメソッドが呼ばれ、fragmentsajax/sample :: sample-fragment
を返している。fragmentsajax/sample
は上記のsample.html
を指しており、その次の:: sample-fragment
はそのhtmlファイル内のth:fragment="sample-fragment"
を指している。つまり<div id="frag1" ...
の箇所だけを使ってレンダリングせよ、という意味になる。そのため/fragsample/frag
にブラウザでアクセスすると次のようなhtmlの断片が返ってくる。
<div id="frag1"> <span>2018-07-07T15:13:14.685116700 frag</span> </div>
最後に、このhtmlの断片をJavaScriptで適当に処理する。
fragments + dom selector + ajax
また、dom selectorでfragments内の要素を指定することもできる。
frag002Click();
をすると対象のコントローラーメソッドはfragmentsajax/sample :: sample-fragment-with-selector//div[id='frag2-sub']
を返している。これはsample-fragment-with-selector
というfragment内のdiv
タグでidがfrag2-sub
のもの、という意味になる。つまり<div id="frag2-sub" ...
の箇所だけを使う、という意味になる。そのため/fragsample/frag-with-selector
にブラウザでアクセスすると次のようなhtmlの断片が返ってくる。
<div id="frag2-sub" class="class1"> <span>2018-07-07T15:18:39.535689500 frag-with-selector</span> </div>
fragmentsの外部化
fragmentsの使用例で良く見かけるのはヘッダー・フッターなど共通部分を外部ファイル化することだが、この外部化したfragmentsを使用することもできる。使用例は省略。
<div id="frag001" th:insert="~{footer :: frag001}" class="hoge"></div>
ただし、複数画面に共通では無い、ある画面のajaxでしか使用しないfragmentsを外部化しても見通しが悪くなるだけだと思われる。
感想
この機能使ってるのを初めて見たときちょっと混乱した。というのも、ajaxだからjsonやxmlを返してJavaScriptで云々してるだろう、という思い込みがあったためである。とはいえ、個人的には、一度理解してしまえばspringとthymeleafの世界に寄せられるのでまぁまぁ便利な機能ではないか、という感触がある。