octobercms / october

Self-hosted CMS platform based on the Laravel PHP Framework.
https://octobercms.com/
Other
11.01k stars 2.21k forks source link

Very simple google map formwidget implementation doesn't work. [420] #3047

Closed KonstantinObuhov closed 6 years ago

KonstantinObuhov commented 7 years ago
Expected behavior

419 This solution works fine. Here is a request log when I updated Location model. http://clip2net.com/s/3N65PYE

Actual behavior

420 It doesn't work because the request looks like this. http://clip2net.com/s/3N663IT

Reproduce steps

I have my own custom formwidget that called "Mapbox" in my plugin what let me to create the location point for each object if I want.

_mapfields.htm

<input id="<?= $this->getId('autocomplete')?>" type="text" placeholder="Введите адрес для поиска" class="form-control w-350 m-t">
<input id="<?= $this->getId('lat')?>" name="lat" type="hidden" value="<?= $marker_lat ?>">
<input id="<?= $this->getId('lng')?>" name="lng" type="hidden" value="<?= $marker_lng ?>">
<div
        id="<?= $this->getId()?>"
        data-handler-changepoint="<?= $this->getEventHandler('onChangePoint') ?>"
        data-lat-id = "#<?= $this->getId('lat')?>"
        data-lng-id = "#<?= $this->getId('lng')?>"
        data-map-lat="<?= $map_lat ?>"
        data-map-lng="<?= $map_lng ?>"
        data-marker-lat="<?= $marker_lat ?>"
        data-marker-lng="<?= $marker_lng ?>"
        data-zoom="<?= $this->mapZoom ?>"
        data-height="<?= $this->mapHeight ?>"
        data-session-key="<?= $this->sessionKey ?>"
>
</div>

_mapbox.htm

<?php if ($this->popupMode): ?>
<a data-toggle="modal" href="#map-popup" class="btn btn-primary btn-lg oc-icon-map-marker">
    Открыть карту
