2828import javax .servlet .http .HttpServletRequest ;
2929import java .text .SimpleDateFormat ;
3030import java .util .ArrayList ;
31+ import java .util .Arrays ;
3132import java .util .Date ;
3233import java .util .HashMap ;
34+ import java .util .HashSet ;
3335import java .util .LinkedHashMap ;
3436import java .util .List ;
3537import java .util .Map ;
@@ -380,7 +382,14 @@ public String generateHtml(FormEntryContext context) {
380382 writer .println ("<div id=\" " + fieldName + "\" class=\" orderwidget-element\" >" );
381383
382384 // Add a section that will contain the selected orders
383- writer .println ("<div id=\" " + fieldName + "_orders\" class=\" orderwidget-order-section\" ></div>" );
385+ // In VIEW mode, pre-render orders as static HTML so the widget is usable without JavaScript
386+ if (context .getMode () == FormEntryContext .Mode .VIEW ) {
387+ writer .println ("<div id=\" " + fieldName + "_orders\" class=\" orderwidget-order-section\" >" );
388+ writer .println (generateStaticOrdersHtml (context ));
389+ writer .println ("</div>" );
390+ } else {
391+ writer .println ("<div id=\" " + fieldName + "_orders\" class=\" orderwidget-order-section\" ></div>" );
392+ }
384393
385394 // Add a section that can contain a selector
386395 writer .println ("<div id=\" " + fieldName + "_header\" class=\" orderwidget-selector-section\" ></div>" );
@@ -444,28 +453,269 @@ public String generateHtml(FormEntryContext context) {
444453 writer .println (StringUtils .isBlank (viewTemplateContent ) ? defaultViewContent : viewTemplateContent );
445454 writer .println ("</div>" );
446455
447- // Add javascript function to initialize widget as appropriate
448- String defaultLoadFn = "orderWidget.initialize" ;
449- String onLoadFn = widgetConfig .getAttributes ().getOrDefault ("onLoadFunction" , defaultLoadFn );
450- writer .println ("<script type=\" text/javascript\" >" );
451- writer .println ("jQuery(function() { " + onLoadFn + "(" );
452- writer .println (constructJavascriptConfig (context ).toJson ());
453- writer .println (")});" );
454- writer .println ("</script>" );
456+ // In ENTER/EDIT modes, initialize the widget via JavaScript
457+ // VIEW mode is pre-rendered as static HTML above and does not require JavaScript
458+ if (context .getMode () != FormEntryContext .Mode .VIEW ) {
459+ String defaultLoadFn = "orderWidget.initialize" ;
460+ String onLoadFn = widgetConfig .getAttributes ().getOrDefault ("onLoadFunction" , defaultLoadFn );
461+ writer .println ("<script type=\" text/javascript\" >" );
462+ writer .println ("jQuery(function() { " + onLoadFn + "(" );
463+ writer .println (constructJavascriptConfig (context ).toJson ());
464+ writer .println (")});" );
465+ writer .println ("</script>" );
466+ }
455467
456468 writer .println ("</div>" );
457469
458470 return writer .getContent ();
459471 }
460472
473+ /**
474+ * Generates static HTML for all existing orders in VIEW mode, replicating the section structure
475+ * that orderWidget.js renderOrdersForRevision() produces so that CSS rules continue to apply correctly.
476+ * Only orders from the current encounter are shown (mirrors shouldRenderOrder() in VIEW mode).
477+ */
478+ protected String generateStaticOrdersHtml (FormEntryContext context ) {
479+ StringBuilder html = new StringBuilder ();
480+ if (initialValue == null || initialValue .isEmpty ()) {
481+ return html .toString ();
482+ }
483+ Date encDate = context .getExistingEncounter () != null ? context .getExistingEncounter ().getEncounterDatetime () : new Date ();
484+ Integer currentEncId = context .getExistingEncounter () != null ? context .getExistingEncounter ().getEncounterId () : null ;
485+
486+ // Flat map of all known orders by id, for previous-order lookup
487+ Map <Integer , Order > allOrdersById = new LinkedHashMap <>();
488+ for (List <Order > orders : initialValue .values ()) {
489+ for (Order o : orders ) {
490+ allOrdersById .put (o .getOrderId (), o );
491+ }
492+ }
493+
494+ // In VIEW mode, shouldRenderOrder() only shows orders from the current encounter.
495+ // Split into existing (REVISE/RENEW/DISCONTINUE) vs new (NEW) — mirrors renderOrdersForRevision().
496+ List <Order > existingOrders = new ArrayList <>();
497+ List <Order > newOrders = new ArrayList <>();
498+ for (Concept c : widgetConfig .getConceptsAndDrugsConfigured ().keySet ()) {
499+ List <Order > conceptOrders = getInitialValueForConcept (c );
500+ if (conceptOrders == null ) {
501+ continue ;
502+ }
503+ for (Order order : conceptOrders ) {
504+ boolean inCurrentEncounter = currentEncId != null && currentEncId .equals (order .getEncounter ().getEncounterId ());
505+ if (!inCurrentEncounter ) {
506+ continue ;
507+ }
508+ if (order .getAction () == Order .Action .NEW ) {
509+ newOrders .add (order );
510+ } else {
511+ existingOrders .add (order );
512+ }
513+ }
514+ }
515+
516+ appendOrderSection (html , "orderwidget-existing-order-section" ,
517+ translate ("htmlformentry.orders.existingOrdersViewTitle" ),
518+ translate ("htmlformentry.orders.noOrders" ),
519+ existingOrders , allOrdersById , encDate , currentEncId , context );
520+
521+ appendOrderSection (html , "orderwidget-new-order-section" ,
522+ translate ("htmlformentry.orders.newOrdersTitle" ),
523+ translate ("htmlformentry.orders.noOrders" ),
524+ newOrders , allOrdersById , encDate , currentEncId , context );
525+
526+ return html .toString ();
527+ }
528+
529+ private void appendOrderSection (StringBuilder html , String sectionClass , String title , String noOrdersText ,
530+ List <Order > orders , Map <Integer , Order > allOrdersById , Date encDate , Integer currentEncId ,
531+ FormEntryContext context ) {
532+ String sectionHidden = orders .isEmpty () ? " style=\" display:none;\" " : "" ;
533+ String noOrdersHidden = orders .isEmpty () ? "" : " style=\" display:none;\" " ;
534+ html .append ("<div class=\" " ).append (sectionClass ).append ("\" " ).append (sectionHidden ).append (">" );
535+ html .append ("<div class=\" orderwidget-section-header\" >" ).append (title ).append ("</div>" );
536+ html .append ("<div class=\" orderwidget-section-no-orders\" " ).append (noOrdersHidden ).append (">" ).append (noOrdersText ).append ("</div>" );
537+ for (Order order : orders ) {
538+ html .append ("<div class=\" orderwidget-orderable-section\" >" );
539+ html .append ("<div class=\" orderwidget-history-section\" >" );
540+ // Mirror shouldRenderPreviousOrder() + the check that the previous is from a different encounter
541+ if (order .getPreviousOrder () != null && order .getAction () != Order .Action .NEW ) {
542+ Integer prevId = order .getPreviousOrder ().getOrderId ();
543+ Order prevOrder = allOrdersById .getOrDefault (prevId , order .getPreviousOrder ());
544+ boolean prevInCurrentEncounter = currentEncId != null && currentEncId .equals (prevOrder .getEncounter ().getEncounterId ());
545+ if (!prevInCurrentEncounter ) {
546+ html .append (buildOrderItemHtml (prevOrder , encDate , currentEncId , context ));
547+ }
548+ }
549+ html .append (buildOrderItemHtml (order , encDate , currentEncId , context ));
550+ html .append ("</div>" );
551+ html .append ("</div>" );
552+ }
553+ html .append ("</div>" );
554+ }
555+
556+ private String buildOrderItemHtml (Order order , Date encDate , Integer currentEncId , FormEntryContext context ) {
557+ boolean isActive = isOrderActive (order , encDate );
558+ boolean inCurrentEncounter = currentEncId != null && currentEncId .equals (order .getEncounter ().getEncounterId ());
559+ String activeClass = isActive ? "order-view-active" : "order-view-inactive" ;
560+ String encounterClass = inCurrentEncounter ? "order-view-current-encounter" : "order-view-different-encounter" ;
561+ StringBuilder html = new StringBuilder ();
562+ html .append ("<div class=\" orderwidget-order-history-item " ).append (activeClass ).append (" " ).append (encounterClass ).append ("\" >" );
563+ Map <String , String > displayValues = getOrderPropertyDisplayValues (order );
564+ Set <String > hiddenProperties = getHiddenPropertiesForAction (order .getAction (), inCurrentEncounter );
565+ // Mirror the same template-building loop used in generateHtml() for _view_template,
566+ // so layout, field order, and custom templates stay consistent with ENTER/EDIT modes.
567+ String templateContent = widgetConfig .getTemplateContent ();
568+ StringBuilder defaultContent = new StringBuilder ();
569+ for (String property : widgets .keySet ()) {
570+ Map <String , String > attrs = widgetConfig .getAttributes (property );
571+ if (attrs == null ) {
572+ continue ;
573+ }
574+ String display = displayValues .getOrDefault (property , "" );
575+ boolean hidden = hiddenProperties .contains (property ) || StringUtils .isBlank (display );
576+ String fieldHtml = generateHtmlForWidget (property , null , attrs , context , display , hidden );
577+ if (StringUtils .isBlank (templateContent )) {
578+ defaultContent .append (fieldHtml );
579+ } else {
580+ templateContent = templateContent .replace (attrs .toString (), fieldHtml );
581+ }
582+ }
583+ html .append (StringUtils .isBlank (templateContent ) ? defaultContent : templateContent );
584+ html .append ("</div>" );
585+ return html .toString ();
586+ }
587+
588+ /**
589+ * @return a map of property name to display string for all properties of the given order
590+ */
591+ protected Map <String , String > getOrderPropertyDisplayValues (Order order ) {
592+ Map <String , String > values = new LinkedHashMap <>();
593+ values .put ("action" , getLabelForProperty ("action" , order .getAction ()));
594+ values .put ("concept" , getLabelForProperty ("concept" , order .getConcept ()));
595+ values .put ("careSetting" , getLabelForProperty ("careSetting" , order .getCareSetting ()));
596+ values .put ("instructions" , getLabelForProperty ("instructions" , order .getInstructions ()));
597+ values .put ("urgency" , getLabelForProperty ("urgency" , order .getUrgency ()));
598+ values .put ("dateActivated" , getLabelForProperty ("dateActivated" , order .getDateActivated ()));
599+ values .put ("scheduledDate" , getLabelForProperty ("scheduledDate" , order .getScheduledDate ()));
600+ values .put ("effectiveStartDate" , getLabelForProperty ("effectiveStartDate" , order .getEffectiveStartDate ()));
601+ values .put ("autoExpireDate" , getLabelForProperty ("autoExpireDate" , order .getAutoExpireDate ()));
602+ values .put ("dateStopped" , getLabelForProperty ("dateStopped" , order .getDateStopped ()));
603+ values .put ("effectiveStopDate" , getLabelForProperty ("effectiveStopDate" , order .getEffectiveStopDate ()));
604+ if (order instanceof DrugOrder ) {
605+ DrugOrder d = (DrugOrder ) order ;
606+ values .put ("drug" , getLabelForProperty ("drug" , d .getDrug ()));
607+ values .put ("drugNonCoded" , getLabelForProperty ("drugNonCoded" , d .getDrugNonCoded ()));
608+ values .put ("dosingType" , getLabelForProperty ("dosingType" , d .getDosingType ()));
609+ values .put ("dosingInstructions" , getLabelForProperty ("dosingInstructions" , d .getDosingInstructions ()));
610+ values .put ("dose" , getLabelForProperty ("dose" , d .getDose ()));
611+ values .put ("doseUnits" , getLabelForProperty ("doseUnits" , d .getDoseUnits ()));
612+ values .put ("route" , getLabelForProperty ("route" , d .getRoute ()));
613+ values .put ("frequency" , getLabelForProperty ("frequency" , d .getFrequency ()));
614+ values .put ("asNeeded" , getLabelForProperty ("asNeeded" , d .getAsNeeded ()));
615+ values .put ("duration" , getLabelForProperty ("duration" , d .getDuration ()));
616+ values .put ("durationUnits" , getLabelForProperty ("durationUnits" , d .getDurationUnits ()));
617+ values .put ("quantity" , getLabelForProperty ("quantity" , d .getQuantity ()));
618+ values .put ("quantityUnits" , getLabelForProperty ("quantityUnits" , d .getQuantityUnits ()));
619+ values .put ("numRefills" , getLabelForProperty ("numRefills" , d .getNumRefills ()));
620+ } else if (order instanceof ServiceOrder ) {
621+ ServiceOrder s = (ServiceOrder ) order ;
622+ values .put ("specimenSource" , getLabelForProperty ("specimenSource" , s .getSpecimenSource ()));
623+ values .put ("laterality" , getLabelForProperty ("laterality" , s .getLaterality ()));
624+ values .put ("clinicalHistory" , getLabelForProperty ("clinicalHistory" , s .getClinicalHistory ()));
625+ values .put ("frequency" , getLabelForProperty ("frequency" , s .getFrequency ()));
626+ values .put ("numberOfRepeats" , getLabelForProperty ("numberOfRepeats" , s .getNumberOfRepeats ()));
627+ values .put ("location" , getLabelForProperty ("location" , s .getLocation ()));
628+ }
629+ if (order .getAction () == Order .Action .DISCONTINUE ) {
630+ values .put ("discontinueReason" , getLabelForProperty ("discontinueReason" , order .getOrderReason ()));
631+ values .put ("discontinueReasonNonCoded" , getLabelForProperty ("discontinueReasonNonCoded" , order .getOrderReasonNonCoded ()));
632+ } else {
633+ values .put ("orderReason" , getLabelForProperty ("orderReason" , order .getOrderReason ()));
634+ values .put ("orderReasonNonCoded" , getLabelForProperty ("orderReasonNonCoded" , order .getOrderReasonNonCoded ()));
635+ }
636+ return values ;
637+ }
638+
639+ /**
640+ * @return the set of property names that should be hidden for the given action type,
641+ * mirroring the visibility logic in orderWidget.js formatOrder()
642+ */
643+ protected Set <String > getHiddenPropertiesForAction (Order .Action action , boolean inCurrentEncounter ) {
644+ List <String > orderableProperties = Arrays .asList ("careSetting" , "concept" , "drug" , "drugNonCoded" );
645+ List <String > dosingProperties = Arrays .asList ("dosingType" , "dose" , "doseUnits" , "route" , "frequency" ,
646+ "asNeeded" , "asNeededCondition" , "instructions" , "dosingInstructions" );
647+ List <String > dispensingProperties = Arrays .asList ("quantity" , "quantityUnits" , "numRefills" );
648+ List <String > discontinueProperties = Arrays .asList ("discontinueReason" , "discontinueReasonNonCoded" );
649+ Set <String > hidden = new HashSet <>();
650+ if (action == Order .Action .NEW ) {
651+ hidden .addAll (discontinueProperties );
652+ hidden .add ("action" );
653+ } else if (action == Order .Action .REVISE ) {
654+ hidden .addAll (discontinueProperties );
655+ if (inCurrentEncounter ) {
656+ hidden .addAll (orderableProperties );
657+ } else {
658+ hidden .add ("action" );
659+ }
660+ } else if (action == Order .Action .RENEW ) {
661+ hidden .addAll (dosingProperties );
662+ hidden .addAll (discontinueProperties );
663+ if (inCurrentEncounter ) {
664+ hidden .addAll (orderableProperties );
665+ } else {
666+ hidden .add ("action" );
667+ }
668+ } else if (action == Order .Action .DISCONTINUE ) {
669+ hidden .addAll (dosingProperties );
670+ hidden .addAll (dispensingProperties );
671+ hidden .add ("orderReason" );
672+ hidden .add ("orderReasonNonCoded" );
673+ hidden .add ("duration" );
674+ hidden .add ("durationUnits" );
675+ if (inCurrentEncounter ) {
676+ hidden .addAll (orderableProperties );
677+ } else {
678+ hidden .add ("action" );
679+ }
680+ }
681+ return hidden ;
682+ }
683+
684+ /**
685+ * @return true if the order is currently active as of the given date
686+ */
687+ protected boolean isOrderActive (Order order , Date asOfDate ) {
688+ if (order .getDateActivated () != null && order .getDateActivated ().after (asOfDate )) {
689+ return false ;
690+ }
691+ Date effectiveStopDate = order .getEffectiveStopDate ();
692+ if (effectiveStopDate != null && !effectiveStopDate .after (asOfDate )) {
693+ return false ;
694+ }
695+ return true ;
696+ }
697+
461698 /**
462699 * @return the html to render for each found property widget
463700 */
464701 public String generateHtmlForWidget (String property , Widget w , Map <String , String > attrs , FormEntryContext context ) {
702+ return generateHtmlForWidget (property , w , attrs , context , null , false );
703+ }
704+
705+ /**
706+ * Renders a single order property field. When displayValue is non-null the field is rendered as
707+ * static text (VIEW mode); otherwise the interactive widget is used (ENTER/EDIT mode).
708+ */
709+ public String generateHtmlForWidget (String property , Widget w , Map <String , String > attrs , FormEntryContext context ,
710+ String displayValue , boolean hidden ) {
465711 StringBuilder ret = new StringBuilder ();
466712 String labelCode = attrs .getOrDefault (OrderTagHandler .LABEL_ATTRIBUTE , "htmlformentry.orders." + property );
467713 String label = translate (labelCode );
468- ret .append ("<div class=\" order-field order-" ).append (property ).append ("\" >" );
714+ ret .append ("<div class=\" order-field order-" ).append (property );
715+ if (hidden ) {
716+ ret .append (" order-field-hidden" );
717+ }
718+ ret .append ("\" >" );
469719 ret .append ("<div class=\" order-field-label order-" ).append (property ).append ("\" >" );
470720 ret .append (label );
471721 ret .append ("</div>" );
@@ -474,7 +724,9 @@ public String generateHtmlForWidget(String property, Widget w, Map<String, Strin
474724 ret .append (" order-field-radio-group" );
475725 }
476726 ret .append ("\" >" );
477- if (w != null ) {
727+ if (displayValue != null ) {
728+ ret .append (displayValue );
729+ } else if (w != null ) {
478730 ret .append (w .generateHtml (context ));
479731 ErrorWidget ew = context .getErrorWidget (w );
480732 if (ew != null ) {
0 commit comments