JointJS Element with ports and tool items (delete, settings etc.) - backbone.js

I managed to enhance my SVG-shape with ports via:
joint.shapes.devs.Element = joint.shapes.basic.Generic.extend(_.extend({},
joint.shapes.basic.PortsModelInterface, {
// SVG markup with ports
});
With this I get the output:
I want to enhance this shape with a delete button. For that I have:
joint.shapes.devs.toolElement = joint.shapes.basic.Generic.extend({
// markup for delete button
});
based on Mike Goodwin´s solution in How to give JointJS elements a remove tool?
My question: How can I combine the PortModelInterface with the delete tool?
The solution should look like this:
Thanks for helping me out.

Solution
The key was this plugin. The author extended the PortsModelInterface with own code for a move-, resize- and ports-tool. I extended it further by implementing the delete functionality. This way the devs-model is open to any extension in case of functionality.
How it´s done
In tooledViewPlugin.js there is joint.plugins.TooledModelInterface = {}. In there I added:
deleteToolMarkup: '<circle fill="red" r="11"/><path transform="scale(.8) translate(-16, -16)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z"/><title>Remove this element from the model</title>',
Below in joint.plugins.TooledViewInterface = {} I wrote
renderDeleteTool: function () {
var deleteContainer = this.$('.deleteTool').empty();
var markup = V(this.model.deleteToolMarkup);
for(var id in markup)
deleteContainer.append(markup[id].node);
}
An example shape with special SVG markup other than a simple rectangle. Note the <g class="deleteTool"/> in the markup:
joint.shapes.devs.UnspecifiedProcess = joint.shapes.devs.Model.extend(_.extend({}, joint.plugins.TooledModelInterface, {
markup: ['<g class="rotatable">',
'<g class="scalable">',
'<rect class="body"/>',
'<g xmlns="http://www.w3.org/2000/svg" transform="translate(-549.49953,-824.87393)" id="layer1">',
'<g transform="matrix(0.933025,0,0,-0.2986125,549.49953,846.37362)" id="g3553">',
'<g transform="scale(0.98976,3.0047)" id="g3555">',
'<g clip-path="url(#clipPath3559)" id="g3557">',
'<path d="m 57.805,0.90155 -57.805,0 0,23.06045 57.805,0 L 72.244,12.432 57.805,0.90155 z" id="path3563" style="fill:#b8cde8;fill-opacity:1;fill-rule:evenodd;stroke:none"/>',
'</g>',
'</g>',
'</g>',
'</g>',
'</g>',
'<g class="inPorts"/>',
'<g class="outPorts"/>',
'<g class="moveTool"/>',
'<g class="resizeTool"/>',
'<g class="portsTool"/>',
'<g class="deleteTool"/>',
'<title class="tooltip"/>',
'</g>'].join(''),
defaults: joint.util.deepSupplement({
type: 'devs.UnspecifiedProcess',
inPorts: [''],
outPorts: [''],
moveTool: true,
resizeTool: true,
size: { width: 100, height: 31},
attrs: {
'.inPorts circle': { fill: '#fff' },
'.outPorts circle': { fill: '#fff' },
'.body': {
width: 67, height: 21,
stroke: 'none'
},
}
}, joint.shapes.devs.Model.prototype.defaults),
}));
joint.shapes.devs.UnspecifiedProcessView = joint.shapes.devs.ModelView.extend(joint.plugins.TooledViewInterface);
The final part is instantiating the element with new joint.shapes.devs.UnspecifiedProcess. I present it to you with my drag and drop logic as it might be useful for you too:
//Drag and drop shapes
if (Modernizr.draganddrop) {
// Mouse position
var posX = 0,
posY = 0;
// Selected Element with start of dragging
var selectedEl = "";
var selectedObj = null;
var oldObj = null;
//
$(".draggable-svg").on("dragstart", function(e) {
selectedEl = this.id;
console.log(selectedEl);
});
$("#drawing-area").on("dragover", function(e) {
e.preventDefault();
posX = e.originalEvent.pageX - sideBarW;
posY = e.originalEvent.pageY - topBarH;
});
$("#drawing-area").on("drop", function(e) {
e.preventDefault();
var element = new joint.shapes.devs[selectedEl]({
position: { x: posX, y: posY }
});
graph.addCell(element);
selectedEl = "";
oldObj = selectedObj;
selectedObj = element;
});
} else {
alert("Your browser is very old. Please update.");
}

Related

Load from JSON with dynamic patterns, patternSourceCanvas is not defined error return by the serialize JSON data

I am trying to save Fabric.js canvas and reload it using loadFromJson. But I am getting error patternSourceCanvas is not defined. I thought I should make it global so I removed var.
How to set the global variable in Fabric JS and use var patternSourceCanvas?
When I use the code below, then everything is working fine and the JSON is loaded easily.
var t = https://image.freepik.com/free-photo/roof-texture_21206171.jpg
fabric.util.loadImage(t, function(t) {
var svg_width = i.width;
console.log('svg widh' + i.width + 'svg height' + i.height);
console.log('img width t'+ t.width + ' img height' + t.height);
if(svg_width >= 300){
if (i.isSameColor && i.isSameColor() || !i.paths) {
i.setPatternFill({
source: t, repeat: 'repeat', offsetX:200 , offsetY : -110 // working
}), e.fabric.renderAll();
console.log('in if image 300 up ', t);
} else if (i.paths){
for (var r = 0; r < i.paths.length; r++)
i.paths[r].setPatternFill({
source: t, repeat: 'repeat' , offsetX: -100 , offsetY:-110
}), e.fabric.renderAll();
console.log('in image elseeee 300 up ', t);
}
}
})
But when I fill some other new shape with the new patternSourceCanvas variable then it's not working. Kindly help me with dynamic patterns.
var t = https://image.freepik.com/free-photo/roof-texture_21206171.jpg
fabric.Image.fromURL(t, function (img) {
img.scaleToHeight(200);
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(img);
patternSourceCanvas.renderAll();
var pattern = new fabric.Pattern({
source: function() {
patternSourceCanvas.setDimensions({
width: img.getScaledWidth() ,
height: img.getScaledHeight()
});
patternSourceCanvas.renderAll();
return patternSourceCanvas.getElement();
},
repeat: r
});
console.log('pattern', pattern);
//p.set('fill', pattern);
canvas.renderAll();
if (i.isSameColor && i.isSameColor() || !i.paths) {
i.setPatternFill(pattern);
} else if(i.paths) {
for (var r = 0; r < i.paths.length; r++) {
i.paths[r].setPatternFill(pattern);
}
}
e.fabric.renderAll();
});
You need to put patternSourceCanvas to global scope/ scope where loadFromJSON can access patternSourceCanvas. Else you can use cors enabled image directly.
DEMO
var imageUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/22/Wikimapia_logotype.svg';
var canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({
width: 200,
height: 200,
strokeWidth: 2,
stroke: '#000'
})
canvas.add(rect);
fabric.Image.fromURL(imageUrl, function(img) {
img.scaleToHeight(200);
var patternSourceCanvas = new fabric.StaticCanvas();
patternSourceCanvas.add(img);
patternSourceCanvas.setDimensions({
width: img.getScaledWidth(),
height: img.getScaledHeight()
});
patternSourceCanvas.renderAll();
var pattern = new fabric.Pattern({
source: patternSourceCanvas.getElement()
},
function(patternObj) {
rect.fill = patternObj;
rect.dirty = true;
canvas.renderAll();
});
}, {
crossOrigin: 'annonymous'
});
function loadfromjson() {
var json = canvas.toJSON();
canvas.clear();
setTimeout(function() {
canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
}, 1000)
}
canvas{
border:2px solid #000;
}
<canvas id="canvas" width="300" height="300"></canvas><br>
<button onclick="loadfromjson()">loadfromjson </button>
<script src='https://rawgit.com/kangax/fabric.js/master/dist/fabric.js'></script>

embed code twitter on Tinymce 4

I am adding a plugin which insert twitter embed code. the problem is that I can see the tweet on the editor but not in the source code and preview. And I can't save it. I saw in forum that I have to add 'http:' to '//platform.twitter.com/widgets.js' and put it before , unfortunately, it's not working. This is the code I put:
tinymce.PluginManager.add('twitter', function(editor, url) {
editor.on('init', function (args) {
editor_id = args.target.id;
});
editor.addButton('twitter', {
text: 'Twitter',
icon: false,
onclick: function () {
editor.windowManager.open({
title: 'Twitter Embed',
body: [
{ type: 'textbox',
size: 40,
height: '100px',
name: 'twitter',
label: 'twitter'
}
],
onsubmit: function(e) {
var embedCode = e.data.twitter;
var script = embedCode.match(/<script.*<\/script>/)[0];
var scriptSrc = script.match(/".*\.js/)[0].split("\"")[1];
console.log(script);
var sc = document.createElement("script");
sc.setAttribute("src", "https:"+scriptSrc);
sc.setAttribute("type", "text/javascript");
var iframe = document.getElementById(editor_id + "_ifr");
var iframeHead = iframe.contentWindow.document.getElementsByTagName('head')[0];
var iframeBody = iframe.contentWindow.document.getElementsByTagName('body')[0];
embedCode1 = embedCode.replace('//platform.twitter.com/widgets.js','https://platform.twitter.com/widgets.js');
iframeBody.appendChild(sc);
editor.insertContent(embedCode1);
iframeHead.appendChild(sc);
// setTimeout(function() {
// iframe.contentWindow.twttr.widgets.load();
// }, 1000)
}
});
}
});
});

Marker Clusterer in DevExtreme Mobile

I'm developing an application in DevExtreme Mobile. In application, I use DXMap in this application. How can I use the marker clusterer structure in DevExtreme Mobile App?
You can use Google Maps Marker Clusterer API to create and manage per-zoom-level clusters for a large number of DevExtreme dxMap markers. Here is an example:
 dxMap Marker Clusterer
This example is based on the approach described in the Google Too Many Markers! article
Here is sample code:
$("#dxMap").dxMap({
zoom: 3,
width: "100%",
height: 800,
onReady: function (s) {
var map = s.originalMap;
var markers = [];
for (var i = 0; i < 100; i++) {
var dataPhoto = data.photos[i];
var latLng = new google.maps.LatLng(dataPhoto.latitude, dataPhoto.longitude);
var marker = new google.maps.Marker({
position: latLng
});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers);
}
});
The kry is to use the google maps api. I did it for my app, here how.
This the html, very simple:
<div data-options="dxView : { name: 'map', title: 'Punti vendita', pane: 'master', secure:true } ">
<div data-bind="dxCommand: { id: 'back', behavior: 'back', type: 'back', visible: false }"></div>
<div data-options="dxContent : { targetPlaceholder: 'content' } ">
<div style="width: 100%; height: 100%;">
<div data-bind="dxMap:options"></div> <!--this for the map-->
<div id="large-indicator" data-bind="dxLoadIndicator: {height: 60,width: 60}" style="display:inline;z-index:99;" />
<div data-bind="dxPopover: {
width: 200,
height: 'auto',
visible: visible,
}">
</div>
</div>
</div>
</div>
When the page loads, the app read the gps coordinates:
function handleViewShown() {
navigator.geolocation.getCurrentPosition(onSuccess, onError, options);
jQuery("#large-indicator").css("display", "none"); //this is just a gif to indicate the user to wait the end of the operation
}
If the gps location is correctly read, I save the coordinates (the center of the map):
function onSuccess(position) {
var lat1 = position.coords.latitude;
var lon1 = position.coords.longitude;
center([lat1, lon1]);
}
And these are the options I set to my dxMap:
options: {
showControls: true,
key: { google: "myGoogleApiKey" },
center: center,
width: "100%",
height: "100%",
zoom: zoom,
provider: "google",
mapType: "satellite",
autoAdjust: false,
onReady: function (s) {
LoadPoints();
var map = s.originalMap;
var markers = [];
for (var i = 0; i < MyPoints().length; i++) {
var data = MyPoints()[i];
var latLng = new google.maps.LatLng(data.location[0], data.location[1]);
var marker = createMarker(latLng, data.title, map, data.idimp);
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers, { imagePath: 'images/m' });
}
},
Where MyPoints is populated calling LoadPoints:
function LoadPoints() {
$.ajax({
type: "POST",
async:false,
contentType: "application/json",
dataType: "json",
url: myApiUrl,
success: function (Response) {
var tempArray = [];
for (var point in Response) {
var location = [Response[p]["latitudine"], Response[p]["longitudine"]];
var title = Response[p]["name"] + " - " + Response[p]["city"];
var temp = { title: title, location: location, tooltip: title, onClick: GoToNavigator, idpoint: Response[p]["id"] };
tempArray.push(temp);
}
MyPoints(tempArray);
},
error: function (Response) {
jQuery("#large-indicator").css("display", "none");
var mex = Response["responseText"];
DevExpress.ui.notify(mex, "error");
}
});
}
Note that in the folder Myproject.Mobile/images I included the images m1.png, m2.png, m3.png, m4.png and m5.png.
You can found them here.

