Details

    • Similar issues:
      KULRICE-10396Extending a bean that has ids on inner beans causes issues, need to define id on inner beans with component id property
      KULRICE-6831Inner scroll bar on firefox
      KULRICE-7639Edit, Copy, or View Agenda in sampleapp yields stacktrace
      KULRICE-2998Recreate the KIM views in the Rice database
      KULRICE-3339Facilitate KEW method for using Principal Id in an Inner Application Role (see NetworkIdRoleAttribute class)
      KULRICE-13147Inefficiencies in view lifecycle and rendering
      KULRICE-5981Breadcrumbs with single page views
      KULRICE-7873Optionally supress the view in breadcrumbs
      KULRICE-8556Progressive disclosure of group/fields within a checkbox or radio group - verify functionality
      KULRICE-6383Refactor view template
    • Epic Link:
    • Rice Module:
      KRAD
    • KRAD Feature Area:
      Document
    • Application Requirement:
      KS
    • KAI Review Status:
      Not Required
    • KTI Review Status:
      Not Required

      Description

      "The InnerView component allows a page or a component from another view to be rendered with the view that contains the component. This is useful for the following reasons:

      1. Content that is reused across many pages can be built in one view and reused. Prevents from having to include the data and controller methods for each view that needs it.
      2. Creation of views can be broken into multiple efforts of form/controllers/view. Valuable for team work.Note there is a restriction that the inner view have the same theme as the parent view. That is the same set of CSS and JavaScript files. If the inner view contains additional CSS or JavaScript files they will be ignored, which might cause the view to malfunction."

      1. kulrice-8862-inner_view_refresh_support.patch
        5 kB
        Mark Fyffe
      2. kulrice-8862.patch
        3 kB
        Mark Fyffe
      3. kulrice-8862.patch
        8 kB
        Mark Fyffe
      1. multiple inner views in iGPS.jpg
        181 kB

        Issue Links

          Activity

          Hide
          Mark Fyffe (Inactive) added a comment - - edited

          Attaching a patch based on Rice 2.3.0-M3 for supporting inner views in KSAP 0.7. The way KSAP implements pop-up dialogs, a "popupForm" element is defined dynamically with an AJAX response then attached to a jquery-popover element. The content of the form is intended to be the full contents of the "kualiForm" <form> element on the dialog view to be rendered within the pop-up, but not the <form> element itself.

          As of Rice 2.3.0-M3 (I haven't yet checked later versions), there is no <div> wrapper providing clean and convenient access within the response page to all of the form's inner elements. Therefore, hidden inputs needed in order to point the inner view at a different controller and form from the page containing the popover are lost.

          This patch adds an "innerViewWrapperId" property to the PageGroup component, then when this property is set on the currentPage, renders a div with the provided ID directly inside the <form> element facilitating the use of a Uif-FormView child with multiple pages to drive pop-up dialogs on an unassociated page.

          For reference, the openPopup() and ksapAjaxSubmitForm() JS functions from KSAP 0.7 are below. These functions were taken directly from UW My Plan 1.5, and have only be modified from the UW version to deal with HTTP errors a little differently. The value passed in the getId parameter of the openPopup() function corresponds to the innerViewWrapperId property on the page component of the inner view.

          /**
           * Open a popup which loads via ajax a separate view's component
           *
           * @param getId - Id of the component from the separate view to select to insert into popup.
           * @param retrieveData - Object of data used to passed to generate the separate view.
           * @param formAction - The action param of the popup inner form.
           * @param popupStyle - Object of css styling to apply to the initial inner div of the popup (will be replaced with remote component)
           * @param popupOptions - Object of settings to pass to the Bubble Popup jQuery Plugin.
           * @param e - An object containing data that will be passed to the event handler.
           */
          function openPopup(getId, retrieveData, formAction, popupStyle, popupOptions, e) {
              stopEvent(e);
              fnCloseAllPopups();
          
              var popupOptionsDefault = {
                  themePath:"../ks-myplan/jquery-popover/jquerypopover-theme/",
                  manageMouseEvents:true,
                  selectable:true,
                  tail:{align:"middle", hidden:false},
                  position:"left",
                  align:"center",
                  alwaysVisible:false,
                  themeMargins:{total:"20px", difference:"5px"},
                  themeName:"myplan",
                  distance:"0px",
                  openingSpeed:5,
                  closingSpeed:5
              };
          
              var target = (e.currentTarget) ? e.currentTarget : e.srcElement;
              var popupItem = (typeof popupOptions.selector == "undefined") ? jQuery(target) : jQuery(target).parents(popupOptions.selector);
          
              if (!popupItem.HasPopOver()) popupItem.CreatePopOver({manageMouseEvents:false});
              var popupSettings = jQuery.extend(popupOptionsDefault, popupOptions);
              var popupHtml = jQuery('<div />').attr("id", "KSAP-Popover");
              if (popupStyle) {
                  jQuery.each(popupStyle, function (property, value) {
                      popupHtml.css(property, value);
                  });
              }
              popupSettings.innerHtml = popupHtml.wrap("<div>").parent().clone().html();
          
              popupItem.ShowPopOver(popupSettings, false);
              popupItem.FreezePopOver();
          
              var popupId = popupItem.GetPopOverID();
          
              fnPositionPopUp(popupId);
              clickOutsidePopOver(popupId, popupItem);
          
              var retrieveForm = '<form id="retrieveForm" action="' + retrieveData.action + '" method="post" />'
              jQuery("body").append(retrieveForm);
          
              var elementToBlock = jQuery("#KSAP-Popover");
          
              var successCallback = function (htmlContent) {
                  var component;
                  if (jQuery("#requestStatus", htmlContent).length <= 0) {
                      var popupForm = jQuery('<form />').attr("id", "popupForm").attr("action", formAction).attr("method", "post");
                      component = jQuery("#" + getId, htmlContent).wrap(popupForm).parent();
                  } else {
                      var pageId = jQuery("#pageId").val();
                      eval(jQuery("input[data-role='script'][data-for='" + pageId + "']", htmlContent).val().replace("#" + pageId, "body"));
                      var errorMessage = '<img src="/student/ks-myplan/images/pixel.gif" alt="" class="icon"><div class="message">' + jQuery("#plan_item_action_response_page", htmlContent).data(kradVariables.VALIDATION_MESSAGES).serverErrors[0] + '</div>';
                      component = jQuery("<div />").addClass("myplan-feedback error").html(errorMessage);
                  }
                  if (jQuery("#KSAP-Popover").length) {
                      popupItem.SetPopOverInnerHtml(component);
                      fnPositionPopUp(popupId);
                      if (popupOptions.close || typeof popupOptions.close === 'undefined') jQuery("#" + popupId + " .jquerypopover-innerHtml").append('<img src="../ks-myplan/images/btnClose.png" class="myplan-popup-close"/>');
                      jQuery("#" + popupId + " img.myplan-popup-close").on('click', function () {
                          popupItem.HidePopOver();
                          fnCloseAllPopups();
                      });
                  }
                  runHiddenScripts(getId);
                  elementToBlock.unblock();
              };
          
              ksapAjaxSubmitForm(retrieveData, successCallback, elementToBlock, "retrieveForm");
              jQuery("form#retrieveForm").remove();
          }
          
          /**
           *   Gathers information for submission to the controller via ajax
           *
           * @param data - Variables and data to be submitted to the controller
           * @param successCallback - Code block to run after a successful return from the controller
           * @param elementToBlock - The html object being effected by the controller call
           * @param formId - Id of the form the submit is being called on
           * @param blockingSettings - Settings for the html object
           */
          function ksapAjaxSubmitForm(data, successCallback, elementToBlock, formId, blockingSettings) {
          	data = ksapAdditionalFormData(data);
          
              var submitOptions = {
                  data:data,
                  success:function (response) {
                      var tempDiv = document.createElement('div');
                      tempDiv.innerHTML = response;
                      var hasError = checkForIncidentReport(response);
                      if (!hasError) successCallback(tempDiv);
                      jQuery("#formComplete").empty();
                  },
                  error:function(jqXHR, textStatus,
                          errorThrown) {
          	         hideLoading();
          	         showGrowl(textStatus + " "
          	             + errorThrown,
          	             "Error");
          	     },
          	     statusCode : {
          	         400 : function() {
          	             showGrowl(
          	                 "400 Bad Request",
          	                 "Fatal Error");
          	         },
          	         500 : function() {
          	             showGrowl(
          	                 "500 Internal Server Error",
          	                 "Fatal Error");
          	         }
          	     }
              };
          
              if (elementToBlock != null && elementToBlock.length) {
                  var elementBlockingOptions = {
                      beforeSend:function () {
                          if (elementToBlock.hasClass("unrendered")) {
                              elementToBlock.append('<img src="' + getConfigParam("kradImageLocation") + 'loader.gif" alt="Loading..." /> Loading...');
                              elementToBlock.show();
                          }
                          else {
                              var elementBlockingDefaults = {
                                  baseZ:500,
                                  message:'<img src="../ks-myplan/images/ajaxLoader16.gif" alt="loading..." />',
                                  fadeIn:0,
                                  fadeOut:0,
                                  overlayCSS:{
                                      backgroundColor:'#fff',
                                      opacity:0
                                  },
                                  css:{
                                      border:'none',
                                      width:'16px',
                                      top:'0px',
                                      left:'0px'
                                  }
                              };
                              elementToBlock.block(jQuery.extend(elementBlockingDefaults, blockingSettings));
                          }
                      },
                      complete:function () {
                          elementToBlock.unblock();
                      },
                      error:function(jqXHR, textStatus,
                              errorThrown) {
          	   	         hideLoading();
          		         showGrowl(textStatus + " "
          		             + errorThrown,
          		             "Error");
                          if (elementToBlock.hasClass("unrendered")) {
                              elementToBlock.hide();
                          }
                          else {
                              elementToBlock.unblock();
                          }
                      }
                  };
              }
              jQuery.extend(submitOptions, elementBlockingOptions);
              var form = jQuery("#" + ((formId) ? formId : "kualiForm"));
              form.ajaxSubmit(submitOptions);
          }
          

          I hope this helps as a starting point for implementing this feature!

          Best wishes,
          Mark

          Show
          Mark Fyffe (Inactive) added a comment - - edited Attaching a patch based on Rice 2.3.0-M3 for supporting inner views in KSAP 0.7. The way KSAP implements pop-up dialogs, a "popupForm" element is defined dynamically with an AJAX response then attached to a jquery-popover element. The content of the form is intended to be the full contents of the "kualiForm" <form> element on the dialog view to be rendered within the pop-up, but not the <form> element itself. As of Rice 2.3.0-M3 (I haven't yet checked later versions), there is no <div> wrapper providing clean and convenient access within the response page to all of the form's inner elements. Therefore, hidden inputs needed in order to point the inner view at a different controller and form from the page containing the popover are lost. This patch adds an "innerViewWrapperId" property to the PageGroup component, then when this property is set on the currentPage, renders a div with the provided ID directly inside the <form> element facilitating the use of a Uif-FormView child with multiple pages to drive pop-up dialogs on an unassociated page. For reference, the openPopup() and ksapAjaxSubmitForm() JS functions from KSAP 0.7 are below. These functions were taken directly from UW My Plan 1.5, and have only be modified from the UW version to deal with HTTP errors a little differently. The value passed in the getId parameter of the openPopup() function corresponds to the innerViewWrapperId property on the page component of the inner view. /** * Open a popup which loads via ajax a separate view's component * * @param getId - Id of the component from the separate view to select to insert into popup. * @param retrieveData - Object of data used to passed to generate the separate view. * @param formAction - The action param of the popup inner form. * @param popupStyle - Object of css styling to apply to the initial inner div of the popup (will be replaced with remote component) * @param popupOptions - Object of settings to pass to the Bubble Popup jQuery Plugin. * @param e - An object containing data that will be passed to the event handler. */ function openPopup(getId, retrieveData, formAction, popupStyle, popupOptions, e) { stopEvent(e); fnCloseAllPopups(); var popupOptionsDefault = { themePath: "../ks-myplan/jquery-popover/jquerypopover-theme/" , manageMouseEvents: true , selectable: true , tail:{align: "middle" , hidden: false }, position: "left" , align: "center" , alwaysVisible: false , themeMargins:{total: "20px" , difference: "5px" }, themeName: "myplan" , distance: "0px" , openingSpeed:5, closingSpeed:5 }; var target = (e.currentTarget) ? e.currentTarget : e.srcElement; var popupItem = (typeof popupOptions.selector == "undefined" ) ? jQuery(target) : jQuery(target).parents(popupOptions.selector); if (!popupItem.HasPopOver()) popupItem.CreatePopOver({manageMouseEvents: false }); var popupSettings = jQuery.extend(popupOptionsDefault, popupOptions); var popupHtml = jQuery('<div />').attr( "id" , "KSAP-Popover" ); if (popupStyle) { jQuery.each(popupStyle, function (property, value) { popupHtml.css(property, value); }); } popupSettings.innerHtml = popupHtml.wrap( "<div>" ).parent().clone().html(); popupItem.ShowPopOver(popupSettings, false ); popupItem.FreezePopOver(); var popupId = popupItem.GetPopOverID(); fnPositionPopUp(popupId); clickOutsidePopOver(popupId, popupItem); var retrieveForm = '<form id= "retrieveForm" action= "' + retrieveData.action + '" method= "post" />' jQuery( "body" ).append(retrieveForm); var elementToBlock = jQuery( "#KSAP-Popover" ); var successCallback = function (htmlContent) { var component; if (jQuery( "#requestStatus" , htmlContent).length <= 0) { var popupForm = jQuery('<form />').attr( "id" , "popupForm" ).attr( "action" , formAction).attr( "method" , "post" ); component = jQuery( "#" + getId, htmlContent).wrap(popupForm).parent(); } else { var pageId = jQuery( "#pageId" ).val(); eval(jQuery( "input[data-role='script'][data- for ='" + pageId + "']" , htmlContent).val().replace( "#" + pageId, "body" )); var errorMessage = '<img src= "/student/ks-myplan/images/pixel.gif" alt= "" class=" icon "><div class=" message ">' + jQuery(" #plan_item_action_response_page", htmlContent).data(kradVariables.VALIDATION_MESSAGES).serverErrors[0] + '</div>'; component = jQuery( "<div />" ).addClass( "myplan-feedback error" ).html(errorMessage); } if (jQuery( "#KSAP-Popover" ).length) { popupItem.SetPopOverInnerHtml(component); fnPositionPopUp(popupId); if (popupOptions.close || typeof popupOptions.close === 'undefined') jQuery( "#" + popupId + " .jquerypopover-innerHtml" ).append('<img src= "../ks-myplan/images/btnClose.png" class= "myplan-popup-close" />'); jQuery( "#" + popupId + " img.myplan-popup-close" ).on('click', function () { popupItem.HidePopOver(); fnCloseAllPopups(); }); } runHiddenScripts(getId); elementToBlock.unblock(); }; ksapAjaxSubmitForm(retrieveData, successCallback, elementToBlock, "retrieveForm" ); jQuery( "form#retrieveForm" ).remove(); } /** * Gathers information for submission to the controller via ajax * * @param data - Variables and data to be submitted to the controller * @param successCallback - Code block to run after a successful return from the controller * @param elementToBlock - The html object being effected by the controller call * @param formId - Id of the form the submit is being called on * @param blockingSettings - Settings for the html object */ function ksapAjaxSubmitForm(data, successCallback, elementToBlock, formId, blockingSettings) { data = ksapAdditionalFormData(data); var submitOptions = { data:data, success:function (response) { var tempDiv = document.createElement('div'); tempDiv.innerHTML = response; var hasError = checkForIncidentReport(response); if (!hasError) successCallback(tempDiv); jQuery( "#formComplete" ).empty(); }, error:function(jqXHR, textStatus, errorThrown) { hideLoading(); showGrowl(textStatus + " " + errorThrown, "Error" ); }, statusCode : { 400 : function() { showGrowl( "400 Bad Request" , "Fatal Error" ); }, 500 : function() { showGrowl( "500 Internal Server Error" , "Fatal Error" ); } } }; if (elementToBlock != null && elementToBlock.length) { var elementBlockingOptions = { beforeSend:function () { if (elementToBlock.hasClass( "unrendered" )) { elementToBlock.append('<img src= "' + getConfigParam(" kradImageLocation ") + 'loader.gif" alt= "Loading..." /> Loading...'); elementToBlock.show(); } else { var elementBlockingDefaults = { baseZ:500, message:'<img src= "../ks-myplan/images/ajaxLoader16.gif" alt= "loading..." />', fadeIn:0, fadeOut:0, overlayCSS:{ backgroundColor:'#fff', opacity:0 }, css:{ border:'none', width:'16px', top:'0px', left:'0px' } }; elementToBlock.block(jQuery.extend(elementBlockingDefaults, blockingSettings)); } }, complete:function () { elementToBlock.unblock(); }, error:function(jqXHR, textStatus, errorThrown) { hideLoading(); showGrowl(textStatus + " " + errorThrown, "Error" ); if (elementToBlock.hasClass( "unrendered" )) { elementToBlock.hide(); } else { elementToBlock.unblock(); } } }; } jQuery.extend(submitOptions, elementBlockingOptions); var form = jQuery( "#" + ((formId) ? formId : "kualiForm" )); form.ajaxSubmit(submitOptions); } I hope this helps as a starting point for implementing this feature! Best wishes, Mark
          Hide
          Mark Fyffe (Inactive) added a comment -

          Note that the openPopup method quoted in a previous comment on this issue has been modified to enforce UTF-8 encoding on dynamically created the popup form.

          If the inner view functionality in KRAD will include a standard means for popup forms based on the original KSAP code, it will be important to include the accept-charset attribute on the form.

          Show
          Mark Fyffe (Inactive) added a comment - Note that the openPopup method quoted in a previous comment on this issue has been modified to enforce UTF-8 encoding on dynamically created the popup form. If the inner view functionality in KRAD will include a standard means for popup forms based on the original KSAP code, it will be important to include the accept-charset attribute on the form.
          Hide
          Mark Fyffe (Inactive) added a comment -

          Added another patch used by the IU Roadmap project to support dynamic page/component refreshes when posting from an inner view rather than the main view on the page.

          This page adds a "formName" attribute to the KRAD Request object, and uses that attribute instead of the hard-coded "kualiForm" to identify the form to post on the page. We have also added formName as a attribute to the retrieveComponent() function in krad.actions.js

          Hope this patch helps in the effort to arrive at a final solution for this issue!

          Show
          Mark Fyffe (Inactive) added a comment - Added another patch used by the IU Roadmap project to support dynamic page/component refreshes when posting from an inner view rather than the main view on the page. This page adds a "formName" attribute to the KRAD Request object, and uses that attribute instead of the hard-coded "kualiForm" to identify the form to post on the page. We have also added formName as a attribute to the retrieveComponent() function in krad.actions.js Hope this patch helps in the effort to arrive at a final solution for this issue!
          Hide
          Mark Fyffe (Inactive) added a comment -

          Updated latest patch to support update-page as well as update-component.

          Show
          Mark Fyffe (Inactive) added a comment - Updated latest patch to support update-page as well as update-component.
          Hide
          Larry Symms added a comment -

          Would like to pull this off the backlog for 2.5 or 2.6. Should be an easy fix given the patches from Mark.

          Show
          Larry Symms added a comment - Would like to pull this off the backlog for 2.5 or 2.6. Should be an easy fix given the patches from Mark.
          Hide
          Mark Fyffe (Inactive) added a comment -

          We have been working to update the script side of inner view for the Degree Map project at IU, and have arrived at a solution that works in a more flexible fashion than the original My Plan code. This was developed on our Rice 2.3 version, and we'll be moving it to Rice 2.4 as a KRAD patch in the near future.

          The inner view wrapper patch is no longer required, but the request/response patch is. We have not yet tested with update-component or update-page functions, but will cover that scenario as we work through the iGPS upgrade to Rice 2.4 over the next month.

          The updated script for Degree Map is below. Note that this is condensed as a first pass - the full contribution will be broken down further, and include a component support on the Java side.

          A few other key points regarding this solution:

          • Scripts from myplan.widgets.js have been deprecated, and consolidated into a single JS object: DmPopup.
            • To create an inner view popup, construct DmPopup, customize params as needed, then call open().
            • To close the popup, call close().
          • DmPopup is intended to be constructed only from the actionScript defined by a child of Uif-Action
          • Multiple inner views may exist at the same time, however...
            • In this implementation only one inner view per page ID may exist at a time.
            • While opening the second inner view with the same page ID, the first will be closed and removed.
            • The one-per-pageId restriction may be lifted through rewriting IDs on the inner view content prior to attaching to the popover (more on that below). This change is planned for the next iGPS revision.
          • Multiple inner views per "outer" view is the key element driving this revision: for Degree Map we have one inner view that pulls up a LightTable of courses then another invoked from the rows in the LightTable for pulling up a summary of the course. The summary view is also linked directly from the requirement, depending on whether or not a single course satisfies the requirement or if there is a choice. See attached screenshot.

          The script defining DmPopup:

          function DmPopup(innerViewId, action, data, e) {
          	this.wrapperId = innerViewId + "_popup";
          	this.formId = innerViewId + "_form";
          
          	this.action = action;
          	this.data = jQuery.extend(data, {
          		pageId : innerViewId + "_page"
          	});
          
          	this.target = (e.currentTarget) ? e.currentTarget : e.srcElement;
          
          	// TODO: move jquery-popover to KRAD, or switch to delivered equivalent
          	this.appUrl = getConfigParam("kradUrl") + '/..';
          	this.closeImageUrl = this.appUrl + '/ks-myplan/images/btnClose.png';
          	this.loadingImageUrl = this.appUrl + '/ks-myplan/images/ajaxLoader16.gif';
          	this.innerViewOptions.themePath = this.appUrl
          			+ '/ks-myplan/jquery-popover/jquerypopover-theme/';
          }
          
          DmPopup.prototype = {
          	innerViewOptions : {
          		manageMouseEvents : true,
          		selectable : true,
          		tail : {
          			align : 'middle',
          			hidden : true
          		},
          		position : 'right',
          		align : 'top',
          		close : true,
          		alwaysVisible : false,
          		themeMargins : {
          			total : '20px',
          			difference : '5px'
          		},
          		sticky : true,
          		themeName : 'transparent',
          		distance : '0px',
          		openingSpeed : 5,
          		closingSpeed : 5,
          	},
          
          	open : function() {
          		var _this = this;
          		this.close();
          
          		var target = jQuery(this.target);
          		if (!target.HasPopOver())
          			target.CreatePopOver({
          				manageMouseEvents : false
          			});
          
          		var loadingImage = jQuery('<img />')
          		loadingImage.attr('src', this.loadingImageUrl);
          		loadingImage.attr('alt', 'Loading...');
          		loadingImage.addClass('arm-dm-popup-loading');
          
          		var wrapper = jQuery('<div />');
          		wrapper.attr('id', this.wrapperId);
          		wrapper.addClass('arm-dm-popup');
          		wrapper.append(loadingImage);
          
          		this.innerViewOptions.innerHtml = wrapper.wrap('<div>').parent()
          				.clone().html();
          		target.ShowPopOver(this.innerViewOptions, false);
          
          		var submitData = ksapAdditionalFormData(this.data);
          		var submitOptions = {
          			data : submitData,
          
          			success : function(response) {
          				var closeImage = jQuery('<img />');
          				closeImage.attr('src', _this.closeImageUrl);
          				closeImage.attr('alt', 'Close');
          				closeImage.addClass('arm-dm-popup-close');
          
          				var innerViewForm = jQuery("#kualiForm", response);
          				innerViewForm.attr("id", _this.formId);
          
          				// TODO: refactor planner, then remove these workarounds
          				innerViewForm.find(".myplan-view").removeClass("myplan-view");
          				innerViewForm.find(".ks-Popover").removeClass("ks-Popover");
          
          				var innerView = jQuery('<div />');
          				innerView.addClass('arm-dm-popup-inner');
          				innerView.append(innerViewForm);
          
          				var innerViewWrapper = jQuery('#' + _this.wrapperId);
          				innerViewWrapper.children('.arm-dm-popup-loading').remove();
          				innerViewWrapper.append(innerView);
          				innerViewWrapper.append(closeImage);
          
          				jQuery('#' + _this.wrapperId + ' .arm-dm-popup-close').click(
          						function() {
          							_this.close();
          						});
          
          				// TODO: make KRAD not jump to the top when loading inner view
          				var body = jQuery('body');
          				var bodyWidth = body.width() - 50;
          				var oScrollTop = body.scrollTop();
          				var oScrollLeft = body.scrollLeft();
          
          				runHiddenScripts(_this.formId);
          
          				body.scrollTop(oScrollTop);
          				body.scrollLeft(oScrollLeft);
          
          				var innerViewOuter = jQuery('#' + target.GetPopOverID());
          
          				var innerViewWidth = innerViewOuter.width();
          				var innerViewRight = innerViewOuter.position().left
          						+ innerViewWidth;
          
          				if (innerViewRight > bodyWidth)
          					innerViewOuter.css('left', (bodyWidth - innerViewWidth)
          							+ 'px');
          
          				_this.innerView = innerView;
          			},
          
          			error : function(jqXHR, textStatus, errorThrown) {
          				_this.close();
          			},
          
          			statusCode : {
          				400 : function() {
          					showGrowl("400 Bad Request", "Fatal Error");
          				},
          				500 : function() {
          					showGrowl("500 Internal Server Error", "Fatal Error");
          				}
          			}
          		};
          
          		var aform = jQuery('<form />');
          		aform.attr('id', 'dm_ajax_form');
          		aform.attr('action', this.action);
          		aform.attr('method', 'POST');
          		aform.ajaxSubmit(submitOptions);
          	},
          
          	close : function() {
          		var wrapper = jQuery(this.target);
          		wrapper.HidePopOver();
          		wrapper.RemovePopOver();
          		jQuery('#' + this.formId).parents("div.jquerypopover").remove();
          		this.innerView = null;
          	}
          
          };
          

          For reference, the action component that creates the inner view is defined below:

          <bean parent="Uif-ActionLinkField" p:label="Courses"
          	p:actionLabel="@\{#line.courseLink\}"
          	p:actionScript="sdm_courseLink('@\{#line.refObjectId\}','@\{#line.displayTerm\}',@\{#line.singleCourse\},e)"
          	p:render="@\{#line.courseLink ne null\}" />
          

          And the function that receives the click event to create the inner view popover:

          function sdm_courseLink(refid, termId, singleCourse, e) {
          	var popup;
          	if (singleCourse)
          		popup = new DmPopup("planner_course_summary",
          				getConfigParam("kradUrl")+"/../myplan/planner",
          				{ methodToCall : "startDialog",
          					"courseId" : refid,
          					"termId" : termId
          				}, e);
          	else
          		popup = new DmPopup("sdm_coursegrid", "coursegrid",
          				{ methodToCall : "startDialog",
          					"placeholderId" : refid,
          					"termId" : termId
          				}, e);
          	stopEvent(e);
          	popup.open();
          }
          

          For full lifecycle operation, there will be some notable steps that need to be taken beyond the client-only approach we've followed so far.

          • The name "KualiForm" is hard-coded throughout KRAD. This may be a non-issue, but will need to be confirmed. While one form per request is perfectly reasonable, care will need to be taken to ensure that the correct form is posted for update-component, update-page, and similar.
          • In the current implementation, it is on the application developer to provide unique IDs. In Rice 2.3 IDs are sequential, so collisions are unavoidable - though dynamic IDs are rarely referred to in iGPS so we've been able to avoid side effects so far. Rice 2.4 will address this through a more sophisticated ID generation approach, but there is still potential for collisions. Client-side rewriting will be insufficient, since that may break update-component callbacks. There will need to be a provision in the ID generation algorithm on the server-side to recognize an inner view and ensure that no duplicate IDs conflicting with the outer view are delivered to the page.
          • Scripts assuming one view per browser window will need to be cleaned up. We are aware of at least one: the script that jumps to the top of the page on load. More problematic scripts may exist and need to be checked for.

          On a side note, it'll be interesting to see if/how next generation UIF components defined by UXI improve the delivery of inner views.

          I hope these comments and sample code are useful!

          Show
          Mark Fyffe (Inactive) added a comment - We have been working to update the script side of inner view for the Degree Map project at IU, and have arrived at a solution that works in a more flexible fashion than the original My Plan code. This was developed on our Rice 2.3 version, and we'll be moving it to Rice 2.4 as a KRAD patch in the near future. The inner view wrapper patch is no longer required, but the request/response patch is. We have not yet tested with update-component or update-page functions, but will cover that scenario as we work through the iGPS upgrade to Rice 2.4 over the next month. The updated script for Degree Map is below. Note that this is condensed as a first pass - the full contribution will be broken down further, and include a component support on the Java side. A few other key points regarding this solution: Scripts from myplan.widgets.js have been deprecated, and consolidated into a single JS object: DmPopup. To create an inner view popup, construct DmPopup, customize params as needed, then call open(). To close the popup, call close(). DmPopup is intended to be constructed only from the actionScript defined by a child of Uif-Action Multiple inner views may exist at the same time, however... In this implementation only one inner view per page ID may exist at a time. While opening the second inner view with the same page ID, the first will be closed and removed. The one-per-pageId restriction may be lifted through rewriting IDs on the inner view content prior to attaching to the popover (more on that below). This change is planned for the next iGPS revision. Multiple inner views per "outer" view is the key element driving this revision: for Degree Map we have one inner view that pulls up a LightTable of courses then another invoked from the rows in the LightTable for pulling up a summary of the course. The summary view is also linked directly from the requirement, depending on whether or not a single course satisfies the requirement or if there is a choice. See attached screenshot. The script defining DmPopup: function DmPopup(innerViewId, action, data, e) { this .wrapperId = innerViewId + "_popup" ; this .formId = innerViewId + "_form" ; this .action = action; this .data = jQuery.extend(data, { pageId : innerViewId + "_page" }); this .target = (e.currentTarget) ? e.currentTarget : e.srcElement; // TODO: move jquery-popover to KRAD, or switch to delivered equivalent this .appUrl = getConfigParam( "kradUrl" ) + '/..'; this .closeImageUrl = this .appUrl + '/ks-myplan/images/btnClose.png'; this .loadingImageUrl = this .appUrl + '/ks-myplan/images/ajaxLoader16.gif'; this .innerViewOptions.themePath = this .appUrl + '/ks-myplan/jquery-popover/jquerypopover-theme/'; } DmPopup.prototype = { innerViewOptions : { manageMouseEvents : true , selectable : true , tail : { align : 'middle', hidden : true }, position : 'right', align : 'top', close : true , alwaysVisible : false , themeMargins : { total : '20px', difference : '5px' }, sticky : true , themeName : 'transparent', distance : '0px', openingSpeed : 5, closingSpeed : 5, }, open : function() { var _this = this ; this .close(); var target = jQuery( this .target); if (!target.HasPopOver()) target.CreatePopOver({ manageMouseEvents : false }); var loadingImage = jQuery('<img />') loadingImage.attr('src', this .loadingImageUrl); loadingImage.attr('alt', 'Loading...'); loadingImage.addClass('arm-dm-popup-loading'); var wrapper = jQuery('<div />'); wrapper.attr('id', this .wrapperId); wrapper.addClass('arm-dm-popup'); wrapper.append(loadingImage); this .innerViewOptions.innerHtml = wrapper.wrap('<div>').parent() .clone().html(); target.ShowPopOver( this .innerViewOptions, false ); var submitData = ksapAdditionalFormData( this .data); var submitOptions = { data : submitData, success : function(response) { var closeImage = jQuery('<img />'); closeImage.attr('src', _this.closeImageUrl); closeImage.attr('alt', 'Close'); closeImage.addClass('arm-dm-popup-close'); var innerViewForm = jQuery( "#kualiForm" , response); innerViewForm.attr( "id" , _this.formId); // TODO: refactor planner, then remove these workarounds innerViewForm.find( ".myplan-view" ).removeClass( "myplan-view" ); innerViewForm.find( ".ks-Popover" ).removeClass( "ks-Popover" ); var innerView = jQuery('<div />'); innerView.addClass('arm-dm-popup- inner '); innerView.append(innerViewForm); var innerViewWrapper = jQuery('#' + _this.wrapperId); innerViewWrapper.children('.arm-dm-popup-loading').remove(); innerViewWrapper.append(innerView); innerViewWrapper.append(closeImage); jQuery('#' + _this.wrapperId + ' .arm-dm-popup-close').click( function() { _this.close(); }); // TODO: make KRAD not jump to the top when loading inner view var body = jQuery('body'); var bodyWidth = body.width() - 50; var oScrollTop = body.scrollTop(); var oScrollLeft = body.scrollLeft(); runHiddenScripts(_this.formId); body.scrollTop(oScrollTop); body.scrollLeft(oScrollLeft); var innerViewOuter = jQuery('#' + target.GetPopOverID()); var innerViewWidth = innerViewOuter.width(); var innerViewRight = innerViewOuter.position().left + innerViewWidth; if (innerViewRight > bodyWidth) innerViewOuter.css('left', (bodyWidth - innerViewWidth) + 'px'); _this.innerView = innerView; }, error : function(jqXHR, textStatus, errorThrown) { _this.close(); }, statusCode : { 400 : function() { showGrowl( "400 Bad Request" , "Fatal Error" ); }, 500 : function() { showGrowl( "500 Internal Server Error" , "Fatal Error" ); } } }; var aform = jQuery('<form />'); aform.attr('id', 'dm_ajax_form'); aform.attr('action', this .action); aform.attr('method', 'POST'); aform.ajaxSubmit(submitOptions); }, close : function() { var wrapper = jQuery( this .target); wrapper.HidePopOver(); wrapper.RemovePopOver(); jQuery('#' + this .formId).parents( "div.jquerypopover" ).remove(); this .innerView = null ; } }; For reference, the action component that creates the inner view is defined below: <bean parent= "Uif-ActionLinkField" p:label= "Courses" p:actionLabel= "@\{#line.courseLink\}" p:actionScript= "sdm_courseLink('@\{#line.refObjectId\}','@\{#line.displayTerm\}',@\{#line.singleCourse\},e)" p:render= "@\{#line.courseLink ne null \}" /> And the function that receives the click event to create the inner view popover: function sdm_courseLink(refid, termId, singleCourse, e) { var popup; if (singleCourse) popup = new DmPopup( "planner_course_summary" , getConfigParam( "kradUrl" )+ "/../myplan/planner" , { methodToCall : "startDialog" , "courseId" : refid, "termId" : termId }, e); else popup = new DmPopup( "sdm_coursegrid" , "coursegrid" , { methodToCall : "startDialog" , "placeholderId" : refid, "termId" : termId }, e); stopEvent(e); popup.open(); } For full lifecycle operation, there will be some notable steps that need to be taken beyond the client-only approach we've followed so far. The name "KualiForm" is hard-coded throughout KRAD. This may be a non-issue, but will need to be confirmed. While one form per request is perfectly reasonable, care will need to be taken to ensure that the correct form is posted for update-component, update-page, and similar. In the current implementation, it is on the application developer to provide unique IDs. In Rice 2.3 IDs are sequential, so collisions are unavoidable - though dynamic IDs are rarely referred to in iGPS so we've been able to avoid side effects so far. Rice 2.4 will address this through a more sophisticated ID generation approach, but there is still potential for collisions. Client-side rewriting will be insufficient, since that may break update-component callbacks. There will need to be a provision in the ID generation algorithm on the server-side to recognize an inner view and ensure that no duplicate IDs conflicting with the outer view are delivered to the page. Scripts assuming one view per browser window will need to be cleaned up. We are aware of at least one: the script that jumps to the top of the page on load. More problematic scripts may exist and need to be checked for. On a side note, it'll be interesting to see if/how next generation UIF components defined by UXI improve the delivery of inner views. I hope these comments and sample code are useful!
          Hide
          Mark Fyffe (Inactive) added a comment -

          Updated patch file for Rice 2.4.2

          Show
          Mark Fyffe (Inactive) added a comment - Updated patch file for Rice 2.4.2

            People

            • Assignee:
              Unassigned
              Reporter:
              William Washington (Inactive)
            • Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

              Dates

              • Created:
                Updated:

                Time Tracking

                Estimated:
                Original Estimate - 3 weeks
                3w
                Remaining:
                Remaining Estimate - 3 weeks
                3w
                Logged:
                Time Spent - Not Specified
                Not Specified

                  Structure Helper Panel