emptyList();
34 | }
35 | Assert.isTrue((index >= 0) && (index <= this.messages.size()), "Invalid message index");
36 | return this.messages.subList(index, this.messages.size());
37 | }
38 |
39 | public void addMessage(String message) {
40 | this.messages.add(message);
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/templates/chat.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Chat
5 |
6 |
7 |
8 | Chat
9 |
10 |
18 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/main/webapp/resources/js/chat.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 |
3 | function ChatViewModel() {
4 |
5 | var that = this;
6 |
7 | that.userName = ko.observable('');
8 | that.chatContent = ko.observable('');
9 | that.message = ko.observable('');
10 | that.messageIndex = ko.observable(0);
11 | that.activePollingXhr = ko.observable(null);
12 |
13 | var keepPolling = false;
14 |
15 | that.joinChat = function() {
16 | if (that.userName().trim() != '') {
17 | keepPolling = true;
18 | pollForMessages();
19 | }
20 | }
21 |
22 | function pollForMessages() {
23 | if (!keepPolling) {
24 | return;
25 | }
26 | var form = $("#joinChatForm");
27 | that.activePollingXhr($.ajax({url : form.attr("action"), type : "GET", data : form.serialize(),cache: false,
28 | success : function(messages) {
29 | for ( var i = 0; i < messages.length; i++) {
30 | that.chatContent(that.chatContent() + messages[i] + "\n");
31 | that.messageIndex(that.messageIndex() + 1);
32 | }
33 | },
34 | error : function(xhr) {
35 | if (xhr.statusText != "abort" && xhr.status != 503) {
36 | resetUI();
37 | console.error("Unable to retrieve chat messages. Chat ended.");
38 | }
39 | },
40 | complete : pollForMessages
41 | }));
42 | $('#message').focus();
43 | }
44 |
45 | that.postMessage = function() {
46 | if (that.message().trim() != '') {
47 | var form = $("#postMessageForm");
48 | $.ajax({url : form.attr("action"), type : "POST",
49 | data : "message=[" + that.userName() + "] " + $("#postMessageForm input[name=message]").val(),
50 | error : function(xhr) {
51 | console.error("Error posting chat message: status=" + xhr.status + ", statusText=" + xhr.statusText);
52 | }
53 | });
54 | that.message('');
55 | }
56 | }
57 |
58 | that.leaveChat = function() {
59 | that.activePollingXhr(null);
60 | resetUI();
61 | this.userName('');
62 | }
63 |
64 | function resetUI() {
65 | keepPolling = false;
66 | that.activePollingXhr(null);
67 | that.message('');
68 | that.messageIndex(0);
69 | that.chatContent('');
70 | }
71 |
72 | }
73 |
74 | //Activate knockout.js
75 | ko.applyBindings(new ChatViewModel());
76 |
77 | });
78 |
79 |
80 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/samples/async/chat/ChatController.java:
--------------------------------------------------------------------------------
1 | package org.springframework.samples.async.chat;
2 |
3 | import java.util.Collections;
4 | import java.util.List;
5 | import java.util.Map;
6 | import java.util.Map.Entry;
7 | import java.util.concurrent.ConcurrentHashMap;
8 |
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.web.bind.annotation.GetMapping;
11 | import org.springframework.web.bind.annotation.PostMapping;
12 | import org.springframework.web.bind.annotation.RequestMapping;
13 | import org.springframework.web.bind.annotation.RequestParam;
14 | import org.springframework.web.bind.annotation.RestController;
15 | import org.springframework.web.context.request.async.DeferredResult;
16 |
17 | @RestController
18 | @RequestMapping("/mvc/chat")
19 | public class ChatController {
20 |
21 | private final ChatRepository chatRepository;
22 |
23 | private final Map>, Integer> chatRequests =
24 | new ConcurrentHashMap>, Integer>();
25 |
26 |
27 | @Autowired
28 | public ChatController(ChatRepository chatRepository) {
29 | this.chatRepository = chatRepository;
30 | }
31 |
32 | @GetMapping
33 | public DeferredResult> getMessages(@RequestParam int messageIndex) {
34 |
35 | final DeferredResult> deferredResult = new DeferredResult>(null, Collections.emptyList());
36 | this.chatRequests.put(deferredResult, messageIndex);
37 |
38 | deferredResult.onCompletion(new Runnable() {
39 | @Override
40 | public void run() {
41 | chatRequests.remove(deferredResult);
42 | }
43 | });
44 |
45 | List messages = this.chatRepository.getMessages(messageIndex);
46 | if (!messages.isEmpty()) {
47 | deferredResult.setResult(messages);
48 | }
49 |
50 | return deferredResult;
51 | }
52 |
53 | @PostMapping
54 | public void postMessage(@RequestParam String message) {
55 |
56 | this.chatRepository.addMessage(message);
57 |
58 | // Update all chat requests as part of the POST request
59 | // See Redis branch for a more sophisticated, non-blocking approach
60 |
61 | for (Entry>, Integer> entry : this.chatRequests.entrySet()) {
62 | List messages = this.chatRepository.getMessages(entry.getValue());
63 | entry.getKey().setResult(messages);
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/java/org/springframework/samples/async/chat/ChatControllerTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2002-2012 the original author or authors.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.springframework.samples.async.chat;
17 |
18 | import static org.easymock.EasyMock.expect;
19 | import static org.easymock.EasyMock.replay;
20 | import static org.easymock.EasyMock.verify;
21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
24 | import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
25 |
26 | import java.util.Arrays;
27 | import java.util.List;
28 |
29 | import org.easymock.EasyMock;
30 | import org.junit.Before;
31 | import org.junit.Test;
32 | import org.springframework.test.web.servlet.MockMvc;
33 |
34 | public class ChatControllerTests {
35 |
36 | private MockMvc mockMvc;
37 |
38 | private ChatRepository chatRepository;
39 |
40 | @Before
41 | public void setup() {
42 | this.chatRepository = EasyMock.createMock(ChatRepository.class);
43 | this.mockMvc = standaloneSetup(new ChatController(this.chatRepository)).build();
44 | }
45 |
46 | @Test
47 | public void getMessages() throws Exception {
48 | List messages = Arrays.asList("a", "b", "c");
49 | expect(this.chatRepository.getMessages(9)).andReturn(messages);
50 | replay(this.chatRepository);
51 |
52 | this.mockMvc.perform(get("/mvc/chat").param("messageIndex", "9"))
53 | .andExpect(status().isOk())
54 | .andExpect(request().asyncStarted())
55 | .andExpect(request().asyncResult(messages));
56 |
57 | verify(this.chatRepository);
58 | }
59 |
60 | @Test
61 | public void getMessagesStartAsync() throws Exception {
62 | expect(this.chatRepository.getMessages(9)).andReturn(Arrays.asList());
63 | replay(this.chatRepository);
64 |
65 | this.mockMvc.perform(get("/mvc/chat").param("messageIndex", "9"))
66 | .andExpect(request().asyncStarted());
67 |
68 | verify(this.chatRepository);
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/org/springframework/samples/async/config/WebMvcConfig.java:
--------------------------------------------------------------------------------
1 | package org.springframework.samples.async.config;
2 |
3 | import java.util.List;
4 |
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.ComponentScan;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.http.converter.HttpMessageConverter;
9 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
10 | import org.springframework.web.servlet.ViewResolver;
11 | import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
12 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
13 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
14 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
15 | import org.thymeleaf.spring5.SpringTemplateEngine;
16 | import org.thymeleaf.spring5.view.ThymeleafViewResolver;
17 | import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
18 | import org.thymeleaf.templateresolver.ITemplateResolver;
19 |
20 | @Configuration
21 | @ComponentScan(basePackages = { "org.springframework.samples.async" })
22 | public class WebMvcConfig extends WebMvcConfigurationSupport {
23 |
24 | @Override
25 | public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
26 | configurer.setDefaultTimeout(30*1000L);
27 | }
28 |
29 | @Override
30 | protected void configureMessageConverters(List> converters) {
31 | converters.add(new MappingJackson2HttpMessageConverter());
32 | }
33 |
34 | public void addViewControllers(ViewControllerRegistry registry) {
35 | registry.addViewController("/").setViewName("chat");
36 | }
37 |
38 | @Override
39 | public void addResourceHandlers(ResourceHandlerRegistry registry) {
40 | registry.addResourceHandler("/resources/**").addResourceLocations("resources/");
41 | }
42 |
43 | @Bean
44 | public ViewResolver viewResolver() {
45 | ThymeleafViewResolver resolver = new ThymeleafViewResolver();
46 | resolver.setTemplateEngine(templateEngine());
47 | return resolver;
48 | }
49 |
50 | @Bean
51 | public SpringTemplateEngine templateEngine() {
52 | SpringTemplateEngine engine = new SpringTemplateEngine();
53 | engine.setTemplateResolver(templateResolver());
54 | return engine;
55 | }
56 |
57 | @Bean
58 | public ITemplateResolver templateResolver() {
59 | ServletContextTemplateResolver resolver = new ServletContextTemplateResolver(getServletContext());
60 | resolver.setPrefix("/WEB-INF/templates/");
61 | resolver.setSuffix(".html");
62 | resolver.setTemplateMode("HTML5");
63 | resolver.setCacheable(false);
64 | return resolver;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | org.springframework.samples
5 | spring-mvc-chat
6 | war
7 | 1.0.0-SNAPSHOT
8 | Chat sample using the Spring MVC Servlet-based async support
9 |
10 |
11 | 5.3.5
12 |
13 |
14 |
15 |
16 | org.springframework
17 | spring-context
18 | ${org.springframework-version}
19 |
20 |
21 | org.springframework
22 | spring-web
23 | ${org.springframework-version}
24 |
25 |
26 | org.springframework
27 | spring-webmvc
28 | ${org.springframework-version}
29 |
30 |
31 | org.springframework
32 | spring-context-support
33 | ${org.springframework-version}
34 |
35 |
36 | org.springframework
37 | spring-tx
38 | ${org.springframework-version}
39 |
40 |
41 | javax.servlet
42 | javax.servlet-api
43 | 4.0.1
44 | provided
45 |
46 |
47 | javax.servlet.jsp
48 | jsp-api
49 | 2.2
50 | provided
51 |
52 |
53 | javax.servlet
54 | jstl
55 | 1.2
56 |
57 |
58 | org.thymeleaf
59 | thymeleaf-spring5
60 | 3.0.12.RELEASE
61 |
62 |
63 | org.slf4j
64 | slf4j-api
65 |
66 |
67 |
68 |
69 | com.fasterxml.jackson.core
70 | jackson-databind
71 | 2.12.0
72 |
73 |
74 | ch.qos.logback
75 | logback-classic
76 | 1.2.3
77 | runtime
78 |
79 |
80 | org.slf4j
81 | jcl-over-slf4j
82 | 1.7.30
83 | runtime
84 |
85 |
86 | org.springframework
87 | spring-test
88 | ${org.springframework-version}
89 |
90 |
91 | junit
92 | junit
93 | 4.13.2
94 | test
95 |
96 |
97 | org.easymock
98 | easymock
99 | 3.6
100 | test
101 |
102 |
103 | org.hamcrest
104 | hamcrest-library
105 | 2.1
106 | test
107 |
108 |
109 |
110 |
111 |
112 | SpringSource repository
113 | https://repo.springsource.org/milestone
114 |
115 | false
116 |
117 |
118 | true
119 |
120 |
121 |
122 | SpringSource snapshot repository
123 | https://repo.springsource.org/snapshot
124 | true
125 | false
126 |
127 |
128 |
129 |
130 |
131 | apache.snapshots
132 | https://repository.apache.org/content/groups/snapshots-group/
133 |
134 |
135 |
136 |
137 | ${project.artifactId}
138 |
139 |
140 | org.apache.maven.plugins
141 | maven-compiler-plugin
142 |
143 |
144 | org.apache.maven.plugins
145 | maven-war-plugin
146 |
147 | false
148 |
149 |
150 |
151 | org.apache.maven.plugins
152 | maven-resources-plugin
153 |
154 | UTF-8
155 |
156 |
157 |
158 | org.apache.maven.plugins
159 | maven-eclipse-plugin
160 |
161 | true
162 | false
163 | 2.0
164 |
165 |
166 |
167 | org.eclipse.jetty
168 | jetty-maven-plugin
169 | 9.4.35.v20201120
170 |
171 |
172 | /${project.artifactId}
173 |
174 |
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/src/main/webapp/resources/js/knockout-2.0.0.js:
--------------------------------------------------------------------------------
1 | // Knockout JavaScript library v2.0.0
2 | // (c) Steven Sanderson - http://knockoutjs.com/
3 | // License: MIT (http://www.opensource.org/licenses/mit-license.php)
4 |
5 | (function(window,undefined){
6 | function c(a){throw a;}var l=void 0,m=!0,o=null,p=!1,r=window.ko={};r.b=function(a,b){for(var d=a.split("."),e=window,f=0;f",b[0];);return 4r.a.k(e,a[b])&&e.push(a[b]);return e},ba:function(a,e){for(var a=a||[],b=[],f=0,d=a.length;fa.length?p:a.substring(0,e.length)===e},hb:function(a){for(var e=Array.prototype.slice.call(arguments,1),b="return ("+a+")",f=0;f",""]||!d.indexOf("
",""]||(!d.indexOf(" | ","
"]||[0,"",""];a="ignored"+
25 | d[1]+a+d[2]+"
";for("function"==typeof window.innerShiv?b.appendChild(window.innerShiv(a)):b.innerHTML=a;d[0]--;)b=b.lastChild;b=r.a.X(b.lastChild.childNodes)}return b};r.a.Z=function(a,b){r.a.U(a);if(b!==o&&b!==l)if("string"!=typeof b&&(b=b.toString()),"undefined"!=typeof jQuery)jQuery(a).html(b);else for(var d=r.a.ma(b),e=0;e"},Ra:function(a,b){var h=d[a];h===l&&c(Error("Couldn't find any memo with ID "+
27 | a+". Perhaps it's already been unmemoized."));try{return h.apply(o,b||[]),m}finally{delete d[a]}},Sa:function(a,f){var d=[];b(a,d);for(var g=0,i=d.length;gb;b++)a=a();return a})};r.toJSON=function(a){a=r.Pa(a);return r.a.qa(a)}})();r.b("ko.toJS",r.Pa);r.b("ko.toJSON",r.toJSON);
43 | r.h={q:function(a){return"OPTION"==a.tagName?a.__ko__hasDomDataOptionValue__===m?r.a.e.get(a,r.c.options.la):a.getAttribute("value"):"SELECT"==a.tagName?0<=a.selectedIndex?r.h.q(a.options[a.selectedIndex]):l:a.value},S:function(a,b){if("OPTION"==a.tagName)switch(typeof b){case "string":r.a.e.set(a,r.c.options.la,l);"__ko__hasDomDataOptionValue__"in a&&delete a.__ko__hasDomDataOptionValue__;a.value=b;break;default:r.a.e.set(a,r.c.options.la,b),a.__ko__hasDomDataOptionValue__=m,a.value="number"===typeof b?
44 | b:""}else if("SELECT"==a.tagName)for(var d=a.options.length-1;0<=d;d--){if(r.h.q(a.options[d])==b){a.selectedIndex=d;break}}else{if(b===o||b===l)b="";a.value=b}}};r.b("ko.selectExtensions",r.h);r.b("ko.selectExtensions.readValue",r.h.q);r.b("ko.selectExtensions.writeValue",r.h.S);
45 | r.j=function(){function a(a,e){for(var d=o;a!=d;)d=a,a=a.replace(b,function(a,b){return e[b]});return a}var b=/\@ko_token_(\d+)\@/g,d=/^[\_$a-z][\_$a-z0-9]*(\[.*?\])*(\.[\_$a-z][\_$a-z0-9]*(\[.*?\])*)*$/i,e=["true","false"];return{D:[],Y:function(b){var e=r.a.z(b);if(3>e.length)return[];"{"===e.charAt(0)&&(e=e.substring(1,e.length-1));for(var b=[],d=o,i,j=0;j$/:
50 | /^\s*ko\s+(.*\:.*)\s*$/,g=f?/^<\!--\s*\/ko\s*--\>$/:/^\s*\/ko\s*$/,i={ul:m,ol:m};r.f={C:{},childNodes:function(b){return a(b)?d(b):b.childNodes},ha:function(b){if(a(b))for(var b=r.f.childNodes(b),e=0,d=b.length;e"),p)}};r.c.uniqueName.Za=0;
70 | r.c.checked={init:function(a,b,d){r.a.s(a,"click",function(){var e;if("checkbox"==a.type)e=a.checked;else if("radio"==a.type&&a.checked)e=a.value;else return;var f=b();"checkbox"==a.type&&r.a.d(f)instanceof Array?(e=r.a.k(r.a.d(f),a.value),a.checked&&0>e?f.push(a.value):!a.checked&&0<=e&&f.splice(e,1)):r.P(f)?f()!==e&&f(e):(f=d(),f._ko_property_writers&&f._ko_property_writers.checked&&f._ko_property_writers.checked(e))});"radio"==a.type&&!a.name&&r.c.uniqueName.init(a,function(){return m})},update:function(a,
71 | b){var d=r.a.d(b());if("checkbox"==a.type)a.checked=d instanceof Array?0<=r.a.k(d,a.value):d;else if("radio"==a.type)a.checked=a.value==d}};r.c.attr={update:function(a,b){var d=r.a.d(b())||{},e;for(e in d)if("string"==typeof e){var f=r.a.d(d[e]);f===p||f===o||f===l?a.removeAttribute(e):a.setAttribute(e,f.toString())}}};
72 | r.c.hasfocus={init:function(a,b,d){function e(a){var e=b();a!=r.a.d(e)&&(r.P(e)?e(a):(e=d(),e._ko_property_writers&&e._ko_property_writers.hasfocus&&e._ko_property_writers.hasfocus(a)))}r.a.s(a,"focus",function(){e(m)});r.a.s(a,"focusin",function(){e(m)});r.a.s(a,"blur",function(){e(p)});r.a.s(a,"focusout",function(){e(p)})},update:function(a,b){var d=r.a.d(b());d?a.focus():a.blur();r.a.sa(a,d?"focusin":"focusout")}};
73 | r.c["with"]={o:function(a){return function(){var b=a();return{"if":b,data:b,templateEngine:r.p.M}}},init:function(a,b){return r.c.template.init(a,r.c["with"].o(b))},update:function(a,b,d,e,f){return r.c.template.update(a,r.c["with"].o(b),d,e,f)}};r.j.D["with"]=p;r.f.C["with"]=m;r.c["if"]={o:function(a){return function(){return{"if":a(),templateEngine:r.p.M}}},init:function(a,b){return r.c.template.init(a,r.c["if"].o(b))},update:function(a,b,d,e,f){return r.c.template.update(a,r.c["if"].o(b),d,e,f)}};
74 | r.j.D["if"]=p;r.f.C["if"]=m;r.c.ifnot={o:function(a){return function(){return{ifnot:a(),templateEngine:r.p.M}}},init:function(a,b){return r.c.template.init(a,r.c.ifnot.o(b))},update:function(a,b,d,e,f){return r.c.template.update(a,r.c.ifnot.o(b),d,e,f)}};r.j.D.ifnot=p;r.f.C.ifnot=m;
75 | r.c.foreach={o:function(a){return function(){var b=r.a.d(a());return!b||"number"==typeof b.length?{foreach:b,templateEngine:r.p.M}:{foreach:b.data,includeDestroyed:b.includeDestroyed,afterAdd:b.afterAdd,beforeRemove:b.beforeRemove,afterRender:b.afterRender,templateEngine:r.p.M}}},init:function(a,b){return r.c.template.init(a,r.c.foreach.o(b))},update:function(a,b,d,e,f){return r.c.template.update(a,r.c.foreach.o(b),d,e,f)}};r.j.D.foreach=p;r.f.C.foreach=m;r.b("ko.allowedVirtualElementBindings",r.f.C);
76 | r.t=function(){};r.t.prototype.renderTemplateSource=function(){c("Override renderTemplateSource")};r.t.prototype.createJavaScriptEvaluatorBlock=function(){c("Override createJavaScriptEvaluatorBlock")};r.t.prototype.makeTemplateSource=function(a){if("string"==typeof a){var b=document.getElementById(a);b||c(Error("Cannot find template with ID "+a));return new r.m.g(b)}if(1==a.nodeType||8==a.nodeType)return new r.m.I(a);c(Error("Unknown template type: "+a))};
77 | r.t.prototype.renderTemplate=function(a,b,d){return this.renderTemplateSource(this.makeTemplateSource(a),b,d)};r.t.prototype.isTemplateRewritten=function(a){return this.allowTemplateRewriting===p?m:this.W&&this.W[a]?m:this.makeTemplateSource(a).data("isRewritten")};r.t.prototype.rewriteTemplate=function(a,b){var d=this.makeTemplateSource(a),e=b(d.text());d.text(e);d.data("isRewritten",m);if("string"==typeof a)this.W=this.W||{},this.W[a]=m};r.b("ko.templateEngine",r.t);
78 | r.$=function(){function a(a,b,d){for(var a=r.j.Y(a),g=r.j.D,i=0;i/g;return{gb:function(a,b){b.isTemplateRewritten(a)||b.rewriteTemplate(a,function(a){return r.$.ub(a,b)})},ub:function(e,f){return e.replace(b,function(b,e,d,j,k,n,t){return a(t,e,f)}).replace(d,function(b,e){return a(e,"<\!-- ko --\>",f)})},Ua:function(a){return r.r.ka(function(b,d){b.nextSibling&&r.xa(b.nextSibling,a,d)})}}}();r.b("ko.templateRewriting",r.$);r.b("ko.templateRewriting.applyMemoizedBindingsToNextSibling",r.$.Ua);r.m={};r.m.g=function(a){this.g=a};
80 | r.m.g.prototype.text=function(){if(0==arguments.length)return"script"==this.g.tagName.toLowerCase()?this.g.text:this.g.innerHTML;var a=arguments[0];"script"==this.g.tagName.toLowerCase()?this.g.text=a:r.a.Z(this.g,a)};r.m.g.prototype.data=function(a){if(1===arguments.length)return r.a.e.get(this.g,"templateSourceData_"+a);r.a.e.set(this.g,"templateSourceData_"+a,arguments[1])};r.m.I=function(a){this.g=a};r.m.I.prototype=new r.m.g;
81 | r.m.I.prototype.text=function(){if(0==arguments.length)return r.a.e.get(this.g,"__ko_anon_template__");r.a.e.set(this.g,"__ko_anon_template__",arguments[0])};r.b("ko.templateSources",r.m);r.b("ko.templateSources.domElement",r.m.g);r.b("ko.templateSources.anonymousTemplate",r.m.I);
82 | (function(){function a(a,b,d){for(var g=0;node=a[g];g++)node.parentNode===b&&(1===node.nodeType||8===node.nodeType)&&d(node)}function b(a,b,h,g,i){var i=i||{},j=i.templateEngine||d;r.$.gb(h,j);h=j.renderTemplate(h,g,i);("number"!=typeof h.length||0a&&c(Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later."));var h=d.data("precompiled");h||(h=d.text()||"",h=jQuery.template(o,"{{ko_with $item.koBindingContext}}"+h+"{{/ko_with}}"),d.data("precompiled",h));
95 | d=[e.$data];e=jQuery.extend({koBindingContext:e},f.templateOptions);e=jQuery.tmpl(h,d,e);e.appendTo(document.createElement("div"));jQuery.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){document.write("