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.gwt.client.ui.input.location;
029
030import org.opencms.gwt.client.Messages;
031import org.opencms.gwt.client.rpc.CmsLog;
032import org.opencms.gwt.client.ui.CmsErrorDialog;
033import org.opencms.gwt.client.ui.CmsPopup;
034import org.opencms.gwt.client.util.CmsClientStringUtil;
035import org.opencms.gwt.client.util.CmsDomUtil;
036import org.opencms.util.CmsStringUtil;
037
038import java.util.ArrayList;
039import java.util.Collections;
040import java.util.LinkedHashMap;
041import java.util.List;
042import java.util.Map;
043
044import com.google.gwt.core.client.JavaScriptObject;
045import com.google.gwt.event.logical.shared.ValueChangeEvent;
046import com.google.gwt.user.client.Command;
047import com.google.gwt.user.client.ui.SuggestOracle;
048
049/**
050 * The location picker controller.<p>
051 */
052public class CmsLocationController {
053
054    /** Flag indicating the API is currently being loaded. */
055    private static boolean loadingApi;
056
057    /** Flag indicating the dynamic error style has been inserted. */
058    private static boolean m_hasInsertedDynamicStyle;
059
060    /** The main API param. */
061    private static final String MAPS_MAIN_PARAM = "v=3.exp";
062
063    /** The places library param. */
064    private static final String MAPS_PLACES_PARAM = "libraries=places";
065
066    /** The URI of google maps API. */
067    private static final String MAPS_URI = "https://maps.googleapis.com/maps/api/js";
068
069    /** The callback to be executed once the API is loaded. */
070    private static List<Command> onApiReady = new ArrayList<Command>();
071
072    /** The parsed configuration JSON object. */
073    private JavaScriptObject m_config;
074
075    /** The previous value. */
076    private CmsLocationValue m_currentValue;
077
078    /** The current location value. */
079    private CmsLocationValue m_editValue;
080
081    /** The goe coder instance. */
082    private JavaScriptObject m_geocoder;
083
084    /** The map. */
085    private JavaScriptObject m_map;
086
087    /** The map marker. */
088    private JavaScriptObject m_marker;
089
090    /** The picker widget. */
091    private CmsLocationPicker m_picker;
092
093    /** The popup displaying the map. */
094    private CmsPopup m_popup;
095
096    /** The popup content widget. */
097    private CmsLocationPopupContent m_popupContent;
098
099    /** The preview map. */
100    private JavaScriptObject m_previewMap;
101
102    /** The preview map marker. */
103    private JavaScriptObject m_previewMarker;
104
105    /**
106     * Constructor.<p>
107     *
108     * @param picker the picker widget
109     * @param configuration the widget configuration
110     */
111    public CmsLocationController(CmsLocationPicker picker, String configuration) {
112
113        parseConfig(configuration);
114        initDynamicStyle();
115        m_picker = picker;
116        m_editValue = CmsLocationValue.parse(
117            "{\"address\": \"London\", \"lat\": 51.5001524, \"lng\": -0.1262362, \"height\": 300, \"width\": 400, \"mode\": \"\", \"type\":\"roadmap\", \"zoom\": 8}");
118        m_currentValue = m_editValue.cloneValue();
119    }
120
121    /**
122     * Called once the API is loaded.<p>
123     */
124    private static void apiReady() {
125
126        loadingApi = false;
127        for (Command callback : onApiReady) {
128            callback.execute();
129        }
130        onApiReady.clear();
131    }
132
133    /**
134     * Returns the available map modes.<p>
135     *
136     * @return the available map modes
137     */
138    private static Map<String, String> getModeItems() {
139
140        Map<String, String> modes = new LinkedHashMap<String, String>();
141        modes.put("dynamic", Messages.get().key(Messages.GUI_LOCATION_DYNAMIC_0));
142        modes.put("static", Messages.get().key(Messages.GUI_LOCATION_STATIC_0));
143        return modes;
144    }
145
146    /**
147     * Returns the available map types.<p>
148     *
149     * @return the available map types
150     */
151    private static Map<String, String> getTypeItems() {
152
153        Map<String, String> types = new LinkedHashMap<String, String>();
154        types.put("roadmap", Messages.get().key(Messages.GUI_LOCATION_ROADMAP_0));
155        types.put("hybrid", Messages.get().key(Messages.GUI_LOCATION_HYBRID_0));
156        types.put("satellite", Messages.get().key(Messages.GUI_LOCATION_SATELLITE_0));
157        types.put("terrain", Messages.get().key(Messages.GUI_LOCATION_TERRAIN_0));
158        return types;
159    }
160
161    /**
162     * Returns the available zoom levels.<p>
163     *
164     * @return the available zoom levels
165     */
166    private static Map<String, String> getZoomItems() {
167
168        Map<String, String> zoomItems = new LinkedHashMap<String, String>();
169        for (int i = 0; i < 21; i++) {
170            String value = String.valueOf(i);
171            zoomItems.put(value, value);
172        }
173        return zoomItems;
174    }
175
176    /**
177     * Returns if the google maps API is already loaded to the window context.<p>
178     *
179     * @return <code>true</code>  if the google maps API is already loaded to the window context
180     */
181    private static boolean isApiLoaded() {
182
183        return isMainApiLoaded() && isPlacesApiLoaded();
184    }
185
186    /**
187     * Returns if the google maps API is already loaded to the window context.<p>
188     *
189     * @return <code>true</code>  if the google maps API is already loaded to the window context
190     */
191    private static native boolean isMainApiLoaded()/*-{
192                var result = $wnd.google !== undefined
193                                && $wnd.google.maps !== undefined
194                                && $wnd.google.maps.Map !== undefined;
195                return result;
196    }-*/;
197
198    /**
199     * Returns if the google maps API is already loaded to the window context.<p>
200     *
201     * @return <code>true</code>  if the google maps API is already loaded to the window context
202     */
203    private static native boolean isPlacesApiLoaded()/*-{
204                var result = $wnd.google !== undefined
205                                && $wnd.google.maps !== undefined
206                                && $wnd.google.maps.places !== undefined;
207                return result;
208    }-*/;
209
210    /**
211     * Returns the current location value.<p>
212     *
213     * @return the location value
214     */
215    public CmsLocationValue getLocationValue() {
216
217        return m_editValue;
218    }
219
220    /**
221     * Returns the JSON string representation of the current value.<p>
222     *
223     * @return the JSON string representation
224     */
225    public String getStringValue() {
226
227        return m_currentValue == null ? "" : m_currentValue.toJSONString();
228    }
229
230    /** Sets the location value as string.
231     *
232     * @param value the string representation of the location value (JSON)
233     **/
234    public void setStringValue(String value) {
235
236        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) {
237            try {
238                m_editValue = CmsLocationValue.parse(value);
239                m_currentValue = m_editValue.cloneValue();
240                displayValue();
241                if ((m_popup != null) && m_popup.isVisible()) {
242                    m_popupContent.displayValues(m_editValue);
243                    updateMarkerPosition();
244                }
245            } catch (Exception e) {
246                CmsLog.log(e.getLocalizedMessage() + "\n" + CmsClientStringUtil.getStackTrace(e, "\n"));
247            }
248        } else {
249            m_currentValue = null;
250            displayValue();
251        }
252    }
253
254    /**
255     * Return a maps API position object according the the current location value.<p>
256     *
257     * @return the position object
258     */
259    protected native JavaScriptObject getCurrentPosition()/*-{
260                var val = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue;
261                return new $wnd.google.maps.LatLng(val.lat, val.lng);
262
263    }-*/;
264
265    /**
266     * Called on address value change.<p>
267     *
268     * @param address the new address
269     */
270    protected native void onAddressChange(String address) /*-{
271                var self = this;
272                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address = address;
273                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_geocoder
274                                .geocode(
275                                                {
276                                                        'address' : address
277                                                },
278                                                function(results, status) {
279                                                        // check to see if we have at least one valid address
280                                                        if (!results
281                                                                        || (status != $wnd.google.maps.GeocoderStatus.OK)
282                                                                        || !results[0].formatted_address) {
283                                                                alert("Address not found");
284                                                                return;
285                                                        }
286                                                        var lat = results[0].geometry.location.lat();
287                                                        var lng = results[0].geometry.location.lng();
288                                                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::setPosition(FFZZ)(lat,lng,true,false);
289                                                });
290    }-*/;
291
292    /**
293     * Called on address suggestion selection.<p>
294     *
295     * @param suggestion the selected suggestion
296     */
297    protected void onAddressChange(SuggestOracle.Suggestion suggestion) {
298
299        try {
300            onAddressChange(suggestion.getDisplayString());
301        } catch (Throwable t) {
302            CmsErrorDialog.handleException(t);
303        }
304    }
305
306    /**
307     * Cancels the location selection.<p>
308     */
309    protected void onCancel() {
310
311        m_popup.hide();
312        if (m_currentValue != null) {
313            m_editValue = m_currentValue;
314        }
315        displayValue();
316    }
317
318    /**
319     * Called on height value change.<p>
320     *
321     * @param height the height
322     */
323    protected void onHeightChange(String height) {
324
325        m_editValue.setHeight(height);
326    }
327
328    /**
329     * Called on latitude value change.<p>
330     *
331     * @param latitude the latitude
332     */
333    protected void onLatitudeChange(String latitude) {
334
335        try {
336            m_editValue.setLatitude(latitude);
337            updateMarkerPosition();
338            updateAddress();
339        } catch (Throwable t) {
340            CmsErrorDialog.handleException(t);
341        }
342    }
343
344    /**
345     * Called on longitude value change.<p>
346     *
347     * @param longitude the longitude
348     */
349    protected void onLongitudeChange(String longitude) {
350
351        try {
352            m_editValue.setLongitude(longitude);
353            updateMarkerPosition();
354            updateAddress();
355        } catch (Throwable t) {
356            CmsErrorDialog.handleException(t);
357        }
358    }
359
360    /**
361     * Called on mode value change.<p>
362     *
363     * @param mode the mode
364     */
365    protected void onModeChange(String mode) {
366
367        m_editValue.setMode(mode);
368    }
369
370    /**
371     * Sets the selected location value.<p>
372     */
373    protected void onOk() {
374
375        ensureFormattedAddress();
376    }
377
378    /**
379     * Ensures the preview map has the right size and is centered.<p>
380     */
381    protected native void onPreviewResize()/*-{
382                var map = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_previewMap;
383                if (map != null) {
384                        $wnd.google.maps.event.trigger(map, 'resize');
385                        var pos = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getCurrentPosition()();
386                        map.setCenter(pos);
387                }
388    }-*/;
389
390    /**
391     * Called on map type change.<p>
392     *
393     * @param type the map type
394     */
395    protected native void onTypeChange(String type)/*-{
396                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.type = type;
397                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_map
398                                .setMapTypeId(type);
399    }-*/;
400
401    /**
402     * Called on width value change.<p>
403     *
404     * @param width the width
405     */
406    protected void onWidthChange(String width) {
407
408        m_editValue.setWidth(width);
409    }
410
411    /**
412     * Called on zoom value change.<p>
413     *
414     * @param zoom the zoom
415     */
416    protected native void onZoomChange(String zoom) /*-{
417                var z = parseInt(zoom);
418                if (!isNaN(z)) {
419                        this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.zoom = z;
420                        var map = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_map;
421                        map.setZoom(z);
422                        var pos = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getCurrentPosition()();
423                        map.panTo(pos);
424                        map.setCenter(pos);
425                }
426    }-*/;
427
428    /**
429    * Fires the value change event for the location picker.<p>
430    *
431    * @param force <code>true</code> to always fire the event
432    */
433    void fireChangeEventOnPicker(boolean force) {
434
435        String val = m_editValue.toJSONString();
436        if (force || (m_currentValue == null) || !val.equals(m_currentValue.toJSONString())) {
437            m_currentValue = m_editValue.cloneValue();
438            displayValue();
439            ValueChangeEvent.fire(m_picker, val);
440        }
441    }
442
443    /**
444     * Displays the map for the current location.<p>
445     */
446    native void initMap() /*-{
447                try {
448                        if (this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_map == null) {
449                                var value = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue;
450                                var type = (value.type == null || value.type == "") ? "roadmap"
451                                                : value.type;
452                                var zoom = parseInt(value.zoom);
453                                if (isNaN(zoom)) {
454                                        zoom = 8;
455                                }
456
457                                var mapOptions = {
458                                        zoom : zoom,
459                                        mapTypeId : type,
460                                        center : new $wnd.google.maps.LatLng(-34.397, 150.644),
461                                        streetViewControl : false
462                                };
463                                var popupContent = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_popupContent;
464                                var canvas = popupContent.@org.opencms.gwt.client.ui.input.location.CmsLocationPopupContent::getMapCanvas()();
465                                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_map = new $wnd.google.maps.Map(
466                                                canvas, mapOptions);
467                                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_geocoder = new $wnd.google.maps.Geocoder();
468                        }
469                } catch (e) {
470                        $wnd.console.log(e.message);
471                }
472                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::updateMarkerPosition()();
473    }-*/;
474
475    /**
476     * Opens the location picker popup.<p>
477     */
478    void openPopup() {
479
480        try {
481            if (m_popup == null) {
482                m_popup = new CmsPopup(Messages.get().key(Messages.GUI_LOCATION_DIALOG_TITLE_0), hasMap() ? 1020 : 420);
483                m_popupContent = new CmsLocationPopupContent(
484                    this,
485                    new CmsLocationSuggestOracle(this),
486                    getModeItems(),
487                    getTypeItems(),
488                    getZoomItems());
489                setFieldVisibility();
490                m_popup.setMainContent(m_popupContent);
491                m_popup.addDialogClose(null);
492            }
493            m_popup.center();
494            m_popup.show();
495            initialize(() -> updateForm());
496        } catch (Throwable t) {
497            CmsErrorDialog.handleException(t);
498        }
499    }
500
501    /**
502     * Displays the values within the picker widget.<p>
503     */
504    private void displayValue() {
505
506        if (m_currentValue == null) {
507            m_picker.displayValue("");
508            m_picker.setPreviewVisible(false);
509            m_picker.setLocationInfo(Collections.<String, String> emptyMap());
510        } else {
511            m_picker.displayValue(m_editValue.getAddress());
512            Map<String, String> infos = new LinkedHashMap<String, String>();
513            if (hasLatLng()) {
514                infos.put(Messages.get().key(Messages.GUI_LOCATION_LATITUDE_0), m_editValue.getLatitudeString());
515                infos.put(Messages.get().key(Messages.GUI_LOCATION_LONGITUDE_0), m_editValue.getLongitudeString());
516                infos.put(Messages.get().key(Messages.GUI_LOCATION_ZOOM_0), String.valueOf(m_editValue.getZoom()));
517            }
518            if (hasSize()) {
519                infos.put(
520                    Messages.get().key(Messages.GUI_LOCATION_SIZE_0),
521                    m_editValue.getWidth() + " x " + m_editValue.getHeight());
522            }
523            if (hasType()) {
524                infos.put(Messages.get().key(Messages.GUI_LOCATION_TYPE_0), m_editValue.getType());
525            }
526            if (hasMode()) {
527                infos.put(Messages.get().key(Messages.GUI_LOCATION_MODE_0), m_editValue.getMode());
528            }
529            m_picker.setLocationInfo(infos);
530            m_picker.setPreviewVisible(true);
531            if (!isApiLoaded()) {
532
533                onApiReady(new Command() {
534
535                    public void execute() {
536
537                        return;
538                    }
539                });
540            }
541        }
542    }
543
544    /**
545     * Checks the current address with the google geocoder to ensure a well formatted address.<p>
546     * Will close the picker popup afterwards.<p>
547     */
548    private native void ensureFormattedAddress()/*-{
549                var self = this;
550                var address = self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address;
551                if (address != null && address.trim().length > 0) {
552                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_geocoder
553                                        .geocode(
554                                                        {
555                                                                'address' : address
556                                                        },
557                                                        function(results, status) {
558                                                                // check to see if we have at least one valid address
559                                                                if (results
560                                                                                && (status == $wnd.google.maps.GeocoderStatus.OK)
561                                                                                && results[0].formatted_address) {
562                                                                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address = results[0].formatted_address;
563                                                                }
564                                                                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::fireChangeAndClose()();
565                                                        });
566                }
567                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::fireChangeAndClose()();
568    }-*/;
569
570    /**
571     * Fires the value change event and closes the picker popup.<p>
572     */
573    private void fireChangeAndClose() {
574
575        fireChangeEventOnPicker(false);
576        m_popup.hide();
577    }
578
579    /**
580     * Returns the Google API key.<p>
581     *
582     * @return the Google API key
583     */
584    private native String getAPIKey()/*-{
585                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.apiKey;
586    }-*/;
587
588    /**
589     * Returns the Google API key message.<p>
590     *
591     * @return the Google API key message
592     */
593    private native String getAPIKeyMessage()/*-{
594                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.apiKeyMessage;
595    }-*/;
596
597    /**
598     * Returns the value display string.<p>
599     *
600     * @return the value
601     */
602    private String getDisplayString() {
603
604        return Messages.get().key(
605            Messages.GUI_LOCATION_DISPLAY_3,
606            m_editValue.getAddress(),
607            m_editValue.getLatitudeString(),
608            m_editValue.getLongitudeString());
609    }
610
611    /**
612     * Evaluates if the address field is configured.<p>
613     *
614     * @return <code>true</code> if the address field is configured
615     */
616    private native boolean hasAddress()/*-{
617                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
618                                .indexOf('address') != -1;
619    }-*/;
620
621    /**
622     * Evaluates if the Google API key is configured.<p>
623     *
624     * @return <code>true</code> if the Google API key is configured
625     */
626    private native boolean hasAPIKey()/*-{
627                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config != null
628                                && this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.apiKey != null;
629    }-*/;
630
631    /**
632     * Evaluates if the lat. lng. fields are configured.<p>
633     *
634     * @return <code>true</code> if the lat. lng. fields are configured
635     */
636    private native boolean hasLatLng()/*-{
637                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
638                                .indexOf('coords') != -1;
639    }-*/;
640
641    /**
642     * Evaluates if the map field is configured.<p>
643     *
644     * @return <code>true</code> if the map field is configured
645     */
646    private native boolean hasMap()/*-{
647                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
648                                .indexOf('map') != -1;
649    }-*/;
650
651    /**
652     * Evaluates if the mode field is configured.<p>
653     *
654     * @return <code>true</code> if the mode field is configured
655     */
656    private native boolean hasMode()/*-{
657                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
658                                .indexOf('mode') != -1;
659    }-*/;
660
661    /**
662     * Evaluates if the size fields are configured.<p>
663     *
664     * @return <code>true</code> if the size fields are configured
665     */
666    private native boolean hasSize()/*-{
667                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
668                                .indexOf('size') != -1;
669    }-*/;
670
671    /**
672     * Evaluates if the type field is configured.<p>
673     *
674     * @return <code>true</code> if the type field is configured
675     */
676    private native boolean hasType()/*-{
677                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
678                                .indexOf('type') != -1;
679    }-*/;
680
681    /**
682     * Evaluates if the zoom field is configured.<p>
683     *
684     * @return <code>true</code> if the zoom field is configured
685     */
686    private native boolean hasZoom()/*-{
687                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
688                                .indexOf('zoom') != -1;
689    }-*/;
690
691    /**
692     * Adds a CSS style rule to display the localized error message on missing API key.<p>
693     */
694    private void initDynamicStyle() {
695
696        if (!m_hasInsertedDynamicStyle) {
697            String message = Messages.get().key(Messages.ERR_LOCATION_MISSING_API_KEY_1, "\\a");
698            message.replace("<br />", "\\\\a");
699            CmsDomUtil.addDynamicStyleRule(
700                "div.gm-err-content:after, div.mapPreview > div > div:empty:after{ content:'"
701                    + message
702                    + "' !important; }");
703            m_hasInsertedDynamicStyle = true;
704        }
705    }
706
707    /**
708     * Initializes the location picker.<p>
709     *
710     * @param callback the callback to execute after initialization
711     */
712    private void initialize(Runnable callback) {
713
714        if (isApiLoaded()) {
715            initMap();
716            callback.run();
717
718        } else {
719            onApiReady(new Command() {
720
721                public void execute() {
722
723                    initMap();
724                    callback.run();
725                }
726            });
727        }
728    }
729
730    /**
731     * Loads the google maps API and initializes the map afterwards.<p>
732     */
733    private native void loadApi()/*-{
734                $wnd.cmsLocationPickerApiReady = function() {
735                        @org.opencms.gwt.client.ui.input.location.CmsLocationController::apiReady()();
736                }
737                var uri = @org.opencms.gwt.client.ui.input.location.CmsLocationController::MAPS_URI;
738                uri += '?'
739                                + @org.opencms.gwt.client.ui.input.location.CmsLocationController::MAPS_MAIN_PARAM
740                                + '&'
741                                + @org.opencms.gwt.client.ui.input.location.CmsLocationController::MAPS_PLACES_PARAM;
742                if (this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::hasAPIKey()()) {
743                        uri += '&key='
744                                        + this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getAPIKey()();
745                        if ($wnd.console) {
746                                $wnd.console
747                                                .log(this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getAPIKeyMessage()());
748                        }
749                } else if ($wnd.console) {
750                        $wnd.console.log("No Google API key available");
751                }
752                uri += '&callback=cmsLocationPickerApiReady';
753
754                var script = $wnd.document.createElement('script');
755                script.src = uri;
756                $wnd.document.body.appendChild(script);
757    }-*/;
758
759    /**
760     * Adds a callback to be executed once the API is ready. Will be executed right away if the API is already loaded.<p>
761     *
762     * @param callback the callback
763     */
764    private void onApiReady(Command callback) {
765
766        if (isApiLoaded()) {
767            callback.execute();
768        } else {
769            onApiReady.add(callback);
770            if (!loadingApi) {
771                loadingApi = true;
772                loadApi();
773            }
774        }
775    }
776
777    /**
778     * Parses the configuration string.<p>
779     *
780     * @param configuration the configuration
781     */
782    private native void parseConfig(String configuration)/*-{
783                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config = JSON
784                                .parse(configuration);
785    }-*/;
786
787    /**
788     * Sets all editable fields visible.<p>
789     */
790    private void setFieldVisibility() {
791
792        m_popupContent.setMapVisible(hasMap());
793        m_popupContent.setAddressVisible(hasAddress());
794        m_popupContent.setLatLngVisible(hasLatLng());
795        m_popupContent.setSizeVisible(hasSize());
796        m_popupContent.setTypeVisible(hasType());
797        m_popupContent.setModeVisible(hasMode());
798        m_popupContent.setZoomVisible(hasZoom());
799    }
800
801    /**
802     * Sets the position values and updates the map view.<p>
803     *
804     * @param latitude the latitude
805     * @param longitude the longitude
806     * @param updateMap <code>true</code> to update the map
807     * @param updateAddress <code>true</code> to update the address from the new position data
808     */
809    private void setPosition(float latitude, float longitude, boolean updateMap, boolean updateAddress) {
810
811        m_editValue.setLatitude(latitude);
812        m_editValue.setLongitude(longitude);
813        m_popupContent.displayValues(m_editValue);
814        if (updateMap) {
815            updateMarkerPosition();
816        }
817        if (updateAddress) {
818            updateAddress();
819        }
820    }
821
822    /**
823     * Updates the address according to the current position data.<p>
824     */
825    private native void updateAddress()/*-{
826                var self = this;
827                var pos = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getCurrentPosition()();
828                // try to evaluate the address from the current position
829                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_geocoder
830                                .geocode(
831                                                {
832                                                        'latLng' : pos
833                                                },
834                                                function(results, status) {
835                                                        var address = "";
836                                                        // check that everything is ok
837                                                        if (status == $wnd.google.maps.GeocoderStatus.OK
838                                                                        && results[0]
839                                                                        && results[0].formatted_address) {
840                                                                // set the new address
841                                                                address = results[0].formatted_address;
842                                                        }
843                                                        if (address != self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address) {
844                                                                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address = address;
845                                                                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::updateForm()();
846                                                        }
847                                                });
848    }-*/;
849
850    /**
851     * Displays the current location value within the popup form.<p>
852     */
853    private void updateForm() {
854
855        m_popupContent.displayValues(m_editValue);
856    }
857
858    /**
859     * Updates the marker position according to the current location value.<p>
860     */
861    private native void updateMarkerPosition()/*-{
862                var map = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_map;
863                var pos = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getCurrentPosition()();
864                var marker = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_marker;
865                if (marker == null) {
866                        try {
867                                var marker = new $wnd.google.maps.Marker({
868                                        position : pos,
869                                        map : map,
870                                        draggable : true
871                                });
872                                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_marker = marker;
873                                var self = this;
874                                // handle marker dnd
875                                $wnd.google.maps.event
876                                                .addListener(
877                                                                marker,
878                                                                "dragend",
879                                                                function() {
880                                                                        var lat = marker.getPosition().lat();
881                                                                        var lng = marker.getPosition().lng();
882                                                                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::setPosition(FFZZ)(lat,lng,false,true);
883                                                                });
884                        } catch (e) {
885                                $wnd.alert(e.message);
886                        }
887                } else {
888                        marker.setPosition(pos);
889                }
890                map.panTo(pos);
891                map.setCenter(pos);
892    }-*/;
893
894}