001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://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: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://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 m_currentHeight = -1; 077 m_rpc = getRpcProxy(I_CmsMaxHeightServerRpc.class); 078 } 079 080 /** 081 * @see com.vaadin.client.ui.AbstractConnector#getState() 082 */ 083 @Override 084 public CmsMaxHeightState getState() { 085 086 return (CmsMaxHeightState)super.getState(); 087 } 088 089 /** 090 * @see com.vaadin.client.ui.AbstractConnector#onUnregister() 091 */ 092 @Override 093 public void onUnregister() { 094 095 super.onUnregister(); 096 m_unregistered = true; 097 removeObserver(); 098 } 099 100 /** 101 * @see com.vaadin.client.extensions.AbstractExtensionConnector#extend(com.vaadin.client.ServerConnector) 102 */ 103 @Override 104 protected void extend(ServerConnector target) { 105 106 // Get the extended widget 107 m_widget = ((ComponentConnector)target).getWidget(); 108 addMutationObserver(m_widget.getElement()); 109 addStateChangeHandler(new StateChangeHandler() { 110 111 /** Serial version id. */ 112 private static final long serialVersionUID = 1L; 113 114 @SuppressWarnings("synthetic-access") 115 public void onStateChanged(StateChangeEvent stateChangeEvent) { 116 117 m_currentHeight = -1; 118 handleMutation(); 119 } 120 }); 121 } 122 123 /** 124 * Handles the widget mutation.<p> 125 */ 126 protected void handleMutation() { 127 128 if (m_unregistered) { 129 removeObserver(); 130 return; 131 } 132 133 int maxHeight = getState().getMaxHeight(); 134 if (m_currentHeight > 0) { 135 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) 136 JavaScriptObject scrollPositionData = saveScrollPositions(m_widget.getElement()); 137 String classToAdd = "o-measuring-height"; 138 m_widget.addStyleName(classToAdd); 139 m_widget.getElement().getStyle().clearHeight(); 140 int computedHeight = m_widget.getOffsetHeight(); 141 m_widget.removeStyleName(classToAdd); 142 if ((computedHeight + 10) < m_currentHeight) { 143 m_currentHeight = -1; 144 m_rpc.fixHeight(m_currentHeight); 145 } else { 146 m_widget.getElement().getStyle().setHeight(m_currentHeight, Unit.PX); 147 } 148 restoreScrollPositions(scrollPositionData); 149 addMutationObserver(m_widget.getElement()); 150 } else if ((maxHeight > 0) && (m_widget.getOffsetHeight() > maxHeight)) { 151 m_currentHeight = maxHeight; 152 m_rpc.fixHeight(m_currentHeight); 153 } 154 } 155 156 /** 157 * Adds a native mutation observer to the widget element.<p> 158 * 159 * @param element the element 160 */ 161 private native void addMutationObserver(Element element)/*-{ 162 var self = this; 163 var observer = new MutationObserver( 164 function(mutations) { 165 self.@org.opencms.ui.client.CmsMaxHeightConnector::handleMutation()(); 166 }); 167 this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver = observer; 168 169 // configuration of the observer: 170 var config = { 171 attributes : true, 172 childList : true, 173 characterData : true, 174 subtree : true 175 }; 176 177 // pass in the target node, as well as the observer options 178 observer.observe(element, config); 179 }-*/; 180 181 /** 182 * Removes the mutation observer.<p> 183 */ 184 private native void removeObserver()/*-{ 185 if (this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver != null) { 186 this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver 187 .disconnect(); 188 this.@org.opencms.ui.client.CmsMaxHeightConnector::m_mutationObserver = null; 189 } 190 }-*/; 191 192 /** 193 * Restores saved scroll positions of scrollable containers.<p> 194 * 195 * @param capturedScrollPositions the saved scroll positions 196 */ 197 private native void restoreScrollPositions(JavaScriptObject capturedScrollPositions) /*-{ 198 for (var i = 0; i < capturedScrollPositions.length; i++) { 199 var entry = capturedScrollPositions[i]; 200 try { 201 var element = entry[0]; 202 var st = entry[1]; 203 element.scrollTop = st; 204 } catch (e) { 205 // ignore 206 } 207 } 208 }-*/; 209 210 /** 211 * Creates a list of pairs of v-scrollable elements and their respective scrollTop attribute beneath the given DOM element.<p> 212 * 213 * @param element the root element 214 * @return a list of pairs [[element1, scrolltop1], [element2, scrolltop2], ...] 215 */ 216 private native JavaScriptObject saveScrollPositions(Element element) /*-{ 217 var result = []; 218 try { 219 var elems = element.querySelectorAll(".v-scrollable"); 220 for (var i = 0; i < elems.length; i++) { 221 var elem = elems[i]; 222 if (elem.scrollHeight > elem.clientHeight) { 223 result.push([ elem, elem.scrollTop ]); 224 } 225 } 226 } catch (e) { 227 } 228 return result; 229 }-*/; 230 231}