001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: https://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: https://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.client; 029 030import org.opencms.ui.components.extensions.CmsMaxHeightExtension; 031import org.opencms.ui.shared.components.CmsMaxHeightState; 032import org.opencms.ui.shared.rpc.I_CmsMaxHeightServerRpc; 033 034import com.google.gwt.core.client.JavaScriptObject; 035import com.google.gwt.dom.client.Element; 036import com.google.gwt.dom.client.Style.Unit; 037import com.google.gwt.user.client.ui.Widget; 038import com.vaadin.client.ComponentConnector; 039import com.vaadin.client.ServerConnector; 040import com.vaadin.client.communication.StateChangeEvent; 041import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; 042import com.vaadin.client.extensions.AbstractExtensionConnector; 043import com.vaadin.shared.ui.Connect; 044 045/** 046 * This connector will manipulate the CSS classes of the extended widget depending on the scroll position.<p> 047 */ 048@Connect(CmsMaxHeightExtension.class) 049public class CmsMaxHeightConnector extends AbstractExtensionConnector { 050 051 /** The serial version id. */ 052 private static final long serialVersionUID = -3661096843568550285L; 053 054 /** The currently set height. */ 055 private int m_currentHeight; 056 057 /** Flag indicating the required height is currently being evaluated. */ 058 private boolean m_evaluating; 059 060 /** The native mutation observer. */ 061 private JavaScriptObject m_mutationObserver; 062 063 /** The RPC proxy. */ 064 private I_CmsMaxHeightServerRpc m_rpc; 065 066 /** Flag which is set when this connector is unregistered. */ 067 private boolean m_unregistered; 068 069 /** The widget to enhance. */ 070 private Widget m_widget; 071 072 /** 073 * Constructor.<p> 074 */ 075 public CmsMaxHeightConnector() { 076 077 m_currentHeight = -1; 078 m_rpc = getRpcProxy(I_CmsMaxHeightServerRpc.class); 079 } 080 081 /** 082 * @see com.vaadin.client.ui.AbstractConnector#getState() 083 */ 084 @Override 085 public CmsMaxHeightState getState() { 086 087 return (CmsMaxHeightState)super.getState(); 088 } 089 090 /** 091 * @see com.vaadin.client.ui.AbstractConnector#onUnregister() 092 */ 093 @Override 094 public void onUnregister() { 095 096 super.onUnregister(); 097 m_unregistered = true; 098 removeObserver(); 099 } 100 101 /** 102 * @see com.vaadin.client.extensions.AbstractExtensionConnector#extend(com.vaadin.client.ServerConnector) 103 */ 104 @Override 105 protected void extend(ServerConnector target) { 106 107 // Get the extended widget 108 m_widget = ((ComponentConnector)target).getWidget(); 109 addMutationObserver(m_widget.getElement()); 110 addStateChangeHandler(new StateChangeHandler() { 111 112 /** Serial version id. */ 113 private static final long serialVersionUID = 1L; 114 115 @SuppressWarnings("synthetic-access") 116 public void onStateChanged(StateChangeEvent stateChangeEvent) { 117 118 m_currentHeight = -1; 119 handleMutation(); 120 } 121 }); 122 } 123 124 /** 125 * Handles the widget mutation.<p> 126 */ 127 protected void handleMutation() { 128 129 if (m_unregistered) { 130 removeObserver(); 131 return; 132 } 133 134 int maxHeight = getState().getMaxHeight(); 135 if (m_currentHeight > 0) { 136 removeObserver(); // prevent 'recursive' call of handleMutation (it's not actually recursive since it's async, but would still lead to an infinite number of calls) 137 JavaScriptObject scrollPositionData = saveScrollPositions(m_widget.getElement()); 138 String classToAdd = "o-measuring-height"; 139 m_widget.addStyleName(classToAdd); 140 m_widget.getElement().getStyle().clearHeight(); 141 int computedHeight = m_widget.getOffsetHeight(); 142 m_widget.removeStyleName(classToAdd); 143 if ((computedHeight + 10) < m_currentHeight) { 144 m_currentHeight = -1; 145 m_rpc.fixHeight(m_currentHeight); 146 } else { 147 m_widget.getElement().getStyle().setHeight(m_currentHeight, Unit.PX); 148 } 149 restoreScrollPositions(scrollPositionData); 150 addMutationObserver(m_widget.getElement()); 151 } else if ((maxHeight > 0) && (m_widget.getOffsetHeight() > maxHeight)) { 152 m_currentHeight = maxHeight; 153 m_rpc.fixHeight(m_currentHeight); 154 } 155 } 156 157 /** 158 * Adds a native mutation observer to the widget element.<p> 159 * 160 * @param element the element 161 */ 162 private native void addMutationObserver(Element element)/*-{ 163 var self = this; 164 var observer = new MutationObserver( 165 function(mutations) { 166 self.@org.opencms.ui.client.CmsMaxHeightConnector::handleMutation()(); 167 }); 168 this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver = observer; 169 170 // configuration of the observer: 171 var config = { 172 attributes : true, 173 childList : true, 174 characterData : true, 175 subtree : true 176 }; 177 178 // pass in the target node, as well as the observer options 179 observer.observe(element, config); 180 }-*/; 181 182 /** 183 * Removes the mutation observer.<p> 184 */ 185 private native void removeObserver()/*-{ 186 if (this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver != null) { 187 this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver 188 .disconnect(); 189 this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver = null; 190 } 191 }-*/; 192 193 /** 194 * Restores saved scroll positions of scrollable containers.<p> 195 * 196 * @param capturedScrollPositions the saved scroll positions 197 */ 198 private native void restoreScrollPositions(JavaScriptObject capturedScrollPositions) /*-{ 199 for (var i = 0; i < capturedScrollPositions.length; i++) { 200 var entry = capturedScrollPositions[i]; 201 try { 202 var element = entry[0]; 203 var st = entry[1]; 204 element.scrollTop = st; 205 } catch (e) { 206 // ignore 207 } 208 } 209 }-*/; 210 211 /** 212 * Creates a list of pairs of v-scrollable elements and their respective scrollTop attribute beneath the given DOM element.<p> 213 * 214 * @param element the root element 215 * @return a list of pairs [[element1, scrolltop1], [element2, scrolltop2], ...] 216 */ 217 private native JavaScriptObject saveScrollPositions(Element element) /*-{ 218 var result = []; 219 try { 220 var elems = element.querySelectorAll(".v-scrollable"); 221 for (var i = 0; i < elems.length; i++) { 222 var elem = elems[i]; 223 if (elem.scrollHeight > elem.clientHeight) { 224 result.push([ elem, elem.scrollTop ]); 225 } 226 } 227 } catch (e) { 228 } 229 return result; 230 }-*/; 231 232}