/*
* Copyright 2015 Anyware Services
*
* 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.
*/
/**
* Singleton class allowing to add or update Map service into selected page and zone
* @private
*/
Ext.define('Ametys.plugins.maps.MapServiceActions', {
singleton: true,
/**
* @private
* @property {Ametys.window.DialogBox} _box The dialog box for service configuration
*/
/**
* @private
* @property {String} _mode the mode, can be 'new' for creation mode, 'edit' for edition mode
*/
/**
* @private
* @property {Function} _callback the callback function called after validating the service
*/
/**
* @private
* @property {Object} _params the service parameters
* @property {String} _params.pageId the page id
* @property {String} _params.zoneName the zone's name, required for service creation
* @property {String} _params.zoneItemId the id of the zone item, required for service edition
* @property {String} _params.serviceId the id of the service
*/
/**
* @private
* @property {Ametys.form.ConfigurableFormPanel} _form the form used to choose the title and the height of the service
*/
/**
* @private
* @property {Ametys.plugins.maps.GMapConfiguration} _gMapConfiguration the configuration of the map.
* It embeds the shared store for the {@link Ametys.plugins.maps.GMapPanel} and {@link Ametys.plugins.maps.MapItemsView} and keeps a reference
* on them in order to be able to call their own methods later on.
*/
/**
* @private
* @property {String[]} _specificMapsParameterNames The names of service's parameters that are specific for the Map
*/
_specificMapsParameterNames: ["center", "zoomLevel", "mapTypeId"],
/**
* @private
* @property {String} _poisParameterName The name of service's points of interest parameter
*/
_poisParameterName: "pointsOfInterest",
/**
* Open the dialog box to configure Maps service
* @param {Object} params the parameters
* @param {String} params.pageId (required) The page id
* @param {String} params.zoneName The zone name. Required for service creation
* @param {String} params.zoneItemId The zone item id. Required for service edition
* @param {String} params.serviceId (required) The service id
* @param {Function} callback the callback function
* @param {String} mode The mode: 'new' for service creation or 'edit' for service configuration
*/
open: function (params, callback, mode)
{
var me = this;
this._params = params;
this._mode = mode;
this._callback = callback;
// function called after the form containing the 2 fields title and height is configured
function configureCallback ()
{
me._initBox (params, Ext.bind(me._createMapDialogBox, me));
}
// configuration of the form
this._configureForm(params, configureCallback);
},
/**
* @private
* Initialize the {@link Ametys.form.ConfigurableFormPanel} used for title and height of the service
* @param {Object} params the parameters
* @param {String} params.pageId (required) The page id
* @param {String} params.zoneName The zone name. Required for service creation
* @param {String} params.zoneItemId The zone item id. Required for service edition
* @param {String} params.serviceId (required) The service id
* @param {Function} callback the function that will be invoked when the form's configuration process is over
*/
_configureForm: function(params, callback)
{
// Fetch the API key for google maps and load the script
Ametys.helper.ChooseLocation.LoadGoogleMaps.getDefaultAPIKey(Ext.bind(this._getAPIKeyCb, this, [params, callback], 1));
},
/**
* @private
* Function invoked after retrieving teh Google API key
* @param {String} apiKey The API key
* @param {Object} params the parameters
* @param {String} params.pageId (required) The page id
* @param {String} params.zoneName The zone name. Required for service creation
* @param {String} params.zoneItemId The zone item id. Required for service edition
* @param {String} params.serviceId (required) The service id
* @param {Function} callback the function that will be invoked when the form's configuration process is over
*/
_getAPIKeyCb: function (apiKey, params, callback)
{
var me = this;
Ametys.helper.ChooseLocation.LoadGoogleMaps.loadScript(apiKey,
function()
{
Ametys.plugins.web.zone.ZoneItemManager.getServiceParameterDefinitions([params.serviceId, params.pageId, params.zoneItemId, params.zoneName], me._configureFormCb, {scope: me, arguments: [callback]});
}
);
},
/**
* @private
* Callback for the form initialization process
* @param {Object} response the configuration of the form panel's fields
* @param {Object} args the callback arguments
* @param {Function} args.callback the callback function
*/
_configureFormCb: function(response, args)
{
// Separate specific Maps parameters and classic parameters (using ConfigurableFormPanel)
var separatedDefinitions = this._separateClassicAndSpecificParameterDefinitions(response.parameters.elements);
// Initialize the form for classic parameters
this._form = Ext.create('Ametys.form.ConfigurableFormPanel', {
defaultFieldConfig: {
labelWidth: 80,
minWidth: 180
},
height: 90,
defaultType: 'textfield',
listeners: {
'afterlayout': Ext.bind(this._focusForm, this)
}
});
this._form.configure(separatedDefinitions.classicElements);
this._specificElementDefinitions = separatedDefinitions.specificElements;
if (Ext.isFunction(args[0]))
{
args[0]();
}
},
/**
* @private
* Separate the classic and specific definitions from given definitions
* The returned {Object} contains 2 elements:
* - {Object} classicElements contains all classic parameter definitions
* - {Object} specificElements contains all specific Map parameter definitions
* @param {Object} allDefinitions all the parameter definitions
* @return {Object} the separated classic and specific parameter definitions
*/
_separateClassicAndSpecificParameterDefinitions: function(allDefinitions)
{
var me = this;
var result = {};
result.classicElements = {};
result.specificElements = {};
Ext.Object.each(allDefinitions, function(key, value, definitions) {
if (Ext.Array.contains(me._specificMapsParameterNames, key) || Ext.String.startsWith(key, me._poisParameterName))
{
result.specificElements[key] = value;
}
else
{
result.classicElements[key] = value;
}
});
return result;
},
/**
* @private
* Focuses the form panel
*/
_focusForm: function()
{
this._form.focus();
},
/**
* @private
* Initialize the dialog box
* @param {Object} params the initial parameters
* @param {Function} callback function invoked after the form's initialization process
*/
_initBox: function (params, callback)
{
if (this._mode == 'edit')
{
Ametys.plugins.web.zone.ZoneItemManager.getServiceParameterValues([params.zoneItemId, params.serviceId], this._getServiceParametersCb, {scope: this, arguments: [callback]});
}
else
{
this._gMapConfiguration = Ext.create ('Ametys.plugins.maps.GMapConfiguration',
{
zoomLevel: this._specificElementDefinitions.zoomLevel['default-value'],
mapTypeId: google.maps.MapTypeId.ROADMAP,
center: {
lat: this._specificElementDefinitions.center['default-value'].latitude,
lng: this._specificElementDefinitions.center['default-value'].longitude
},
pois:[]
});
this._form.setValues(); // setValues must always be called for configurable form panel in order to complete its initialization
if (Ext.isFunction(callback))
{
callback();
}
}
},
/**
* @private
* Callback function invoked after retrieving service parameters in 'edit' mode
* @param {Object} response the server response
* @param {Object} args the callback arguments
* @param {Function} args.callback the callback function invoked
*/
_getServiceParametersCb: function (response, args)
{
// Separate specific Maps parameters and classic parameters
var separatedValues = this._separateClassicAndSpecificParameterValues(response);
// Set classic values for the configurable form panel
this._form.setValues(separatedValues.classicValues);
// Set specific values for the GMapConfiguration object
var me = this;
this._gMapConfiguration = Ext.create ('Ametys.plugins.maps.GMapConfiguration',
{
zoomLevel: parseInt(separatedValues.specificValues.values.zoomLevel),
mapTypeId: separatedValues.specificValues.values.mapTypeId,
center: {
lat: parseFloat(separatedValues.specificValues.values.center.latitude),
lng: parseFloat(separatedValues.specificValues.values.center.longitude)
},
pois:me._getPoisAsArray(separatedValues.specificValues)
});
if (Ext.isFunction(args[0]))
{
args[0]();
}
},
/**
* @private
* Separate the classic and specific values from given values
* The returned {Object} contains 2 elements:
* - {Object} classicValues contains all classic parameter values
* - {Object} specificValues contains all specific Map parameter values
* @param {Object} allValues all the parameter values
* @return {Object} the separated classic and specific parameter values
*/
_separateClassicAndSpecificParameterValues: function(allValues)
{
var me = this;
var result = {};
result.classicValues = {};
result.classicValues.values = {};
result.classicValues.repeaters = [];
result.specificValues = {};
result.specificValues.values = {};
result.specificValues.repeaters = [];
// Manage repeaters
// 1. Find the points of interest repeater
var pointsOfInterest = Ext.Array.findBy(allValues.repeaters, function(repeater) {
return me._poisParameterName == repeater.name;
});
// 2. Add the points of interest repeater to the specific repeaters
Ext.Array.push(result.specificValues.repeaters, pointsOfInterest);
// 3. Remove it from from the classic repeaters
var classicRepeaters = Ext.Array.remove(allValues.repeaters, pointsOfInterest);
// 3. if there are other repeaters, add them to the classic values
if (classicRepeaters.length > 0)
{
result.classicValues.repeaters = classicRepeaters;
}
// Manage values
Ext.Object.each(allValues.values, function(key, value, values) {
if (Ext.Array.contains(me._specificMapsParameterNames, key) || Ext.String.startsWith(key, me._poisParameterName))
{
result.specificValues.values[key] = value;
}
else
{
result.classicValues.values[key] = value;
}
});
return result;
},
/**
* @private
* Retrieves the points of interest found in the given values as an array
* @param {Object} values object containing the points of interest values
* @return {Object[]} the points of interest as an array
*/
_getPoisAsArray: function(values)
{
var pois = [];
var poisCount = values.repeaters[0].count;
for (var i = 1; i <= poisCount; i++)
{
var poi = {};
poi.title = values.values[this._poisParameterName + "[" + i + "]/title"];
poi.description = values.values[this._poisParameterName + "[" + i + "]/description"];
poi.gtype = values.values[this._poisParameterName + "[" + i + "]/type"];
// marker poi
if (poi.gtype == 'marker')
{
poi.icon = values.values[this._poisParameterName + "[" + i + "]/icon"];
poi.lat = values.values[this._poisParameterName + "[" + i + "]/point"].latitude;
poi.lng = values.values[this._poisParameterName + "[" + i + "]/point"].longitude;
}
else if (poi.gtype == 'polygon')
{
// polygon poi
poi.color = values.values[this._poisParameterName + "[" + i + "]/color"];
poi.points = [];
Ext.Array.each(values.values[this._poisParameterName + "[" + i + "]/points"], function(point)
{
Ext.Array.push(poi.points, {lat: point.latitude, lng: point.longitude});
});
}
Ext.Array.push(pois, poi);
}
return pois;
},
/**
* @private
* Create the Maps service dialog box
*/
_createMapDialogBox: function ()
{
this._box = Ext.create('Ametys.window.DialogBox', {
title:"{{i18n plugin.web:PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS}}",
iconCls: 'ametysicon-hammer2',
width: 1000,
height: 500,
border: false,
alwaysOnTop: -1, // prioritize the window to the bottom of the z-index stack.
layout: {
type: 'hbox',
align: 'stretch'
},
items: [
{
width: 350,
layout: {
type: 'vbox',
align: 'stretch'
},
items: [
this._form,
this._gMapConfiguration.getMapItemsPanel({flex: 1})
]
},
this._gMapConfiguration.getGMapPanel({flex: 1})
],
closeAction: 'hide',
defaultFocus: this._form,
referenceHolder: true,
defaultButton: 'validate',
buttons : [
{
reference: 'validate',
text :"{{i18n plugin.web:PLUGINS_WEB_ZONE_ADDSERVICEACTION_OK}}",
handler : this._validate,
scope: this
},
{
text :"{{i18n plugin.web:PLUGINS_WEB_ZONE_ADDSERVICEACTION_CANCEL}}",
handler : function () { this._box.close() },
scope: this
}
]
});
this._box.show();
},
/**
* @private
* Validate the dialog box and create or edit the service.
*/
_validate: function ()
{
// Check form
var invalidFields = this._form.getInvalidFields();
if (invalidFields.length > 0)
{
Ametys.Msg.show({
title: "{{i18n plugin.web:PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS_SAVE}}",
msg: "{{i18n plugin.web:PLUGINS_WEB_CONTENT_ADDSERVICE_PARAMETERS_SAVE_INVALIDFIELDS}}" + Ametys.form.SaveHelper.getInvalidFieldsAsReadableList(invalidFields),
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.ERROR
});
return;
}
var values = this._form.getJsonValues();
// get map parameters
var mapConfiguration = this._gMapConfiguration;
var center = mapConfiguration.getCenter();
values['center'] = {latitude: center.lat, longitude: center.lng};
values['zoomLevel'] = mapConfiguration.getZoomLevel();
values['mapTypeId'] = mapConfiguration.getMapTypeId();
var pois = mapConfiguration.getPoisAsArray();
values["_pointsOfInterest/size"] = pois.length;
for (var arrayIndex = 0; arrayIndex < pois.length; arrayIndex++)
{
var repeaterIndex = arrayIndex + 1;
// Always put -1, the points of interest can't be re-ordered
values['_pointsOfInterest[' + repeaterIndex + ']/previous-position'] = -1;
for (var name in pois[arrayIndex])
{
if ('gtype' == name)
{
values['pointsOfInterest[' + repeaterIndex + ']/type'] = pois[arrayIndex].gtype;
}
else if ('lat' == name || 'lng' == name)
{
values['pointsOfInterest[' + repeaterIndex + ']/point'] = {latitude: pois[arrayIndex].lat, longitude: pois[arrayIndex].lng}
}
else if ('points' == name)
{
var points = [];
Ext.Array.each(pois[arrayIndex].points, function(point) {
Ext.Array.push(points, {latitude: point.lat, longitude: point.lng});
});
values['pointsOfInterest[' + repeaterIndex + ']/points'] = points;
}
else
{
values['pointsOfInterest[' + repeaterIndex + ']/' + name] = pois[arrayIndex][name];
}
}
}
if (this._mode == 'new')
{
Ametys.plugins.web.zone.ZoneItemManager.addService([this._params.pageId, this._params.zoneName, this._params.serviceId, values], this._saveParametersCb, {scope: this});
}
else
{
Ametys.plugins.web.zone.ZoneItemManager.editServiceParameterValues([this._params.zoneItemId, this._params.serviceId, values], this._saveParametersCb, {scope: this});
}
},
/**
* @private
* Callback after saving service
* @param {Object} response The server response
* @param {Object} args The callback arguments
*/
_saveParametersCb: function (response, args)
{
var errors = response.error;
if (!Ext.isEmpty(errors))
{
var details = "";
for (var error in errors)
{
details += error + ": " + errors[error].join('; ') + "\n";
}
Ametys.log.ErrorDialog.display({
title: "{{i18n plugin.web:PLUGINS_WEB_DAOS_PAGE_CONFIGURESERVICE_FAILED_TITLE}}",
text: "{{i18n plugin.web:PLUGINS_WEB_DAOS_PAGE_CONFIGURESERVICE_FAILED_DESC}}",
details: details,
category: this.self.getName()
});
}
else
{
this._box.close();
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.MODIFIED, // TODO zoningChanged + selectionchanged ?,
parameters: {"creation": "service"},
targets: [{
id: Ametys.message.MessageTarget.PAGE,
parameters: {
ids: [response.id],
'zone-name': response['zone-name'],
'zoneitem-id': response['zoneitem-id']
}
}]
});
// Execute callback function passed to #open method
if (Ext.isFunction(this._callback))
{
this._callback(this._params);
}
}
}
});