SWF 4장 뷰 랜더링
4. 뷰 랜더링
4.1. 소개
이번 장에서는 view-state 엘리먼트를 사용하여 플로우에서 뷰를 랜더링하는 방법을 살펴본다.
4.2. 뷰 상태 정의하기
view-state 엘리먼트를 사용하여 뷰를 랜더링하고 사용자 이벤트를 대기하는 플로우 스탭을 정의한다.
<view-state id="enterBookingDetails">
<transition on="submit" to="reviewBooking" />
</view-state>
규약에 따라, view-state는 자신의 id를 flow가 위치한 디렉터리에서 뷰 템플릿으로 맵핑한다. 예를 들어, 만약에 flow 자체가 /WEB-INF/hotels/booking에 있다면 위의 state는 /WEB-INF/hotels/booking/enterBookingDetails.xhtml을 랜더링할 것이다.
아래는 예제 디렉터리 구조로 뷰와 메시지 번들 같은 기타 리소스가 그들의 플로우 정의와 같이 있는 것을 보여준다.
4.3. 뷰 식별자 작성하기
view 속성을 사용하여 랜더링 할 뷰를 명시적으로 기입한다.
4.3.1. 플로우 상대 뷰 id
뷰 id는 플로우가 있는 작업 디렉터리에 위치한 뷰 리소스에 대한 상대 경로가 될 수 있다.
<view-state id="enterBookingDetails" view="bookingDetails.xhtml">
4.3.2. 절대 뷰 id
뷰 id는 웹 애플리케이션 루트 디렉터리에 있는 뷰의 절대 경로가 될 수도 있다.
<view-state id="enterBookingDetails" view="/WEB-INF/hotels/booking/bookingDetails.xhtml">
4.3.3. 논리 뷰 id
스프링 MVC의 뷰 프레임워크 같은 몇몇 뷰 프레임워크에서 뷰 id는 프레임워크가 판단할 떄 사용할 논리적인 식별자가 될 수도 있다.
<view-state id="enterBookingDetails" view="bookingDetails">
스프링 MVC 연동 뿐에서 어떻게 MVC ViewResolver 기반 시설과 연동하는지 더 많은 정보를 살펴보도록 하자.
4.4. 뷰 스코프
view-state는 들어올 때마다 새로운 viewScope을 할당한다. 이 스코프는 Ajax 요청처럼 같은 뷰에서 여러 요청에 걸쳐 객체를 조작할 때 유용하다. view-state는 해당 state를 나갈 때 viewScope을 소멸한다.
4.4.1. 뷰 변수 할당하기
var 태그를 사용하여 뷰 변수를 선언한다. 플로우 변수처럼, 뷰 상태가 다시 시작하면 모든 @Autowired 레퍼런스를 자동으로 복원한다.
<var name="searchCriteria" class="com.mycompany.myapp.hotels.SearchCriteria" />
4.4.2. viewScope 변수 할당하기
on-render 태그를 사용하여 뷰 랜더링하기 전에 액션 결과에 변수를 할당한다.
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
4.4.3. 뷰 스코프에 있는 객체 조작하기
뷰 스코프에 있는 객체는 보통 동일한 뷰에서 여러 요청에 의해 조작된다. 다음 예제 페이지는 검색 결과 목록을 보여준다. 목록은 매번 랜더링 하기 전에 뷰 스코프에서 수정된다. 비동기 이벤트 핸들러가 현재 데이터 페이지를 수정하고 검색 결과 부분에 대한 요청을 다시 랜더링 한다.
<view-state id="searchResults">
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)"
result="viewScope.hotels" />
</on-render>
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="searchResultsFragment" />
</transition>
<transition on="previous">
<evaluate expression="searchCriteria.previousPage()" />
<render fragments="searchResultsFragment" />
</transition>
</view-state>
4.5. render 액션 실행하기
on-render 엘리먼트를 사용하여 뷰 랜더링을 하기전에 한 개 이상의 액션을 실행한다. 뷰의 일부만 다시 랜더링하는 것을 포함하여 랜더 액션은 이후의 리프래시와 마찬가지로 초기 랜더링 단계에 실행된다.
<on-render>
<evaluate expression="bookingService.findHotels(searchCriteria)" result="viewScope.hotels" />
</on-render>
4.6. 모델로 바인딩하기
model 속성을 사용하여 뷰를 바인딩할 모델 객체를 선언한다. 이 속성은 보통 폼 같은 데이터 컨트롤을 랜더링하는 뷰랑 같이 사용한다. 폼 데이터 바인딩과 검증 작업을 모델 객체에 대한 메타데이터로 주도할 수 있다.
다음 예는 booking 모델을 다루는 enterBookingDetails State다.
<view-state id="enterBookingDetails" model="booking">
모델은 flowScope 또는 viewScope 같이 접근 가능한 모든 스코프에 있는 객체다. model을 정의하면 뷰 이벤트가 발생할 때 다음 작업이 이뤄진다.
- 뷰에서 모델로 바인딩. 뷰 포스트백(postback)을 할 때 사용자 입력 값을 모델 객체 속성으로 바인딩한다.
- 모델 검증. 바인딩을 한 다음, 만약 모델 객체가 검증을 필요로 하면, 검증 로직을 호출한다.
뷰 상태 전이를 할 수 있는 플로우 이벤트가 발생할 때, 모델 바인딩은 반드시 성공적으로 완료되어야 한다. 만약 바인딩이 실패하면, 뷰는 다시 랜더링하여 사용자가 다시 편집할 수 있게 한다.
4.7. 타입 변환 수행하기
뷰 포스트백을 할 때 모델 바인딩이 발생하면, 바인딩 시스템은 필요하다면 입력 값을 타겟 모델 속성 타입으로 변환을 시도할 것이다. 숫자들과 기본타입, 이넘, 그리고 Date에 대한 기본 컨버터(Converter)는 자동으로 적용된다. 사용자는 또한 자신들이 직접 정의한 타입에 대한 컨버터를 등록할 수 있고 기본 컨버터를 재정의 할 수도 있다.
4.7.1. 컨버터 구현하기
컨버터를 구현하려면, org.springframework.binding.convert.converters.TwoWayConverter 인터페이스를 구현하라. 입력값으로 문자열을 받아서 사용자가 정의한 객체로 변경하는 컨버터를 만들 때 이 인터페이스 구현을 편리하게 해주는 기본 클래스 StringToObject를 제공한다. 간단하게 이 클래슬르 상속하고 두 개의 매서드를 재정의하면 된다.
protected abstract Object toObject(String string, Class targetClass) throws Exception;
protected abstract String toString(Object object) throws Exception;
toObject(String, Class)는 입력 문자열을 객체 타입으로 바꿔야 하고 toString(Object)는 그 반대 일을 해야 한다.
다음 예제는 문자열을 MonetaryAmount로 변환하는 컨버터다.
public class StringToMonetaryAmount extends StringToObject {
public StringToMonetaryAmount() {
super(MonetaryAmount.class);
}
@Override
protected Object toObject(String string, Class targetClass) {
return MonetaryAmount.valueOf(string);
}
@Override
protected String toString(Object object) {
MonetaryAmount amount = (MonetaryAmount) object;
return amount.toString();
}
}
더 많은 컨버터 구현 예제를 보고 싶다면 org.springframework.binding.convert.converters 패키지에 미리 만들어둔 컨버터들을 참고하라.
4.7.2. 컨버터 등록하기
여러분이 작성한 컨버터를 등록하거나 기본 컨버터를 변경하고 싶다면, org.springframework.binding.convert.service.DefaultConversionService를 상속하고 addDefaultConverters() 매서드를 재정의 하라. addConverter(Converter) 매서드를 사용하여 String과 MonetaryAmount 같은 두 타입을 변환할 때 사용할 주요 컨버터(Primary Converter)를 등록하라. 부가적으로 addConverter(String, Converter)를 사용하여 같은 타입에 대한 여러 대체 컨버터(Alternate Converter)를 등록할 수 있다. 예를 들어, java.util.Date를 여러 형태의 문자열로 변환할 수 있겠다.
대체 컨버터는 유일한 converterId로 인덱싱을 하는데 모델 바인딩을 설정할 때 참조할 수 있다. converterId를 지정하지 않으면 주요 컨버터를 사용한다.
ConversionService는 웹 플로우가 런타임에 어떤 타입을 다른 타입으로 변환 해주는 변환 실행기를 룩업할 때 사용하는 객체다. 애플리케이션 당 하나의 ConversionService가 있다. System Setup 절을 참조하여 애플리케이션 전반에 걸쳐 커스텀 컨버터를 등록하는 ConversionService 구현체를 설정하는 방법을 살펴보라. 또한 자세한 내용은 Convert API를 참고하라.
4.8. 바인딩 방지하기
bind 속성을 사용하여 특정 뷰 이벤트에 대한 검증과 모델 바인딩을 방지할 수 있다. 다음 예는 cancel 이벤트가 발생했을 때 바인딩을 방지한다.
<view-state id="enterBookingDetails" model="booking">
<transition on="proceed" to="reviewBooking">
<transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
4.9. 명시적으로 바인딩 설정하기
binder 엘리먼트를 사용하여 뷰에서 사용할 수 있는 모델 바인딩 집합을 설정할 수 있다. 이것은 특히 스프링 MVC 환경에서 뷰 마다 "허용하는 입력값" 집합을 제한할 때 유용하다.
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
만약에 binder 엘리먼트를 설정하지 않으면 모델의 모든 public 속성이 뷰에서 바인딩하는 대상이 된다. binder 엘리먼트를 사용하여 오직 명시적으로 표현한 속성만 바인딩 한다.
각각의 바인딩은 컨버터를 적용하여 모델 속성 값을 사용자에게 익숙한 형태로 변경할 수 있을 것이다. 만약 컨버터를 명시하지 않으면 모델 속성 타입에 따라 기본 컨버터를 사용하게 된다.
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" converter="shortDate" />
<binding property="checkoutDate" converter="shortDate" />
<binding property="creditCard" />
<binding property="creditCardName" />
<binding property="creditCardExpiryMonth" />
<binding property="creditCardExpiryYear" />
</binder>
<transition on="proceed" to="reviewBooking" />
<transition on="cancel" to="cancel" bind="false" />
</view-state>
위의 예제에서 shortDate 컨버터를 chekingDate와 checkoutDate 속성에 적용하고 있다. 커스텀 컨버터를 애플리케이션의 ConventionService로 등록할 수도 있다.
각각의 바인딩은 또한 필수 여부를 확인하여 사용자가 입력한 값이 비어있다면 검증 에러를 발생시키도록 할 수 있다.
<view-state id="enterBookingDetails" model="booking">
<binder>
<binding property="checkinDate" converter="shortDate" required="true" />
<binding property="checkoutDate" converter="shortDate" required="true" />
<binding property="creditCard" required="true" />
<binding property="creditCardName" required="true" />
<binding property="creditCardExpiryMonth" required="true" />
<binding property="creditCardExpiryYear" required="true" />
</binder>
<transition on="proceed" to="reviewBooking">
<transition on="cancel" to="bookingCancelled" bind="false" />
</view-state>
위의 예제에서 모든 바인딩을 필수로 설정했다. 만약 바인딩 할 입력값이 하나라도 비었다면 에러를 만들고 뷰를 에러와 함께 다시 보여줄 것이다.
4.10. 모델 검증하기
모델 검증은 도델 객체에 기반하여 설정한 제약사항으로 주도한다. 웹 플로우는 그러한 제약 사항을 프로그래밍으로 제공한다.
4.10.1. 프로그래밍적인 검증
모델 검증을 프로그래밍적으로 수행하는 두 가지 방법이 있다. 하나는 검증 로직을 모델 객체 내부에 구현하는 것이다. 두 번째는 별도의 Validator를 구현하는 것이다. 두 가지 방법 모두 ValidationContext를 사용하여 에러 메시지를 기록하고 현재 사용자 정보에 접근할 수 있다.
4.10.1.1. 모델 검증 매서드 구현하기
모델 객체 내부에 검증 로직을 정의하는 것은 그 상태를 검증하는 가장 간단한 방법이다. 한 번 이런 로직을 웹 플로우 규약에 맞게 만들어 두면 웹 플로우가 자동으로 그 로직을 view-state 포스트백 라이프사이클 동안에 호출할 것이다. 웹 플로구 규약은 뷰에서 수정하는 모델 속성 서브셋을 쉽게 검증할 수 있게 해준다. 이렇게 하려면 간단하게 validate${state} 형태의 이름을 가진 public 매서드를 만들면 된다. ${state}는 검증을 수행할 view-state의 id다. 예를 보자.
public class Booking {
private Date checkinDate;
private Date checkoutDate;
...
public void validateEnterBookingDetails(ValidationContext context) {
MessageContext messages = context.getMessages();
if (checkinDate.before(today())) {
messages.addMessage(new MessageBuilder().error().source("checkinDate").
defaultText("Check in date must be a future date").build());
} else if (!checkinDate.before(checkoutDate)) {
messages.addMessage(new MessageBuilder().error().source("checkoutDate").
defaultText("Check out date must be later than check in date").build());
}
}
}
위 예제에서, Booking 모델을 편집하는 enterBookingDetails view-state로 전이가 발생하면 웹 플로우는 해당 전이에 검증을 방지하지 않았다면 validateEnterBookingDetails(ValidationContext) 매서드를 자동으로 호출한다. 그런 view-state 예제는 아래와 같다.
<view-state id="enterBookingDetails" model="booking">
<transition on="proceed" to="reviewBooking">
</view-state>
모든 검증 메소드를 정의했다. 일반적으로 플로우는 모델을 여러 뷰에서 편집한다. 이 경우에 검증을 수행할 필요가 있는 각각의 view-state에 대한 검증 매서드를 정의했을 것이다.
4.10.1.2. Validator 구현하기
두 번째 방법은 모델 객체를 검증할 Validator라고 하는 별도의 객체를 정의하는 것이다. 그렇게 하려면 우선 ${model}Validator 패턴의 이름을 가진 클래스를 만든다. ${model}은 booking 같은 모델 표현식 형태를 띈다. 그런 다음 validate${state} 형태의 이름을 가진 매서드를 정의한다. ${state}는 enteringBookingDetails같은 view-state id에 해당한다. 클래스는 스프링 빈으로 배포될 수 있어야 한다. 여러 검증 매서드를 정의할 수 있다, 예제를 보자.
@Component
public class BookingValidator {
public void validateEnterBookingDetails(Booking booking, ValidationContext context) {
MessageContext messages = context.getMessages();
if (booking.getCheckinDate().before(today())) {
messages.addMessage(new MessageBuilder().error().source("checkinDate").
defaultText("Check in date must be a future date").build());
} else if (!booking.getCheckinDate().before(booking.getCheckoutDate())) {
messages.addMessage(new MessageBuilder().error().source("checkoutDate").
defaultText("Check out date must be later than check in date").build());
}
}
}
위 예제에서 Booking 모델을 편집하는 enterBookingDetilas view-state로 전이가 발생하면 웹 플로우는 validateEnterBookingDetails(Booking, ValidationContext) 매서드를 자동으로 호출할 것이다. 단 이때 해당 전이에 대한 검증을 방지한 상태가 아니어야 한다.
또한 Validator는 스프링 MVC의 Errors 객체로 받을 수 있다. 이 객체는 기존의 스프링 검증기를 호출할 때 필수다.
검증기는 스프링 빈으로 등록되어야 하고 ${model}Validator 라는 이름 규약을 지켜야 한다 그럼 자동으로 감지되고 호출된다. 위 예제에서 스프링 2.5 클래스패스-스캔을 하게 되면 @Component를 감지하고 자동으로 bookingValidator라는 이름의 빈으로 등록이 된다. 그러면 bokking 모델 검증이 필요할 때 이 bookingValidator 인스턴스가 호출될 것이다.
4.10.2. ValidationContext
ValidationContext는 MessageContext를 가져와서 검증을 하는 동안 메시지를 기록한다. 또한 userEvent와 현재 사용자의 Principal 같이 현재 사용자에 대한 정보를 공개한다. 이러한 정보는 검증 로직을 접속한 사용자 또는 UI의 어떤 버튼 또는 링크에 기반하여 커스터마이징 할 수 있도록 해준다. 자세한 정보는 ValidationContext API를 참고하라.
4.11. 검증 방지하기
validate 속성을 사용하여 특정 뷰 이벤트에 대한 모델 검증을 방지할 수 있다.
<view-state id="chooseAmenities" model="booking">
<transition on="proceed" to="reviewBooking">
<transition on="back" to="enterBookingDetails" validate="false" />
</view-state>
위 예제는 back 이벤트가 발생할 경우 바인딩은 하지만 검증은 하지 않는다.
4.12. 뷰 전이 실행하기
하나 또는 그 이상의 transition 엘리먼트를 정의하여 뷰에서 발생하는 사용자 이벤트를 다룰 수 있다. 트랜지션은 사용자한테 다른 뷰를 보여주거나 단순하게 액션을 실행하거나 현재 뷰를 다시 보여주는 일을 한다. 또한 트랜지션은 애이작스(Ajax)를 다룰 때 프레그먼츠("fragments")라고 하는 뷰의 일부를 다시 보여주길 요청할 수도 있다. 마지막으로 글로벌("global") 트랜지션이라고 하는 여러 뷰에 걸쳐서 공유하는 것을 정의할 수도 있다.
뷰 트랜지션을 정의하는 방법을 다음 절에서 살펴보도록 하자.
4.12.1. 트랜지션 액션
view-state 트랜지션을 수행하기 전에 하나 또는 그 이상의 액션을 실행할 수 있다. 이러한 액션은 아마 현재 view-state를 종료하지 못하게 에러 결과를 반환할 수도 있다. 만약 에러 결과가 있다면, 뷰는 다시 보여질 것이며 사용자에게 적절한 메시지를 보여줄 것이다.
만약 트랜지션 액션이 일반 자바 매서드를 호출하면, 호출된 매서드는 false를 반환하여 트랜지션 실행을 방지할 수 있다. 이런 기술은 서비스-계층 매서드에서 발생한 예외를 다룰 때 사용할 수 있겠다. 아래 예제는 서비스를 호출하는 액션을 호출하고 예외 상황을 다룬다.
<transition on="submit" to="bookingConfirmed">
<evaluate expression="bookingAction.makeBooking(booking, messageContext)" />
</transition>
public class BookingAction {
public boolean makeBooking(Booking booking, MessageContext context) {
try {
bookingService.make(booking);
return true;
} catch (RoomNotAvailableException e) {
context.addMessage(builder.error().
.defaultText("No room is available at this hotel").build());
return false;
}
}
}
4.12.2. Global 트랜지션
플로우의 global-transitions 엘리먼트를 사용하여 여러 뷰에 걸쳐 적용할 트랜지션을 생성한다, Global-transition은 종종 레이아웃을 구성하는 메뉴 링크 같은 것을 다룰 때 사용한다.
<global-transitions>
<transition on="login" to="login">
<transition on="logout" to="logout">
</global-transitions>
4.12.3. 이벤트 핸들러
view-state에서 타겟이 없는 트랜지션을 정의할 수도 있다. 이러한 트랜지션을 "이벤트 핸들러"라고 한다.
<transition on="event">
<!-- Handle event -->
</transition>
이런 이벤트 핸들러는 플로우의 상태를 변경하지 않는다. 단지 그들의 액션을 실행하고 현재 뷰 또는 하나 이상의 프레그먼트를 다시 보여준다.
4.12.4. 프레그먼트 다시 보여주기
트랜지션에서 render 엘리먼트를 사용하여 이벤트를 처리한 다음 현재 뷰에서 일부분만 다시 보여주도록 요청할 수 있다.
<transition on="next">
<evaluate expression="searchCriteria.nextPage()" />
<render fragments="searchResultsFragment" />
</transition>
fragments 속성은 다시 보여주길 원하는 뷰 엘리먼트(들)의 id(여러개)가 될 것이다. 여러 엘리먼트를 다시 보여주도록 설정하려면 콤마로 구분하여 적으면 된다.
이렇게 일부를 다시 보여주는 기법은화면의 특정 부위를 업데이트하는 Ajax에 의한 이벤로 사용된다.
4.13. 메시지 다루기
스프링 웹 플로우의 MessageContext는 플로우 실행 도중에 메시지를 기록하기 위한 API다. 일반 텍스트 메시지는 물론이고 스프링 MessageSource를 사용하는 국제화 메시지도 추가할 수 있다. 메시지는 뷰에서 보여지며 자동으로 플로우 실행 리다이렉트에도 살아남을 것이다. 세 종류의 메시지를 제공한다. info, warning, error다. 또한, 편리한 MessageBuilder를 제공하여 메시지 작성을 돕고 있다.
4.13.1. 일반 텍스트 메시지 추가하기
MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate")
.defaultText("Check in date must be a future date").build());
context.addMessage(builder.warn().source("smoking")
.defaultText("Smoking is bad for your health").build());
context.addMessage(builder.info()
.defaultText("We have processed your reservation - thank you and enjoy your stay").build());
4.13.2. 국체화 메시지 추가하기
MessageContext context = ...
MessageBuilder builder = new MessageBuilder();
context.addMessage(builder.error().source("checkinDate").code("checkinDate.notFuture").build());
context.addMessage(builder.warn().source("smoking").code("notHealthy")
.resolvableArg("smoking").build());
context.addMessage(builder.info().code("reservationConfirmation").build());
4.13.3. 메시지 번들 사용하기
국제화 메시지는 스프링 MessageSource로 접근하는 메시지 번들에 정의한다. 플로우-관련 메시지 번들을 만들려면, 간단하게 message.properties 파일을 플로우 디렉터리에 만든다. messages.properties 파일을 만들고 지원하고자 하하는 지역 Locale에 해당하는 .properties 파일을 각각 추가한다.
#messages.properties
checkinDate=Check in date must be a future date
notHealthy={0} is bad for your health
reservationConfirmation=We have processed your reservation - thank you and enjoy your stay
뷰 또는 플로우에서 여러분은 resourceBundle EL 변수를 사용하여 메시지 번들에 접근할 수도 있다.
<h:outputText value="#{resourceBundle.reservationConfirmation}" />
4.13.4. 시스템이 생성한 메시지 이해하기
웹 플로우 자체가 사용자에게 보여줄 메시지를 생성하는 몇몇 부분이 있다. 이런 일이 발생하는 중요한 지점 중 하나는 뷰에서 모델로 데이터 바인딩을 할 때다. 변환 에러 같은 바인딩 에러가 발생하면 웹 플로우는 자동으로 에러를 여러분 리소스번들에서 가져온 메시지로 맵핑한다. 보여줄 메시지를 가져오려고 웹 플로우는 바인딩 에러 코드와 타겟 속성 이름을 담고 있는 리소스 키를 시도한다.
아래 예제는 Booking 객체의 checkinDate 속성에 바인딩을 생각해보고 있다. 사용자가 알파벳 문자열을 입력했다고 하자. 이런 경우 타입 변환 에러가 발생할 것이다. 웹 플로우는 "typeMismatch' 에러 코드를 여러분 리소스 번들 중에 있는 메시지를 찾을 때 다음 키를 사용할 것이다.
booking.checkinDate.typeMismatch
키의 앞 부분은 모델 클래스의 짧은 이름이다. 두 번째 부분은 속성의 이름이다, 세 번째 부분은 에러 코드다. 모델 속성에 바인딩이 실패하면 사용자에게 보여줄 유일한 메시지를 이런식으로 찾는다. 아마 다음과 같은 메시지일 수 있다.
booking.checkinDate.typeMismatch=The check in date must be in the format yyyy-mm-dd.
만약 저 형식에 해당하는 키를 찾지 못하면, 보다 일반적인 키로 시도해본다. 이 키는 간단한 에러 코드 형태다. 속성의 플드 이름은 메시지 인자로 제공된다.
typeMismatch=The {0} field is of the wrong type.
4.14. 팝업 보여주기
popup 속성을 사용하여 모델 팝업 창에서 뷰를 랜더링 한다.
<view-state id="changeSearchCriteria" view="enterSearchCriteria.xhtml" popup="true">
스프링 자바스크립트랑 같이 웹 플로우를 사용하면, 팝업을 보여줄 때 클라이언트 쪽 코드를 사용할 필요가 없다. 웹 플로우는 팝업에서 뷰로 리다이렉트를 요청하는 사용자에게 응답을 보낼 것이고 그 결과 클라이언트는 요청은 잘 처리될 것이다.
4.15. 뷰 백트랙킹(backtracking)
기본적으로, 뷰 스테이트를 종료하고 새로운 뷰 스테이트로 전이하면, 여러분은 브라우저의 뒤로 가기 버튼을 사용하여 이전 상태로 이동할 수 있다. 이러한 뷰 스테이트 히스토리 정책은 history 속성을 사용하여 트랜지션 당 설정할 수 있다.
4.15.1. 히스토리 방지하기
history 속성을 discard로 설정하여 뷰 백트랙킹을 방지할 수 있다.
<transition on="cancel" to="bookingCancelled" history="discard">
4.15.2. 히스토리 금지하기
history 속성을 invalidate로 설정하여 뷰 백트랙킹을 방지하고 이전의 뷰로 전이하는 것도 방지한다.
<transition on="confirm" to="bookingConfirmed" history="invalidate">