123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- /*
- * Copyright 2000-2018 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
- package com.vaadin.client.extensions;
-
- import java.util.logging.Level;
- import java.util.logging.Logger;
-
- import com.google.gwt.core.client.JavaScriptObject;
- import com.vaadin.client.LayoutManager;
- import com.vaadin.client.ServerConnector;
- import com.vaadin.client.communication.StateChangeEvent;
- import com.vaadin.client.ui.AbstractComponentConnector;
- import com.vaadin.client.ui.layout.ElementResizeEvent;
- import com.vaadin.client.ui.layout.ElementResizeListener;
- import com.vaadin.server.Responsive;
- import com.vaadin.shared.extension.responsive.ResponsiveState;
- import com.vaadin.shared.ui.Connect;
- import com.vaadin.shared.util.SharedUtil;
-
- /**
- * The client side connector for the Responsive extension.
- *
- * @author Vaadin Ltd
- * @since 7.2
- */
- @SuppressWarnings("GwtInconsistentSerializableClass")
- @Connect(Responsive.class)
- public class ResponsiveConnector extends AbstractExtensionConnector
- implements ElementResizeListener {
-
- /**
- * The target component which we will monitor for width changes.
- */
- protected AbstractComponentConnector target;
-
- /**
- * All the width breakpoints found for this particular instance.
- */
- protected JavaScriptObject widthBreakpoints;
-
- /**
- * All the height breakpoints found for this particular instance.
- */
- protected JavaScriptObject heightBreakpoints;
-
- /**
- * All width-range breakpoints found from the style sheets on the page.
- * Common for all instances.
- */
- protected static JavaScriptObject widthRangeCache;
-
- /**
- * All height-range breakpoints found from the style sheets on the page.
- * Common for all instances.
- */
- protected static JavaScriptObject heightRangeCache;
-
- /**
- * The theme that was in use when the width and height range caches were
- * created.
- */
- protected static String parsedTheme;
-
- private static Logger getLogger() {
- return Logger.getLogger(ResponsiveConnector.class.getName());
- }
-
- private static void error(String message) {
- getLogger().log(Level.SEVERE, message);
- }
-
- private static void warning(String message) {
- getLogger().warning(message);
- }
-
- @Override
- protected void extend(ServerConnector target) {
- this.target = (AbstractComponentConnector) target;
- // Start listening for size changes
- LayoutManager.get(getConnection()).addElementResizeListener(
- this.target.getWidget().getElement(), this);
- }
-
- /**
- * Construct the list of selectors that should be matched against in the
- * range selectors.
- *
- * @return The selectors in a comma delimited string.
- */
- protected String constructSelectorsForTarget() {
- String primaryStyle = target.getState().primaryStyleName;
- StringBuilder selectors = new StringBuilder();
- selectors.append('.').append(primaryStyle);
-
- if (target.getState().styles != null
- && !target.getState().styles.isEmpty()) {
- for (String style : target.getState().styles) {
- selectors.append(",.").append(style);
- selectors.append(",.").append(primaryStyle).append('.')
- .append(style);
- selectors.append(",.").append(style).append('.')
- .append(primaryStyle);
- selectors.append(",.").append(primaryStyle).append('-')
- .append(style);
- }
- }
-
- // Allow the ID to be used as the selector as well for ranges
- if (target.getState().id != null) {
- selectors.append(",#").append(target.getState().id);
- }
- return selectors.toString();
- }
-
- @Override
- public void onUnregister() {
- super.onUnregister();
- LayoutManager.get(getConnection()).removeElementResizeListener(
- target.getWidget().getElement(), this);
- }
-
- @Override
- public void onStateChanged(StateChangeEvent event) {
- super.onStateChanged(event);
- // Changing the theme may introduce new style sheets so we may need to
- // rebuild the cache
- if (widthRangeCache == null
- || !SharedUtil.equals(parsedTheme, getCurrentThemeName())) {
- // updating break points
- searchForBreakPoints();
- }
- // Get any breakpoints from the styles defined for this widget
- getBreakPointsFor(constructSelectorsForTarget());
- // make sure that the ranges are updated at least once regardless of
- // resize events.
- updateRanges();
- }
-
- private String getCurrentThemeName() {
- return getConnection().getUIConnector().getActiveTheme();
- }
-
- private void searchForBreakPoints() {
- searchForBreakPointsNative();
- parsedTheme = getCurrentThemeName();
- }
-
- /**
- * Build a cache of all 'width-range' and 'height-range' attribute selectors
- * found in the stylesheets.
- */
- private static native void searchForBreakPointsNative()
- /*-{
-
- // Initialize variables
- @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache = [];
- @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache = [];
-
- var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache;
- var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache;
-
- // Can't do squat if we can't parse stylesheets
- if (!$doc.styleSheets)
- return;
-
- var sheets = $doc.styleSheets;
-
- // Loop all stylesheets on the page and process them individually
- for (var i = 0, len = sheets.length; i < len; i++) {
- var sheet = sheets[i];
- @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(sheet);
- }
-
- }-*/;
-
- /**
- * Process an individual stylesheet object. Any @import statements are
- * handled recursively. Regular rule declarations are searched for
- * 'width-range' and 'height-range' attribute selectors.
- *
- * @param sheet
- */
- private static native void searchStylesheetForBreakPoints(
- final JavaScriptObject sheet)
- /*-{
-
- // Inline variables for easier reading
- var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache;
- var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache;
-
- // Get all the rulesets from the stylesheet
- var theRules = new Array();
- var IEOrEdge = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()() || @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isEdge()();
-
- try {
- if (sheet.cssRules) {
- theRules = sheet.cssRules
- } else if (sheet.rules) {
- theRules = sheet.rules
- }
- } catch (e) {
- // FF spews if trying to access rules for cross domain styles
- @ResponsiveConnector::warning(*)("Can't process styles from " + sheet.href +
- ", probably because of cross domain issues: " + e);
- return;
- }
-
- // Loop through the rulesets
- for (var i = 0, len = theRules.length; i < len; i++) {
- var rule = theRules[i];
-
- if (rule.type == 3) {
- // @import rule, traverse recursively
- @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(rule.styleSheet);
-
- } else if (rule.type == 1 || !rule.type) {
- // Regular selector rule
-
- // Helper function
- var pushToCache = function(ranges, selector, min, max) {
- // Avoid adding duplicates
- var duplicate = false;
- for (var l = 0, len3 = ranges.length; l < len3; l++) {
- var bp = ranges[l];
- if (selector == bp[0] && min == bp[1] && max == bp[2]) {
- duplicate = true;
- break;
- }
- }
- if (!duplicate) {
- ranges.push([selector, min, max]);
- }
- };
-
- // Array of all of the separate selectors in this ruleset
- var haystack = rule.selectorText.split(",");
-
- // IE/Edge parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both
- var selectorRegEx = IEOrEdge ? /\[.*\]([\.|#]\S+)/ : /([\.|#]\S+?)\[.*\]/;
-
- // Loop all the selectors in this ruleset
- for (var k = 0, len2 = haystack.length; k < len2; k++) {
-
- // Split the haystack into parts.
- var widthRange = haystack[k].match(/\[width-range.*?\]/);
- var heightRange = haystack[k].match(/\[height-range.*?\]/);
- var selector = haystack[k].match(selectorRegEx);
-
- if (selector != null) {
- selector = selector[1];
-
- // Check for width-ranges.
- if (widthRange != null) {
- var minMax = widthRange[0].match(/\[width-range~?=["|'](.*?)-(.*?)["|']\]/i);
- var min = minMax[1];
- var max = minMax[2];
-
- pushToCache(widthRanges, selector, min, max);
- }
-
- // Check for height-ranges.
- if (heightRange != null) {
- var minMax = heightRange[0].match(/\[height-range~?=["|'](.*?)-(.*?)["|']\]/i);
- var min = minMax[1];
- var max = minMax[2];
-
- pushToCache(heightRanges, selector, min, max);
- }
- }
- }
- }
- }
-
- }-*/;
-
- /**
- * Get all matching ranges from the cache for this particular instance.
- *
- * @param selectors
- */
- private native void getBreakPointsFor(final String selectors)
- /*-{
-
- var selectors = selectors.split(",");
-
- var widthBreakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::widthBreakpoints = [];
- var heightBreakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::heightBreakpoints = [];
-
- var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache;
- var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache;
-
- for (var i = 0, len = widthRanges.length; i < len; i++) {
- var bp = widthRanges[i];
- for (var j = 0, len2 = selectors.length; j < len2; j++) {
- if (bp[0] == selectors[j])
- widthBreakpoints.push(bp);
- }
- }
-
- for (var i = 0, len = heightRanges.length; i < len; i++) {
- var bp = heightRanges[i];
- for (var j = 0, len2 = selectors.length; j < len2; j++) {
- if (bp[0] == selectors[j])
- heightBreakpoints.push(bp);
- }
- }
-
- // Only for debugging
- // console.log("Breakpoints for", selectors.join(","), widthBreakpoints, heightBreakpoints);
-
- }-*/;
-
- private String currentWidthRanges = "";
- private String currentHeightRanges = "";
-
- @Override
- public void onElementResize(final ElementResizeEvent event) {
- updateRanges();
- }
-
- @Override
- public ResponsiveState getState() {
- return (ResponsiveState) super.getState();
- }
-
- private void updateRanges() {
- LayoutManager layoutManager = LayoutManager.get(getConnection());
- com.google.gwt.user.client.Element element = target.getWidget()
- .getElement();
- int width = layoutManager.getOuterWidth(element);
- int height = layoutManager.getOuterHeight(element);
-
- String oldWidthRanges = currentWidthRanges;
- String oldHeightRanges = currentHeightRanges;
-
- // Loop through breakpoints and see which one applies to this width
- currentWidthRanges = resolveBreakpoint("width", width);
-
- if (!currentWidthRanges.isEmpty()) {
- element.setAttribute("width-range", currentWidthRanges);
- } else {
- element.removeAttribute("width-range");
- }
-
- // Loop through breakpoints and see which one applies to this height
- currentHeightRanges = resolveBreakpoint("height", height);
-
- if (!currentHeightRanges.isEmpty()) {
- element.setAttribute("height-range", currentHeightRanges);
- } else {
- element.removeAttribute("height-range");
- }
-
- // If a new breakpoint is triggered, ensure all sizes are updated in
- // case some new styles are applied
- if (!currentWidthRanges.equals(oldWidthRanges)
- || !currentHeightRanges.equals(oldHeightRanges)) {
- layoutManager.setNeedsMeasureRecursively(
- ResponsiveConnector.this.target);
- }
- }
-
- private native String resolveBreakpoint(String which, int size)
- /*-{
-
- // Default to "width" breakpoints
- var breakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::widthBreakpoints;
-
- // Use height breakpoints if we're measuring the height
- if (which == "height")
- breakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::heightBreakpoints;
-
- // Output string that goes into either the "width-range" or "height-range" attribute in the element
- var ranges = "";
-
- // Loop the breakpoints
- for (var i = 0, len = breakpoints.length; i < len; i++) {
- var bp = breakpoints[i];
-
- var min = parseInt(bp[1]);
- var max = parseInt(bp[2]);
-
- if (!isNaN(min) && !isNaN(max)) {
- if (min <= size && size <= max) {
- ranges += " " + bp[1] + "-" + bp[2];
- }
- } else if (!isNaN(min)) {
- if (min <= size) {
- ranges += " " + bp[1] + "-";
- }
- } else if (!isNaN(max)) {
- if (size <= max) {
- ranges += " -" + bp[2];
- }
- }
- }
-
- // Trim the output and return it
- return ranges.replace(/^\s+/, "");
-
- }-*/;
-
- }
|