blob: a18640798c57cdd6b8083708526806a72142a231 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* global define, module, require, exports */
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery',
'd3',
'nf.Common',
'nf.Client',
'nf.CanvasUtils'],
function ($, d3, nfCommon, nfClient, nfCanvasUtils) {
return (nf.Funnel = factory($, d3, nfCommon, nfClient, nfCanvasUtils));
});
} else if (typeof exports === 'object' && typeof module === 'object') {
module.exports = (nf.Funnel =
factory(require('jquery'),
require('d3'),
require('nf.Common'),
require('nf.Client'),
require('nf.CanvasUtils')));
} else {
nf.Funnel = factory(root.$,
root.d3,
root.nf.Common,
root.nf.Client,
root.nf.CanvasUtils);
}
}(this, function ($, d3, nfCommon, nfClient, nfCanvasUtils) {
'use strict';
var nfConnectable;
var nfDraggable;
var nfSelectable;
var nfContextMenu;
var dimensions = {
width: 48,
height: 48
};
// -----------------------------
// funnels currently on the graph
// -----------------------------
var funnelMap;
// -----------------------------------------------------------
// cache for components that are added/removed from the canvas
// -----------------------------------------------------------
var removedCache;
var addedCache;
// --------------------
// component containers
// --------------------
var funnelContainer;
// --------------------------
// privately scoped functions
// --------------------------
/**
* Selects the funnel elements against the current funnel map.
*/
var select = function () {
return funnelContainer.selectAll('g.funnel').data(funnelMap.values(), function (d) {
return d.id;
});
};
/**
* Renders the funnels in the specified selection.
*
* @param {selection} entered The selection of funnels to be rendered
* @param {boolean} selected Whether the element should be selected
* @return the entered selection
*/
var renderFunnels = function (entered, selected) {
if (entered.empty()) {
return entered;
}
var funnel = entered.append('g')
.attrs({
'id': function (d) {
return 'id-' + d.id;
},
'class': 'funnel component'
})
.classed('selected', selected)
.call(nfCanvasUtils.position);
// funnel border
funnel.append('rect')
.attrs({
'rx': 2,
'ry': 2,
'class': 'border',
'width': function (d) {
return d.dimensions.width;
},
'height': function (d) {
return d.dimensions.height;
},
'fill': 'transparent',
'stroke': 'transparent'
});
// funnel body
funnel.append('rect')
.attrs({
'rx': 2,
'ry': 2,
'class': 'body',
'width': function (d) {
return d.dimensions.width;
},
'height': function (d) {
return d.dimensions.height;
},
'filter': 'url(#component-drop-shadow)',
'stroke-width': 0
});
// funnel icon
funnel.append('text')
.attrs({
'class': 'funnel-icon',
'x': 9,
'y': 34
})
.text('\ue803');
// always support selection
funnel.call(nfSelectable.activate).call(nfContextMenu.activate);
return funnel;
};
/**
* Updates the funnels in the specified selection.
*
* @param {selection} updated The funnels to be updated
*/
var updateFunnels = function (updated) {
if (updated.empty()) {
return;
}
// funnel border authorization
updated.select('rect.border')
.classed('unauthorized', function (d) {
return d.permissions.canRead === false;
});
// funnel body authorization
updated.select('rect.body')
.classed('unauthorized', function (d) {
return d.permissions.canRead === false;
});
updated.each(function () {
var funnel = d3.select(this);
// update the component behavior as appropriate
nfCanvasUtils.editable(funnel, nfConnectable, nfDraggable);
});
};
/**
* Removes the funnels in the specified selection.
*
* @param {selection} removed The funnels to be removed
*/
var removeFunnels = function (removed) {
removed.remove();
};
var nfFunnel = {
/**
* Initializes of the Processor handler.
*
* @param nfConnectableRef The nfConnectable module.
* @param nfDraggableRef The nfDraggable module.
* @param nfSelectableRef The nfSelectable module.
* @param nfContextMenuRef The nfContextMenu module.
*/
init: function (nfConnectableRef, nfDraggableRef, nfSelectableRef, nfContextMenuRef) {
nfConnectable = nfConnectableRef;
nfDraggable = nfDraggableRef;
nfSelectable = nfSelectableRef;
nfContextMenu = nfContextMenuRef;
funnelMap = d3.map();
removedCache = d3.map();
addedCache = d3.map();
// create the funnel container
funnelContainer = d3.select('#canvas').append('g')
.attrs({
'pointer-events': 'all',
'class': 'funnels'
});
},
/**
* Adds the specified funnel entity.
*
* @param funnelEntities The funnel
* @param options Configuration options
*/
add: function (funnelEntities, options) {
var selectAll = false;
if (nfCommon.isDefinedAndNotNull(options)) {
selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
}
// get the current time
var now = new Date().getTime();
var add = function (funnelEntity) {
addedCache.set(funnelEntity.id, now);
// add the funnel
funnelMap.set(funnelEntity.id, $.extend({
type: 'Funnel',
dimensions: dimensions
}, funnelEntity));
};
// determine how to handle the specified funnel status
if ($.isArray(funnelEntities)) {
$.each(funnelEntities, function (_, funnelEntity) {
add(funnelEntity);
});
} else if (nfCommon.isDefinedAndNotNull(funnelEntities)) {
add(funnelEntities);
}
// select
var selection = select();
// enter
var entered = renderFunnels(selection.enter(), selectAll);
// update
updateFunnels(selection.merge(entered));
},
/**
* Populates the graph with the specified funnels.
*
* @argument {object | array} funnelEntities The funnels to add
* @argument {object} options Configuration options
*/
set: function (funnelEntities, options) {
var selectAll = false;
var transition = false;
var overrideRevisionCheck = false;
if (nfCommon.isDefinedAndNotNull(options)) {
selectAll = nfCommon.isDefinedAndNotNull(options.selectAll) ? options.selectAll : selectAll;
transition = nfCommon.isDefinedAndNotNull(options.transition) ? options.transition : transition;
overrideRevisionCheck = nfCommon.isDefinedAndNotNull(options.overrideRevisionCheck) ? options.overrideRevisionCheck : overrideRevisionCheck;
}
var set = function (proposedFunnelEntity) {
var currentFunnelEntity = funnelMap.get(proposedFunnelEntity.id);
// set the funnel if appropriate due to revision and wasn't previously removed
if ((nfClient.isNewerRevision(currentFunnelEntity, proposedFunnelEntity) && !removedCache.has(proposedFunnelEntity.id)) || overrideRevisionCheck === true) {
funnelMap.set(proposedFunnelEntity.id, $.extend({
type: 'Funnel',
dimensions: dimensions
}, proposedFunnelEntity));
}
};
if ($.isArray(funnelEntities)) {
$.each(funnelMap.keys(), function (_, key) {
var currentFunnelEntity = funnelMap.get(key);
var isPresent = $.grep(funnelEntities, function (proposedFunnelEntity) {
return proposedFunnelEntity.id === currentFunnelEntity.id;
});
// if the current funnel is not present and was not recently added, remove it
if (isPresent.length === 0 && !addedCache.has(key)) {
funnelMap.remove(key);
}
});
$.each(funnelEntities, function (_, funnelEntity) {
set(funnelEntity);
});
} else if (nfCommon.isDefinedAndNotNull(funnelEntities)) {
set(funnelEntities);
}
// select
var selection = select();
// enter
var entered = renderFunnels(selection.enter(), selectAll);
// update
var updated = selection.merge(entered);
updated.call(updateFunnels).call(nfCanvasUtils.position, transition);
// exit
selection.exit().call(removeFunnels);
},
/**
* If the funnel id is specified it is returned. If no funnel id
* specified, all funnels are returned.
*
* @param {string} id
*/
get: function (id) {
if (nfCommon.isUndefined(id)) {
return funnelMap.values();
} else {
return funnelMap.get(id);
}
},
/**
* If the funnel id is specified it is refresh according to the current
* state. If not funnel id is specified, all funnels are refreshed.
*
* @param {string} id Optional
*/
refresh: function (id) {
if (nfCommon.isDefinedAndNotNull(id)) {
d3.select('#id-' + id).call(updateFunnels);
} else {
d3.selectAll('g.funnel').call(updateFunnels);
}
},
/**
* Reloads the funnel state from the server and refreshes the UI.
* If the funnel is currently unknown, this function just returns.
*
* @param {string} id The funnel id
*/
reload: function (id) {
if (funnelMap.has(id)) {
var funnelEntity = funnelMap.get(id);
return $.ajax({
type: 'GET',
url: funnelEntity.uri,
dataType: 'json'
}).done(function (response) {
nfFunnel.set(response);
});
}
},
/**
* Positions the component.
*
* @param {string} id The id
*/
position: function (id) {
d3.select('#id-' + id).call(nfCanvasUtils.position);
},
/**
* Removes the specified funnel.
*
* @param {array|string} funnelIds The funnel id
*/
remove: function (funnelIds) {
var now = new Date().getTime();
if ($.isArray(funnelIds)) {
$.each(funnelIds, function (_, funnelId) {
removedCache.set(funnelId, now);
funnelMap.remove(funnelId);
});
} else {
removedCache.set(funnelIds, now);
funnelMap.remove(funnelIds);
}
// apply the selection and handle all removed funnels
select().exit().call(removeFunnels);
},
/**
* Removes all processors.
*/
removeAll: function () {
nfFunnel.remove(funnelMap.keys());
},
/**
* Expires the caches up to the specified timestamp.
*
* @param timestamp
*/
expireCaches: function (timestamp) {
var expire = function (cache) {
cache.each(function (entryTimestamp, id) {
if (timestamp > entryTimestamp) {
cache.remove(id);
}
});
};
expire(addedCache);
expire(removedCache);
}
};
return nfFunnel;
}));