Selaa lähdekoodia

Customer portal : Sticky form button when form is to long to be fully displayed in the screen

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@4077 a333f486-631f-4898-b8df-5754b55c2be0
glajarige 9 vuotta sitten
vanhempi
commit
46d99a30f6

+ 48 - 5
datamodels/2.x/itop-portal-base/portal/src/views/bricks/object/mode_create.html.twig

@@ -2,6 +2,7 @@
 {# Object brick create layout #}
 {# Object brick create layout #}
 
 
 {% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_form' %}
 {% set sFormId = (form.id is defined and form.id is not null) ? form.id : 'object_form' %}
+{% set sFormIdSanitized = sFormId|replace({'-': ''}) %}
 {% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
 {% set tIsModal = (tIsModal is defined and tIsModal == true) ? true : false %}
 
 
 <form id="{{ sFormId }}" method="POST" action="{{ form.renderer.GetEndpoint()|raw }}">
 <form id="{{ sFormId }}" method="POST" action="{{ form.renderer.GetEndpoint()|raw }}">
@@ -43,23 +44,65 @@
 
 
 <script type="text/javascript">
 <script type="text/javascript">
 	$(document).ready(function(){
 	$(document).ready(function(){
-		var oFieldSet = $('#{{ sFormId }} > .form_fields').field_set({{ form.fieldset|json_encode()|raw }});
-		
+		// Form field set declaration
+		var oFieldSet_{{ sFormIdSanitized }} = $('#{{ sFormId }} > .form_fields').field_set({{ form.fieldset|json_encode()|raw }});
+		// Form handler declaration
 		$('#{{ sFormId }}').portal_form_handler({
 		$('#{{ sFormId }}').portal_form_handler({
 			formmanager_class: "{{ form.formmanager_class|escape('js') }}",
 			formmanager_class: "{{ form.formmanager_class|escape('js') }}",
 			formmanager_data: {{ form.formmanager_data|json_encode()|raw }},
 			formmanager_data: {{ form.formmanager_data|json_encode()|raw }},
-			field_set: oFieldSet,
+			field_set: oFieldSet_{{ sFormIdSanitized }},
 			submit_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_submit, .form_btn_transition'),
 			submit_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_submit, .form_btn_transition'),
 			cancel_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_cancel'),
 			cancel_btn_selector: $('#{{ sFormId }}').parent().find('.form_btn_cancel'),
 			submit_url: {% if form.submit_callback is not null %}"{{ form.submit_callback }}"{% else %}null{% endif %},
 			submit_url: {% if form.submit_callback is not null %}"{{ form.submit_callback }}"{% else %}null{% endif %},
 			cancel_url: {% if form.cancel_callback is not null %}"{{ form.cancel_callback }}"{% else %}null{% endif %},
 			cancel_url: {% if form.cancel_callback is not null %}"{{ form.cancel_callback }}"{% else %}null{% endif %},
 			endpoint: "{{ form.renderer.GetEndpoint()|raw }}",
 			endpoint: "{{ form.renderer.GetEndpoint()|raw }}",
-			is_modal: {% if tIsModal is defined and tIsModal == true %}true{% else %}false{% endif %}
+			is_modal: {% if tIsModal == true %}true{% else %}false{% endif %}
+		});
+		
+		// Sticky buttons handler
+		// Note : This pattern if to prevent performance issues
+		// - Cloning buttons
+		var oNormalRegularButtons_{{ sFormIdSanitized }} = $('#{{ sFormId }} .form_btn_regular');
+		var oStickyRegularButtons_{{ sFormIdSanitized }} = oNormalRegularButtons_{{ sFormIdSanitized }}.clone(true, true);
+		oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('sticky');
+		{% if tIsModal == true %}
+			$('#{{ sFormId }}').closest('.modal').append(oStickyRegularButtons_{{ sFormIdSanitized }});
+		{% else %}
+			$('#{{ sFormId }}').closest('#main-content').append(oStickyRegularButtons_{{ sFormIdSanitized }});
+		{% endif %}
+		// - Global timeout for any
+		var oScrollTimeout;
+		// - Scroll handler
+		scrollHandler_{{ sFormIdSanitized }} = function () {
+			if($('#{{ sFormId }} .form_buttons').visible())
+			{
+				oStickyRegularButtons_{{ sFormIdSanitized }}.addClass('closed');
+			}
+			else
+			{
+				oStickyRegularButtons_{{ sFormIdSanitized }}.removeClass('closed');
+			}
+		};
+		// - Event binding
+		$({% if tIsModal == true %}'.modal.in'{% else %}window{% endif %}).off('scroll').on('scroll', function () {
+			if (oScrollTimeout) {
+				// Clear the timeout, if one is pending
+				clearTimeout(oScrollTimeout);
+				oScrollTimeout = null;
+			}
+			oScrollTimeout = setTimeout(scrollHandler_{{ sFormIdSanitized }}, 50);
 		});
 		});
+		// - First time call
+		scrollHandler_{{ sFormIdSanitized }}();
 		
 		
-		{% if tIsModal is defined and tIsModal == true %}
+		{% if tIsModal == true %}
+			// Scroll top (because sometimes when several modals have been opened)
 			$('#{{ sFormId }}').closest('.modal').scrollTop(0);
 			$('#{{ sFormId }}').closest('.modal').scrollTop(0);
 			$('#{{ sFormId }}').closest('.modal').find('.modal-footer').hide();
 			$('#{{ sFormId }}').closest('.modal').find('.modal-footer').hide();
+			// Remove sticky button when closing modal
+			$('#{{ sFormId }}').closest('.modal').on('hidden.bs.modal', function () {
+				oStickyRegularButtons_{{ sFormIdSanitized }}.remove();
+			});
 		{% endif %}
 		{% endif %}
 	});
 	});
 </script>
 </script>

+ 5 - 3
datamodels/2.x/itop-portal-base/portal/src/views/layout.html.twig

@@ -58,6 +58,8 @@
 		<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.fileupload.js"></script>
 		<script type="text/javascript" src="{{ app['combodo.absolute_url'] }}js/jquery.fileupload.js"></script>
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/js/bootstrap.min.js"></script>
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/bootstrap/js/bootstrap.min.js"></script>
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/latinise/latinise.min.js"></script>
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/latinise/latinise.min.js"></script>
+		{# Visible.js to check if an element is visible on screen #}
+		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/jquery-visible/js/jquery.visible.min.js"></script>
 		{# Moment.js #}
 		{# Moment.js #}
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/moment/js/moment.min.js"></script>
 		<script type="text/javascript" src="{{ app['combodo.portal.base.absolute_url'] }}lib/moment/js/moment.min.js"></script>
 		{# Datatables #}
 		{# Datatables #}
@@ -209,7 +211,7 @@
 				<div class="logo">
 				<div class="logo">
 					{# This is a debug helper to know in which screen size we are #}
 					{# This is a debug helper to know in which screen size we are #}
 					{% if app['debug'] %}
 					{% if app['debug'] %}
-						<div>Debug : Taille <span class="hidden-sm hidden-md hidden-lg">XS</span><span class="hidden-xs hidden-md hidden-lg">SM</span><span class="hidden-xs hidden-sm hidden-lg">MD</span><span class="hidden-xs hidden-sm hidden-md">LG</span></div>
+						<div style="position: fixed; bottom: 0px; left: 0px; z-index: 9999;">Debug : Taille <span class="hidden-sm hidden-md hidden-lg">XS</span><span class="hidden-xs hidden-md hidden-lg">SM</span><span class="hidden-xs hidden-sm hidden-lg">MD</span><span class="hidden-xs hidden-sm hidden-md">LG</span></div>
 					{% endif %}
 					{% endif %}
 					<a href="{{ app.url_generator.generate('p_home') }}" title="{{ app['combodo.portal.instance.conf'].properties.name|dict_s }}">
 					<a href="{{ app.url_generator.generate('p_home') }}" title="{{ app['combodo.portal.instance.conf'].properties.name|dict_s }}">
 						<img src="{{ app['combodo.portal.instance.conf'].properties.logo }}" alt="{{ app['combodo.portal.instance.conf'].properties.name|dict_s }}" />
 						<img src="{{ app['combodo.portal.instance.conf'].properties.logo }}" alt="{{ app['combodo.portal.instance.conf'].properties.name|dict_s }}" />
@@ -223,12 +225,12 @@
 		<div class="container-fluid" id="main-wrapper">
 		<div class="container-fluid" id="main-wrapper">
 			<div class="row">
 			<div class="row">
 				<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
 				<div class="col-xs-12 col-sm-9 col-md-10 col-sm-offset-3 col-md-offset-2">
-					<section class="row row-eq-height-sm" id="main-header">
+					<section class="row" id="main-header">
 						{% block pMainHeader %}
 						{% block pMainHeader %}
 						{% endblock %}
 						{% endblock %}
 					</section>
 					</section>
 
 
-					<section class="row row-eq-height-sm" id="main-content">
+					<section class="row" id="main-content">
 						{% block pMainContent %}
 						{% block pMainContent %}
 						{% endblock %}
 						{% endblock %}
 					</section>
 					</section>

+ 102 - 62
datamodels/2.x/itop-portal-base/portal/web/css/portal.css

@@ -638,18 +638,6 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 	bottom: 0px;
 	bottom: 0px;
 	height: 0px;
 	height: 0px;
 }
 }
-#drag_overlay.drag_in{
-	animation: show-drop-zone 0.3s ease-out forwards;
-	-webkit-animation: show-drop-zone 0.3s ease-out forwards;
-	-moz-animation: show-drop-zone 0.3s ease-out forwards;
-	-ms-animation: show-drop-zone 0.3s ease-out forwards;
-}
-#drag_overlay.drag_out{
-	animation: hide-drop-zone 0.3s ease-out forwards;
-	-webkit-animation: hide-drop-zone 0.3s ease-out forwards;
-	-moz-animation: hide-drop-zone 0.3s ease-out forwards;
-	-ms-animation: hide-drop-zone 0.3s ease-out forwards;
-}
 #drag_overlay .overlay_content{
 #drag_overlay .overlay_content{
 	margin-top: 5em;
 	margin-top: 5em;
     width: 100%;
     width: 100%;
@@ -661,56 +649,71 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 #drag_overlay .overlay_content .message{
 #drag_overlay .overlay_content .message{
 	font-size: 1.5em;
 	font-size: 1.5em;
 }
 }
-@keyframes show-drop-zone{
-	100% {
-		height: 20%;
-	}
-}
-@-webkit-keyframes show-drop-zone{
-	100% {
-		height: 20%;
-	}
-}
-@-moz-keyframes show-drop-zone{
-	100% {
-		height: 20%;
-	}
-}
-@-ms-keyframes show-drop-zone{
-	100% {
-		height: 20%;
-	}
-}
-@keyframes hide-drop-zone{
-	0% {
-		height: 20%;
-	}
-	100% {
-		height: 0%;
-	}
-}
-@-webkit-keyframes hide-drop-zone{
-	0% {
-		height: 20%;
-	}
-	100% {
-		height: 0%;
-	}
-}
-@-moz-keyframes hide-drop-zone{
-	0% {
-		height: 20%;
-	}
-	100% {
-		height: 0%;
-	}
-}
-@-ms-keyframes hide-drop-zone{
-	0% {
-		height: 20%;
-	}
-	100% {
-		height: 0%;
+/* Attachments drag & drop zone, only for none mobile devices */
+@media (min-width: 768px){
+	#drag_overlay.drag_in{
+		animation: show-drop-zone 0.3s ease-out forwards;
+		-webkit-animation: show-drop-zone 0.3s ease-out forwards;
+		-moz-animation: show-drop-zone 0.3s ease-out forwards;
+		-ms-animation: show-drop-zone 0.3s ease-out forwards;
+	}
+	#drag_overlay.drag_out{
+		animation: hide-drop-zone 0.3s ease-out forwards;
+		-webkit-animation: hide-drop-zone 0.3s ease-out forwards;
+		-moz-animation: hide-drop-zone 0.3s ease-out forwards;
+		-ms-animation: hide-drop-zone 0.3s ease-out forwards;
+	}
+	@keyframes show-drop-zone{
+		100% {
+			height: 20%;
+		}
+	}
+	@-webkit-keyframes show-drop-zone{
+		100% {
+			height: 20%;
+		}
+	}
+	@-moz-keyframes show-drop-zone{
+		100% {
+			height: 20%;
+		}
+	}
+	@-ms-keyframes show-drop-zone{
+		100% {
+			height: 20%;
+		}
+	}
+	@keyframes hide-drop-zone{
+		0% {
+			height: 20%;
+		}
+		100% {
+			height: 0%;
+		}
+	}
+	@-webkit-keyframes hide-drop-zone{
+		0% {
+			height: 20%;
+		}
+		100% {
+			height: 0%;
+		}
+	}
+	@-moz-keyframes hide-drop-zone{
+		0% {
+			height: 20%;
+		}
+		100% {
+			height: 0%;
+		}
+	}
+	@-ms-keyframes hide-drop-zone{
+		0% {
+			height: 20%;
+		}
+		100% {
+			height: 0%;
+		}
 	}
 	}
 }
 }
 
 
@@ -726,6 +729,9 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 .form_buttons .form_btn_transitions{
 .form_buttons .form_btn_transitions{
 	margin-bottom: 20px;
 	margin-bottom: 20px;
 }
 }
+.form_btn_regular.sticky{
+	display: none;
+}
 @media (min-width: 768px){
 @media (min-width: 768px){
 	.form_buttons .form_btn_transitions{
 	.form_buttons .form_btn_transitions{
 		float: left !important;
 		float: left !important;
@@ -736,6 +742,40 @@ table .group-actions .item-action-wrapper .panel-body > p:last-child{
 	.form_buttons .form_btn_regular btn{
 	.form_buttons .form_btn_regular btn{
 		width: inherit;
 		width: inherit;
 	}
 	}
+	/* Making regular button sticky */
+	.form_btn_regular.sticky{
+		display: block;
+		position: fixed;
+		bottom: 0px;
+		padding: 15px;
+		background-color: #FFF; /* TODO : SASS this to panel bg */
+		border: 1px solid #DDD; /* TODO : SASS this to panel border */
+		border-radius: 4px; /* TODO : SASS this to panel border */
+		transition: bottom 0.3s;
+	}
+	.form_btn_regular.sticky.closed{
+		bottom: -80px;
+	}
+	/* - For regular layout */
+	#main-content .form_btn_regular.sticky{
+		right: 15px; /* TODO : SASS this to col-xs-12 padding */
+	}
+	/* - For modal layout */
+	.modal.in .form_btn_regular.sticky{
+		margin-left: 61%;
+	}
+}
+@media (min-width: 992px) {
+	/* - For modal layout */
+	.modal.in .form_btn_regular.sticky{
+		margin-left: 70%;
+	}
+}
+@media (min-width: 1200px) {
+	/* - For modal layout */
+	.modal.in .form_btn_regular.sticky{
+		margin-left: 73%;
+	}
 }
 }
 
 
 /* CKEditor : Adding BS error feedback */
 /* CKEditor : Adding BS error feedback */

+ 1 - 1
datamodels/2.x/itop-portal-base/portal/web/index.php

@@ -65,7 +65,7 @@ $oApp->register(new Silex\Provider\TwigServiceProvider(), array(
 ));
 ));
 
 
 // Configuring Silex application
 // Configuring Silex application
-$oApp['debug'] = false;
+$oApp['debug'] = true;
 $oApp['combodo.absolute_url'] = utils::GetAbsoluteUrlAppRoot();
 $oApp['combodo.absolute_url'] = utils::GetAbsoluteUrlAppRoot();
 $oApp['combodo.portal.base.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/';
 $oApp['combodo.portal.base.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/itop-portal-base/portal/web/';
 $oApp['combodo.portal.instance.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_MODULE_ID . '/';
 $oApp['combodo.portal.instance.absolute_url'] = utils::GetAbsoluteUrlAppRoot() . 'env-' . utils::GetCurrentEnvironment() . '/' . PORTAL_MODULE_ID . '/';

+ 51 - 43
datamodels/2.x/itop-portal-base/portal/web/js/portal_form_handler.js

@@ -125,59 +125,67 @@ $(function()
 
 
 							// Scrolling to top so the user can see messages
 							// Scrolling to top so the user can see messages
 							$('body').scrollTop(0);
 							$('body').scrollTop(0);
-						}
 						
 						
-						// If everything is okay, we close the form and reload it.
-						if(oValidation.valid)
-						{
-							if(me.options.is_modal)
-							{
-								me.element.closest('.modal').modal('hide');
-							}
-							
-							// Checking if we have to redirect to another page
-							if(oValidation.redirection !== undefined)
+							// If everything is okay, we close the form and reload it.
+							if(oValidation.valid)
 							{
 							{
-								var oRedirection = oValidation.redirection;
-								var bRedirectionAjax = (oRedirection.ajax !== undefined) ? oRedirection.ajax : false;
-								var sUrl = null;
-								
-								// URL priority order :
-								// redirection.url > me.option.submit_url > redirection.alternative_url
-								if(oRedirection.url !== undefined)
-								{
-									sUrl = oRedirection.url;
-								}
-								else if(me.options.submit_url !== null)
-								{
-									sUrl = me.options.submit_url;
-								}
-								else if(oRedirection.alternative_url !== undefined)
+								if(me.options.is_modal)
 								{
 								{
-									sUrl = oRedirection.alternative_url;
+									me.element.closest('.modal').modal('hide');
 								}
 								}
-								
-								if(sUrl !== null)
+
+								// Checking if we have to redirect to another page
+								if(oValidation.redirection !== undefined)
 								{
 								{
-									if(bRedirectionAjax)
+									var oRedirection = oValidation.redirection;
+									var bRedirectionAjax = (oRedirection.ajax !== undefined) ? oRedirection.ajax : false;
+									var sUrl = null;
+
+									// URL priority order :
+									// redirection.url > me.option.submit_url > redirection.alternative_url
+									if(oRedirection.url !== undefined)
 									{
 									{
-										// Creating a new modal
-										var oModalElem = $('#modal-for-all').clone();
-										oModalElem.attr('id', '').appendTo('body');
-										// Loading content
-										oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
-										oModalElem.find('.modal-content').load(sUrl);
-										oModalElem.modal('show');
+										sUrl = oRedirection.url;
 									}
 									}
-									else
+									else if(me.options.submit_url !== null)
+									{
+										sUrl = me.options.submit_url;
+									}
+									else if(oRedirection.alternative_url !== undefined)
+									{
+										sUrl = oRedirection.alternative_url;
+									}
+
+									if(sUrl !== null)
 									{
 									{
-										setTimeout(function() { location.href = sUrl; }, 400);
+										if(bRedirectionAjax)
+										{
+											// Creating a new modal
+											var oModalElem = $('#modal-for-all').clone();
+											oModalElem.attr('id', '').appendTo('body');
+											// Loading content
+											oModalElem.find('.modal-content').html($('#page_overlay .overlay_content').html());
+											oModalElem.find('.modal-content').load(sUrl);
+											oModalElem.modal('show');
+										}
+										else
+										{
+											// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
+											// Note : We use a timeout because .always() is called right after here and will hide the loader
+											setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
+											// Redirecting after a few ms so the user can see what happend
+											setTimeout(function() { location.href = sUrl; }, 400);
+										}
 									}
 									}
 								}
 								}
-							}
-							else if(me.options.submit_url !== null)
-							{
-								setTimeout(function() { location.href = me.options.submit_url; }, 400);
+								else if(me.options.submit_url !== null)
+								{
+									// Showing loader while redirecting, otherwise user tend to click somewhere in the page.
+									// Note : We use a timeout because .always() is called right after here and will hide the loader
+									setTimeout(function(){ me._disableFormBeforeLoading(); }, 50);
+									// Redirecting after a few ms so the user can see what happend
+									setTimeout(function() { location.href = me.options.submit_url; }, 400);
+								}
 							}
 							}
 						}
 						}
 					}
 					}

+ 1 - 0
datamodels/2.x/itop-portal-base/portal/web/lib/jquery-visible/js/jquery.visible.min.js

@@ -0,0 +1 @@
+!function(t){var i=t(window);t.fn.visible=function(t,e,o){if(!(this.length<1)){var r=this.length>1?this.eq(0):this,n=r.get(0),f=i.width(),h=i.height(),o=o?o:"both",l=e===!0?n.offsetWidth*n.offsetHeight:!0;if("function"==typeof n.getBoundingClientRect){var g=n.getBoundingClientRect(),u=g.top>=0&&g.top<h,s=g.bottom>0&&g.bottom<=h,c=g.left>=0&&g.left<f,a=g.right>0&&g.right<=f,v=t?u||s:u&&s,b=t?c||a:c&&a;if("both"===o)return l&&v&&b;if("vertical"===o)return l&&v;if("horizontal"===o)return l&&b}else{var d=i.scrollTop(),p=d+h,w=i.scrollLeft(),m=w+f,y=r.offset(),z=y.top,B=z+r.height(),C=y.left,R=C+r.width(),j=t===!0?B:z,q=t===!0?z:B,H=t===!0?R:C,L=t===!0?C:R;if("both"===o)return!!l&&p>=q&&j>=d&&m>=L&&H>=w;if("vertical"===o)return!!l&&p>=q&&j>=d;if("horizontal"===o)return!!l&&m>=L&&H>=w}}}}(jQuery);