1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.uif.freemarker;
17
18 import java.io.IOException;
19 import java.io.Writer;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24
25 import org.apache.commons.lang.StringEscapeUtils;
26 import org.kuali.rice.krad.uif.UifConstants;
27 import org.kuali.rice.krad.uif.component.Component;
28 import org.kuali.rice.krad.uif.component.ComponentBase;
29 import org.kuali.rice.krad.uif.container.CollectionGroup;
30 import org.kuali.rice.krad.uif.container.Group;
31 import org.kuali.rice.krad.uif.layout.LayoutManager;
32 import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
33 import org.kuali.rice.krad.uif.widget.Disclosure;
34 import org.kuali.rice.krad.uif.widget.Pager;
35 import org.kuali.rice.krad.uif.widget.Tooltip;
36 import org.springframework.util.StringUtils;
37
38 import freemarker.core.Environment;
39 import freemarker.core.InlineTemplateUtils;
40 import freemarker.core.Macro;
41 import freemarker.ext.beans.BeansWrapper;
42 import freemarker.template.ObjectWrapper;
43 import freemarker.template.TemplateException;
44 import freemarker.template.TemplateModel;
45 import freemarker.template.TemplateModelException;
46
47
48
49
50
51
52 public class FreeMarkerInlineRenderUtils {
53
54
55
56
57
58
59
60
61
62
63 @SuppressWarnings("unchecked")
64 public static <T> T resolve(Environment env, String name) {
65 TemplateModel tm = resolveModel(env, name);
66 try {
67 return (T) getBeansWrapper(env).unwrap(tm);
68 } catch (TemplateModelException e) {
69 throw new IllegalArgumentException("Failed to unwrap " + name + ", template model " + tm, e);
70 }
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84
85 public static <T> T resolve(Environment env, String name, Class<T> type) {
86 Object rv = resolve(env, name);
87
88 if ((rv instanceof Collection) && !Collection.class.isAssignableFrom(type)) {
89 Collection<?> rc = (Collection<?>) rv;
90 if (rc.isEmpty()) {
91 return null;
92 } else {
93 rv = rc.iterator().next();
94 }
95 }
96
97 if ("".equals(rv) && !String.class.equals(type)) {
98 return null;
99 } else {
100 return type.cast(rv);
101 }
102 }
103
104
105
106
107
108
109
110
111 public static BeansWrapper getBeansWrapper(Environment env) {
112 ObjectWrapper wrapper = env.getObjectWrapper();
113
114 if (!(wrapper instanceof BeansWrapper)) {
115 throw new UnsupportedOperationException("FreeMarker environment uses unsupported ObjectWrapper " + wrapper);
116 }
117
118 return (BeansWrapper) wrapper;
119 }
120
121
122
123
124
125
126
127
128
129
130 public static TemplateModel resolveModel(Environment env, String name) {
131 try {
132 return env.getVariable(name);
133 } catch (TemplateModelException e) {
134 throw new IllegalArgumentException("Failed to resolve " + name + " in current freemarker environment", e);
135 }
136 }
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155 public static void renderTemplate(Environment env, Component component, String body,
156 boolean componentUpdate, boolean includeSrc, Map<String, TemplateModel> tmplParms)
157 throws TemplateException, IOException {
158
159 if (component == null) {
160 return;
161 }
162
163 String s;
164 Writer out = env.getOut();
165 if ((component.isRender() && (!component.isRetrieveViaAjax() || componentUpdate))
166 ||
167 (component.getProgressiveRender() != null && !component.getProgressiveRender().equals("")
168 && !component.isProgressiveRenderViaAJAX() && !component.isProgressiveRenderAndRefresh())) {
169
170 if (StringUtils.hasText(s = component.getPreRenderContent())) {
171 out.write(StringEscapeUtils.escapeHtml(s));
172 }
173
174 if (component.isSelfRendered()) {
175 out.write(component.getRenderedHtmlOutput());
176 } else {
177 if (includeSrc) {
178 env.include(component.getTemplate(), env.getTemplate().getEncoding(), true);
179 }
180
181 Macro fmMacro = (Macro) env.getMainNamespace().get(component.getTemplateName());
182
183 if (fmMacro == null) {
184 throw new TemplateException("No macro found using " + component.getTemplateName(), env);
185 }
186
187 Map<String, Object> args = new java.util.HashMap<String, Object>();
188 args.put(component.getComponentTypeName(), component);
189
190 if (tmplParms != null) {
191 args.putAll(tmplParms);
192 }
193
194 if (StringUtils.hasText(body)) {
195 args.put("body", body);
196 }
197
198 InlineTemplateUtils.invokeMacro(env, fmMacro, args, null);
199 }
200
201 if (StringUtils.hasText(s = component.getEventHandlerScript())) {
202 renderScript(s, component, null, out);
203 }
204
205 if (StringUtils.hasText(s = component.getPostRenderContent())) {
206 out.append(StringEscapeUtils.escapeHtml(s));
207 }
208
209 }
210
211 if (componentUpdate) {
212 return;
213 }
214
215 String methodToCallOnRefresh = ((ComponentBase) component).getMethodToCallOnRefresh();
216 if (!StringUtils.hasText(methodToCallOnRefresh)) {
217 methodToCallOnRefresh = "";
218 }
219
220 if (StringUtils.hasText(s = component.getProgressiveRender())) {
221 if (!component.isRender()
222 && (component.isProgressiveRenderViaAJAX() || component.isProgressiveRenderAndRefresh())) {
223 out.write("<span id=\"");
224 out.write(component.getId());
225 out.write("\" data-role=\"placeholder\" class=\"uif-placeholder\"></span>");
226 }
227
228 for (String cName : component.getProgressiveDisclosureControlNames()) {
229 renderScript(
230 "var condition = function(){return ("
231 + component.getProgressiveDisclosureConditionJs()
232 + ");};setupProgressiveCheck('" + StringEscapeUtils.escapeJavaScript(cName)
233 + "', '" + component.getId() + "', '" + component.getBaseId() + "', condition,"
234 + component.isProgressiveRenderAndRefresh() + ", '"
235 + methodToCallOnRefresh + "');"
236 , component, null, out);
237 }
238
239 renderScript("hiddenInputValidationToggle('" + component.getId() + "');", null, null, out);
240 }
241
242 if ((component.isProgressiveRenderViaAJAX() && !StringUtils.hasLength(component.getProgressiveRender())) ||
243 (!component.isRender() && (component.isDisclosedByAction() || component.isRefreshedByAction())) ||
244 component.isRetrieveViaAjax()) {
245 out.write("<span id=\"");
246 out.write(component.getId());
247 out.write("\" data-role=\"placeholder\" class=\"uif-placeholder\"></span>");
248 }
249
250 if (StringUtils.hasText(component.getConditionalRefresh())) {
251 for (String cName : component.getConditionalRefreshControlNames()) {
252 renderScript(
253 "var condition = function(){return ("
254 + component.getConditionalRefreshConditionJs()
255 + ");};setupRefreshCheck('" + StringEscapeUtils.escapeJavaScript(cName) + "', '"
256 + component.getId() + "', condition,'"
257 + methodToCallOnRefresh + "');", null, null, out);
258 }
259 }
260
261 List<String> refreshWhenChanged = component.getRefreshWhenChangedPropertyNames();
262 if (refreshWhenChanged != null) {
263 for (String cName : refreshWhenChanged) {
264 renderScript(
265 "setupOnChangeRefresh('" + StringEscapeUtils.escapeJavaScript(cName) + "', '"
266 + component.getId()
267 + "','" + methodToCallOnRefresh + "');", null, null, out);
268 }
269 }
270
271 renderTooltip(component, out);
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285 public static void renderTooltip(Component component, Writer out) throws IOException {
286 Tooltip tt = component.getToolTip();
287 if (tt != null && StringUtils.hasText(tt.getTooltipContent())) {
288 String templateOptionsJSString = tt.getTemplateOptionsJSString();
289 renderScript("createTooltip('" + component.getId() + "', '" + tt.getTooltipContent() + "', "
290 + (templateOptionsJSString == null ? "''" : templateOptionsJSString) + ", " + tt.isOnMouseHover()
291 + ", " + tt.isOnFocus() + ");", component, null, out);
292 renderScript("addAttribute('" + component.getId() + "', 'class', 'uif-tooltip', true);", component, null,
293 out);
294 }
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309 public static void renderScript(String script, Component component, String role, Writer out) throws IOException {
310 if (script == null || "".equals(script.trim()))
311 return;
312 out.write("<input name=\"script\" type=\"hidden\" data-role=\"");
313 out.write(role == null ? "script" : role);
314 out.write("\" ");
315
316 if (component != null && component.getId() != null) {
317 out.write("data-for=\"");
318 out.write(component.getId());
319 out.write("\" ");
320 }
321
322 out.write("value=\"");
323 out.write(StringEscapeUtils.escapeHtml(script));
324 out.write("\" />");
325 }
326
327
328
329
330
331
332
333
334
335
336
337
338
339 public static void renderAttrBuild(Component component, Writer out) throws IOException {
340 String s;
341 if (component instanceof ComponentBase) {
342 ComponentBase componentBase = (ComponentBase) component;
343 if (StringUtils.hasText(s = componentBase.getStyleClassesAsString())) {
344 out.write(" class=\"");
345 out.write(s);
346 out.write("\"");
347 }
348 }
349
350 if (StringUtils.hasText(s = component.getStyle())) {
351 out.write(" style=\"");
352 out.write(s);
353 out.write("\"");
354 }
355
356 if (StringUtils.hasText(s = component.getTitle())) {
357 out.write(" title=\"");
358 out.write(s);
359 out.write("\"");
360 }
361 }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382 public static void renderOpenDiv(Component component, Writer out) throws IOException {
383 out.write("<div id=\"");
384 out.write(component.getId());
385 out.write("\"");
386 renderAttrBuild(component, out);
387 out.write(component.getSimpleDataAttributes());
388 out.write(">");
389 }
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410 public static void renderCloseDiv(Writer out) throws IOException {
411 out.write("</div>");
412 }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434 public static void renderOpenGroupWrap(Environment env, Group group) throws IOException, TemplateException {
435 Writer out = env.getOut();
436 renderOpenDiv(group, out);
437 renderTemplate(env, group.getHeader(), null, false, false, null);
438
439 if (group.isRenderLoading()) {
440 out.write("<div id=\"");
441 out.write(group.getId());
442 out.write("_disclosureContent\" data-role=\"placeholder\"> Loading... </div>");
443 } else {
444 Disclosure disclosure = group.getDisclosure();
445 if (disclosure != null && disclosure.isRender()) {
446 out.write("<div id=\"");
447 out.write(group.getId() + UifConstants.IdSuffixes.DISCLOSURE_CONTENT);
448 out.write("\" data-role=\"disclosureContent\" data-open=\"");
449 out.write(Boolean.toString(disclosure.isDefaultOpen()));
450 out.write("\" class=\"uif-disclosureContent\">");
451 }
452 renderTemplate(env, group.getValidationMessages(), null, false, false, null);
453 renderTemplate(env, group.getInstructionalMessage(), null, false, false, null);
454 }
455 }
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477 public static void renderCloseGroupWrap(Environment env, Group group) throws IOException, TemplateException {
478 Writer out = env.getOut();
479
480 boolean renderLoading = group.isRenderLoading();
481 if (!renderLoading) {
482 renderTemplate(env, group.getFooter(), null, false, false, null);
483 }
484
485 Disclosure disclosure = group.getDisclosure();
486 if (disclosure != null && disclosure.isRender()) {
487 if (!renderLoading) {
488 out.write("</div>");
489 }
490 Map<String, TemplateModel> tmplParms = new HashMap<String, TemplateModel>();
491 tmplParms.put("parent", env.getObjectWrapper().wrap(group));
492 renderTemplate(env, disclosure, null, false, false, tmplParms);
493 }
494
495 renderCloseDiv(out);
496 }
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511 public static void renderCollectionGroup(Environment env, CollectionGroup group) throws IOException,
512 TemplateException {
513 renderOpenGroupWrap(env, group);
514
515 Map<String, TemplateModel> tmplParms = new HashMap<String, TemplateModel>();
516 tmplParms.put("componentId", env.getObjectWrapper().wrap(group.getId()));
517 renderTemplate(env, group.getCollectionLookup(), null, false, false, tmplParms);
518
519 if ("TOP".equals(group.getAddLinePlacement())) {
520 if (group.isRenderAddBlankLineButton()) {
521 renderTemplate(env, group.getAddBlankLineAction(), null, false, false, null);
522 }
523
524 if (group.isAddViaLightBox()) {
525 renderTemplate(env, group.getAddViaLightBoxAction(), null, false, false, null);
526 }
527 }
528
529 LayoutManager layoutManager = group.getLayoutManager();
530 String managerTemplateName = layoutManager.getTemplateName();
531 List<? extends Component> items = group.getItems();
532
533 if ("uif_stacked".equals(managerTemplateName)) {
534 renderStacked(env, items, (StackedLayoutManager) layoutManager, group);
535 } else {
536 Macro fmMacro = (Macro) env.getMainNamespace().get(layoutManager.getTemplateName());
537
538 if (fmMacro == null) {
539 throw new TemplateException("No macro found using " + layoutManager.getTemplateName(), env);
540 }
541
542 Map<String, Object> args = new java.util.HashMap<String, Object>();
543 args.put("items", items);
544 args.put("manager", group.getLayoutManager());
545 args.put("container", group);
546 InlineTemplateUtils.invokeMacro(env, fmMacro, args, null);
547 }
548
549 if ("BOTTOM".equals(group.getAddLinePlacement())) {
550 if (group.isRenderAddBlankLineButton()) {
551 renderTemplate(env, group.getAddBlankLineAction(), null, false, false, null);
552 }
553
554 if (group.isAddViaLightBox()) {
555 renderTemplate(env, group.getAddViaLightBoxAction(), null, false, false, null);
556 }
557 }
558
559 renderCloseGroupWrap(env, group);
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575 public static void renderStacked(Environment env, List<? extends Component> items, StackedLayoutManager manager,
576 CollectionGroup container) throws IOException, TemplateException {
577 String s;
578 Writer out = env.getOut();
579
580 Pager pager = manager.getPagerWidget();
581 Map<String, TemplateModel> pagerTmplParms = null;
582 if (pager != null && container.isUseServerPaging()) {
583 pagerTmplParms = new HashMap<String, TemplateModel>();
584 pagerTmplParms.put("parent", env.getObjectWrapper().wrap(container));
585 renderTemplate(env, pager, null, false, false, pagerTmplParms);
586 }
587
588 out.write("<div id=\"");
589 out.write(manager.getId());
590 out.write("\"");
591
592 if (StringUtils.hasText(s = manager.getStyle())) {
593 out.write(" style=\"");
594 out.write(s);
595 out.write("\"");
596 }
597
598 if (StringUtils.hasText(s = manager.getStyleClassesAsString())) {
599 out.write(" class=\"");
600 out.write(s);
601 out.write("\"");
602 }
603
604 out.write(">");
605
606 Group wrapperGroup = manager.getWrapperGroup();
607 if (wrapperGroup != null) {
608 renderTemplate(env, wrapperGroup, null, false, false, null);
609 } else {
610 for (Group item : manager.getStackedGroups()) {
611 renderTemplate(env, item, null, false, false, null);
612 }
613 }
614
615 out.write("</div>");
616
617 if (pager != null && container.isUseServerPaging()) {
618 pagerTmplParms = new HashMap<String, TemplateModel>();
619 pagerTmplParms.put("parent", env.getObjectWrapper().wrap(container));
620 renderTemplate(env, pager, null, false, false, pagerTmplParms);
621 }
622 }
623
624 }