</a>
<div class="control-popup modal fade" id="map-popup" tabindex="-1" role="dialog" aria-hidden="true"
     style="display: none;">
    <div class="modal-dialog size-giant">
        <div class="modal-content">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
            <?= $this->makePartial('mapfields') ?>`
        </div>
    </div>
</div>
<?php else: ?>
<?= $this->makePartial('mapfields') ?>
<?php endif ?>
<script>
    function initMap($) {
        var input = document.getElementById("<?= $this->getId('autocomplete')?>"),
            $mapBox = $('#<?= $this->getId()?>'),
            config = $mapBox.data(),
            $inputLat = $(config.latId),
            $inputLng = $(config.lngId),
            map = new GMaps({
                div: $mapBox[0],
                lat: config.mapLat,
                lng: config.mapLng,
                height: config.height,
                width: '100%',
                zoom: +config.zoom
            }),
            formData = function($el) {
                var $form = $el.closest('form'),
                    formData = [];
                if ($form.length > 0) {
                    return $form.serializeArray();
                }
            };

        //Создание автокомплита по адресам
        var autocomplete = new google.maps.places.Autocomplete(input),
            marker = map.createMarker({
                lat: config.markerLat,
                lng: config.markerLng,
                draggable: true,
                dragend: function (e, f) {
                    $inputLat.val(e.latLng.lat());
                    $inputLng.val(e.latLng.lng());
                    //Запрос на смену координат
                    $.request(config.handlerChangepoint, {
                        data: formData($mapBox)
                    });
                }
            });

        map.addControl({content: input, position: 'TOP_CENTER'});
        map.addMarker(marker);

        autocomplete.addListener('place_changed', function () {
            var place = autocomplete.getPlace();
            if (!place.geometry) {
                window.alert("Нет информации об адресе - '" + place.name + "'");
                return;
            }
            var newLat = place.geometry.location.lat(),
                newLng = place.geometry.location.lng();
            if (place.geometry.viewport) {
                map.fitBounds(place.geometry.viewport);
            } else {
                map.setCenter(place.geometry.location);
                map.setZoom(16);  // Почему 16? Так круто выглядит
            }
            marker.setPosition(place.geometry.location);
            $inputLat.val(newLat);
            $inputLng.val(newLng);

            console.log(formData($mapBox));
            //Запрос на смену координат
            $.request(config.handlerChangepoint, {
                data: formData($mapBox)
            });
        });

        //Fix form submitting
        google.maps.event.addDomListener(input, 'keydown', function (e) {
            if (e.keyCode === 13) {
                e.preventDefault();
            }
        });
        //Если карта в табе
        $(window).on('oc.updateUi shown.bs.modal', function (e) {
            map.refresh();
            map.setCenter(config.mapLat, config.mapLng);
        });
    };
    initMap(jQuery);
</script>

MapBox.php

<?php namespace Topkvestov\City\FormWidgets;

use Backend\Classes\FormField;
use Response;
use Flash;
use Backend\Classes\FormWidgetBase;
use ApplicationException;
use Exception;
use Topkvestov\City\Models\Location;

class MapBox extends FormWidgetBase
{
    use \Backend\Traits\FormModelWidget;

//    public function widgetDetals() {
//        return [
//            'name' => 'Mapbox',
//            'description' => 'Поле добавления точки на карте'
//        ];
//    }

    protected $defaultAlias = 'mapbox';

    /**
     * @var bool Set true if you want to load map inside popup
     */
    public $popupMode = false;

    /**
     * @var string map height px
     */
    public $mapHeight = '600px';

    /**
     * @var int Map zoom
     */
    public $mapZoom = 11;

    /**
     * @var float Lat for map center (Moscow)
     */
    public $defaultCenterLat = 55.75222;

    /**
     * @var float Lng for map center (Moscow)
     */
    public $defaultCenterLng = 37.61556;

    /**
     * @var array Supported relations
     */
    protected $supportedRelations = ['morphOne'];

    public function init()
    {
        $this->fillFromConfig([
            'zoomLocation',
            'defaultCenterLat',
            'defaultCenterLng',
            'popupMode'
        ]);

        $this->checkRelationType();
//        $this->checkResetPoint();
//        if (get('set_point')) {
//            $this->checkResetPoint();
//        }
    }
    protected function prepareVars()
    {
        $this->getRelationModel();
        $filedName = $this->fieldName;
        $location = $this->model->$filedName;

        $this->vars['marker_lat'] = ($location) ? $location->lat : null;
        $this->vars['marker_lng'] = ($location) ? $location->lng: null;
        $this->vars['map_lat'] = $this->defaultCenterLat;
        $this->vars['map_lng'] = $this->defaultCenterLng;
        $this->vars['name'] = $this->formField->getName();

        $this->vars['getRelationModel'] = $this->getRelationModel();
        $this->vars['getRelationObject'] = $this->getRelationObject();
        $this->vars['$this->sessionKey'] = $this->sessionKey;
        $this->vars['list'] = $this
            ->getRelationObject()
            ->withDeferred($this->sessionKey)
            ->get();
        $this->vars['check'] = ($this->model)::has($filedName)->get();
        $this->vars['$this->model'] = $this->model;

        if ($location && $location->exists && $location->lat && $location->lng) {
            $this->vars['map_lat'] = $this->vars['marker_lat'];
            $this->vars['map_lng'] = $this->vars['marker_lng'];
        }

        dump($this->vars);
    }

    public function onChangePoint()
    {
        $locationModel = $this->getRelationModel();
        $locationRelation = $this->getRelationObject();
        $filedName = $this->fieldName;
//        dump($_POST);
        if (!$this->model->$filedName) {
            $location = $locationModel;
            $location->lat = post('lat');
            $location->lng = post('lng');
            $location->save();
            if ($this->model->exists) {
                $locationRelation->add($location, null);
            } else {
                $locationRelation->add($location, $this->sessionKey);
            }
        } else if ($location = $this->model->$filedName) {
            $location->lat = post('lat');
            $location->lng = post('lng');
            $location->save();
        }
        Flash::success('Локация установлена!');
    }

    public function onTest()
    {
        Flash::success('Привет!');
    }
    /**
     *
     * Check relation type
     */
    protected function checkRelationType()
    {
        $relationType = $this->getRelationType();
        if (!in_array($relationType, $this->supportedRelations)) {
            throw new ApplicationException('Wrong relation type for field '. $this->formField->fieldName.'!');
        }
    }
    /**
     * @inheritDoc
     */
    public function render()
    {
        $this->prepareVars();
        return $this->makePartial('mapbox');
    }

    /**
     * @inheritDoc
     */
    public function getSaveValue($value)
    {
        return FormField::NO_SAVE_DATA;
    }

    public function loadAssets()
    {
        $this->addCss('css/example.css');
        $this->addJs('//maps.google.com/maps/api/js?key=my_key&libraries=places');
        $this->addJs('js/gmaps.min.js');
    }

}

Model Location.php

<?php namespace Topkvestov\City\Models;

use Model;

/**
 * Model
 */
class Location extends Model
{
    use \October\Rain\Database\Traits\Validation;
    use \October\Rain\Database\Traits\Nullable;

    /**
     * @var array Nullable attributes.
     */
    protected $nullable = [
        'lat',
        'lng'
    ];
    /*
     * Validation
     */
    public $rules = [
    ];

    /*
     * Casts
     */
//    protected $casts = [
//        'lat' => 'float',
//        'lng' => 'float'
//    ];

    /**
     * @var string The database table used by the model.
     */
    public $table = 'topkvestov_city_locations';
    public $morphTo = [
        'locationable' => []
    ];

    /*
     * Возвращает json координт
     * */
    public function getLatLngAttribute()
    {
        $lat = $this->lat;
        $lng = $this->lng;
        return json_encode(compact(['lat', 'lng']));
    }
}
October build

420

Why it may be wrong?

KonstantinObuhov commented 7 years ago

I've made investigation and figure some problems out on line https://github.com/octobercms/october/blob/cfd3d543162d302ccac88b3227b7fd69195bc575/modules/system/assets/js/framework.js#L82 any custom data will be converted from an array of objects to an object of objects.

For example data in ajax request like this:

$(document).ready(function(){
    getSelects = function() {
        return $('.value-review select');
    };

    $selects = getSelects();

    $selects.on('change', function(e) {
        $selectValues = getSelects().serializeArray();
        console.log($selectValues);
        $.request('onUpdateFinalValue', {
            data: $selectValues
        });
    });
});

For correct working it must be an array of objects http://api.jquery.com/jquery.param/ (If the object passed is in an Array, it must be an array of objects in the format returned by .serializeArray())

KonstantinObuhov commented 7 years ago

For working code above. We need to pass all data to data property as json object. I convert my data to json object with $.map. It works fine.

formData = function($el) {
                var $form = $el.closest('form'),
                    formData = {};
                if ($form.length > 0) {
                    var formDataS = $form.serializeArray();

                    $.map(formDataS, function(n, i){
                        formData[n['name']] = n['value'];
                    });

                    return formData;
                }
            };