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();
496            updateForm();
497        } catch (Throwable t) {
498            CmsErrorDialog.handleException(t);
499        }
500    }
501
502    /**
503     * Displays the values within the picker widget.<p>
504     */
505    private void displayValue() {
506
507        if (m_currentValue == null) {
508            m_picker.displayValue("");
509            m_picker.setPreviewVisible(false);
510            m_picker.setLocationInfo(Collections.<String, String> emptyMap());
511        } else {
512            m_picker.displayValue(m_editValue.getAddress());
513            Map<String, String> infos = new LinkedHashMap<String, String>();
514            if (hasLatLng()) {
515                infos.put(Messages.get().key(Messages.GUI_LOCATION_LATITUDE_0), m_editValue.getLatitudeString());
516                infos.put(Messages.get().key(Messages.GUI_LOCATION_LONGITUDE_0), m_editValue.getLongitudeString());
517                infos.put(Messages.get().key(Messages.GUI_LOCATION_ZOOM_0), String.valueOf(m_editValue.getZoom()));
518            }
519            if (hasSize()) {
520                infos.put(
521                    Messages.get().key(Messages.GUI_LOCATION_SIZE_0),
522                    m_editValue.getWidth() + " x " + m_editValue.getHeight());
523            }
524            if (hasType()) {
525                infos.put(Messages.get().key(Messages.GUI_LOCATION_TYPE_0), m_editValue.getType());
526            }
527            if (hasMode()) {
528                infos.put(Messages.get().key(Messages.GUI_LOCATION_MODE_0), m_editValue.getMode());
529            }
530            m_picker.setLocationInfo(infos);
531            m_picker.setPreviewVisible(true);
532            if (!isApiLoaded()) {
533
534                onApiReady(new Command() {
535
536                    public void execute() {
537
538                        return;
539                    }
540                });
541            }
542        }
543    }
544
545    /**
546     * Checks the current address with the google geocoder to ensure a well formatted address.<p>
547     * Will close the picker popup afterwards.<p>
548     */
549    private native void ensureFormattedAddress()/*-{
550                var self = this;
551                var address = self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address;
552                if (address != null && address.trim().length > 0) {
553                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_geocoder
554                                        .geocode(
555                                                        {
556                                                                'address' : address
557                                                        },
558                                                        function(results, status) {
559                                                                // check to see if we have at least one valid address
560                                                                if (results
561                                                                                && (status == $wnd.google.maps.GeocoderStatus.OK)
562                                                                                && results[0].formatted_address) {
563                                                                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address = results[0].formatted_address;
564                                                                }
565                                                                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::fireChangeAndClose()();
566                                                        });
567                }
568                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::fireChangeAndClose()();
569    }-*/;
570
571    /**
572     * Fires the value change event and closes the picker popup.<p>
573     */
574    private void fireChangeAndClose() {
575
576        fireChangeEventOnPicker(false);
577        m_popup.hide();
578    }
579
580    /**
581     * Returns the Google API key.<p>
582     *
583     * @return the Google API key
584     */
585    private native String getAPIKey()/*-{
586                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.apiKey;
587    }-*/;
588
589    /**
590     * Returns the Google API key message.<p>
591     *
592     * @return the Google API key message
593     */
594    private native String getAPIKeyMessage()/*-{
595                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.apiKeyMessage;
596    }-*/;
597
598    /**
599     * Returns the value display string.<p>
600     *
601     * @return the value
602     */
603    private String getDisplayString() {
604
605        return Messages.get().key(
606            Messages.GUI_LOCATION_DISPLAY_3,
607            m_editValue.getAddress(),
608            m_editValue.getLatitudeString(),
609            m_editValue.getLongitudeString());
610    }
611
612    /**
613     * Evaluates if the address field is configured.<p>
614     *
615     * @return <code>true</code> if the address field is configured
616     */
617    private native boolean hasAddress()/*-{
618                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
619                                .indexOf('address') != -1;
620    }-*/;
621
622    /**
623     * Evaluates if the Google API key is configured.<p>
624     *
625     * @return <code>true</code> if the Google API key is configured
626     */
627    private native boolean hasAPIKey()/*-{
628                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config != null
629                                && this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.apiKey != null;
630    }-*/;
631
632    /**
633     * Evaluates if the lat. lng. fields are configured.<p>
634     *
635     * @return <code>true</code> if the lat. lng. fields are configured
636     */
637    private native boolean hasLatLng()/*-{
638                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
639                                .indexOf('coords') != -1;
640    }-*/;
641
642    /**
643     * Evaluates if the map field is configured.<p>
644     *
645     * @return <code>true</code> if the map field is configured
646     */
647    private native boolean hasMap()/*-{
648                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
649                                .indexOf('map') != -1;
650    }-*/;
651
652    /**
653     * Evaluates if the mode field is configured.<p>
654     *
655     * @return <code>true</code> if the mode field is configured
656     */
657    private native boolean hasMode()/*-{
658                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
659                                .indexOf('mode') != -1;
660    }-*/;
661
662    /**
663     * Evaluates if the size fields are configured.<p>
664     *
665     * @return <code>true</code> if the size fields are configured
666     */
667    private native boolean hasSize()/*-{
668                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
669                                .indexOf('size') != -1;
670    }-*/;
671
672    /**
673     * Evaluates if the type field is configured.<p>
674     *
675     * @return <code>true</code> if the type field is configured
676     */
677    private native boolean hasType()/*-{
678                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
679                                .indexOf('type') != -1;
680    }-*/;
681
682    /**
683     * Evaluates if the zoom field is configured.<p>
684     *
685     * @return <code>true</code> if the zoom field is configured
686     */
687    private native boolean hasZoom()/*-{
688                return this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config.edit
689                                .indexOf('zoom') != -1;
690    }-*/;
691
692    /**
693     * Adds a CSS style rule to display the localized error message on missing API key.<p>
694     */
695    private void initDynamicStyle() {
696
697        if (!m_hasInsertedDynamicStyle) {
698            String message = Messages.get().key(Messages.ERR_LOCATION_MISSING_API_KEY_1, "\\a");
699            message.replace("<br />", "\\\\a");
700            CmsDomUtil.addDynamicStyleRule(
701                "div.gm-err-content:after, div.mapPreview > div > div:empty:after{ content:'"
702                    + message
703                    + "' !important; }");
704            m_hasInsertedDynamicStyle = true;
705        }
706    }
707
708    /**
709     * Initializes the location picker.<p>
710     */
711    private void initialize() {
712
713        if (isApiLoaded()) {
714            initMap();
715        } else {
716            onApiReady(new Command() {
717
718                public void execute() {
719
720                    initMap();
721                }
722            });
723        }
724    }
725
726    /**
727     * Loads the google maps API and initializes the map afterwards.<p>
728     */
729    private native void loadApi()/*-{
730                $wnd.cmsLocationPickerApiReady = function() {
731                        @org.opencms.gwt.client.ui.input.location.CmsLocationController::apiReady()();
732                }
733                var uri = @org.opencms.gwt.client.ui.input.location.CmsLocationController::MAPS_URI;
734                uri += '?'
735                                + @org.opencms.gwt.client.ui.input.location.CmsLocationController::MAPS_MAIN_PARAM
736                                + '&'
737                                + @org.opencms.gwt.client.ui.input.location.CmsLocationController::MAPS_PLACES_PARAM;
738                if (this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::hasAPIKey()()) {
739                        uri += '&key='
740                                        + this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getAPIKey()();
741                        if ($wnd.console) {
742                                $wnd.console
743                                                .log(this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getAPIKeyMessage()());
744                        }
745                } else if ($wnd.console) {
746                        $wnd.console.log("No Google API key available");
747                }
748                uri += '&callback=cmsLocationPickerApiReady';
749
750                var script = $wnd.document.createElement('script');
751                script.src = uri;
752                $wnd.document.body.appendChild(script);
753    }-*/;
754
755    /**
756     * Adds a callback to be executed once the API is ready. Will be executed right away if the API is already loaded.<p>
757     *
758     * @param callback the callback
759     */
760    private void onApiReady(Command callback) {
761
762        if (isApiLoaded()) {
763            callback.execute();
764        } else {
765            onApiReady.add(callback);
766            if (!loadingApi) {
767                loadingApi = true;
768                loadApi();
769            }
770        }
771    }
772
773    /**
774     * Parses the configuration string.<p>
775     *
776     * @param configuration the configuration
777     */
778    private native void parseConfig(String configuration)/*-{
779                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_config = JSON
780                                .parse(configuration);
781    }-*/;
782
783    /**
784     * Sets all editable fields visible.<p>
785     */
786    private void setFieldVisibility() {
787
788        m_popupContent.setMapVisible(hasMap());
789        m_popupContent.setAddressVisible(hasAddress());
790        m_popupContent.setLatLngVisible(hasLatLng());
791        m_popupContent.setSizeVisible(hasSize());
792        m_popupContent.setTypeVisible(hasType());
793        m_popupContent.setModeVisible(hasMode());
794        m_popupContent.setZoomVisible(hasZoom());
795    }
796
797    /**
798     * Sets the position values and updates the map view.<p>
799     *
800     * @param latitude the latitude
801     * @param longitude the longitude
802     * @param updateMap <code>true</code> to update the map
803     * @param updateAddress <code>true</code> to update the address from the new position data
804     */
805    private void setPosition(float latitude, float longitude, boolean updateMap, boolean updateAddress) {
806
807        m_editValue.setLatitude(latitude);
808        m_editValue.setLongitude(longitude);
809        m_popupContent.displayValues(m_editValue);
810        if (updateMap) {
811            updateMarkerPosition();
812        }
813        if (updateAddress) {
814            updateAddress();
815        }
816    }
817
818    /**
819     * Updates the address according to the current position data.<p>
820     */
821    private native void updateAddress()/*-{
822                var self = this;
823                var pos = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getCurrentPosition()();
824                // try to evaluate the address from the current position
825                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_geocoder
826                                .geocode(
827                                                {
828                                                        'latLng' : pos
829                                                },
830                                                function(results, status) {
831                                                        var address = "";
832                                                        // check that everything is ok
833                                                        if (status == $wnd.google.maps.GeocoderStatus.OK
834                                                                        && results[0]
835                                                                        && results[0].formatted_address) {
836                                                                // set the new address
837                                                                address = results[0].formatted_address;
838                                                        }
839                                                        if (address != self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address) {
840                                                                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_editValue.address = address;
841                                                                self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::updateForm()();
842                                                        }
843                                                });
844    }-*/;
845
846    /**
847     * Displays the current location value within the popup form.<p>
848     */
849    private void updateForm() {
850
851        m_popupContent.displayValues(m_editValue);
852    }
853
854    /**
855     * Updates the marker position according to the current location value.<p>
856     */
857    private native void updateMarkerPosition()/*-{
858                var map = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_map;
859                var pos = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::getCurrentPosition()();
860                var marker = this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_marker;
861                if (marker == null) {
862                        try {
863                                var marker = new $wnd.google.maps.Marker({
864                                        position : pos,
865                                        map : map,
866                                        draggable : true
867                                });
868                                this.@org.opencms.gwt.client.ui.input.location.CmsLocationController::m_marker = marker;
869                                var self = this;
870                                // handle marker dnd
871                                $wnd.google.maps.event
872                                                .addListener(
873                                                                marker,
874                                                                "dragend",
875                                                                function() {
876                                                                        var lat = marker.getPosition().lat();
877                                                                        var lng = marker.getPosition().lng();
878                                                                        self.@org.opencms.gwt.client.ui.input.location.CmsLocationController::setPosition(FFZZ)(lat,lng,false,true);
879                                                                });
880                        } catch (e) {
881                                $wnd.alert(e.message);
882                        }
883                } else {
884                        marker.setPosition(pos);
885                }
886                map.panTo(pos);
887                map.setCenter(pos);
888    }-*/;
889
890}