]> source.dussan.org Git - vaadin-framework.git/commitdiff
Enable setting loading indicator delays from the server (#7448)
authorArtur Signell <artur@vaadin.com>
Mon, 25 Mar 2013 21:37:45 +0000 (23:37 +0200)
committerVaadin Code Review <review@vaadin.com>
Wed, 3 Apr 2013 06:42:55 +0000 (06:42 +0000)
* Refactored LoadingIndicator to a separate class on client side to enable customization and to remove clutter from ApplicationConnection

Change-Id: I12e94294beed9c65a5710bdfe2486bc0f1b92bd9

client/src/com/vaadin/client/ApplicationConnection.java
client/src/com/vaadin/client/VLoadingIndicator.java [new file with mode: 0644]
client/src/com/vaadin/client/ui/ui/UIConnector.java
server/src/com/vaadin/ui/LoadingIndicator.java [new file with mode: 0644]
server/src/com/vaadin/ui/UI.java
shared/src/com/vaadin/shared/ui/ui/UIState.java
uitest/src/com/vaadin/tests/components/ui/LoadingIndicatorConfigurationTest.java [new file with mode: 0644]

index a043ec6c0b8136c09de98f72fea226e047818b31..d59abc892a5cfe45f4f2525475b6c826098c90cd 100644 (file)
@@ -177,11 +177,6 @@ public class ApplicationConnection {
 
     private VContextMenu contextMenu = null;
 
-    private Timer loadTimer;
-    private Timer loadTimer2;
-    private Timer loadTimer3;
-    private Element loadElement;
-
     private final UIConnector uIConnector;
 
     protected boolean applicationRunning = false;
@@ -378,6 +373,8 @@ public class ApplicationConnection {
 
     private CommunicationErrorHandler communicationErrorDelegate = null;
 
+    private VLoadingIndicator loadingIndicator;
+
     public static class MultiStepDuration extends Duration {
         private int previousStep = elapsedMillis();
 
@@ -404,6 +401,8 @@ public class ApplicationConnection {
         layoutManager = GWT.create(LayoutManager.class);
         layoutManager.setConnection(this);
         tooltip = GWT.create(VTooltip.class);
+        loadingIndicator = GWT.create(VLoadingIndicator.class);
+        loadingIndicator.setConnection(this);
     }
 
     public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) {
@@ -436,7 +435,7 @@ public class ApplicationConnection {
 
         tooltip.setOwner(uIConnector.getWidget());
 
-        showLoadingIndicator();
+        getLoadingIndicator().trigger();
 
         scheduleHeartbeat();
 
@@ -987,7 +986,7 @@ public class ApplicationConnection {
      */
     protected boolean isCSSLoaded() {
         return cssLoaded
-                || DOM.getElementPropertyInt(loadElement, "offsetHeight") != 0;
+                || getLoadingIndicator().getElement().getOffsetHeight() != 0;
     }
 
     /**
@@ -1085,25 +1084,7 @@ public class ApplicationConnection {
         }
         hasActiveRequest = true;
         requestStartTime = new Date();
-        // show initial throbber
-        if (loadTimer == null) {
-            loadTimer = new Timer() {
-                @Override
-                public void run() {
-                    /*
-                     * IE7 does not properly cancel the event with
-                     * loadTimer.cancel() so we have to check that we really
-                     * should make it visible
-                     */
-                    if (loadTimer != null) {
-                        showLoadingIndicator();
-                    }
-
-                }
-            };
-            // First one kicks in at 300ms
-        }
-        loadTimer.schedule(300);
+        loadingIndicator.trigger();
         eventBus.fireEvent(new RequestStartingEvent(this));
     }
 
@@ -1129,7 +1110,7 @@ public class ApplicationConnection {
             @Override
             public void execute() {
                 if (!hasActiveRequest()) {
-                    hideLoadingIndicator();
+                    getLoadingIndicator().hide();
 
                     // If on Liferay and session expiration management is in
                     // use, extend session duration on each request.
@@ -1182,54 +1163,6 @@ public class ApplicationConnection {
         }
     }
 
-    private void showLoadingIndicator() {
-        // show initial throbber
-        if (loadElement == null) {
-            loadElement = DOM.createDiv();
-            DOM.setStyleAttribute(loadElement, "position", "absolute");
-            DOM.appendChild(uIConnector.getWidget().getElement(), loadElement);
-            VConsole.log("inserting load indicator");
-        }
-        DOM.setElementProperty(loadElement, "className", "v-loading-indicator");
-        DOM.setStyleAttribute(loadElement, "display", "block");
-        // Initialize other timers
-        loadTimer2 = new Timer() {
-            @Override
-            public void run() {
-                DOM.setElementProperty(loadElement, "className",
-                        "v-loading-indicator-delay");
-            }
-        };
-        // Second one kicks in at 1500ms from request start
-        loadTimer2.schedule(1200);
-
-        loadTimer3 = new Timer() {
-            @Override
-            public void run() {
-                DOM.setElementProperty(loadElement, "className",
-                        "v-loading-indicator-wait");
-            }
-        };
-        // Third one kicks in at 5000ms from request start
-        loadTimer3.schedule(4700);
-    }
-
-    private void hideLoadingIndicator() {
-        if (loadTimer != null) {
-            loadTimer.cancel();
-            loadTimer = null;
-        }
-        if (loadTimer2 != null) {
-            loadTimer2.cancel();
-            loadTimer3.cancel();
-            loadTimer2 = null;
-            loadTimer3 = null;
-        }
-        if (loadElement != null) {
-            DOM.setStyleAttribute(loadElement, "display", "none");
-        }
-    }
-
     /**
      * Checks if deferred commands are (potentially) still being executed as a
      * result of an update from the server. Returns true if a deferred command
@@ -1251,20 +1184,25 @@ public class ApplicationConnection {
         }
     }
 
+    /**
+     * Returns the loading indicator used by this ApplicationConnection
+     * 
+     * @return The loading indicator for this ApplicationConnection
+     */
+    public VLoadingIndicator getLoadingIndicator() {
+        return loadingIndicator;
+    }
+
     /**
      * Determines whether or not the loading indicator is showing.
      * 
      * @return true if the loading indicator is visible
+     * @deprecated As of 7.1. Use {@link #getLoadingIndicator()} and
+     *             {@link VLoadingIndicator#isVisible()}.isVisible() instead.
      */
+    @Deprecated
     public boolean isLoadingIndicatorVisible() {
-        if (loadElement == null) {
-            return false;
-        }
-        if (loadElement.getStyle().getProperty("display").equals("none")) {
-            return false;
-        }
-
-        return true;
+        return getLoadingIndicator().isVisible();
     }
 
     private static native ValueMap parseJSONResponse(String jsonText)
diff --git a/client/src/com/vaadin/client/VLoadingIndicator.java b/client/src/com/vaadin/client/VLoadingIndicator.java
new file mode 100644 (file)
index 0000000..ca29d6a
--- /dev/null
@@ -0,0 +1,218 @@
+package com.vaadin.client;
+
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+
+/**
+ * Class representing the loading indicator for Vaadin applications. The loading
+ * indicator has four states: "triggered", "initial", "delay" and "wait". When
+ * {@link #trigger()} is called the indicator moves to its "triggered" state and
+ * then transitions from one state to the next when the timeouts specified using
+ * the set*StateDelay methods occur.
+ * 
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class VLoadingIndicator {
+
+    private static final String PRIMARY_STYLE_NAME = "v-loading-indicator";
+
+    private ApplicationConnection connection;
+
+    private int initialStateDelay = 300;
+    private int delayStateDelay = 1500;
+    private int waitStateDelay = 5000;
+
+    private Timer initialTimer = new Timer() {
+        @Override
+        public void run() {
+            show();
+        }
+    };
+    private Timer delayStateTimer = new Timer() {
+        @Override
+        public void run() {
+            getElement().setClassName(PRIMARY_STYLE_NAME + "-delay");
+        }
+    };
+    private Timer waitStateTimer = new Timer() {
+        @Override
+        public void run() {
+            getElement().setClassName(PRIMARY_STYLE_NAME + "-wait");
+        }
+    };
+
+    private Element element;
+
+    /**
+     * Returns the delay (in ms) which must pass before the loading indicator
+     * moves into the "initial" state and is shown to the user
+     * 
+     * @return The delay (in ms) until moving into the "initial" state. Counted
+     *         from when {@link #trigger()} is called.
+     */
+    public int getInitialStateDelay() {
+        return initialStateDelay;
+    }
+
+    /**
+     * Sets the delay (in ms) which must pass before the loading indicator moves
+     * into the "initial" state and is shown to the user
+     * 
+     * @param initialStateDelay
+     *            The delay (in ms) until moving into the "initial" state.
+     *            Counted from when {@link #trigger()} is called.
+     */
+    public void setInitialStateDelay(int initialStateDelay) {
+        this.initialStateDelay = initialStateDelay;
+    }
+
+    /**
+     * Returns the delay (in ms) which must pass before the loading indicator
+     * moves to its "delay" state.
+     * 
+     * @return The delay (in ms) until the loading indicator moves into its
+     *         "delay" state. Counted from when {@link #trigger()} is called.
+     */
+    public int getDelayStateDelay() {
+        return delayStateDelay;
+    }
+
+    /**
+     * Sets the delay (in ms) which must pass before the loading indicator moves
+     * to its "delay" state.
+     * 
+     * @param delayStateDelay
+     *            The delay (in ms) until the loading indicator moves into its
+     *            "delay" state. Counted from when {@link #trigger()} is called.
+     */
+    public void setDelayStateDelay(int delayStateDelay) {
+        this.delayStateDelay = delayStateDelay;
+    }
+
+    /**
+     * Returns the delay (in ms) which must pass before the loading indicator
+     * moves to its "wait" state.
+     * 
+     * @return The delay (in ms) until the loading indicator moves into its
+     *         "wait" state. Counted from when {@link #trigger()} is called.
+     */
+    public int getWaitStateDelay() {
+        return waitStateDelay;
+    }
+
+    /**
+     * Sets the delay (in ms) which must pass before the loading indicator moves
+     * to its "wait" state.
+     * 
+     * @param loadingIndicatorThirdDelay
+     *            The delay (in ms) from the event until changing the loading
+     *            indicator into its "wait" state. Counted from when
+     *            {@link #trigger()} is called.
+     */
+    public void setWaitStateDelay(int loadingIndicatorThirdDelay) {
+        waitStateDelay = loadingIndicatorThirdDelay;
+    }
+
+    /**
+     * Triggers displaying of this loading indicator. The loading indicator will
+     * actually be shown by {@link #show()} when the initial delay (as specified
+     * by {@link #getInitialStateDelay()}) has passed.
+     * <p>
+     * The loading indicator will be hidden if shown when calling this method.
+     * </p>
+     */
+    public void trigger() {
+        hide();
+        initialTimer.schedule(getInitialStateDelay());
+    }
+
+    /**
+     * Shows the loading indicator in its standard state and triggers timers for
+     * transitioning into the "delay" and "wait" states.
+     */
+    public void show() {
+        // Reset possible style name and display mode
+        getElement().setClassName(PRIMARY_STYLE_NAME);
+        getElement().getStyle().setDisplay(Display.BLOCK);
+
+        // Schedule the "delay" loading indicator
+        int delayStateTimerDelay = getDelayStateDelay()
+                - getInitialStateDelay();
+        if (delayStateTimerDelay >= 0) {
+            delayStateTimer.schedule(delayStateTimerDelay);
+        }
+
+        // Schedule the "wait" loading indicator
+        int waitStateTimerDelay = getWaitStateDelay() - getInitialStateDelay();
+        if (waitStateTimerDelay >= 0) {
+            waitStateTimer.schedule(waitStateTimerDelay);
+        }
+    }
+
+    /**
+     * Returns the {@link ApplicationConnection} which uses this loading
+     * indicator
+     * 
+     * @return The ApplicationConnection for this loading indicator
+     */
+    public ApplicationConnection getConnection() {
+        return connection;
+    }
+
+    /**
+     * Sets the {@link ApplicationConnection} which uses this loading indicator.
+     * Only used internally.
+     * 
+     * @param connection
+     *            The ApplicationConnection for this loading indicator
+     */
+    void setConnection(ApplicationConnection connection) {
+        this.connection = connection;
+    }
+
+    /**
+     * Hides the loading indicator (if visible). Cancels any possibly running
+     * timers.
+     */
+    public void hide() {
+        initialTimer.cancel();
+        delayStateTimer.cancel();
+        waitStateTimer.cancel();
+
+        getElement().getStyle().setDisplay(Display.NONE);
+    }
+
+    /**
+     * Returns whether or not the loading indicator is showing.
+     * 
+     * @return true if the loading indicator is visible, false otherwise
+     */
+    public boolean isVisible() {
+        if (getElement().getStyle().getDisplay()
+                .equals(Display.NONE.getCssName())) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns the root element of the loading indicator
+     * 
+     * @return The loading indicator DOM element
+     */
+    public Element getElement() {
+        if (element == null) {
+            element = DOM.createDiv();
+            element.getStyle().setPosition(Position.ABSOLUTE);
+            getConnection().getUIConnector().getWidget().getElement()
+                    .appendChild(element);
+        }
+        return element;
+    }
+
+}
index b8b7786d21fadee3d7b3beecbc56d210e64434f6..f4524882faf1ddd092b9741d0182bbbb5a100bbb 100644 (file)
@@ -615,5 +615,15 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
             getConnection().getVTooltip().setMaxWidth(
                     getState().tooltipConfiguration.maxWidth);
         }
+
+        if (stateChangeEvent
+                .hasPropertyChanged("loadingIndicatorConfiguration")) {
+            getConnection().getLoadingIndicator().setInitialStateDelay(
+                    getState().loadingIndicatorConfiguration.initialDelay);
+            getConnection().getLoadingIndicator().setWaitStateDelay(
+                    getState().loadingIndicatorConfiguration.waitStateDelay);
+            getConnection().getLoadingIndicator().setDelayStateDelay(
+                    getState().loadingIndicatorConfiguration.delayStateDelay);
+        }
     }
 }
diff --git a/server/src/com/vaadin/ui/LoadingIndicator.java b/server/src/com/vaadin/ui/LoadingIndicator.java
new file mode 100644 (file)
index 0000000..5740ee7
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2000-2013 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.ui;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.ui.UIState.LoadingIndicatorConfiguration;
+
+/**
+ * Provides method for configuring the loading indicator.
+ * 
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface LoadingIndicator extends Serializable {
+    /**
+     * Sets the delay before the loading indicator is shown. The default is
+     * 300ms.
+     * 
+     * @param initialDelay
+     *            The initial delay (in ms)
+     */
+    public void setInitialDelay(int initialDelay);
+
+    /**
+     * Returns the delay before the loading indicator is shown.
+     * 
+     * @return The initial delay (in ms)
+     */
+    public int getInitialDelay();
+
+    /**
+     * Sets the delay before the loading indicator goes into the "delay" state.
+     * The delay is calculated from the time when the loading indicator was
+     * triggered. The default is 1500ms.
+     * 
+     * @param delayStateDelay
+     *            The delay before going into the "delay" state (in ms)
+     */
+    public void setDelayStateDelay(int delayStateDelay);
+
+    /**
+     * Returns the delay before the loading indicator goes into the "delay"
+     * state. The delay is calculated from the time when the loading indicator
+     * was triggered.
+     * 
+     * @return The delay before going into the "delay" state (in ms)
+     */
+    public int getDelayStateDelay();
+
+    /**
+     * Sets the delay before the loading indicator goes into the "wait" state.
+     * The delay is calculated from the time when the loading indicator was
+     * triggered. The default is 5000ms.
+     * 
+     * @param waitStateDelay
+     *            The delay before going into the "wait" state (in ms)
+     */
+    public void setWaitStateDelay(int waitStateDelay);
+
+    /**
+     * Returns the delay before the loading indicator goes into the "wait"
+     * state. The delay is calculated from the time when the loading indicator
+     * was triggered.
+     * 
+     * @return The delay before going into the "wait" state (in ms)
+     */
+    public int getWaitStateDelay();
+}
+
+class LoadingIndicatorImpl implements LoadingIndicator {
+    private UI ui;
+
+    public LoadingIndicatorImpl(UI ui) {
+        this.ui = ui;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.ui.LoadingIndicator#setInitialDelay(int)
+     */
+    @Override
+    public void setInitialDelay(int initialDelay) {
+        getState().initialDelay = initialDelay;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.ui.LoadingIndicator#getInitialDelay()
+     */
+    @Override
+    public int getInitialDelay() {
+        return getState(false).initialDelay;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.ui.LoadingIndicator#setDelayStateDelay(int)
+     */
+    @Override
+    public void setDelayStateDelay(int delayStateDelay) {
+        getState().delayStateDelay = delayStateDelay;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.ui.LoadingIndicator#getDelayStateDelay()
+     */
+    @Override
+    public int getDelayStateDelay() {
+        return getState(false).delayStateDelay;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.ui.LoadingIndicator#setWaitStateDelay(int)
+     */
+    @Override
+    public void setWaitStateDelay(int waitStateDelay) {
+        getState().waitStateDelay = waitStateDelay;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see com.vaadin.ui.LoadingIndicator#getWaitStateDelay()
+     */
+    @Override
+    public int getWaitStateDelay() {
+        return getState(false).waitStateDelay;
+    }
+
+    private LoadingIndicatorConfiguration getState() {
+        return ui.getState().loadingIndicatorConfiguration;
+    }
+
+    private LoadingIndicatorConfiguration getState(boolean markAsDirty) {
+        return ui.getState(markAsDirty).loadingIndicatorConfiguration;
+    }
+
+}
index e9499da7f3485520f43874a7ac1d190e53fcdcfd..a20c2b2087495e51a6c5eced9a963213aa085d58 100644 (file)
@@ -116,6 +116,8 @@ public abstract class UI extends AbstractSingleComponentContainer implements
 
     private Page page = new Page(this);
 
+    private LoadingIndicator loadingIndicator = new LoadingIndicatorImpl(this);
+
     /**
      * Scroll Y position.
      */
@@ -1106,4 +1108,14 @@ public abstract class UI extends AbstractSingleComponentContainer implements
     public Tooltip getTooltip() {
         return tooltip;
     }
+
+    /**
+     * Retrieves the object used for configuring the loading indicator.
+     * 
+     * @return The instance used for configuring the loading indicator
+     */
+    public LoadingIndicator getLoadingIndicator() {
+        return loadingIndicator;
+    }
+
 }
index cc6897dd0d4b1975402aad2470116b025d36264e..d5ee4c30e69615a38bd1fee83bb914b8f09889e7 100644 (file)
@@ -21,6 +21,13 @@ import com.vaadin.shared.ui.TabIndexState;
 
 public class UIState extends TabIndexState {
     public TooltipConfiguration tooltipConfiguration = new TooltipConfiguration();
+    public LoadingIndicatorConfiguration loadingIndicatorConfiguration = new LoadingIndicatorConfiguration();
+
+    public static class LoadingIndicatorConfiguration implements Serializable {
+        public int initialDelay = 300;
+        public int delayStateDelay = 1500;
+        public int waitStateDelay = 5000;
+    }
 
     public static class TooltipConfiguration implements Serializable {
         public int openDelay = 750;
diff --git a/uitest/src/com/vaadin/tests/components/ui/LoadingIndicatorConfigurationTest.java b/uitest/src/com/vaadin/tests/components/ui/LoadingIndicatorConfigurationTest.java
new file mode 100644 (file)
index 0000000..0f15ff2
--- /dev/null
@@ -0,0 +1,99 @@
+package com.vaadin.tests.components.ui;
+
+import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUIWithLog;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.NativeButton;
+import com.vaadin.ui.TextField;
+
+public class LoadingIndicatorConfigurationTest extends AbstractTestUIWithLog {
+
+    private TextField initialDelay;
+    private TextField delayStateDelay;
+    private TextField waitStateDelay;
+
+    @Override
+    protected void setup(VaadinRequest request) {
+        final TextField delayField = new TextField("Delay (ms)");
+        delayField.setConverter(Integer.class);
+        delayField.setConvertedValue(1000);
+
+        NativeButton delayButton = new NativeButton("Wait");
+        delayButton.addClickListener(new ClickListener() {
+
+            @Override
+            public void buttonClick(ClickEvent event) {
+                try {
+                    Thread.sleep((Integer) delayField.getConvertedValue());
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+
+        initialDelay = createIntegerTextField("Initial delay (ms)",
+                getState().loadingIndicatorConfiguration.initialDelay);
+        initialDelay.addValueChangeListener(new Property.ValueChangeListener() {
+            @Override
+            public void valueChange(ValueChangeEvent event) {
+                getLoadingIndicator().setInitialDelay(
+                        (Integer) initialDelay.getConvertedValue());
+            }
+        });
+        delayStateDelay = createIntegerTextField("Delay state delay (ms)",
+                getState().loadingIndicatorConfiguration.delayStateDelay);
+        delayStateDelay
+                .addValueChangeListener(new Property.ValueChangeListener() {
+                    @Override
+                    public void valueChange(ValueChangeEvent event) {
+                        getLoadingIndicator().setDelayStateDelay(
+                                (Integer) delayStateDelay.getConvertedValue());
+                    }
+                });
+        waitStateDelay = createIntegerTextField("Wait state delay (ms)",
+                getState().loadingIndicatorConfiguration.waitStateDelay);
+        waitStateDelay
+                .addValueChangeListener(new Property.ValueChangeListener() {
+                    @Override
+                    public void valueChange(ValueChangeEvent event) {
+                        getLoadingIndicator().setWaitStateDelay(
+                                (Integer) waitStateDelay.getConvertedValue());
+                    }
+                });
+
+        getLayout()
+                .addComponents(initialDelay, delayStateDelay, waitStateDelay);
+
+        HorizontalLayout hl = new HorizontalLayout();
+        hl.setMargin(true);
+        hl.setDefaultComponentAlignment(Alignment.BOTTOM_RIGHT);
+        hl.addComponents(delayField, delayButton);
+        addComponent(hl);
+
+    }
+
+    private TextField createIntegerTextField(String caption, int initialValue) {
+        TextField tf = new TextField(caption);
+        tf.setId(caption);
+        tf.setConverter(Integer.class);
+        tf.setImmediate(true);
+        tf.setConvertedValue(initialValue);
+        return tf;
+    }
+
+    @Override
+    protected String getTestDescription() {
+        return "Tests that loading indicator delay can be configured";
+    }
+
+    @Override
+    protected Integer getTicketNumber() {
+        return 7448;
+    }
+
+}