angular-google-maps not showing infowindow on mouseover event

I am using angularjs-google-map http://angular-ui.github.io/angular-google-maps/#!/api
I can add multiple markers with showing infoWindow by cliking on a marker, but now I need to show the marker infoWindow when the mouse enters the area of the marker icon and hide it when the mouse leaves the area of the marker icon instead of using the click event.
You can look on this example on Jsfiddle https://jsfiddle.net/meher12/bgb36q7b/ to get idea about my purpose !
My HTML code:
<ui-gmap-google-map center="map.center" zoom="map.zoom" dragging="map.dragging" bounds="map.bounds"
events="map.events" options="map.options" pan="true" control="map.control">
<ui-gmap-markers models="map.randomMarkers" coords="'self'" icon="'icon'"
doCluster="map.doClusterRandomMarkers" clusterOptions="map.clusterOptions" modelsbyref="true"
events="map.markersEvents" options="'options'"
>
<ui-gmap-windows show="'showWindow'" ng-cloak>
<div>
<p>This is an info window</p>
</div>
</ui-gmap-windows>
</ui-gmap-markers>
</ui-gmap-google-map>
</div>
My JS code:
myApp.controller('MainController', function ($scope,uiGmapGoogleMapApi) {
$scope.numOfMarkers = 25;
uiGmapGoogleMapApi.then(function(maps) { $scope.googleVersion = maps.version; });
$scope.map = {
center: {
latitude: 45,
longitude: -73
},
zoom: 10,
options: {
streetViewControl: false,
panControl: false,
maxZoom: 20,
minZoom: 3
},
dragging: false,
bounds: {},
randomMarkers: [],
doClusterRandomMarkers: true,
currentClusterType: 'standard',
clusterOptions: {
title: 'Hi I am a Cluster!', gridSize: 60, ignoreHidden: true, minimumClusterSize: 2
}
};
$scope.map.markersEvents = {
mouseover: function (marker, eventName, model, args) {
model.options.labelContent = "Position - lat: " + model.latitude + " lon: " + model.longitude;
marker.showWindow = true;
$scope.$apply();
},
mouseout: function (marker, eventName, model, args) {
model.options.labelContent = " ";
marker.showWindow = false;
$scope.$apply();
}
};
var genRandomMarkers = function (numberOfMarkers, scope) {
var markers = [];
for (var i = 0; i < numberOfMarkers; i++) {
markers.push(createRandomMarker(i, scope.map.bounds))
}
scope.map.randomMarkers = markers;
};
var createRandomMarker = function (i, bounds, idKey) {
var lat_min = bounds.southwest.latitude,
lat_range = bounds.northeast.latitude - lat_min,
lng_min = bounds.southwest.longitude,
lng_range = bounds.northeast.longitude - lng_min;
if (idKey == null)
idKey = "id";
var latitude = lat_min + (Math.random() * lat_range);
var longitude = lng_min + (Math.random() * lng_range);
var ret = {
latitude: latitude,
longitude: longitude,
title: 'm' + i,
showWindow: false,
options: {
labelContent: ' ',
labelAnchor: "22 0",
labelClass: "marker-labels",
draggable: true
}
};
ret[idKey] = i;
return ret;
};
$scope.genRandomMarkers = function (numberOfMarkers) {
genRandomMarkers(numberOfMarkers, $scope);
};
$scope.removeMarkers = function () {
$scope.map.randomMarkers = [];
};
});
Like you see on my JS code I have created markerEvents and I can get the marker label changed on mouse events but still not showing the infoWindow attached to each marker on the map when the mouse Event is fired, despite its value is changing and take the correct value.
Someone Have an idea to resolve this issue ?
Feel free to put your changes to my Jsfiddle code :)
You must set model.showWindow instead of marker.showWindow
You can set model.show = true instead of model.showWindow, in html remove show =showWindow.

