1 33 48 package com.icesoft.faces.component.selectinputdate; 49 50 import com.icesoft.faces.component.CSS_DEFAULT; 51 import com.icesoft.faces.component.ext.HtmlCommandLink; 52 import com.icesoft.faces.component.ext.HtmlGraphicImage; 53 import com.icesoft.faces.component.ext.HtmlOutputText; 54 import com.icesoft.faces.component.ext.renderkit.FormRenderer; 55 import com.icesoft.faces.component.util.CustomComponentUtils; 56 import com.icesoft.faces.context.DOMContext; 57 import com.icesoft.faces.renderkit.dom_html_basic.DomBasicInputRenderer; 58 import com.icesoft.faces.renderkit.dom_html_basic.HTML; 59 import com.icesoft.faces.renderkit.dom_html_basic.PassThruAttributeRenderer; 60 import com.icesoft.faces.util.CoreUtils; 61 import org.apache.commons.logging.Log; 62 import org.apache.commons.logging.LogFactory; 63 import org.w3c.dom.Element ; 64 import org.w3c.dom.NodeList ; 65 import org.w3c.dom.Text ; 66 67 import javax.faces.component.NamingContainer; 68 import javax.faces.component.UIComponent; 69 import javax.faces.component.UIInput; 70 import javax.faces.component.UIParameter; 71 import javax.faces.component.UIViewRoot; 72 import javax.faces.context.FacesContext; 73 import javax.faces.context.ResponseWriter; 74 import javax.faces.convert.Converter; 75 import javax.faces.convert.ConverterException; 76 import java.io.IOException ; 77 import java.text.DateFormat ; 78 import java.text.DateFormatSymbols ; 79 import java.text.ParseException ; 80 import java.text.SimpleDateFormat ; 81 import java.util.Calendar ; 82 import java.util.Date ; 83 import java.util.Locale ; 84 import java.util.Map ; 85 import java.util.regex.Matcher ; 86 import java.util.regex.Pattern ; 87 88 93 public class SelectInputDateRenderer 94 extends DomBasicInputRenderer { 95 private static final Log log = 97 LogFactory.getLog(SelectInputDateRenderer.class); 98 99 private final String CALENDAR_TABLE = "_calendarTable"; 100 private final String CALENDAR_INPUTTEXT = "_calendarInputtext"; 101 private final String CALENDAR_BUTTON = "_calendarButton"; 102 private final String CALENDAR_POPUP = "_calendarPopup"; 103 private final String HIDDEN_FIELD_NAME = "showPopup"; 104 private final String DATE_SELECTED = "dateSelected"; 105 106 private final String PREV_MONTH = "_prevmo"; 108 private final String NEXT_MONTH = "_nextmo"; 109 private final String PREV_YEAR = "_prevyr"; 110 private final String NEXT_YEAR = "_nextyr"; 111 112 private final String CALENDAR = "_calendar"; 114 115 String [] weekdays = new String [7]; 117 String [] weekdaysLong = new String [7]; 118 String [] months = new String [12]; 119 120 Locale currentLocale = null; 122 123 124 127 public boolean getRendersChildren() { 128 129 return true; 130 } 131 132 135 public void encodeChildren(FacesContext facesContext, 136 UIComponent uiComponent) { 137 138 } 139 140 private String getHiddenFieldName(FacesContext facesContext, 141 UIComponent uiComponent) { 142 UIComponent form = findForm(uiComponent); 143 String formId = form.getClientId(facesContext); 144 String clientId = uiComponent.getClientId(facesContext); 145 String hiddenFieldName = formId 146 + NamingContainer.SEPARATOR_CHAR 147 + UIViewRoot.UNIQUE_ID_PREFIX 148 + clientId 149 + HIDDEN_FIELD_NAME; 150 return hiddenFieldName; 151 } 152 153 156 public void encodeEnd(FacesContext facesContext, UIComponent uiComponent) 157 throws IOException { 158 validateParameters(facesContext, uiComponent, SelectInputDate.class); 159 DOMContext domContext = 160 DOMContext.attachDOMContext(facesContext, uiComponent); 161 SelectInputDate selectInputDate = (SelectInputDate) uiComponent; 162 163 UIComponent parentForm = findForm(selectInputDate); 165 if (parentForm == null) { 167 log.error("SelectInputDate::must be in a FORM"); 168 return; 169 } 170 String clientId; 171 if (!domContext.isInitialized()) { 172 Element root = domContext.createRootElement(HTML.DIV_ELEM); 173 174 setRootElementId(facesContext, root, uiComponent); 175 clientId = uiComponent.getClientId(facesContext); 176 if (selectInputDate.isRenderAsPopup()) { 177 if (log.isTraceEnabled()) { 178 log.trace("Render as popup"); 179 } 180 Element dateText = domContext.createElement(HTML.INPUT_ELEM); 181 dateText.setAttribute(HTML.VALUE_ATTR, 182 selectInputDate.formatDate( 183 (Date ) selectInputDate 184 .getValue())); 185 dateText.setAttribute(HTML.ID_ATTR, 186 clientId + CALENDAR_INPUTTEXT); 187 dateText.setAttribute(HTML.NAME_ATTR, 188 clientId + CALENDAR_INPUTTEXT); 189 dateText.setAttribute(HTML.CLASS_ATTR, 190 selectInputDate.getCalendarInputClass()); 191 dateText.setAttribute(HTML.ONFOCUS_ATTR, "setFocus('');"); 192 dateText.setAttribute("onkeypress", this.ICESUBMIT); 193 dateText.setAttribute(HTML.ONBLUR_ATTR, this.ICESUBMITPARTIAL); 194 if (selectInputDate.getAutocomplete() != null) { 195 dateText.setAttribute("autocomplete", 196 selectInputDate.getAutocomplete()); 197 } 198 String tooltip = selectInputDate.getPopupDateFormat(); 200 dateText.setAttribute(HTML.TITLE_ATTR, 201 "Date Format: " + tooltip); 202 root.appendChild(dateText); 203 Element calendarButton = 204 domContext.createElement(HTML.INPUT_ELEM); 205 calendarButton 206 .setAttribute(HTML.ID_ATTR, clientId + CALENDAR_BUTTON); 207 calendarButton.setAttribute(HTML.NAME_ATTR, 208 clientId + CALENDAR_BUTTON); 209 calendarButton.setAttribute(HTML.TYPE_ATTR, "image"); 210 String resolvedSrc = 211 CoreUtils.resolveResourceURL( facesContext, 212 selectInputDate.getImageDir() + 213 selectInputDate.getOpenPopupImage() ); 214 calendarButton.setAttribute(HTML.SRC_ATTR, resolvedSrc ); 215 calendarButton.setAttribute(HTML.ALT_ATTR, "Open Popup Calendar"); 216 calendarButton.setAttribute(HTML.TITLE_ATTR , "Open Popup Calendar"); 217 calendarButton.setAttribute(HTML.ONFOCUS_ATTR, "setFocus('');"); 218 String onClick = "document.forms['" + 220 parentForm.getClientId(facesContext) + "']['" + 221 this.getLinkId(facesContext, uiComponent) + 222 "'].value='" + clientId + CALENDAR_BUTTON + 223 "';" 224 + "document.forms['" + 225 parentForm.getClientId(facesContext) + "']['" + 226 getHiddenFieldName(facesContext, uiComponent) + 227 "'].value='toggle';" 228 + "iceSubmitPartial( document.forms['" + 229 parentForm.getClientId(facesContext) + 230 "'], this,event); return false;"; 231 calendarButton.setAttribute(HTML.ONCLICK_ATTR, onClick); 232 root.appendChild(calendarButton); 233 if (!domContext.isStreamWriting()) { 234 Text br = domContext.createTextNode("<br/>"); 235 root.appendChild(br); 236 } 237 Element calendarDiv = domContext.createElement(HTML.DIV_ELEM); 238 calendarDiv 239 .setAttribute(HTML.ID_ATTR, clientId + CALENDAR_POPUP); 240 calendarDiv.setAttribute(HTML.NAME_ATTR, 241 clientId + CALENDAR_POPUP); 242 calendarDiv.setAttribute(HTML.STYLE_ELEM, 243 "display:none;position:absolute;overflow:hidden;z-index:10;"); 244 calendarDiv.setAttribute(HTML.TITLE_ATTR, "A Popup Calendar where a date can be selected."); 245 Element table = domContext.createElement(HTML.TABLE_ELEM); 246 table.setAttribute(HTML.ID_ATTR, clientId + CALENDAR_TABLE); 247 table.setAttribute(HTML.NAME_ATTR, clientId + CALENDAR_TABLE); 248 table.setAttribute(HTML.CLASS_ATTR, 249 selectInputDate.getStyleClass()); 250 table.setAttribute(HTML.STYLE_ATTR, "position:absolute;"); 251 table.setAttribute(HTML.CELLPADDING_ATTR, "0"); 252 table.setAttribute(HTML.CELLSPACING_ATTR, "0"); 253 String mouseOver = selectInputDate.getOnmouseover(); 255 table.setAttribute(HTML.ONMOUSEOVER_ATTR, mouseOver); 256 String mouseOut = selectInputDate.getOnmouseout(); 257 table.setAttribute(HTML.ONMOUSEOUT_ATTR, mouseOut); 258 String mouseMove = selectInputDate.getOnmousemove(); 259 table.setAttribute(HTML.ONMOUSEMOVE_ATTR, mouseMove); 260 table.setAttribute(HTML.SUMMARY_ATTR,"This table contains a Calendar where a date can be selected."); 261 calendarDiv.appendChild(table); 262 Text iframe = domContext.createTextNode("<!--[if lte IE"+ 263 " 6.5]><iframe class=\"iceSelInpDateIFrameFix\"></iframe><![endif]-->"); 264 calendarDiv.appendChild(iframe); 265 root.appendChild(calendarDiv); 266 FormRenderer.addHiddenField(facesContext, getHiddenFieldName( 268 facesContext, uiComponent)); 269 } else { 270 if (log.isTraceEnabled()) { 271 log.trace("Select input Date Normal"); 272 } 273 Element table = domContext.createElement(HTML.TABLE_ELEM); 274 table.setAttribute(HTML.ID_ATTR, clientId + CALENDAR_TABLE); 275 table.setAttribute(HTML.NAME_ATTR, clientId + CALENDAR_TABLE); 276 table.setAttribute(HTML.CLASS_ATTR, 277 selectInputDate.getStyleClass()); 278 table.setAttribute(HTML.CELLPADDING_ATTR, "0"); 279 table.setAttribute(HTML.CELLSPACING_ATTR, "0"); 280 String mouseOver = selectInputDate.getOnmouseover(); 282 table.setAttribute(HTML.ONMOUSEOVER_ATTR, mouseOver); 283 String mouseOut = selectInputDate.getOnmouseout(); 284 table.setAttribute(HTML.ONMOUSEOUT_ATTR, mouseOut); 285 String mouseMove = selectInputDate.getOnmousemove(); 286 table.setAttribute(HTML.ONMOUSEMOVE_ATTR, mouseMove); 287 table.setAttribute(HTML.SUMMARY_ATTR,"This table contains a Calendar where a date can be selected."); 288 root.appendChild(table); 289 } 290 } 291 clientId = uiComponent.getClientId(facesContext); 292 currentLocale = facesContext.getViewRoot().getLocale(); 293 294 Date value; 295 296 if (selectInputDate.isNavEvent()) { 297 if (log.isTraceEnabled()) { 298 log.trace("Rendering Nav Event"); 299 } 300 value = selectInputDate.getNavDate(); 301 } else { 302 if (log.isTraceEnabled()) { 303 log.trace("Logging non nav event"); 304 } 305 try { 306 Converter converter = getConverter(selectInputDate); 307 if (converter instanceof DateConverter) { 308 value = ((DateConverter) converter) 309 .getAsDate(facesContext, uiComponent); 310 } else { 311 value = CustomComponentUtils.getDateValue(selectInputDate); 312 } 313 } catch (IllegalArgumentException illegalArgumentException) { 314 value = null; 315 } 316 } 317 318 Calendar timeKeeper = Calendar.getInstance(currentLocale); 319 timeKeeper.setTime(value != null ? value : new Date ()); 320 321 DateFormatSymbols symbols = new DateFormatSymbols (currentLocale); 322 323 weekdays = mapWeekdays(symbols); 324 weekdaysLong = mapWeekdaysLong(symbols); 325 months = mapMonths(symbols); 326 327 int lastDayInMonth = timeKeeper.getActualMaximum(Calendar.DAY_OF_MONTH); 329 int currentDay = timeKeeper.get(Calendar.DAY_OF_MONTH); 331 if (currentDay > lastDayInMonth) { 332 currentDay = lastDayInMonth; 333 } 334 335 Object currentValue = selectInputDate.getValue(); 337 338 timeKeeper.set(Calendar.DAY_OF_MONTH, 1); 339 340 int weekDayOfFirstDayOfMonth = 341 mapCalendarDayToCommonDay(timeKeeper.get(Calendar.DAY_OF_WEEK)); 342 343 int weekStartsAtDayIndex = 344 mapCalendarDayToCommonDay(timeKeeper.getFirstDayOfWeek()); 345 346 ResponseWriter writer = facesContext.getResponseWriter(); 348 349 Element root = (Element ) domContext.getRootNode(); 350 351 if (selectInputDate.isRenderAsPopup()) { 352 if (log.isTraceEnabled()) { 353 log.trace("SelectInputDate as Popup"); 354 } 355 356 Element dateText = (Element ) root.getFirstChild(); 358 359 if (currentValue != null) { 360 dateText.setAttribute(HTML.VALUE_ATTR, 361 selectInputDate.formatDate( 362 (Date ) currentValue)); 363 } 364 365 Element calendarButton = 367 (Element ) root.getFirstChild().getNextSibling(); 368 369 Element calendarDiv = (Element ) root.getLastChild(); 372 boolean popupState = selectInputDate.isShowPopup(); 373 374 if (popupState) { 375 calendarDiv.setAttribute(HTML.STYLE_ELEM, 376 "z-index:10;display:block;position:absolute;"); 377 String resolvedSrc = 378 CoreUtils.resolveResourceURL( facesContext, 379 selectInputDate.getImageDir() + 380 selectInputDate.getClosePopupImage() ); 381 calendarButton.setAttribute(HTML.SRC_ATTR, resolvedSrc ); 382 calendarButton.setAttribute(HTML.ALT_ATTR, "Close Popup Calendar"); 383 calendarButton.setAttribute(HTML.TITLE_ATTR , "Close Popup Calendar"); 384 } else { 385 calendarDiv.setAttribute(HTML.STYLE_ELEM, 386 "display:none;position:absolute;"); 387 String resolvedSrc = 388 CoreUtils.resolveResourceURL( facesContext, 389 selectInputDate.getImageDir() + 390 selectInputDate.getOpenPopupImage() ); 391 calendarButton.setAttribute(HTML.SRC_ATTR, resolvedSrc ); 392 calendarButton.setAttribute(HTML.ALT_ATTR, "Open Popup Calendar"); 393 calendarButton.setAttribute(HTML.TITLE_ATTR , "Open Popup Calendar"); 394 } 395 396 NodeList tables = root.getElementsByTagName(HTML.TABLE_ELEM); 398 Element table = (Element ) tables.item(0); 400 401 PassThruAttributeRenderer 402 .renderAttributes(facesContext, uiComponent, null); 403 404 Element tr1 = domContext.createElement(HTML.TR_ELEM); 405 406 table.appendChild(tr1); 407 408 writeMonthYearHeader(domContext, facesContext, writer, 409 selectInputDate, timeKeeper, 410 currentDay, weekdays, months, tr1, 411 selectInputDate.getMonthYearRowClass()); 412 413 Element tr2 = domContext.createElement(HTML.TR_ELEM); 414 table.appendChild(tr2); 415 416 writeWeekDayNameHeader(domContext, weekStartsAtDayIndex, weekdays, 417 facesContext, writer, selectInputDate, tr2, 418 selectInputDate.getWeekRowClass()); 419 420 writeDays(domContext, facesContext, writer, selectInputDate, 421 timeKeeper, 422 currentDay, weekStartsAtDayIndex, 423 weekDayOfFirstDayOfMonth, 424 lastDayInMonth, weekdays, table); 425 426 } else { 427 if (log.isTraceEnabled()) { 428 log.trace("renderNormal::endcodeEnd"); 429 } 430 Element table = (Element ) root.getFirstChild(); 432 433 PassThruAttributeRenderer 434 .renderAttributes(facesContext, uiComponent, null); 435 436 Element tr1 = domContext.createElement(HTML.TR_ELEM); 437 table.appendChild(tr1); 438 439 writeMonthYearHeader(domContext, facesContext, writer, 440 selectInputDate, timeKeeper, 441 currentDay, weekdays, months, tr1, 442 selectInputDate.getMonthYearRowClass()); 443 444 Element tr2 = domContext.createElement(HTML.TR_ELEM); 445 446 writeWeekDayNameHeader(domContext, weekStartsAtDayIndex, weekdays, 447 facesContext, writer, selectInputDate, tr2, 448 selectInputDate.getWeekRowClass()); 449 450 table.appendChild(tr2); 451 452 writeDays(domContext, facesContext, writer, selectInputDate, 453 timeKeeper, 454 currentDay, weekStartsAtDayIndex, 455 weekDayOfFirstDayOfMonth, 456 lastDayInMonth, weekdays, table); 457 458 } 459 460 selectInputDate.getChildren().removeAll(selectInputDate.getChildren()); 462 463 domContext.stepOver(); 465 domContext.streamWrite(facesContext, uiComponent); 466 } 467 468 private void writeMonthYearHeader(DOMContext domContext, 469 FacesContext facesContext, 470 ResponseWriter writer, 471 SelectInputDate inputComponent, 472 Calendar timeKeeper, 473 int currentDay, String [] weekdays, 474 String [] months, Element headerTr, 475 String styleClass) 476 throws IOException { 477 478 Element table = domContext.createElement(HTML.TABLE_ELEM); 479 table.setAttribute(HTML.CELLPADDING_ATTR, "0"); 480 table.setAttribute(HTML.CELLSPACING_ATTR, "0"); 481 table.setAttribute(HTML.WIDTH_ATTR, "100%"); 482 table.setAttribute(HTML.SUMMARY_ATTR,"Month Year navigation header"); 483 484 Element tr = domContext.createElement(HTML.TR_ELEM); 485 486 Element headertd = domContext.createElement(HTML.TD_ELEM); 487 table.appendChild(tr); 488 headertd.appendChild(table); 489 headerTr.appendChild(headertd); 490 491 headertd.setAttribute(HTML.COLSPAN_ATTR, "7"); 493 Calendar cal = shiftMonth(facesContext, timeKeeper, currentDay, -1); 495 writeCell(domContext, facesContext, writer, inputComponent, 496 "<", cal.getTime(), styleClass, tr, 497 ((SelectInputDate) inputComponent).getImageDir() + 498 ((SelectInputDate) inputComponent).getMovePreviousImage(), -1); 499 500 Element td = domContext.createElement(HTML.TD_ELEM); 501 td.setAttribute(HTML.CLASS_ATTR, styleClass); 502 td.setAttribute(HTML.WIDTH_ATTR, "40%"); 503 Text text = domContext 504 .createTextNode(months[timeKeeper.get(Calendar.MONTH)] + ""); 505 td.appendChild(text); 506 507 tr.appendChild(td); 508 509 cal = shiftMonth(facesContext, timeKeeper, currentDay, 1); 510 int calYear = cal.get(Calendar.YEAR); 511 512 if (inputComponent.getHightlightRules().containsKey(Calendar.YEAR+"$"+calYear)) { 513 inputComponent.setHighlightYearClass(inputComponent.getHightlightRules().get(Calendar.YEAR+"$"+calYear) + " "); 514 } else { 515 inputComponent.setHighlightYearClass(""); 516 } 517 518 int calMonth = cal.get(Calendar.MONTH); 519 if (inputComponent.getHightlightRules().containsKey(Calendar.MONTH+"$"+calMonth)) { 520 inputComponent.setHighlightMonthClass(inputComponent.getHightlightRules().get(Calendar.MONTH+"$"+calMonth) + " "); 521 } else { 522 inputComponent.setHighlightMonthClass(""); 523 } 524 writeCell(domContext, facesContext, writer, inputComponent, 525 ">", cal.getTime(), styleClass, tr, 526 ((SelectInputDate) inputComponent).getImageDir() + 527 ((SelectInputDate) inputComponent).getMoveNextImage(), -1); 528 529 Element emptytd = domContext.createElement(HTML.TD_ELEM); 531 emptytd.setAttribute(HTML.CLASS_ATTR, styleClass); 532 Text emptytext = domContext.createTextNode(""); 533 emptytd.appendChild(emptytext); 534 535 tr.appendChild(emptytd); 536 537 cal = shiftYear(facesContext, timeKeeper, currentDay, -1); 539 540 writeCell(domContext, facesContext, writer, inputComponent, 541 "<<", cal.getTime(), styleClass, tr, 542 ((SelectInputDate) inputComponent).getImageDir() + 543 ((SelectInputDate) inputComponent).getMovePreviousImage(), -1); 544 545 Element yeartd = domContext.createElement(HTML.TD_ELEM); 546 yeartd.setAttribute(HTML.CLASS_ATTR, styleClass); 547 Text yeartext = 548 domContext.createTextNode("" + timeKeeper.get(Calendar.YEAR)); 549 yeartd.appendChild(yeartext); 550 551 tr.appendChild(yeartd); 552 553 cal = shiftYear(facesContext, timeKeeper, currentDay, 1); 554 555 writeCell(domContext, facesContext, writer, inputComponent, 556 ">>", cal.getTime(), styleClass, tr, 557 ((SelectInputDate) inputComponent).getImageDir() + 558 ((SelectInputDate) inputComponent).getMoveNextImage(), -1); 559 560 } 561 562 private Calendar shiftMonth(FacesContext facesContext, 563 Calendar timeKeeper, int currentDay, 564 int shift) { 565 Calendar cal = copyCalendar(facesContext, timeKeeper); 566 567 cal.set(Calendar.MONTH, cal.get(Calendar.MONTH) + shift); 568 569 if (currentDay > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) { 570 currentDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 571 } 572 573 cal.set(Calendar.DAY_OF_MONTH, currentDay); 574 return cal; 575 } 576 577 private Calendar shiftYear(FacesContext facesContext, 578 Calendar timeKeeper, int currentDay, int shift) { 579 Calendar cal = copyCalendar(facesContext, timeKeeper); 580 581 cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) + shift); 582 583 if (currentDay > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) { 584 currentDay = cal.getActualMaximum(Calendar.DAY_OF_MONTH); 585 } 586 587 cal.set(Calendar.DAY_OF_MONTH, currentDay); 588 return cal; 589 } 590 591 private Calendar copyCalendar(FacesContext facesContext, 592 Calendar timeKeeper) { 593 Calendar cal = 594 Calendar.getInstance(facesContext.getViewRoot().getLocale()); 595 cal = (Calendar ) timeKeeper.clone(); 596 return cal; 597 } 598 599 private void writeWeekDayNameHeader(DOMContext domContext, 600 int weekStartsAtDayIndex, 601 String [] weekdays, 602 FacesContext facesContext, 603 ResponseWriter writer, 604 UIInput inputComponent, Element tr, 605 String styleClass) 606 throws IOException { 607 for (int i = weekStartsAtDayIndex; i < weekdays.length; i++) { 609 writeCell(domContext, facesContext, 610 writer, inputComponent, weekdays[i], null, styleClass, tr, 611 null, i); 612 } 613 614 for (int i = 0; i < weekStartsAtDayIndex; i++) { 617 writeCell(domContext, facesContext, writer, 618 inputComponent, weekdays[i], null, styleClass, tr, null, i); 619 } 620 } 621 622 private void writeDays(DOMContext domContext, FacesContext facesContext, 623 ResponseWriter writer, 624 SelectInputDate inputComponent, Calendar timeKeeper, 625 int currentDay, int weekStartsAtDayIndex, 626 int weekDayOfFirstDayOfMonth, int lastDayInMonth, 627 String [] weekdays, Element table) 628 throws IOException { 629 Calendar cal; 630 631 int space = (weekStartsAtDayIndex < weekDayOfFirstDayOfMonth) ? 632 (weekDayOfFirstDayOfMonth - weekStartsAtDayIndex) 633 : (weekdays.length - weekStartsAtDayIndex + 634 weekDayOfFirstDayOfMonth); 635 636 if (space == weekdays.length) { 637 space = 0; 638 } 639 640 int columnIndexCounter = 0; 641 642 Element tr1 = null; 643 for (int i = 0; i < space; i++) { 644 if (columnIndexCounter == 0) { 645 tr1 = domContext.createElement(HTML.TR_ELEM); 646 table.appendChild(tr1); 647 } 648 649 writeCell(domContext, facesContext, writer, inputComponent, "", 650 null, inputComponent.getDayCellClass(), tr1, null, i); 651 columnIndexCounter++; 652 } 653 654 Element tr2 = null; 655 for (int i = 0; i < lastDayInMonth; i++) { 656 if (columnIndexCounter == 0) { 657 tr2 = domContext.createElement(HTML.TR_ELEM); 659 table.appendChild(tr2); 660 } 661 662 cal = copyCalendar(facesContext, timeKeeper); 663 cal.set(Calendar.DAY_OF_MONTH, 664 i + 1); 666 int day = 0; 669 int month = 0; 670 int year = 0; 671 try { 672 Date currentDate = (Date ) inputComponent.getValue(); 673 Calendar current = Calendar.getInstance(); 674 current.setTime(currentDate); 675 676 day = current.get(Calendar.DAY_OF_MONTH); month = current.get(Calendar.MONTH); year = current.get(Calendar.YEAR); 679 } catch (Exception e) { 680 } 682 683 684 if (inputComponent.getHightlightRules().size()>0) { 685 int weekOfYear = cal.get(Calendar.WEEK_OF_YEAR); 686 int weekOfMonth = cal.get(Calendar.WEEK_OF_MONTH); 687 int date =cal.get(Calendar.DATE); 688 int dayOfYear = cal.get(Calendar.DAY_OF_YEAR); 689 int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); 690 int dayOfWeekInMonth = cal.get(Calendar.DAY_OF_WEEK_IN_MONTH); 691 692 if (inputComponent.getHightlightRules().containsKey(Calendar.WEEK_OF_YEAR+"$"+weekOfYear)) { 693 inputComponent.addHighlightWeekClass(String.valueOf(inputComponent.getHightlightRules().get(Calendar.WEEK_OF_YEAR+"$"+weekOfYear))); 694 } 695 if (inputComponent.getHightlightRules().containsKey(Calendar.WEEK_OF_MONTH+"$"+weekOfMonth)) { 696 inputComponent.addHighlightWeekClass(String.valueOf(inputComponent.getHightlightRules().get(Calendar.WEEK_OF_MONTH+"$"+weekOfMonth))); 697 } 698 if (inputComponent.getHightlightRules().containsKey(Calendar.DATE+"$"+date)) { 699 inputComponent.addHighlightDayClass(String.valueOf(inputComponent.getHightlightRules().get(Calendar.DATE+"$"+date))); 700 } 701 if (inputComponent.getHightlightRules().containsKey(Calendar.DAY_OF_YEAR+"$"+dayOfYear)) { 702 inputComponent.addHighlightDayClass(String.valueOf(inputComponent.getHightlightRules().get(Calendar.DAY_OF_YEAR+"$"+dayOfYear))); 703 } 704 if (inputComponent.getHightlightRules().containsKey(Calendar.DAY_OF_WEEK+"$"+dayOfWeek)) { 705 inputComponent.addHighlightDayClass(String.valueOf(inputComponent.getHightlightRules().get(Calendar.DAY_OF_WEEK+"$"+dayOfWeek))); 706 } 707 if (inputComponent.getHightlightRules().containsKey(Calendar.DAY_OF_WEEK_IN_MONTH+"$"+dayOfWeekInMonth)) { 708 inputComponent.addHighlightDayClass(String.valueOf(inputComponent.getHightlightRules().get(Calendar.DAY_OF_WEEK_IN_MONTH+"$"+dayOfWeekInMonth))); 709 } 710 } 711 712 String cellStyle = inputComponent.getHighlightDayCellClass() + inputComponent.getDayCellClass(); 713 714 715 if ((cal.get(Calendar.DAY_OF_MONTH) == day) && 716 (cal.get(Calendar.MONTH) == month) && 717 (cal.get(Calendar.YEAR) == year)) { 718 cellStyle = inputComponent.getCurrentDayCellClass(); 719 } 720 721 722 if ((cal.get(Calendar.DAY_OF_MONTH) == day) && 724 (cal.get(Calendar.MONTH) == month) && 725 (cal.get(Calendar.YEAR) == year)) { 726 cellStyle = inputComponent.getCurrentDayCellClass(); 727 } 728 729 if (tr2 == null) { 730 writeCell(domContext, facesContext, writer, 732 inputComponent, String.valueOf(i + 1), cal.getTime(), 733 cellStyle, tr1, null, i); 734 } else { 735 writeCell(domContext, facesContext, writer, 737 inputComponent, String.valueOf(i + 1), cal.getTime(), 738 cellStyle, tr2, null, i); 739 } 740 741 columnIndexCounter++; 742 743 if (columnIndexCounter == weekdays.length) { 744 columnIndexCounter = 0; 745 } 746 inputComponent.resetHighlightClasses(Calendar.WEEK_OF_YEAR); 747 } 748 749 if ((columnIndexCounter != 0) && (tr2 != null)) { 750 for (int i = columnIndexCounter; i < weekdays.length; i++) { 751 writeCell(domContext, facesContext, writer, 752 inputComponent, "", null, 753 inputComponent.getDayCellClass(), tr2, null, i); 754 } 755 } 756 757 } 758 759 private void writeCell(DOMContext domContext, FacesContext facesContext, 760 ResponseWriter writer, UIInput component, 761 String content, 762 Date valueForLink, String styleClass, Element tr, 763 String imgSrc, int weekDayIndex) 764 throws IOException { 765 Element td = domContext.createElement(HTML.TD_ELEM); 766 tr.appendChild(td); 767 768 if (styleClass != null) { 769 td.setAttribute(HTML.CLASS_ATTR, styleClass); 770 } 771 772 if (valueForLink == null) { 773 Text text = domContext.createTextNode(content); 774 td.setAttribute(HTML.TITLE_ATTR,weekdaysLong[weekDayIndex]); 775 td.appendChild(text); 776 } else { 777 domContext.setCursorParent(td); 779 domContext.streamWrite(facesContext, component, 780 domContext.getRootNode(), td); 781 writeLink(content, component, facesContext, valueForLink, 782 styleClass, imgSrc, td); 783 domContext.stepOver(); 785 } 786 787 } 788 789 private void writeLink(String content, 790 UIInput component, 791 FacesContext facesContext, 792 Date valueForLink, 793 String styleClass, 794 String imgSrc, 795 Element td) 796 throws IOException { 797 798 Converter converter = getConverter(component); 799 HtmlCommandLink link = new HtmlCommandLink(); 800 Calendar cal = Calendar.getInstance(currentLocale); 801 cal.setTime(valueForLink); 802 String month = months[cal.get(Calendar.MONTH)]; 803 String year = String.valueOf(cal.get(Calendar.YEAR)); 804 int dayInt = cal.get(Calendar.DAY_OF_WEEK); 805 dayInt = mapCalendarDayToCommonDay(dayInt); 806 String day = weekdaysLong[dayInt]; 807 String altText = ""; 808 if (content.equals("<")) { 810 link.setId(component.getId() + this.PREV_MONTH); 811 altText = "Move to previous month " + month; 812 } else if (content.equals(">")) { 813 link.setId(component.getId() + this.NEXT_MONTH); 814 altText = "Move to next month " + month; 815 } else if (content.equals(">>")) { 816 link.setId(component.getId() + this.NEXT_YEAR); 817 altText = "Move to next year " + year; 818 } else if (content.equals("<<")) { 819 link.setId(component.getId() + this.PREV_YEAR); 820 altText = "Move to previous year " + year; 821 } else { 822 link.setId(component.getId() + "_" + content.hashCode() + this 823 .CALENDAR); 824 if (log.isDebugEnabled()) { 825 log.debug("linkId=" + component.getId() + "_" + 826 content.hashCode() + this.CALENDAR); 827 } 828 } 829 830 link.setPartialSubmit(true); 831 link.setTransient(true); 832 link.setImmediate(component.isImmediate()); 833 834 if (imgSrc != null) { 835 HtmlGraphicImage img = new HtmlGraphicImage(); 836 img.setUrl(imgSrc); 837 img.setHeight("16"); 838 img.setWidth("17"); 839 img.setStyle("border:none;"); 840 img.setAlt(altText); 841 img.setId(component.getId() + "_" + content.hashCode() + "_img"); 842 img.setTransient(true); 843 link.getChildren().add(img); 844 } else { 845 HtmlOutputText text = new HtmlOutputText(); 846 text.setValue(content); 847 text.setId(component.getId() + "_" + content.hashCode() + "_text"); 848 text.setTransient(true); 849 text.setTitle(day); 850 link.getChildren().add(text); 851 } 852 UIParameter parameter = new UIParameter(); 854 parameter.setId( 855 component.getId() + "_" + valueForLink.getTime() + "_param"); 856 parameter.setTransient(true); 857 parameter.setName(component.getClientId(facesContext)); 858 parameter.setValue( 859 converter.getAsString(facesContext, component, valueForLink)); 860 861 component.getChildren().add(link); 862 link.getChildren().add(parameter); 863 864 if (!content.equals("<") && !content.equals(">") && 866 !content.equals(">>") && !content.equals("<<")) { 867 parameter = new UIParameter(); 869 parameter.setId(component.getId() + "_" + valueForLink.getTime() + 870 "_" + DATE_SELECTED); 871 parameter.setName(getHiddenFieldName(facesContext, component)); 872 parameter.setValue("false"); 873 link.getChildren().add(parameter); 874 } 875 link.encodeBegin(facesContext); 876 link.encodeChildren(facesContext); 877 link.encodeEnd(facesContext); 878 td.setAttribute(HTML.ID_ATTR, link.getClientId(facesContext) + "td"); 879 try { 880 Integer.parseInt(content); 881 ((SelectInputDate) component).getLinkMap() 882 .put(link.getClientId(facesContext), td); 883 if (styleClass.equals(CSS_DEFAULT.DEFAULT_CALENDAR + CSS_DEFAULT 884 .DEFAULT_CURRENTDAYCELL_CLASS)) { 885 ((SelectInputDate) component) 886 .setSelectedDayLink(link.getClientId(facesContext)); 887 } 888 } catch (NumberFormatException e) { 889 890 } 891 892 893 } 894 895 private Converter getConverter(UIInput component) { 896 Converter converter = component.getConverter(); 897 898 if (converter == null) { 899 converter = new CalendarDateTimeConverter(); 900 } 901 return converter; 902 } 903 904 private int mapCalendarDayToCommonDay(int day) { 905 switch (day) { 906 case Calendar.TUESDAY: 907 return 1; 908 case Calendar.WEDNESDAY: 909 return 2; 910 case Calendar.THURSDAY: 911 return 3; 912 case Calendar.FRIDAY: 913 return 4; 914 case Calendar.SATURDAY: 915 return 5; 916 case Calendar.SUNDAY: 917 return 6; 918 default: 919 return 0; 920 } 921 } 922 923 private static String [] mapWeekdays(DateFormatSymbols symbols) { 924 String [] weekdays = new String [7]; 925 926 String [] localeWeekdays = symbols.getShortWeekdays(); 927 928 weekdays[0] = localeWeekdays[Calendar.MONDAY]; 929 weekdays[1] = localeWeekdays[Calendar.TUESDAY]; 930 weekdays[2] = localeWeekdays[Calendar.WEDNESDAY]; 931 weekdays[3] = localeWeekdays[Calendar.THURSDAY]; 932 weekdays[4] = localeWeekdays[Calendar.FRIDAY]; 933 weekdays[5] = localeWeekdays[Calendar.SATURDAY]; 934 weekdays[6] = localeWeekdays[Calendar.SUNDAY]; 935 936 return weekdays; 937 } 938 939 private static String [] mapWeekdaysLong(DateFormatSymbols symbols) { 940 String [] weekdays = new String [7]; 941 942 String [] localeWeekdays = symbols.getWeekdays(); 943 944 weekdays[0] = localeWeekdays[Calendar.MONDAY]; 945 weekdays[1] = localeWeekdays[Calendar.TUESDAY]; 946 weekdays[2] = localeWeekdays[Calendar.WEDNESDAY]; 947 weekdays[3] = localeWeekdays[Calendar.THURSDAY]; 948 weekdays[4] = localeWeekdays[Calendar.FRIDAY]; 949 weekdays[5] = localeWeekdays[Calendar.SATURDAY]; 950 weekdays[6] = localeWeekdays[Calendar.SUNDAY]; 951 952 return weekdays; 953 } 954 955 959 public static String [] mapMonths(DateFormatSymbols symbols) { 960 String [] months = new String [12]; 961 962 String [] localeMonths = symbols.getMonths(); 963 964 months[0] = localeMonths[Calendar.JANUARY]; 965 months[1] = localeMonths[Calendar.FEBRUARY]; 966 months[2] = localeMonths[Calendar.MARCH]; 967 months[3] = localeMonths[Calendar.APRIL]; 968 months[4] = localeMonths[Calendar.MAY]; 969 months[5] = localeMonths[Calendar.JUNE]; 970 months[6] = localeMonths[Calendar.JULY]; 971 months[7] = localeMonths[Calendar.AUGUST]; 972 months[8] = localeMonths[Calendar.SEPTEMBER]; 973 months[9] = localeMonths[Calendar.OCTOBER]; 974 months[10] = localeMonths[Calendar.NOVEMBER]; 975 months[11] = localeMonths[Calendar.DECEMBER]; 976 977 return months; 978 } 979 980 985 public String getLinkId(FacesContext facesContext, 986 UIComponent uiComponent) { 987 UIComponent form = findForm(uiComponent); 989 String formId = form.getClientId(facesContext); 990 return formId + ":_idcl"; 991 } 992 993 private boolean checkLink(String clickedLink, String clientId) { 994 if (clickedLink == null) { 995 return false; 996 } 997 boolean found = false; 998 String REGEX = clientId; 999 String INPUT = clickedLink; 1000 Pattern pattern; 1001 Matcher matcher; 1002 1003 pattern = Pattern.compile(REGEX); 1004 matcher = pattern.matcher(INPUT); 1005 1006 while (matcher.find()) { 1007 found = true; 1008 } 1009 1010 return found; 1011 } 1012 1013 1016 public void decode(FacesContext facesContext, UIComponent component) { 1017 validateParameters(facesContext, component, SelectInputDate.class); 1018 Map requestParameterMap = 1019 facesContext.getExternalContext().getRequestParameterMap(); 1020 Object linkId = getLinkId(facesContext, component); 1021 Object clickedLink = requestParameterMap.get(linkId); 1022 String clientId = component.getClientId(facesContext); 1023 1024 Map parameter = 1025 facesContext.getExternalContext().getRequestParameterMap(); 1026 String param = 1027 (String ) parameter.get(component.getClientId(facesContext)); 1028 if (param != null) { 1029 1030 1031 if (log.isDebugEnabled()) { 1032 log.debug("linkId::" + linkId + " clickedLink::" + 1033 clickedLink + " clientId::" + clientId); 1034 } 1035 1036 if (checkLink((String ) clickedLink, clientId)) { 1037 if (log.isDebugEnabled()) { 1038 log.debug("---------------------------------"); 1039 log.debug("----------START::DECODE----------"); 1040 log.debug("---------------------------------"); 1041 log.debug("decode::linkId::" + linkId + "=" + clickedLink + 1042 " clientId::" + clientId); 1043 } 1044 1045 if (((String ) clickedLink).endsWith(this.PREV_MONTH) || 1046 ((String ) clickedLink).endsWith(this.NEXT_MONTH) || 1047 ((String ) clickedLink).endsWith(this.PREV_YEAR) || 1048 ((String ) clickedLink).endsWith(this.NEXT_YEAR)) { 1049 if (log.isDebugEnabled()) { 1050 log.debug("-------------Navigation Event-------------"); 1051 } 1052 decodeNavigation(facesContext, component); 1053 } else if (((String ) clickedLink).endsWith(this.CALENDAR)) { 1054 if (log.isDebugEnabled()) { 1055 log.debug( 1056 "-------------Select Date Event-------------"); 1057 } 1058 decodeSelectDate(facesContext, component); 1059 } else 1060 if (((String ) clickedLink).endsWith(this.CALENDAR_BUTTON)) { 1061 if (log.isDebugEnabled()) { 1062 log.debug( 1063 "-------------Popup Event-------------------"); 1064 } 1065 decodePopup(facesContext, component); 1066 } 1067 } else { 1068 if (log.isDebugEnabled()) { 1069 log.debug("-------------InputText enterkey Event ??----"); 1070 } 1071 decodeInputText(facesContext, component); 1072 } 1073 1074 } 1075 } 1076 1077 private void decodeNavigation(FacesContext facesContext, 1078 UIComponent component) { 1079 Map requestParameterMap = 1080 facesContext.getExternalContext().getRequestParameterMap(); 1081 SelectInputDate dateSelect = (SelectInputDate) component; 1082 1083 if (log.isDebugEnabled()) { 1085 log.debug("setNavDate::"); 1086 log.debug("#################################"); 1087 } 1088 dateSelect.setNavEvent(true); 1089 dateSelect.setNavDate((Date ) getConvertedValue(facesContext, dateSelect, 1090 requestParameterMap.get( 1091 dateSelect.getClientId( 1092 facesContext)))); 1093 } 1094 1095 private void decodePopup(FacesContext facesContext, UIComponent component) { 1096 Map requestParameterMap = 1097 facesContext.getExternalContext().getRequestParameterMap(); 1098 String popupState = getHiddenFieldName(facesContext, component); 1099 String showPopup = (String ) requestParameterMap.get(popupState); 1100 SelectInputDate dateSelect = (SelectInputDate) component; 1101 1102 if (log.isDebugEnabled()) { 1103 log.debug("decodePopup::" + showPopup); 1104 log.debug("#################################"); 1105 } 1106 if (showPopup != null) { 1108 1109 dateSelect.setShowPopup(!dateSelect.isShowPopup()); 1110 } 1111 dateSelect.setNavEvent(false); 1113 } 1114 1115 private void decodeSelectDate(FacesContext facesContext, 1116 UIComponent component) { 1117 1118 Map requestParameterMap = 1119 facesContext.getExternalContext().getRequestParameterMap(); 1120 String popupState = getHiddenFieldName(facesContext, component); 1121 String showPopup = (String ) requestParameterMap.get(popupState); 1122 SelectInputDate dateSelect = (SelectInputDate) component; 1123 1124 if (log.isDebugEnabled()) { 1125 log.debug("selectDate::showPopup" + showPopup); 1126 log.debug("#################################"); 1127 } 1128 if (showPopup != null) { 1129 if (showPopup.equalsIgnoreCase("true")) { 1130 dateSelect.setShowPopup(true); 1131 } else { 1132 dateSelect.setShowPopup(false); 1133 } 1134 } 1135 if (log.isDebugEnabled()) { 1136 log.debug("decodeUIInput::"); 1137 log.debug("#################################"); 1138 } 1139 CustomComponentUtils.decodeUIInput(facesContext, component); 1140 dateSelect.setNavEvent(false); 1142 } 1143 1144 private void decodeInputText(FacesContext facesContext, 1145 UIComponent component) { 1146 Map requestParameterMap = 1147 facesContext.getExternalContext().getRequestParameterMap(); 1148 String popupState = getHiddenFieldName(facesContext, component); 1149 String showPopup = (String ) requestParameterMap.get(popupState); 1150 SelectInputDate dateSelect = (SelectInputDate) component; 1151 String clientId = dateSelect.getClientId(facesContext); 1152 Object linkId = getLinkId(facesContext, component); 1153 Object clickedLink = requestParameterMap.get(linkId); 1154 1155 if ((requestParameterMap.containsKey(clientId)) && 1157 dateSelect.isRenderAsPopup()) { 1158 if (log.isDebugEnabled()) { 1159 log.debug("decoding InputText EnterKey::"); 1160 log.debug("###################################"); 1161 } 1162 if (showPopup != null) { 1163 String inputTextDateId = component.getClientId(facesContext) + 1164 CALENDAR_INPUTTEXT; 1165 Object inputTextDate = requestParameterMap.get(inputTextDateId); 1166 1167 if (checkLink((String ) clickedLink, clientId)) { 1168 if (showPopup.equalsIgnoreCase("true")) { 1169 dateSelect.setShowPopup(true); 1170 } else { 1171 dateSelect.setShowPopup(false); 1172 } 1173 } 1174 if (String.valueOf(inputTextDate).equals("")) { 1175 dateSelect.setSubmittedValue("null"); 1176 } else { 1177 dateSelect.setSubmittedValue(inputTextDate); 1178 } 1179 } 1180 } 1181 } 1182 1183 1186 public Object getConvertedValue(FacesContext facesContext, 1187 UIComponent uiComponent, 1188 Object submittedValue) 1189 throws ConverterException { 1190 validateParameters(facesContext, uiComponent, SelectInputDate.class); 1191 Converter converter; 1192 1193 converter = new CalendarDateTimeConverter(); 1194 1195 if (!(submittedValue == null || submittedValue instanceof String )) { 1196 throw new IllegalArgumentException ( 1197 "Submitted value of type String expected"); 1198 } 1199 Object o = converter.getAsObject(facesContext, uiComponent, 1200 (String ) submittedValue); 1201 1202 return o; 1203 } 1204 1205 1208 public interface DateConverter extends Converter { 1209 public Date getAsDate(FacesContext facesContext, 1210 UIComponent uiComponent); 1211 } 1212 1213 1216 public static class CalendarDateTimeConverter implements Converter { 1217 1218 1221 public Object getAsObject(FacesContext facesContext, 1222 UIComponent uiComponent, String s) { 1223 1224 if (s == null || s.trim().length() == 0) { 1225 return null; 1226 } else if ("null".equals(s)){ 1227 return null; 1228 } 1229 1230 DateFormat dateFormat = null; 1231 1232 if (uiComponent instanceof SelectInputDate && 1233 ((SelectInputDate) uiComponent).isRenderAsPopup()) { 1234 String popupDateFormat = 1235 ((SelectInputDate) uiComponent).getPopupDateFormat(); 1236 dateFormat = new SimpleDateFormat (popupDateFormat); 1237 } else { 1238 dateFormat = createStandardDateFormat(facesContext); 1239 } 1240 1241 try { 1242 return dateFormat.parse(s); 1243 } 1244 catch (ParseException e) { 1245 ConverterException ex = new ConverterException(e); 1246 throw ex; 1247 } 1248 } 1249 1250 1255 public static String createJSPopupFormat(FacesContext facesContext, 1256 String popupDateFormat) { 1257 1258 if (popupDateFormat == null) { 1259 SimpleDateFormat defaultDateFormat = 1260 createStandardDateFormat(facesContext); 1261 popupDateFormat = defaultDateFormat.toPattern(); 1262 } 1263 1264 StringBuffer jsPopupDateFormat = new StringBuffer (); 1265 1266 for (int i = 0; i < popupDateFormat.length(); i++) { 1267 char c = popupDateFormat.charAt(i); 1268 1269 if (c == 'M') { 1270 jsPopupDateFormat.append('M'); 1271 } else if (c == 'd') { 1272 jsPopupDateFormat.append('d'); 1273 } else if (c == 'y') { 1274 jsPopupDateFormat.append('y'); 1275 } else if (c == ' ') { 1276 jsPopupDateFormat.append(' '); 1277 } else if (c == '.') { 1278 jsPopupDateFormat.append('.'); 1279 } else if (c == '/') { 1280 jsPopupDateFormat.append('/'); 1281 } 1282 } 1283 return jsPopupDateFormat.toString().trim(); 1284 } 1285 1286 1289 public String getAsString(FacesContext facesContext, 1290 UIComponent uiComponent, Object o) { 1291 Date date = (Date ) o; 1292 1293 if (date == null) { 1294 return null; 1295 } 1296 1297 DateFormat dateFormat = null; 1298 1299 if (uiComponent instanceof SelectInputDate && 1300 ((SelectInputDate) uiComponent).isRenderAsPopup()) { 1301 String popupDateFormat = 1302 ((SelectInputDate) uiComponent).getPopupDateFormat(); 1303 dateFormat = new SimpleDateFormat (popupDateFormat); 1304 } else { 1305 dateFormat = createStandardDateFormat(facesContext); 1306 } 1307 1308 return dateFormat.format(date); 1309 } 1310 1311 private static SimpleDateFormat createStandardDateFormat( 1312 FacesContext facesContext) { 1313 DateFormat dateFormat; 1314 dateFormat = DateFormat 1315 .getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, 1316 facesContext 1317 .getViewRoot().getLocale()); 1318 1319 if (dateFormat instanceof SimpleDateFormat ) { 1320 return new SimpleDateFormat ("dd.MM.yyyy"); 1323 } else { 1324 return new SimpleDateFormat ("dd.MM.yyyy"); 1325 } 1326 } 1327 1328 } 1329 1330} 1331 | Popular Tags |