/**
* @fileoverview bu.Workspace is an object that manages a collection of
* {@link bu.Viewer} arranged through a {@link bu.Layout}.
* Created 27/03/2017.
* @author josea.hernandez@blom.no (Jose Antonio Hernandez)
* @copyright Blom Data S.L. 2017
*/
goog.provide('bu.Workspace');
goog.require('ol');
goog.require('ol.Object');
goog.require('ol.events');
/**
* @classdesc
* The workspace is the core component of BlomUrbex API. It manages a collection
* of several {@link bu.Viewer} arranged in a {@link bu.Layout}.
*
* var workspace = new bu.Workspace({
* layout: {
* name: 'Dual view',
* views: [
* {
* column: 0,
* viewer: new bu.ortho.OpenLayersViewer({
* center: [0, 0],
* zoom: 1
* layers: [
* "OSM"
* ],
* baselayer: bu.Orientation.ORTHO
* })
* },
* {
* column: 1,
* viewer: new bu.street.MarzipanoViewer({
* swfPath: 'swf/marzipano.swf'
* })
* }
* ]
* },
* target: 'workspace'
* });
*
* The above snippet creates a workspace inside a div element with id 'workspace'
* and initializes it with a dual-view layout in two columns using an ortho view
* for the first column and a street view for the second column.
*
* The constructor places a viewport container (with CSS class name
* `bu-workspace-viewport`) in the target element (see `getViewerContainer()`),
* and then several containers inside the viewport, one per each different value
* of column property (with a CSS class name `bu-workspace-container`) and
* finally several subcontainers inside the previous ones, one per each view with
* same column value (with a CSS class name `bu-workspace-subcontainer`). This
* simple but powerful method allow the creation of complex layouts.
*
* @constructor
* @extends {ol.Object}
* @param {bu.WorkspaceOptions} options Workspace options.
* @api
*/
bu.Workspace = function(options) {
ol.Object.call(this);
/**
* @private
* @type {Element}
*/
this.viewport_ = document.createElement('DIV');
this.viewport_.className = 'bu-workspace-viewport';
this.viewport_.style.position = 'relative';
this.viewport_.style.overflow = 'hidden';
this.viewport_.style.width = '100%';
this.viewport_.style.height = '100%';
// prevent page zoom on IE >= 10 browsers
this.viewport_.style.msTouchAction = 'none';
this.viewport_.style.touchAction = 'none';
/**
* @type {Array.<Element>}
* @private
*/
this.containers_ = null;
/**
* @type {Array.<Element>}
* @private
*/
this.subcontainers_ = null;
/**
* @type {Array.<bu.Viewer>}
* @private
*/
this.viewers_ = null;
/**
* @type {function(Event)|undefined}
* @private
*/
this.handleResize_;
/**
* @private
* @type {Array.<ol.EventsKey>}
*/
this.viewerChangeHandlerKeys_ = null;
ol.events.listen(this, ol.Object.getChangeEventType(bu.WorkspaceProperty.TARGET),
this.handleTargetChanged_, this);
ol.events.listen(this, ol.Object.getChangeEventType(bu.WorkspaceProperty.LAYOUT),
this.handleLayoutChanged_, this);
this.setTarget(options.target);
//this.setLayout(options.layout);
this.setupLayout(options.layout);
};
ol.inherits(bu.Workspace, ol.Object);
/**
* Get the layout using {@link bu.ViewType} in the definition.
* @return {bu.Layout} The current layout.
* @observable
* @api stable
*/
bu.Workspace.prototype.getLayout = function() {
return this.layout_;
};
/**
* Get the target in which the workspace is rendered.
* Note that this returns what is entered as an option or in setTarget:
* if that was an element, it returns an element; if a string, it returns that.
* @return {Element|string|undefined} The Element or id of the Element that the
* workspace is rendered in.
* @observable
* @api stable
*/
bu.Workspace.prototype.getTarget = function() {
return /** @type {Element|string|undefined} */ (
this.get(bu.WorkspaceProperty.TARGET));
};
/**
* Set the target element to render this workspace into.
* @param {Element|string|undefined} target The Element or id of the Element
* that the workspace is rendered in.
* @observable
* @api stable
*/
bu.Workspace.prototype.setTarget = function(target) {
this.set(bu.WorkspaceProperty.TARGET, target);
};
/**
* Get the DOM element into which this workspace is rendered. In contrast to
* `getTarget` this method always return an `Element`, or `null` if the
* workspace has no target.
* @return {Element} The element that the workspace is rendered in.
* @api
*/
bu.Workspace.prototype.getTargetElement = function() {
var target = this.getTarget();
if (target !== undefined) {
return typeof target === 'string' ? document.getElementById(target) : target;
} else {
return null;
}
};
/**
*
* @inheritDoc
*/
bu.Workspace.prototype.disposeInternal = function() {
this.setTarget(null);
ol.Object.prototype.disposeInternal.call(this);
};
/**
* @private
*/
bu.Workspace.prototype.handleStreetViewChanged_ = function(viewIndex) {
var layout = this.getLayout();
if (layout && layout.views.length > 0) {
var syncedViews = layout.views[viewIndex].syncedViews;
if (syncedViews && syncedViews.length > 0) {
var viewer = layout.views[viewIndex].viewer;
var center = viewer.getCenter();
var rotation = viewer.getRotation();
var fov = viewer.getFOV();
for (var i = 0; i < syncedViews.length; i++) {
var syncedViewer = layout.views[syncedViews[i]].viewer;
if (syncedViewer instanceof bu.ortho.Viewer) {
if (syncedViewer instanceof bu.ortho.openlayers.Viewer) {
syncedViewer.updatePanoramaFOV(center, rotation,
fov, viewIndex);
if (syncedViewer.getSyncCenterWithStreetViewers()) {
//Careful here: a bu.street.Viewer.getCenter() returns always latlon
var projectedCenter = syncedViewer.toCurrentProjection(center,
"EPSG:4326")
if (projectedCenter != null) {
syncedViewer.setCenter(projectedCenter);
}
}
}
} else if (syncedViewer instanceof bu.street.Viewer) {
syncedViewer.setCenter(center);
}
}
}
}
}
/**
* @private
*/
bu.Workspace.prototype.handleOrthoViewChanged_ = function(viewIndex) {
var layout = this.getLayout();
if (layout && layout.views.length > 0) {
var syncedViews = layout.views[viewIndex].syncedViews;
if (syncedViews && syncedViews.length > 0) {
var viewer = layout.views[viewIndex].viewer;
var center = viewer.getCenter();
var resolution = viewer.getResolution();
var rotation = viewer.getRotation();
for (var i = 0; i < syncedViews.length; i++) {
var syncedViewer = layout.views[syncedViews[i]].viewer;
if (syncedViewer instanceof bu.ortho.Viewer) {
syncedViewer.setCenter(center);
syncedViewer.setResolution(resolution);
syncedViewer.setRotation(rotation);
} else if (syncedViewer instanceof bu.street.Viewer) {
}
}
}
}
};
/**
* @private
*/
bu.Workspace.prototype.handleOrthoViewPanoramaClicked_ = function(viewIndex, evt) {
var layout = this.getLayout();
if (layout && layout.views.length > 0) {
var syncedViews = layout.views[viewIndex].syncedViews;
if (syncedViews && syncedViews.length > 0) {
var viewer = layout.views[viewIndex].viewer;
for (var i = 0; i < syncedViews.length; i++) {
var syncedViewer = layout.views[syncedViews[i]].viewer;
if (syncedViewer instanceof bu.street.Viewer) {
syncedViewer.setImageID(evt.image.id);
}
}
}
}
};
/**
* @private
*/
bu.Workspace.prototype.handleTargetChanged_ = function() {
// target may be undefined, null, a string or an Element.
// If it's a string we convert it to an Element before proceeding.
// If it's not now an Element we remove the div elements from the DOM.
// If it's an Element we append the div elements to it.
var targetElement;
if (this.getTarget()) {
targetElement = this.getTargetElement();
}
if (!targetElement) {
for (i = 0; i < this.divs_.length; i++) {
ol.dom.removeNode(this.divs_[i]);
}
ol.dom.removeNode(this.viewport_);
} else {
targetElement.appendChild(this.viewport_);
}
};
/**
* @private
*/
//bu.Workspace.prototype.handleLayoutChanged_ = function() {
bu.Workspace.prototype.setupLayout = function(layout) {
//The final structure of divs would be:
// div_target
// |___ div_container (columns)
// |___ div_subcontainer (rows inside each column)
// |___ viewer (for example, an ol.Map div_target)
if (!layout){
return false;
}
// We save current layout schema on a local variable
this.layout_ = layout;
//var layout = this.getLayout();
if (layout && layout.views && layout.views.length > 0) {
//If previous layout exists remove all content
if (this.containers_) {
//Unset change handlers
if (this.viewerChangeHandlerKeys_) {
for (var i = 0, ii = this.viewerChangeHandlerKeys_.length; i < ii; ++i) {
ol.events.unlistenByKey(this.viewerChangeHandlerKeys_[i]);
}
this.viewerChangeHandlerKeys_ = null;
}
//Remove viewers
if (this.viewers_) {
for (var i = 0, ii = this.viewers_.length; i < ii; ++i) {
this.viewers_[i].setTarget(null);
}
this.viewers_ = null;
}
//Remove subcontainers
if (this.subcontainers_) {
for (var i = 0, ii = this.subcontainers_.length; i < ii; ++i) {
ol.dom.removeNode(this.subcontainers_[i]);
}
this.subcontainers_ = null;
}
//Remove column containers
if (this.containers_) {
for (var i = 0, ii = this.containers_.length; i < ii; ++i) {
ol.dom.removeNode(this.containers_[i]);
}
this.containers_ = null;
}
}
//Create column containers
var columnRows = [];
var maxcolumn = 0;
for (var i = 0; i < layout.views.length; i++) {
var column = layout.views[i].column;
if (maxcolumn < column) maxcolumn = column;
}
for (var i = 0; i <= maxcolumn; i++) {
columnRows[i] = 0;
}
for (var i = 0; i < layout.views.length; i++) {
var column = layout.views[i].column;
if (column < 0) column = 0;
columnRows[column] = columnRows[column] + 1;
}
var columnCount = maxcolumn + 1;
var w = (columnCount == 1 ? 100.0 : 100.0 / columnCount);
if (w != 100.0) w = parseFloat(w.toFixed(2));
this.containers_ = [];
for (var c = 0; c < columnCount; c++) {
//Ensure that sum of all column widths equals exactly to 100.0%
if (columnCount != 1 && c == columnCount - 1) {
w = 100.0 - w * (columnCount - 1);
}
this.containers_[c] = document.createElement('DIV');
this.containers_[c].className = 'bu-workspace-container';
this.containers_[c].style.position = 'relative';
this.containers_[c].style.overflow = 'hidden';
this.containers_[c].style.float = 'left';
this.containers_[c].style.width = w.toFixed(2) + '%';
this.containers_[c].style.height = '100%';
// prevent page zoom on IE >= 10 browsers
this.containers_[c].style.msTouchAction = 'none';
this.containers_[c].style.touchAction = 'none';
}
//Create subcontainers and add to containers
var rowCount = [];
this.subcontainers_ = [];
for (var i = 0; i < layout.views.length; i++) {
var column = layout.views[i].column;
//Ensure that sum of all row heights equals exactly to 100.0%
if (typeof rowCount[column] === 'undefined')
rowCount[column] = 1;
else
rowCount[column] = rowCount[column] + 1;
var h = (columnRows[column] <= 1 ?
100.0 : 100.0 / columnRows[column]);
if (h != 100.0) h = parseFloat(h.toFixed(2));
if (columnRows[column] != 1 && rowCount[column] == columnRows[column]) {
h = 100.0 - h * (columnRows[column] - 1);
}
this.subcontainers_[i] = document.createElement('DIV');
this.subcontainers_[i].className = 'bu-workspace-subcontainer';
this.subcontainers_[i].style.position = 'relative';
this.subcontainers_[i].style.overflow = 'hidden';
this.subcontainers_[i].style.width = '100%';
this.subcontainers_[i].style.height = h.toFixed(2) + '%';
// prevent page zoom on IE >= 10 browsers
this.subcontainers_[i].style.msTouchAction = 'none';
this.subcontainers_[i].style.touchAction = 'none';
this.containers_[column].appendChild(this.subcontainers_[i]);
}
//Add containers to viewport
if (this.viewport_)
for (var i = 0; i < this.containers_.length; i++)
this.viewport_.appendChild(this.containers_[i]);
//Add viewers to subcontainers if they are defined
this.viewers_ = [];
for (var i = 0; i < layout.views.length; i++) {
var viewer = layout.views[i].viewer;
if (viewer) {
this.viewers_.push(viewer);
viewer.setTarget(this.subcontainers_[i]);
}
}
//Set change handlers
this.viewerChangeHandlerKeys_ = [];
for (var i = 0; i < layout.views.length; i++) {
var viewer = layout.views[i].viewer;
if (viewer instanceof bu.ortho.Viewer) {
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.ortho.ViewerProperty.CENTER),
this.handleOrthoViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.ortho.ViewerProperty.RESOLUTION),
this.handleOrthoViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.ortho.ViewerProperty.ROTATION),
this.handleOrthoViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer,
bu.ortho.ViewerEventType.PANORAMACLICKED,
this.handleOrthoViewPanoramaClicked_.bind(this, i))
);
} else if (viewer instanceof bu.street.Viewer) {
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.street.ViewerProperty.IMAGEID),
this.handleStreetViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.street.ViewerProperty.ROTATION),
this.handleStreetViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.street.ViewerProperty.FOV),
this.handleStreetViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.street.ViewerProperty.PITCH),
this.handleStreetViewChanged_.bind(this, i))
);
this.viewerChangeHandlerKeys_.push(
ol.events.listen(viewer, ol.Object.getChangeEventType(
bu.street.ViewerProperty.YAW),
this.handleStreetViewChanged_.bind(this, i))
);
}
}
}
};