how to Fit row height to content in ng-grid?

Any idea how to Fit row height to content in ng-grid? See that the text is wrapped but not shown as the row has a fix height.
I saw several posts claiming there is no support for that... thought maybe someone can enlighten me...
Thanks.
Here is my code:
this.gridOptions = { data: 'tenants.elements',
selectedItems: this.mySelections,
enableColumnResize: true,
enableColumnReordering: true,
enableRowReordering: true,
showSelectionCheckbox: true,
selectWithCheckboxOnly: true,
showFooter: true,
//sortInfo: {fields: ['percent'], directions: ['desc']},
columnDefs:
[
{width: '7%', field:'apartment', displayName:'דירה'},
{width: '2%', field:'tenant_type', displayName:'-', cellTemplate: '<i ng-class="tenants.getTenantType(row.entity.tenant_type)" class="fa fa-child"></i>'},
{width: '2%', field:'defecto', displayName:'-', cellTemplate: '<i ng-show="row.entity.defecto" class="fa fa-child defecto"></i>'},
{width: '30%', field:'tenant_name', displayName:'שם דייר', cellTemplate: '{{row.entity.tenant_name}}'},
{width: '25%' ,field:'phones', displayName:'טלפונים'},
{width: '25%' ,field:'mails', displayName:'מיילים'}
],
filterOptions: this.filterOptions,
plugins: [new ngGridAutoRowHeightPlugin()]
//plugins: [new ngGridFlexibleHeightPlugin()]
}
Here is a plugin, some code taken from the ngGridFlexibleHeightPlugin
Be warned, that height will be changed on all rows by maximum height
ngGridAutoRowHeightPlugin = function () {
var self = this;
self.grid = null;
self.scope = null;
self.init = function (scope, grid, services) {
self.domUtilityService = services.DomUtilityService;
self.grid = grid;
self.scope = scope;
var recalcHeightForData = function () { setTimeout(innerRecalcForData, 1); };
var innerRecalcForData = function () {
var gridId = self.grid.gridId,
rowHeight = self.grid.config.rowHeight;
$('.' + gridId + ' .ngRow [ng-cell-text]').each(function (index, cellText) {
//+10 for default top and bottom padding of .ngCellText class
rowHeight = Math.max(rowHeight, $(cellText).outerHeight() + 10);
});
if (self.grid.config.rowHeight < rowHeight) {
self.grid.config.rowHeight = rowHeight;
//update grid's scope.rowHeight as vertical bars height depends on it
if (scope.$$phase == '$apply' || scope.$$phase == '$digest') {
updateGridScopeHeight();
} else {
scope.$apply(updateGridScopeHeight);
}
self.domUtilityService.RebuildGrid(self.scope, self.grid);
function updateGridScopeHeight() {
self.grid.$root.scope().rowHeight = rowHeight;
}
}
};
self.scope.catHashKeys = function () {
var hash = '',
idx;
for (idx in self.scope.renderedRows) {
hash += self.scope.renderedRows[idx].$$hashKey;
}
return hash;
};
self.scope.$watch('catHashKeys()', innerRecalcForData);
self.scope.$watch(self.grid.config.data, recalcHeightForData);
};
};
And also, add this style rule to your css (after ng-grid css)
.ngCellText {
white-space: normal !important;
}
Plunker here

Resources