Skip to content

Instantly share code, notes, and snippets.

@elquimista
Created July 1, 2016 03:26
Show Gist options
  • Save elquimista/677af350749de3dd631596494ba8a01d to your computer and use it in GitHub Desktop.
Save elquimista/677af350749de3dd631596494ba8a01d to your computer and use it in GitHub Desktop.
SVGAnimatedTransformList.prototype.translate = function (x, y) {
var xForms = this.baseVal,
xFormTranslate = undefined,
transform = undefined;
for (var i = 0, n = xForms.numberOfItems; i < n; i ++) {
transform = xForms.getItem(i);
if (transform.type === SVGTransform.SVG_TRANSFORM_TRANSLATE) {
xFormTranslate = transform;
break;
}
if (transform.type === SVGTransform.SVG_TRANSFORM_MATRIX) {
xFormTranslate = transform;
break;
}
}
if (xFormTranslate) {
if (x) {
xFormTranslate.matrix.e = x;
}
if (y) {
xFormTranslate.matrix.f = y;
}
}
};
var RAPPID = function() {
var $ = window.jQuery,
graph, paper, paperScroller, snaplines, stencil, nav, inspector, halo, gOptions,
shapeSize = [50, 50],
shapeMargin = [20, 10],
shapeTextMarginTop = 5,
iconSize = [32, 32],
shapeBigSize = [150, 150],
zoomMax = 1,
zoomMin = 0.4,
zoomStep = 0.2,
defaultPaperSize = [$('#paper').width() || 1500, $('#paper').height() || 900],
wfAutoSaveInterval = 1000, // in milliseconds
chartPieRadius = 60,
chartSerieFillColor = ['#8bce5d', '#53abdd', '#c377b1', '#ffe891', '#888888', '#53abdd', '#c377b1', '#ffe891', '#888888'],
logTimerId,
chart = new joint.shapes.chart.Pie({
attrs: {
'.slice-inner-label': { fill: '#000' }
},
position: { x: 0, y: 0 },
size: { width: chartPieRadius * 2, height: chartPieRadius * 2 },
serieDefaults: {
degree: 360,
startAngle: 0,
showLegend: false
},
sliceDefaults: {
innerLabel: '{label}'
},
series: []
}),
$statusBarEl = $('#status_bar'),
configErrors = {},
linkDownFlag = false,
graphHasChanged = false,
agentDownFlag = false,
$goalMarkerEl = V('<path d="M 0.000 10.000 L 11.756 16.180 L 9.511 3.090 L 19.021 -6.180 L 5.878 -8.090 L 0.000 -20.000 L -5.878 -8.090 L -19.021 -6.180 L -9.511 3.090 L -11.756 16.180 L 0.000 10.000" fill="#f8b600" stroke="#f8b600" transform="translate(75 145)"></path>').node,
// Used for showing sticky config option icons for agents that are not configured yet.
halo2 = {},
configIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH3wwECAsV5RQSlAAABn1JREFUWMOtl12MZUURx3//6j73zr0zu7ODIrssJLODDBDN7vIAGDWyxtEEUF/A+GDCI348GEWfwIAG0PiAJMSgr5po4osakjVEwK9EDPjAh9Gwuw4fsrgoCO7szNyPc06VD/fe2Zk7d1ggdtLpU3266l9dXdXVpaWlIwaIYTNgFeJQyBbBuqI8EopusHcarhN8AjgcsB+YHvKuCU4BzwQ8vA5HL0MnfynXOhQnwf+k8N0gZ0sLLS0dSSMFhuBcFbK9EB8PlatwRQu+IrjJI84DQpIMkAZ6RwQ+GAOQSSvAgx24993oqV/J8ylIjyvqNhCbFEgLC/MGyIA14NpQeiRR3lhTzEj3NODHEXGVpFY7Z99TFDFXFJprFNpTFMwO+0xKMZWSS6KMmPKIgw3plj7sm4Xff6mT118sojghothkgrSwMG8GWgWWQuluV+99wcJ50kPATUDMFoVf0GxqriisnbMVkpKEJGzYCzO1crZdOWsm55BU99wBrm6hG18o4g/Xh/3zDVE8K6I5tER678K8dUBHQukut95j4nAT/cYjLm2alfumptJco5GSJB8yjTrSoI/NZ0kzOVs7JfXc69L9/Cx97jXx2CdDz70qiuMiGoBVwKGQ3V2m3h8VlxRw1CP2TudcXdxuFa2UVA/PeNQ2PLaqoCoh4uwc4EAdwZSZLm618q6iqD1id4YH/yauvLO23tWh1AdsP/CUwn+W6mYDfuIRF07nXF04NZWF8M2Aw+9wR4DNzmJ75pAZUdfb1o2U3tdspqESsxl++qj53L25LhcD5cOh9H23/jHFNwOuaZiVe5vNYmRWbRo3t/Ztt1EcOkS4Uy8vs3bnnURVIWnjKEa8AHubzVS5V133y3ej76z1iy/cb57tVrf+XxSXA7cCXNBs5jwUogmCwh212+TLLkOtFpqeJi8uot27oa43fGLSBt7TbGYNyM8/rbjmq259e0FBA77oEa1dOVftnFTvAP52msZkODCVErNF4R5Bhi+/LrCTcD5wk0nsKYoUsVXIOLjMiPV1qmPHiG4X1tepTpwgVlYgpS0OGWOjRzBbZEsDK93wPHFJbsKSR1zYzsmnzMzHmMaVGQlf+/Y9qNUeEN0OUftAuZ0UH841ZGqn5GeqaraQbjDBxwDaKcfoatUE5slGnqxtjK3arJAE7ZxH/5YycCUSTTPF0P4xAWY8EqZvv53i0CFwp1peZu2OO7ZEwTjvhnIBTTMN1x20gIuMwe210x63CBhFweIimpqCdpt86aXbomAS74hOw2QG7DNglwCTFOcA37KNqhr8cyeqajD3Jrybd2cDPICGvdnp7iRgcw44S7+5H+zUDDgTgEeE3oGAiQY6x2Z8gAfQN8FJB6o4a8O3fBQT7XZuS9YRMQz3UwY8SQQ9921h+NbBt6/aiVeCntcRgwvrGQt4FGC9riaG4Tu9hne0ZMB6VY/+P2I9eMSkl7u1W9c9bBPD/8sPRrSAfnis17WZdLqEo3YRvAr83CP4b1nWmoC0bSpiS9gN6J3BR/wmcbqsvB7wHj2Alm0+RA8eMKlzpqryWlVHYvvNt0WoBDkPn9KGct4almPgwSDcunXN6bI0k6jg/vMC7D7zxkH0LPA9gH/3elVF7JiOt2TDTodYW9uWDcfBR+O/er0qBuQPD6LH7zNv6OalI+mY4GuVinnpdx5xzehJxgQlRk8yzLCZGZCI1dVBHkhp6zrOJqNTvV59piyTSc+uEB9catSnbynN0rsW5u1wKH29Tr0XLH6bpc/03Wd77tVMTmZoQigNX8KdDtHtANpIxZvbyKFf6XXrM2WVTFop4VPXhj2/Gmr8Vbhl4GmFfyPXzQ+Flku4zqRX1qoqv7TeKTt1HUli8529AZQz5GJghTHgJNF1j5c6nWoEXsGnrwie/JZ58wlFXQDpwMK8NUDHRbwoipvdXn5O/KKQPlLD/pWq8tLdCzMVkkzaMOvI1OJsghHQd4/Xy7J+tdejisgmHe/DdYvBEw9YNH89LNGcYWUUoCZwTMSyKO7K8Z8POz+aQQ3BB7p1XZypKnXcvYrwAMUAixgK6rvHalX562XJa/2+derahkf1gzeIz360l/7x3cKbjyrq1maf2qk43Q+xFCpX4PIW3Cq48e0Wp124d0566iE8nwJ7XOHjxakmledrwPsDLSI7gMqFIN6AC9pwvZ2jPHd4uANH23Dy7xY6FRQvgv9ZEbuA8fL8f40EPnOmwLnoAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1LTEyLTA0VDA4OjExOjIxLTA1OjAwhYihSgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0xMi0wNFQwODoxMToyMS0wNTowMPTVGfYAAAAASUVORK5CYII=',
configIconGreen = 'data:image/png;base64,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',
// Customized Halo - Halo2
Halo2 = function(options) {
// possible options: { cellView }
this.options = options;
this.$el = undefined;
}
;
Halo2.prototype = {
_options: {
tinyThreshold: 40,
smallThreshold: 80
},
render: function() {
var cellView = this.options.cellView,
$cell = cellView.$el,
cellPos = $cell.offset(),
paperPos = $('#paper .paper').offset(),
$haloEl = $('<div class="halo surrounding halo2"></div>'),
cellSize = cellView.model.attributes.size,
width = cellSize.width * paperScroller._sx,
height = cellSize.height * paperScroller._sy,
_options = this._options,
$handleEl = $('<div class="handle config"></div>');
$haloEl.css('width', width + 'px')
.css('height', height + 'px')
.css('left', (cellPos.left - paperPos.left) + 'px')
.css('top', (cellPos.top - paperPos.top) + 'px')
.attr('data-model-id', cellView.model.id)
.toggleClass('tiny', width < _options.tinyThreshold && height < _options.tinyThreshold)
.toggleClass('small', !$haloEl.hasClass('tiny') && width < _options.smallThreshold && height < _options.smallThreshold);
$handleEl.css('background-image', 'url("' + configIcon + '")')
.appendTo($haloEl);
$haloEl.appendTo(cellView.paper.$el);
this.$el = $haloEl;
},
remove: function() {
if (this.$el) {
this.$el.remove();
this.$el = undefined;
}
},
update: function() {
try {
var cellView = this.options.cellView,
$cell = cellView.$el,
cellPos = $cell.offset(),
paperPos = $('#paper .paper').offset(),
cellSize = cellView.model.attributes.size,
width = cellSize.width * paperScroller._sx,
height = cellSize.height * paperScroller._sy,
_options = this._options,
$el = this.$el;
$el.css('width', width + 'px')
.css('height', height + 'px')
.css('left', (cellPos.left - paperPos.left) + 'px')
.css('top', (cellPos.top - paperPos.top) + 'px')
.toggleClass('tiny', width < _options.tinyThreshold && height < _options.tinyThreshold)
.toggleClass('small', !$el.hasClass('tiny') && width < _options.smallThreshold && height < _options.smallThreshold);
} catch (error) {
console.log(error);
}
}
};
/**
* getWordWrapText()
*
* @param String text
* @param Object options
*/
function getWordWrapText(text, options) {
var lines = joint.util.breakText(text, options).split("\n"),
textContent = '';
for (var i = 0, n = lines.length; i < n; i ++) {
textContent += '<tspan dy="' + (i > 0 ? 1 : 0) + 'em" x="0" class="v-line">' + lines[i] + '</tspan>';
}
return textContent;
}
/**
* wrapText(): Wrap texts in SVG using joint.util.breakText
*
* @param Array size
* @param Bool isOnPaper
*/
function wrapText(size, isOnPaper) {
var xForms, xFormTranslate;
if (isOnPaper) {
// Yes, it's on the paper
$('.wrap').each(function () {
var $this = $(this),
klass = $this.attr('class'),
model = graph.getCell( $this.parents('[model-id]').attr('model-id') ),
modelText = model && model.attributes.textRaw;
if (!model) return;
model.attributes.attrs.text.text = modelText;
$this.html(getWordWrapText(modelText, { width: size[0] }))
.attr('fill', '#000')
.attr('font-size', '14');
this.transform.translate(null, size[1] / 2);
$this.attr('class', klass.replace(/\bwrap\b/, ''));
});
} else {
// No, it's not on the paper. Then it must be on the STENCIL. No doubt!!
$('.wrap').each(function () {
var $this = $(this),
klass = $this.attr('class');
this.transform.translate(null, size[1] + shapeTextMarginTop);
$this.attr('class', klass.replace(/\bwrap\b/, ''));
});
}
}
/**
* logStatus()
*
* @param Any data
* @param Number duration milliseconds
*/
function logStatus(data, duration) {
clearTimeout(logTimerId);
$statusBarEl.html(data);
if (duration) {
logTimerId = setTimeout(function () { $statusBarEl.html(''); }, duration);
}
}
/**
* saveGraphAsync()
*
*/
function saveGraphAsync() {
var workflow_draft = graph.toJSON();
workflow_draft.options = {
paperWidth: defaultPaperSize[0],
paperHeight: defaultPaperSize[1],
paperOrigin: {x: 0, y: 0}
};
if (paper && paperScroller) {
workflow_draft.options = {
paperWidth: paper.options.width / paperScroller._sx,
paperHeight: paper.options.height / paperScroller._sy,
paperOrigin: {
x: paper.options.origin.x / paperScroller._sx,
y: paper.options.origin.y / paperScroller._sy
}
}
}
return $.ajax({
url: gOptions.save_to,
type: 'post',
dataType: 'json',
data: {
id: gOptions.workflow_id,
workflow_draft: JSON.stringify(workflow_draft)
}
});
}
/**
* saveWorkflow()
*
* @param Function doneCallback
*/
function saveWorkflow(doneCallback) {
logStatus('<div class="alert alert-info">Saving...</div>');
saveGraphAsync().then(function(data) {
configErrors = data.errors;
if (data.isValid) {
logStatus('<div class="alert alert-success">The workflow has been saved!</div>', 5000);
} else {
logStatus('<div class="alert alert-warning">The workflow has been saved, BUT we have invalid config.</div>');
}
if (doneCallback) {
doneCallback(data);
}
}, function(jqXHR, textStatus, errorThrown) {
logStatus('<div class="alert alert-danger">Uh oh, seems we lost connection. The workflow could not be saved.</div>');
});
}
/**
* cronSaveWorkflow()
*/
function cronSaveWorkflow() {
if (graphHasChanged) {
saveWorkflow();
graphHasChanged = false;
}
}
/**
* isAgent()
*
* @param joint.dia.ElementView|joint.dia.Element cell
* @return Bool
*/
function isAgent(cell) {
if (cell.model) {
return (cell.model.attributes.config != undefined);
} else {
return (cell.attributes.config != undefined);
}
}
/**
* validateConnection()
*
* @param joint.dia.ElementView cellViewS,
* @param magnetS
* @param joint.dia.ElementView cellViewT,
* @param magnetT
* @param String end
* @param joint.dia.LinkView linkView
*/
function validateConnection(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
if (isAgent(cellViewT) && cellViewS.model.id != cellViewT.model.id &&
cellViewT.model.attributes.cannot_receive_events === false) {
return true;
}
return false;
}
/**
* initializePaper(): Create a graph, paper and wrap the paper in a PaperScroller.
*/
function initializePaper() {
var workflow_draft = gOptions.workflow_draft,
wfdOptions = workflow_draft.options,
$paperContainer = $('#paper');
graph = new joint.dia.Graph;
paper = new joint.dia.Paper({
width: defaultPaperSize[0],
height: defaultPaperSize[1],
gridSize: 10,
model: graph,
async: { batchSize: 1 },
defaultLink: new joint.dia.Link({
attrs: {
'.connection': { stroke: '#8d8d94', 'stroke-width': 3 },
'.marker-target': { stroke: '#8d8d94', fill: '#272634', d: 'M5.5,15.499,15.8,21.447,15.8,9.552z' },
'.marker-source': { stroke: '#8d8d94', fill: '#272634' }
}
}),
linkConnectionPoint: joint.util.shapePerimeterConnectionPoint,
validateConnection: validateConnection
});
paperScroller = new joint.ui.PaperScroller({
paper: paper,
autoResizePaper: true,
padding: 50,
baseWidth: 200,
baseHeight: 200
});
$paperContainer.append(paperScroller.$el);
paper.on('render:done', function() {
var cells = graph.getElements(),
cellView, attributes,
agentsConfigByType = gOptions.agentsConfigByType,
updatedAttrs;
if (wfdOptions) {
if (wfdOptions.paperOrigin) {
paper.setOrigin(workflow_draft.options.paperOrigin.x, workflow_draft.options.paperOrigin.y);
nav.updatePaper(paper.options.width, paper.options.height);
}
if (wfdOptions.paperWidth && wfdOptions.paperHeight) {
paper.setDimensions(wfdOptions.paperWidth, wfdOptions.paperHeight);
}
}
wrapText(shapeBigSize, true);
for (var i = 0, n = cells.length; i < n; i ++) {
if (!isAgent(cells[i])) continue;
attributes = cells[i].attributes;
cellView = paper.findViewByModel(cells[i]);
checkAgentIfConfiguredAndDoIt(cellView);
initAgentSubtitle(cellView);
if (attributes.is_goal) {
showGoalMarker(cellView);
}
updatedAttrs = agentsConfigByType[attributes.config.type] && agentsConfigByType[attributes.config.type].attrs;
if (updatedAttrs) {
attributes.config = agentsConfigByType[attributes.config.type].config;
if (updatedAttrs.rect) {
cells[i].attr('rect/fill', updatedAttrs.rect.fill);
cells[i].attr('rect/stroke', updatedAttrs.rect.stroke);
} else if (updatedAttrs.circle) {
cells[i].attr('circle/fill', updatedAttrs.circle.fill);
cells[i].attr('circle/stroke', updatedAttrs.circle.stroke);
} else if (updatedAttrs.path) {
cells[i].attr('path/fill', updatedAttrs.path.fill);
cells[i].attr('path/stroke', updatedAttrs.path.stroke);
}
cells[i].attr('image/xlink:href', updatedAttrs.image['xlink:href']);
}
}
if (!gOptions.wf_valid) {
logStatus('<div class="alert alert-warning">Some agents have invalid configuration.</div>');
}
configErrors = gOptions.wf_errors;
paperScroller.zoomToFit({
padding: 100,
maxScaleX: 1,
maxScaleY: 1
});
setInterval(cronSaveWorkflow, wfAutoSaveInterval);
});
paper.on('cell:pointerdown', function(cellView) {
if (isAgent(cellView)) {
agentDownFlag = true;
}
});
paper.on('cell:pointerup', function() {
agentDownFlag = false;
});
paper.on('blank:pointerdown', paperScroller.startPanning);
$paperContainer.on('contextmenu', function(e) { e.preventDefault(); });
snaplines = new joint.ui.Snaplines({ paper: paper });
snaplines.startListening();
graph.on('add', function(cell) {
if (isAgent(cell)) {
var $image = $('image', '[model-id=' + cell.id + ']'),
image = cell.attr('image'),
newX = (shapeBigSize[0] - iconSize[0]) / 2,
newY = shapeBigSize[1] / 2 - iconSize[1] - 10;
cell.set('size', { width: shapeBigSize[0], height: shapeBigSize[1] });
$image.attr('x', newX).attr('y', newY);
image.x = newX;
image.y = newY;
wrapText(shapeBigSize, true);
initializeDefaultConfigValues(cell);
}
});
if (workflow_draft.cells && workflow_draft.cells.length > 0) {
graph.fromJSON(workflow_draft);
} else {
setInterval(cronSaveWorkflow, wfAutoSaveInterval);
}
graph.on('change', function(cell) {
if (cell.id != chart.id && linkDownFlag == false) {
graphHasChanged = true;
}
});
graph.on('remove', function(cell) {
if (cell instanceof joint.dia.Link && cell.get('target').id) {
var link = cell,
source = graph.getCell(link.get('source').id),
sourceAttr = source.attributes,
groupKey = link.attributes.groupKey;
if (groupKey) {
sourceAttr.targetIds[groupKey] = _.without(sourceAttr.targetIds[groupKey], link.get('target').id);
} else {
sourceAttr.targetIds = _.without(sourceAttr.targetIds, link.get('target').id);
}
}
if (cell.id != chart.id && linkDownFlag == false) {
graphHasChanged = true;
}
});
paper.on('scale resize', function() {
for (var modelId in halo2) {
halo2[modelId].update();
}
});
}
/**
* createCustomShapes(): Create custom elements.
*/
function createCustomShapes() {
joint.shapes.custom = {};
joint.shapes.custom.CircleEx = joint.shapes.basic.Circle.extend({
markup: '<g class="scalable"><circle/></g><image/><text/>',
defaults: joint.util.deepSupplement({
type: 'custom.CircleEx'
}, joint.shapes.basic.Circle.prototype.defaults)
});
joint.shapes.custom.RectEx = joint.shapes.basic.Rect.extend({
markup: '<g class="scalable"><rect/></g><image/><text/>',
defaults: joint.util.deepSupplement({
type: 'custom.RectEx'
}, joint.shapes.basic.Rect.prototype.defaults)
});
joint.shapes.custom.PathEx = joint.shapes.basic.Path.extend({
markup: '<g class="scalable"><path/></g><image/><text/>',
defaults: joint.util.deepSupplement({
type: 'custom.PathEx'
}, joint.shapes.basic.Path.prototype.defaults)
});
}
/**
* initializeStencil(): Create and populate stencil.
*
* @param Object stencilData
*/
function initializeStencil(stencilData) {
stencil = new joint.ui.Stencil({
paper: paperScroller,
width: (shapeSize[0] + shapeMargin[0]) * 3,
groups: stencilData.groups,
dropAnimation: { duration: 200, easing: 'swing' }
});
$('#stencil').append(stencil.render().el)
.find('.content').mCustomScrollbar({ theme: 'minimal-dark' });
stencil.$el.on('contextmenu', function(e) { e.preventDefault(); });
$('.stencil-paper-drag').on('contextmenu', function(e) { e.preventDefault(); });
var layoutOptions = {
columns: 3,
columnWidth: shapeSize[0] + shapeMargin[0],
rowHeight: shapeSize[1] * 2 + shapeTextMarginTop
},
shapes = stencilData.shapes;
_.each(stencilData.groups, function(group, name) {
stencil.load(shapes[name], name);
joint.layout.GridLayout.layout(stencil.getGraph(name), layoutOptions);
stencil.getPaper(name).fitToContent(1, 1, 30);
});
wrapText(shapeSize);
}
/**
* initializeHaloAndInspector()
*/
function initializeHaloAndInspector() {
paper.on('cell:mouseover', function(cellView) {
if (linkDownFlag || agentDownFlag) return;
// We don't want a Halo for links.
if (cellView.model instanceof joint.dia.Link || !isAgent(cellView)) return;
var model = cellView.model,
modelId = model.id,
attributes = model.attributes,
configHandleOptions;
showAgentSubtitle(cellView, false);
if (halo && halo.options.cellView === cellView) return;
// Removes the sticky halo
if (halo2[modelId]) {
halo2[modelId].remove();
delete halo2[modelId];
}
halo = new joint.ui.Halo({ cellView: cellView });
halo.removeHandle('clone');
halo.removeHandle('resize');
halo.removeHandle('rotate');
halo.removeHandle('fork');
if (attributes.cannot_create_events) {
halo.removeHandle('link');
}
configHandleOptions = {
name: 'config',
position: 'n'
};
if (isAgentConfigured(cellView)) {
configHandleOptions.icon = configIconGreen;
} else {
configHandleOptions.icon = configIcon;
configHandleOptions.attrs = {
'.config': {
'data-toggle': 'tooltip',
'data-placement': 'top',
'title': configErrors[modelId] ? configErrors[modelId].join('\n') : 'Invalid config'
}
};
}
halo.addHandle(configHandleOptions);
halo.render();
$('.handle.config[data-toggle="tooltip"]').tooltip();
halo.on('action:link:add', function(link) {
var sourceId = link.get('source').id,
targetId = link.get('target').id,
source = graph.getCell(sourceId),
sourceAttr = source.attributes,
sourceType = sourceAttr.config.type,
activeSliceIndex = chart.prop('active-slice');
linkDownFlag = false;
chart.remove();
if ( !sourceId || !targetId ||
(sourceType == 'Split' && activeSliceIndex === undefined) ||
(sourceType == 'Balance' && activeSliceIndex === undefined) ||
(sourceType == 'Send Email' && activeSliceIndex === undefined) ||
(sourceType == 'Twitter Status' && activeSliceIndex === undefined)) {
link.remove();
} else {
if (sourceType == 'Split' || sourceType == 'Balance' || sourceType == 'Send Email' ||
sourceType == 'Twitter Status' || sourceType == 'Twitter Message') {
var chartData = chart.get('series')[0].data[activeSliceIndex],
groupKey = chartData.groupKey;
link.label(0, { position: 0.5, attrs: { text: { text: chartData.label } } });
link.attributes.groupKey = groupKey;
if (!sourceAttr.targetIds) {
sourceAttr.targetIds = {};
}
if (!sourceAttr.targetIds[groupKey]) {
sourceAttr.targetIds[groupKey] = [];
}
sourceAttr.targetIds[groupKey].push(targetId);
}
else {
if (!sourceAttr.targetIds) {
sourceAttr.targetIds = [];
}
sourceAttr.targetIds.push(targetId);
}
graph.trigger('change', link);
}
chart.prop('active-slice', undefined);
if (halo2[sourceId] === undefined) {
halo && (halo.remove(), halo = undefined);
checkAgentIfConfiguredAndDoIt(paper.findViewByModel(source));
}
if (targetId) {
paper.trigger('cell:mouseover', paper.findViewByModel(graph.getCell(targetId)));
}
});
halo.on('action:config:pointerdown', function(e) {
e.stopPropagation();
if (model instanceof joint.dia.Link) return;
createInspector(cellView);
});
halo.on('action:link:pointerdown', function(e) {
var bbox = model.getBBox(),
pieValue, chartView, $chartViewDataEl;
linkDownFlag = true;
// Display pie chart for Split and Balance agents.
if (attributes.config.type === 'Split') {
var criteria = attributes.criteriaUI,
serieData = [];
if (criteria && criteria.length > 0) {
chart.prop('position', { x: bbox.x + bbox.width - chartPieRadius, y: bbox.y + bbox.height / 2 - chartPieRadius });
for (var i = 0, n = criteria.length, v = parseFloat((100 / n).toFixed(2)); i < n; i ++) {
serieData.push({
value: v,
label: criteria[i].weight_label + ' (' + criteria[i].weight + ')',
fill: chartSerieFillColor[i],
groupKey: criteria[i].weight_label.toString()
});
}
chart.prop('series', [{ data: serieData }]);
chart.prop('active-slice', undefined);
graph.addCell(chart);
chart.toFront();
chartView = paper.findViewByModel(chart);
$chartViewDataEl = chartView.$('.data');
chartView.$('.slice-inner-label').each(function () {
var $textEl = $(this);
$textEl.attr('data-slice', $textEl.parents('.slice').attr('data-slice'));
$textEl.appendTo($chartViewDataEl);
});
chartView.on('mouseover', function(slice, e) {
var $slice = $('.slice[data-serie="' + slice.serieIndex + '"][data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl),
elSlice = V($slice[0]);
// _.each(this.$('.slice'), function(slice) { V(slice).scale(1); $('.slice-inner-label', $(slice)).css('display', 'none'); });
_.each($('.slice', $chartViewDataEl), function(slice) { V(slice).scale(1); });
$('.slice-inner-label', $chartViewDataEl).css('display', 'none');
elSlice.scale(1.2);
chart.prop('active-slice', slice.sliceIndex);
// $('.slice-inner-label', $slice).css('display', 'block');
$('.slice-inner-label[data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl).css('display', 'block');
});
}
}
else if (attributes.config.type === 'Balance') {
chart.prop('position', { x: bbox.x + bbox.width - chartPieRadius, y: bbox.y + bbox.height / 2 - chartPieRadius });
chart.prop('series', [{ data: [
{ value: 50, label: '50', fill: chartSerieFillColor[0], groupKey: '50' },
{ value: 50, label: '50', fill: chartSerieFillColor[1], groupKey: '50' }
] }]);
chart.prop('active-slice', undefined);
graph.addCell(chart);
chart.toFront();
chartView = paper.findViewByModel(chart);
$chartViewDataEl = chartView.$('.data');
chartView.$('.slice-inner-label').each(function () {
var $textEl = $(this);
$textEl.attr('data-slice', $textEl.parents('.slice').attr('data-slice'));
$textEl.appendTo($chartViewDataEl);
});
chartView.on('mouseover', function(slice, e) {
var $slice = $('.slice[data-serie="' + slice.serieIndex + '"][data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl),
elSlice = V($slice[0]);
// _.each(this.$('.slice'), function(slice) { V(slice).scale(1); $('.slice-inner-label', $(slice)).css('display', 'none'); });
_.each($('.slice', $chartViewDataEl), function(slice) { V(slice).scale(1); });
$('.slice-inner-label', $chartViewDataEl).css('display', 'none');
elSlice.scale(1.2);
chart.prop('active-slice', slice.sliceIndex);
// $('.slice-inner-label', $slice).css('display', 'block');
$('.slice-inner-label[data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl).css('display', 'block');
});
}
else if (attributes.config.type === 'Send Email') {
chart.prop('position', { x: bbox.x + bbox.width - chartPieRadius, y: bbox.y + bbox.height / 2 - chartPieRadius });
pieValue = 100 / 7;
chart.prop('series', [{ data: [
{ value: pieValue, label: 'On Bounce', fill: chartSerieFillColor[0], groupKey: 'on_bounce' },
{ value: pieValue, label: 'On Click', fill: chartSerieFillColor[1], groupKey: 'on_click' },
{ value: pieValue, label: 'On Open', fill: chartSerieFillColor[2], groupKey: 'on_open' },
{ value: pieValue, label: 'On Unsubscribe', fill: chartSerieFillColor[3], groupKey: 'on_unsubscribe' },
{ value: pieValue, label: 'On Reply', fill: chartSerieFillColor[4], groupKey: 'on_reply' },
{ value: pieValue, label: 'On Send', fill: chartSerieFillColor[5], groupKey: 'on_send' },
{ value: pieValue, label: 'On No Response', fill: chartSerieFillColor[6], groupKey: 'on_no_response' }
] }]);
chart.prop('active-slice', undefined);
graph.addCell(chart);
chart.toFront();
chartView = paper.findViewByModel(chart);
$chartViewDataEl = chartView.$('.data');
chartView.$('.slice-inner-label').each(function () {
var $textEl = $(this);
$textEl.attr('data-slice', $textEl.parents('.slice').attr('data-slice'));
$textEl.appendTo($chartViewDataEl);
});
chartView.on('mouseover', function(slice, e) {
var $slice = $('.slice[data-serie="' + slice.serieIndex + '"][data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl),
elSlice = V($slice[0]);
// _.each(this.$('.slice'), function(slice) { V(slice).scale(1); $('.slice-inner-label', $(slice)).css('display', 'none'); });
_.each($('.slice', $chartViewDataEl), function(slice) { V(slice).scale(1); });
$('.slice-inner-label', $chartViewDataEl).css('display', 'none');
elSlice.scale(1.2);
chart.prop('active-slice', slice.sliceIndex);
// $('.slice-inner-label', $slice).css('display', 'block');
$('.slice-inner-label[data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl).css('display', 'block');
});
}
else if (attributes.config.type === 'Twitter Status') {
chart.prop('position', { x: bbox.x + bbox.width - chartPieRadius, y: bbox.y + bbox.height / 2 - chartPieRadius });
pieValue = 100 / 4;
chart.prop('series', [{ data: [
{ value: pieValue, label: 'Following Us', fill: chartSerieFillColor[0], groupKey: 'following_us' },
{ value: pieValue, label: 'Not Following Us', fill: chartSerieFillColor[1], groupKey: 'not_following_us' },
{ value: pieValue, label: 'Blocking Us', fill: chartSerieFillColor[2], groupKey: 'blocking_us' },
{ value: pieValue, label: 'Muted By Us', fill: chartSerieFillColor[3], groupKey: 'muted_by_us' }
] }]);
chart.prop('active-slice', undefined);
graph.addCell(chart);
chart.toFront();
chartView = paper.findViewByModel(chart);
$chartViewDataEl = chartView.$('.data');
chartView.$('.slice-inner-label').each(function () {
var $textEl = $(this);
$textEl.attr('data-slice', $textEl.parents('.slice').attr('data-slice'));
$textEl.appendTo($chartViewDataEl);
});
chartView.on('mouseover', function(slice, e) {
var $slice = $('.slice[data-serie="' + slice.serieIndex + '"][data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl),
elSlice = V($slice[0]);
// _.each(this.$('.slice'), function(slice) { V(slice).scale(1); $('.slice-inner-label', $(slice)).css('display', 'none'); });
_.each($('.slice', $chartViewDataEl), function(slice) { V(slice).scale(1); });
$('.slice-inner-label', $chartViewDataEl).css('display', 'none');
elSlice.scale(1.2);
chart.prop('active-slice', slice.sliceIndex);
// $('.slice-inner-label', $slice).css('display', 'block');
$('.slice-inner-label[data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl).css('display', 'block');
});
}
else if (attributes.config.type === 'Twitter Message') {
chart.prop('position', { x: bbox.x + bbox.width - chartPieRadius, y: bbox.y + bbox.height / 2 - chartPieRadius });
pieValue = 100 / 5;
chart.prop('series', [{ data: [
{ value: pieValue, label: '@Mentions Us', fill: chartSerieFillColor[0], groupKey: 'mentions_us' },
{ value: pieValue, label: 'DMs Us', fill: chartSerieFillColor[1], groupKey: 'dms_us' },
{ value: pieValue, label: 'Blocking Us', fill: chartSerieFillColor[2], groupKey: 'blocking_us' },
{ value: pieValue, label: 'On Send', fill: chartSerieFillColor[3], groupKey: 'on_send' },
{ value: pieValue, label: 'On No Response', fill: chartSerieFillColor[4], groupKey: 'on_no_response' }
] }]);
chart.prop('active-slice', undefined);
graph.addCell(chart);
chart.toFront();
chartView = paper.findViewByModel(chart);
$chartViewDataEl = chartView.$('.data');
chartView.$('.slice-inner-label').each(function () {
var $textEl = $(this);
$textEl.attr('data-slice', $textEl.parents('.slice').attr('data-slice'));
$textEl.appendTo($chartViewDataEl);
});
chartView.on('mouseover', function(slice, e) {
var $slice = $('.slice[data-serie="' + slice.serieIndex + '"][data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl),
elSlice = V($slice[0]);
// _.each(this.$('.slice'), function(slice) { V(slice).scale(1); $('.slice-inner-label', $(slice)).css('display', 'none'); });
_.each($('.slice', $chartViewDataEl), function(slice) { V(slice).scale(1); });
$('.slice-inner-label', $chartViewDataEl).css('display', 'none');
elSlice.scale(1.2);
chart.prop('active-slice', slice.sliceIndex);
// $('.slice-inner-label', $slice).css('display', 'block');
$('.slice-inner-label[data-slice="' + slice.sliceIndex + '"]', $chartViewDataEl).css('display', 'block');
});
}
});
});
paper.on('cell:mouseout', function(cellView, e) {
if (linkDownFlag || agentDownFlag || !isAgent(cellView)) return;
var $te = $(e.relatedTarget);
if (($te.hasClass('handle') && ($te.parents('.halo2').length == 0)) ||
$te.parents('[model-id=' + cellView.model.id + ']').length > 0) return;
if (halo) {
halo.remove();
halo = undefined;
checkAgentIfConfiguredAndDoIt(cellView);
}
showAgentSubtitle(cellView);
});
paper.$el.on('mouseenter', '.halo2 .handle.config', function() {
var modelId = $(this).parents('.halo2').attr('data-model-id');
paper.trigger('cell:mouseover', paper.findViewByModel(paper.getModelById(modelId)));
});
paper.$el.on('mouseleave', '.halo .handle', function(e) {
if (!halo || linkDownFlag || agentDownFlag) return;
var $te = $(e.relatedTarget),
cellView = halo.options.cellView;
if ($te.parents('[model-id=' + cellView.model.id + ']').length > 0 ||
($te.hasClass('handle') && ($te.parents('.halo2').length == 0))) return;
checkAgentIfConfiguredAndDoIt(cellView);
halo.remove();
halo = undefined;
showAgentSubtitle(cellView);
});
}
/**
* createInspector()
*
* @param joint.dia.ElementView cellView
*/
function createInspector(cellView) {
// No need to re-render inspector if the cellView didn't change.
// -------------------------------------------------------------
if (!inspector || inspector.options.cellView !== cellView) {
if (inspector) {
// Set unsaved changes to the model and clean up the old inspector if there was one.
inspector.updateCell();
inspector.remove();
}
var $dlgEl = $('#inspector_dialog'),
attributes = cellView.model.attributes,
config = attributes.config,
agentType = config.type,
addServiceURL = '',
fields = config.fields,
inputs = {
is_goal: { type: 'toggle', label: 'Set this as goal' }
},
$isGoalDivEl;
for (var i = 0, n = fields.length; i < n; i ++) {
inputs[fields[i]] = {};
}
initializeInspectorInputs(inputs, attributes);
inspector = new joint.ui.Inspector({
cellView: cellView,
inputs: inputs,
live: false
});
$('[data-field="schedule"]', '#inspector_dialog_header').remove();
$dlgEl.find('.inspector-container').html(inspector.render().el);
$dlgEl.find('.modal-title')
.html(attributes.attrs.text.text)
.after($('[data-field="schedule"]', '.inspector').addClass('pull-right'));
$dlgEl.find('.description').html( config.description || '' );
$dlgEl.find('.btn-list-add').addClass('btn btn-xs');
$dlgEl.find('.btn-list-del').addClass('btn btn-xs pull-right').html('&times;');
if (agentHasField(cellView, 'segment_id')) {
$dlgEl.find('[data-field="segment_id"]').append('<a href="' + gOptions.add_segment_url + '" class="btn btn-xs btn-primary">Add New Segment</a>');
}
if (agentHasField(cellView, 'service_id')) {
switch (agentType) {
case 'Facebook Update':
case 'Twitter Action':
case 'Twitter Message':
case 'Twitter Status':
addServiceURL = gOptions.add_twitter_service_url; break;
case 'Send Email':
addServiceURL = gOptions.add_email_service_url; break;
}
$dlgEl.find('[data-field="service_id"]').append('<a href="' + addServiceURL + '" class="btn btn-xs btn-primary">Add New Service</a>');
}
if (agentHasField(cellView, 'message_template_id')) {
$dlgEl.find('[data-field="message_template_id"]').append('<a href="' + gOptions.add_message_template_url + '" class="btn btn-xs btn-primary">Add New Message Template</a>');
}
if (agentHasField(cellView, 'campaign_list_id')) {
$dlgEl.find('[data-field="campaign_list_id"]').append('<a href="' + gOptions.add_campaign_list_url + '" class="btn btn-xs btn-primary">Add New Campaign List</a>');
}
if (agentType == 'Twitter Action') {
var actionValue = $dlgEl.find('[data-attribute="criteria/action"]').val();
$dlgEl.find('[data-field="criteria/list_name"]').toggle(actionValue == 'list' || actionValue == 'unlist');
}
$dlgEl.find('select > option[value=""]').prop('disabled', true);
$dlgEl.find('.modal-footer').find('[data-field="is_goal"]').remove();
var dataFieldId, $dataFieldDivEl, $inputEl;
$dlgEl.find('[data-type="toggle"]').each(function () {
$inputEl = $(this);
$dataFieldDivEl = $inputEl.parents('[data-field]');
dataFieldId = $dataFieldDivEl.attr('data-field');
if (dataFieldId === 'is_goal') {
$dataFieldDivEl.prependTo($dlgEl.find('.modal-footer'));
}
$dataFieldDivEl
.addClass('checkbox')
.css('float', 'left')
.css('margin-top', '5px');
$inputEl.attr('id', dataFieldId).insertBefore($('label', $dataFieldDivEl));
$('label', $dataFieldDivEl).attr('for', dataFieldId);
$('div.toggle', $dataFieldDivEl).remove();
});
// $('.toggle', $isGoalDivEl).remove();
$dlgEl.modal('show');
}
}
/**
* readableScheduleText()
*
* @param String text
* @return String
*/
function readableScheduleText(text) {
text = text.toString();
if (!text || text.trim().length === 0) return '';
var matches,
timeUnitsSingular = { 'm': 'minute', 'h': 'hour', 'd': 'day' },
timeUnitsPlural = { 'm': 'minutes', 'h': 'hours', 'd': 'days' };
if (matches = text.match(/^every_(\d+)(.+)/)) {
text = 'Every ' + ( parseInt(matches[1], 10) > 1 ? matches[1] + ' ' + timeUnitsPlural[matches[2]] : timeUnitsSingular[matches[2]] );
}
else if (matches = text.match(/^(\d+)(.+)/)) {
text = matches[1] + ' ' + matches[2].toUpperCase();
}
else {
text = text.charAt(0).toUpperCase() + text.slice(1);
}
return text;
}
/**
* initializeDefaultConfigValues()
*
* @param joint.dia.Cell cell
*/
function initializeDefaultConfigValues(cell) {
var attributes = cell.attributes,
agentType = attributes.config.type;
if (agentHasField(cell, 'workflow_id')) {
attributes.workflow_id = gOptions.workflow_id;
}
if (agentType === 'Has Facebook' ||
agentType === 'Has Email' ||
agentType === 'Has Twitter') {
if (agentHasField(cell, 'criteria')) {
attributes.criteria = attributes.config.criteria;
}
}
attributes.schedule = attributes.default_schedule;
}
/**
* initializeInspectorInputs()
*
* @param Object inputs
* @param Object attributes
*/
function initializeInspectorInputs(inputs, attributes) {
var config = attributes.config,
criteria = config.criteria,
tmp, options,
type = config.type;
if (type.match(/Segment$/)) {
type = type.match(/(.*) Segment$/)[1].toLowerCase();
}
for (var field in inputs) {
switch (field) {
case 'segment_id':
tmp = config.dropdowns.segments;
options = [{ value: '', content: 'Choose segment..' }];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i].value, content: tmp[i].name });
}
inputs[field] = {
type: 'select', label: 'Segments', options: options, index: 1
};
// Assume these agents are triggers and let's add another trigger-specific field
inputs.only_new = {
type: 'toggle', label: ['Only new ', type, ' that matches this segment'].join('')
};
break;
case 'schedule':
tmp = config.dropdowns.schedule;
options = [];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i], content: readableScheduleText(tmp[i]) });
}
inputs[field] = {
type: 'select', label: 'Schedule', options: options
};
break;
case 'service_id':
tmp = config.dropdowns.services;
if (config.type === 'Twitter Action' || config.type === 'Twitter Status' || config.type === 'Twitter Message') {
tmp = config.dropdowns.twitter_services;
}
else if (config.type === 'Facebook Update') {
tmp = config.dropdowns.facebook_services;
}
else if (config.type === 'Send Email') {
tmp = config.dropdowns.email_services;
}
options = [{ value: '', content: 'Choose service..' }];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i].value, content: tmp[i].name });
}
inputs[field] = {
type: 'select', label: 'Services', options: options, index: 1
};
break;
case 'campaign_list_id':
tmp = config.dropdowns.campaigns_list;
options = [{ value: '', content: 'Choose campaign..' }];
for (var i in tmp) {
options.push({ value: tmp[i].id, content: tmp[i].name });
}
inputs[field] = {
type: 'select', label: 'Campaigns', options: options, index: 1
};
options = [{ value: '', content: 'Choose stage..' }];
if (attributes.campaign_list_id) {
tmp = config.dropdowns.campaigns_list[attributes.campaign_list_id].stages;
for (var i in tmp) {
options.push({ value: tmp[i].id, content: tmp[i].name });
}
}
inputs['stage_id'] = {
type: 'select', label: 'Stages', options: options, index: 2
};
// Assume this is a campaign list agent and let's add another campaign-specific field
inputs.only_new = {
type: 'toggle', label: 'Only new list that matches this segment'
};
break;
case 'message_template_id':
tmp = config.dropdowns.message_templates;
options = [{ value: '', content: 'Choose message template..' }];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i].message_template.id, content: tmp[i].message_template.name });
}
inputs[field] = {
type: 'select', label: 'Message Template', options: options
};
break;
case 'user_ids':
tmp = config.dropdowns.users;
options = [{ value: '', content: 'Choose a user..' }];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i].user.id, content: tmp[i].user.name + ' (' + tmp[i].user.login + ')' });
}
inputs[field] = {
type: 'select', label: 'User', options: options
};
break;
case 'criteria':
if (config.type == "Add Field" || config.type == "Modify Field") {
inputs.criteria = {
type: 'list',
item: {
type: 'object',
properties: {
field_name: { type: 'text', label: 'Field Name' },
field_value: { type: 'text', label: 'Field Value' }
}
},
label: 'Click the {+} button to add a field and value.',
attrs: {
label: { class: 'custom-label' }
},
index: 99
};
}
else if (config.type == 'Filter') {
inputs.criteria = { type: 'text', label: 'Criteria', index: 99 };
}
else if (config.type == 'Twitter Action') {
inputs.criteria = {
action: {
type: 'select',
label: 'Action',
options: [
{ value: '', content: 'Choose action..' },
{ value: 'follow', content: 'Follow' },
{ value: 'unfollow', content: 'Unfollow' },
{ value: 'list', content: 'List' },
{ value: 'unlist', content: 'Unlist' }
],
index: 98
},
list_name: { type: 'text', label: 'List Name', index: 99 }
};
}
else if (config.type == "Split") {
inputs.criteriaUI = {
type: 'list',
item: {
type: 'object',
properties: {
weight_label: { type: 'text', label: 'Label' },
weight: { type: 'text', label: 'Weight' }
}
},
label: 'Click the {+} button to add a weight.',
attrs: {
label: { class: 'custom-label' }
},
index: 99
};
}
else if (config.type == 'Delay') {
inputs.criteria = {
length: { type: 'text', label: 'Length', attrs: { type: 'number' }, index: 98 },
unit: {
type: 'select',
label: 'Type',
options: [
{ value: '', content: 'Choose..' },
{ value: 'minutes', content: 'Minutes' },
{ value: 'hours', content: 'Hours' },
{ value: 'days', content: 'Days' },
{ value: 'weeks', content: 'Weeks' },
{ value: 'months', content: 'Months' }
],
index: 99
}
};
}
else if (config.type == 'Twitter Message') {
tmp = config.dropdowns.message_type;
options = [{ value: '', content: 'Choose message type..' }];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i], content: tmp[i].replace(/\w/, function(match, capture) { return match.toUpperCase(); }) });
}
inputs.criteria = {
type: 'select', label: 'Message Type', options: options, index: 99
};
}
else if (config.type == 'Limit') {
inputs.criteria = { type: 'text', label: 'Limit', index: 99 };
}
else if (config.type == "Update Field") {
inputs.criteria = {
type: 'list',
item: {
type: 'object',
properties: {
field_name: { type: 'text', label: 'Field Name' },
field_value: { type: 'text', label: 'Field Value' }
}
},
label: 'Click the {+} button to add a field and value.',
attrs: {
label: { class: 'custom-label' }
},
index: 99
};
}
else if (config.type == 'Has Profile') {
tmp = config.dropdowns.services;
options = [{ value: '', content: 'Choose..' }];
for (var i = 0, n = tmp.length; i < n; i ++) {
options.push({ value: tmp[i], content: tmp[i].replace(/\w/, function(match, capture) { return match.toUpperCase(); }) });
}
inputs.criteria = { type: 'select', label: 'Has Profile', options: options, index: 99 };
}
else if (config.type == 'Modify Campaign List') {
inputs.criteria = {
type: 'select',
label: 'Action',
options: [
{ value: '', content: 'Choose action..' },
{ value: 'add', content: 'Add' },
{ value: 'remove', content: 'Remove' }
],
index: 99
};
}
else {
delete inputs.criteria;
}
break;
default:
}
}
}
/**
* agentHasField()
*
* @param joint.dia.ElementView|joint.dia.Cell cell
* @param String fieldName
* @return Bool
*/
function agentHasField(cell, fieldName) {
var model = cell.model || cell,
fields = model.attributes.config.fields;
return (fields.indexOf(fieldName) >= 0);
}
/**
* isAgentConfigured()
*
* @param joint.dia.ElementView cellView
* @return Bool
*/
function isAgentConfigured(cellView) {
if (!cellView || !cellView.model) return false;
var attributes = cellView.model.attributes,
agentType = attributes.config.type,
data, k;
if (agentHasField(cellView, 'criteria')) {
data = attributes.criteria;
if (!data) return false;
if (data instanceof Array && data.length === 0) return false;
if (agentType == 'Add Field' || agentType == 'Update Field') {
for (var i = 0, n = data.length; i < n; i ++) {
if (data[i].field_name.toString().match(/^\s*$/) || data[i].field_value.toString().match(/^\s*$/)) {
return false;
}
}
} else if (agentType == 'Filter') {
if (data.toString().match(/^\s*$/)) return false;
} else if (agentType == 'Twitter Action') {
if (!data.action || data.action.toString().match(/^\s*$/)) return false;
if (data.action.toString().match(/^(list|unlist)$/) && data.list_name.toString().match(/^\s*$/)) return false;
} else if (agentType == 'Split') {
data = attributes.criteriaUI;
for (var i = 0, n = data.length; i < n; i ++) {
if (data[i].weight_label.toString().match(/^\s*$/) || data[i].weight.toString().match(/^\s*$/)) {
return false;
}
}
} else if (agentType == 'Twitter Message') {
if (data.toString().match(/^\s*$/)) return false;
} else if (agentType == 'Delay') {
if (data.length.toString().match(/^\s*$/) || !data.unit || data.unit.toString().match(/^\s*$/)) return false;
} else if (agentType == 'Limit') {
if (data.toString().match(/^\s*$/)) return false;
} else if (agentType == 'Has Profile') {
if (data.toString().match(/^\s*$/)) return false;
} else {
for (k in data) {
if (data[k].toString().match(/^\s*$/)) {
return false;
}
}
}
}
if (agentHasField(cellView, 'segment_id')) {
data = attributes.segment_id;
if (!data) return false;
}
if (agentHasField(cellView, 'service_id')) {
data = attributes.service_id;
if (!data) return false;
}
if (agentHasField(cellView, 'message_template_id')) {
data = attributes.message_template_id;
if (!data) return false;
}
if (agentHasField(cellView, 'campaign_list_id')) {
data = attributes.campaign_list_id;
if (!data || !attributes.stage_id) return false;
}
if (agentHasField(cellView, 'user_ids')) {
data = attributes.user_ids;
if (!data) return false;
}
return true;
}
/**
* checkAgentIfConfiguredAndDoIt()
*
* @param joint.dia.ElementView cellView
*/
function checkAgentIfConfiguredAndDoIt(cellView) {
var modelId = cellView.model.id;
if (isAgentConfigured(cellView))
{
// In case agent is configured, remove config link.
halo2[modelId] && halo2[modelId].remove();
delete halo2[modelId];
}
else
{
// This case implies agent is not configured.
if (halo2[modelId] === undefined) {
var h = new Halo2({ cellView: cellView });
h.render();
halo2[modelId] = h;
}
}
}
/**
* initializeNavigator()
*/
function initializeNavigator() {
nav = new joint.ui.Navigator({
paperScroller: paperScroller,
width: 200,
height: 100,
padding: 10,
zoomOptions: { max: zoomMax, min: zoomMin }
});
nav.$el.appendTo('#navigator');
nav.render();
$('#zoom-in').on('click', function() {
paperScroller.zoom(zoomStep, { max: zoomMax });
});
$('#zoom-out').on('click', function() {
paperScroller.zoom(-zoomStep, { min: zoomMin });
});
$('#toggle-panning').on('click', function() {
});
}
/**
* clearGraph()
*/
function clearGraph() {
for (var k in halo2) {
halo2[k].remove();
delete halo2[k];
}
graph.clear();
}
/**
* initAgentSubtitle()
*
* @param joint.dia.ElementView cellView
*/
function initAgentSubtitle(cellView) {
var subtitle = cellView.model.attributes.subtitle,
subtitleAlt = cellView.model.attributes.subtitleAlt,
$cellEl = cellView.$el,
subtitleTextNode = $cellEl.find('.agent-subtitle');
if (subtitle || subtitleAlt) {
if (subtitleTextNode.length == 0) {
subtitleTextNode = V('<text class="agent-subtitle" display="null" xml:space="preserve" font-size="14" text-anchor="middle" fill="#fff" y="0.8em" transform="matrix(1 0 0 1 0 0)"></text>').node;
cellView.el.appendChild(subtitleTextNode);
subtitleTextNode = $(subtitleTextNode);
}
subtitleTextNode.attr('data-normal-text', subtitle).attr('data-hover-text', subtitleAlt);
if (subtitle) {
showAgentSubtitle(cellView);
}
}
else {
if (subtitleTextNode.length > 0) {
subtitleTextNode.remove();
}
}
}
function showAgentSubtitle(cellView, showNormalText) {
var $cellEl = cellView.$el,
subtitleTextNode = $cellEl.find('.agent-subtitle'),
subtitleNormalText = subtitleTextNode.attr('data-normal-text'),
subtitleHoverText = subtitleTextNode.attr('data-hover-text'),
$textTitleEl = $cellEl.find('text.title'),
x, y;
if (subtitleTextNode.length == 0) return;
if (showNormalText === undefined) {
showNormalText = true;
}
if (showNormalText) {
$textTitleEl.show();
subtitleNormalText = subtitleNormalText || "";
x = shapeBigSize[0] / 2;
y = shapeBigSize[1] / 2 + $textTitleEl[0].getBBox().height + 5;
subtitleTextNode.html(getWordWrapText(subtitleNormalText, { width: shapeBigSize[0] }))[0].transform.translate(x, y);
}
else if (!showNormalText && subtitleHoverText !== undefined) {
$textTitleEl.hide();
x = shapeBigSize[0] / 2;
y = shapeBigSize[1] / 2;
subtitleTextNode.html(getWordWrapText(subtitleHoverText, { width: shapeBigSize[0] }))[0].transform.translate(x, y);
}
}
/**
* showGoalMarker()
*
* @param joint.dia.ElementView cellView
*/
function showGoalMarker(cellView) {
cellView.el.appendChild($goalMarkerEl);
}
/**
* initializeMisc()
*/
function initializeMisc() {
$('#btn_clear').on('click', function() {
clearGraph();
});
$('#btn_save').on('click', function() {
saveWorkflow();
});
$('#btn_save_close').on('click', function() {
saveWorkflow(function (data) {
window.location.replace(gOptions.workflows_path);
});
});
$('#btn_open_png').on('click', function() {
paper.toPNG(function (dataURL) {
window.open(dataURL);
});
});
$('#inspector_dialog').on('hide.bs.modal', function() {
if (inspector) {
inspector.remove();
inspector = null;
}
});
$('#inspector_btn_save').on('click', function() {
if (inspector) {
inspector.updateCell();
var cellView = inspector.options.cellView,
attributes = cellView.model.attributes,
agentType = attributes.config.type,
tmp;
// Special process for Split agent
if (agentType === 'Split') {
attributes.criteria = {};
for (var i = 0, criteriaUI = attributes.criteriaUI, n = criteriaUI.length; i < n; i ++) {
attributes.criteria[criteriaUI[i].weight_label] = criteriaUI[i].weight;
}
}
checkAgentIfConfiguredAndDoIt(cellView);
if (isAgentConfigured(cellView)) {
if (agentHasField(cellView, 'segment_id')) {
attributes.subtitle = $('select[data-attribute="segment_id"] option:selected', '#inspector_dialog').html();
}
else {
switch (agentType) {
case 'Facebook Update':
case 'Twitter Message':
case 'Twitter Status':
attributes.subtitle = $('select[data-attribute="service_id"] option:selected', '#inspector_dialog').html();
break;
case 'Send Email':
attributes.subtitle = $('select[data-attribute="message_template_id"] option:selected', '#inspector_dialog').html();
break;
case 'Twitter Action':
attributes.subtitle = $('select[data-attribute="criteria/action"] option:selected', '#inspector_dialog').html();
break;
case 'Delay':
attributes.subtitle = [attributes.criteria.length, $('select[data-attribute="criteria/unit"] option:selected', '#inspector_dialog').html()].join(' ');
break;
case 'Limit':
attributes.subtitle = attributes.criteria.toString();
break;
case 'Has Profile':
attributes.subtitle = $('select[data-attribute="criteria"] option:selected', '#inspector_dialog').html();
break;
case 'Split':
attributes.subtitle = $('[data-attribute="criteriaUI"] .list-item', '#inspector_dialog').length.toString();
break;
case 'Campaign List':
attributes.subtitleAlt = [$('[data-attribute="campaign_list_id"] option:selected', '#inspector_dialog').html(), $('[data-attribute="stage_id"] option:selected', '#inspector_dialog').html()].join(': ');
break;
case 'Modify Campaign List':
attributes.subtitle = $('select[data-attribute="criteria"] option:selected', '#inspector_dialog').html();
attributes.subtitleAlt = [$('[data-attribute="campaign_list_id"] option:selected', '#inspector_dialog').html(), $('[data-attribute="stage_id"] option:selected', '#inspector_dialog').html()].join(': ');
break;
default:
}
}
}
else {
delete attributes.subtitle;
}
initAgentSubtitle(cellView);
if (attributes.is_goal) {
for (var i = 0, cells = graph.getCells(), n = cells.length; i < n; i ++) {
if (cells[i] === cellView.model) continue;
delete cells[i].attributes.is_goal;
}
showGoalMarker(cellView);
}
$('#inspector_dialog').modal('hide');
}
});
$('#inspector_dialog').on('click', '.btn-list-add', function () {
$('.btn-list-del', '#inspector_dialog').addClass('btn btn-xs pull-right').html('&times;');
if (inspector.options.cellView.model.attributes.config.type == 'Split' && $('.list-item', '#inspector_dialog').length >= 5) {
$(this).prop('disabled', true);
}
})
.on('click', '[data-attribute="criteria/action"]', function() {
var value = $(this).val();
$('[data-field="criteria/list_name"]', '#inspector_dialog').toggle(value == 'list' || value == 'unlist');
})
.on('change', '[data-attribute="campaign_list_id"]', function () {
var options = '<option value="" selected disabled>Choose stage..</option>',
campaign_list_id = $(this).val();
$('[data-field="stage_id"]', '#inspector_dialog').toggle(campaign_list_id != '');
if (campaign_list_id) {
for (var i = 0,
stages = inspector.options.cellView.model.attributes.config.dropdowns.campaigns_list[campaign_list_id].stages,
n = stages.length;
i < n; i ++) {
options += '<option value="' + stages[i].id + '">' + stages[i].name + '</option>';
}
}
$('[data-attribute="stage_id"]', '#inspector_dialog').html(options);
});
$(document).on('click.btnListDel', '.btn-list-del', function () {
if (inspector.options.cellView.model.attributes.config.type == 'Split') {
$('.btn-list-add', '#inspector_dialog').prop('disabled', false);
}
});
}
return {
shapeSize: shapeSize,
iconSize: iconSize,
init: function(stencilData, options) {
gOptions = options;
initializePaper();
initializeStencil(stencilData);
initializeHaloAndInspector();
initializeNavigator();
initializeMisc();
},
createCustomShapes: function() {
createCustomShapes();
},
/* for debugging */
exposed: function() {
return {
paper: paper,
graph: graph,
paperScroller: paperScroller,
nav: nav,
stencil: stencil,
snaplines: snaplines,
halo: halo,
inspector: inspector,
chart: chart,
gOptions: gOptions
};
}
};
}();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment