Using JSPlumb in an angular.js directive - angularjs

I'm trying to use jsPlumb along with AngularJS and have run into some problems. I started with the jsplumb-ng example from github and attempted to modify the jsPlumb parameters to mimic the statemachine demo from the jsPlumb site. The jsplumb-ng approach creates a jsPlumbCanvas directive and it contains jsPlumbNodes. As a starting point I tried to place some static nodes from the statemachine demo and then add in two nodes controlled by the angular controller. The demo nodes are placed on the canvas correctly and connections are created. The angular nodes are created and jsPlumb has trouble in the makeTarget method and prints messages to the console.
TypeError: Cannot set property '_jsPlumbTarget' of undefined {stack: (...), message: "Cannot set property '_jsPlumbTarget' of undefined"}
I would greatly appreciate any advice to get moving forward again!
Apologize up front because I'm dancing around the fact that I do not have enough reputation to post images of the problem.
i.imgur.com/Ao4qzm9.png
i.imgur.com/OMS0FGE.png
Plunk
index.html
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link href="https://code.jquery.com/ui/1.11.3/themes/smoothness/jquery-ui.css" rel="stylesheet">
<link data-require="bootstrap#3.3.1" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="https://jsplumbtoolkit.com/css/jsplumb.css" />
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.3.x" src="https://code.angularjs.org/1.3.14/angular.js" data-semver="1.3.14"></script>
<script data-require="jquery#*" data-semver="2.1.3" src="http://code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="https://code.jquery.com/ui/1.11.3/jquery-ui.js"></script>
<script data-require="bootstrap#3.3.1" data-semver="3.3.1" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
<script data-require="jsplumb#*" data-semver="1.7.3" src="https://jsplumbtoolkit.com/js/jquery.jsPlumb-1.7.3-min.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.0.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="DiagramEditorCtrl">
<div id="diagramView" style="overflow: hidden;" ng-cloak>
<js-plumb-canvas
on-connection="onConnection1"
x="posx"
y="posy"
zoom="zoomlevel"
ng-drop="true"
ng-drop-success="onDropComplete1($data,$event)"
>
<div ng-repeat="state in stateObjects track by state.uuid">
<js-plumb-node state-object="state" style="positon: relative;"></js-plumb-node>
</div>
<js-plumb-connection ng-click="setActiveConnection($index)" ng-repeat="connection in stateConnections" ng-model="connection">
</js-plumb-connection>
</js-plumb-canvas>
</div>
</body>
</html>
app.js
var app = angular.module('plunker', []);
app.controller('DiagramEditorCtrl', ['$scope',
function($scope) {
$scope.zoomlevel = 85;
$scope.posx = 14;
$scope.posy = 14;
$scope.stateObjects = [{
'name': 'jsPlumb',
'uuid': '123',
'style': '',
'x': 300,
'y': 150
}, {
'name': 'Brewer',
'uuid': '124',
'style': '',
'x': 45,
'y': 175
}
];
// Defaults the connections between objects. Will come from a service.
$scope.stateConnections = [
// {
// targetUUID: 'ds1',
// sourceUUID: 'sn1'
// }
];
var instance = jsPlumb.instance;
$scope.onConnection = function(instance, connection, targetUUID,
sourceUUID) {
$scope.stateConnections.push({
'targetUUID': targetUUID,
'sourceUUID': sourceUUID,
'conn': connection
});
$scope.$apply();
};
}
])
.directive('jsPlumbCanvas', function() {
var jsPlumbZoomCanvas = function(instance, zoom, el, transformOrigin) {
transformOrigin = transformOrigin || [0, 0];
var p = ['webkit', 'moz', 'ms', 'o'],
s = 'scale(' + zoom + ')',
oString = (transformOrigin[0] * 100) + '% ' + (transformOrigin[1] *
100) +
'%';
for (var i = 0; i < p.length; i++) {
el.style[p[i] + 'Transform'] = s;
el.style[p[i] + 'TransformOrigin'] = oString;
}
el.style.transform = s;
el.style.transformOrigin = oString;
instance.setZoom(zoom);
};
var def = {
restrict: 'E',
scope: {
onConnection: '=onConnection',
zoom: '=',
x: '=',
y: '='
},
controller: function($scope) {
this.scope = $scope;
},
transclude: true,
templateUrl: 'workspace.html',
link: function(scope, element, attr) {
angular.element(document).ready(function () {
var instance = jsPlumb.getInstance({
Endpoint : ['Dot', {radius:2}],
HoverPaintStyle : {strokeStyle:'#1e8151', lineWidth:2 },
ConnectionOverlays : [
[ 'Arrow', {
location:1,
id:'arrow',
length:14,
foldback:0.8
} ],
[ 'Label', { label:'Connecting...', id:'label', cssClass:'aLabel' }]
],
Container:'workspace-container'
});
scope.jsPlumbInstance = instance;
/*
* Standard JSPlumb Statemachine example that works fine.
*/
var windows = jsPlumb.getSelector('.workspace-container .node');
instance.draggable(windows);
instance.bind('click', function(c) {
instance.detach(c);
});
instance.bind('connection', function(info) {
info.connection.getOverlay('label').setLabel(info.connection.id);
});
instance.doWhileSuspended(function() {
var isFilterSupported = instance.isDragFilterSupported();
if (isFilterSupported) {
instance.makeSource(windows, {
filter:'.ep',
anchor:'Continuous',
connector:[ 'StateMachine', { curviness:20 } ],
connectorStyle:{ strokeStyle:'#5c96bc', lineWidth:2, outlineColor:'transparent', outlineWidth:4 },
maxConnections:5,
onMaxConnections:function(info, e) {
alert('Maximum connections (' + info.maxConnections + ') reached');
}
});
}
else {
var eps = jsPlumb.getSelector('.ep');
for (var i = 0; i < eps.length; i++) {
var e = eps[i], p = e.parentNode;
instance.makeSource(e, {
parent:p,
anchor:'Continuous',
connector:[ 'StateMachine', { curviness:20 } ],
connectorStyle:{ strokeStyle:'#5c96bc',lineWidth:2, outlineColor:'transparent', outlineWidth:4 },
maxConnections:5,
onMaxConnections:function(info, e) {
alert('Maximum connections (' + info.maxConnections + ') reached');
}
});
}
}
});
instance.makeTarget(windows, {
dropOptions:{ hoverClass:'dragHover' },
anchor:'Continuous',
allowLoopback:true
});
instance.connect({ source:'opened', target:'phone1' });
instance.connect({ source:'phone1', target:'phone1' });
jsPlumb.fire('jsPlumbDemoLoaded', instance);
/*
* End standard JSPlumb statemachine example
*/
});
}
};
return def;
})
.directive('jsPlumbNode', function() {
var def = {
restrict: 'E',
require: '^jsPlumbCanvas',
scope: {
node: '=stateObject'
},
// At one point, I tried to have a real uuid assigned as the div#id, but
// JSPlumb seemed to see {{node.uuid}}
// id="{{node.uuid}}"
template: '<div class="node" ' +
' ng-style="{ \'left\':node.x, \'top\':node.y }" >' +
' <div id="ds1-nh" class="node-header" ></div>' +
' <div id="ds1-nb" class="node-body">' +
'{{node.name}}' +
' </div>' +
' <div id="ds1-nt" class="node-tools">' +
' <div id="ds1-nt-o" class="node-tool-options">' +
' <span ng-click="configure($index)" class="glyphicon glyphicon-cog"></span>' +
' </div>' +
' <div id="ds1-nt-d" class="node-tool-delete">' +
' <span ng-click="trash($index)" class="glyphicon glyphicon-trash"></span>' +
' </div>' +
' </div>' +
'</div>',
link: function($scope, element, attrs, jsPlumbCanvas) {
// removed dependence on uuid4 for this plunker.
$scope.node.uuid = '1234-123-1233';
angular.element(document).ready(function () {
var instance = jsPlumbCanvas.scope.jsPlumbInstance;
function displayMaxConnectionError(info, e) {
alert('Maximum connections (' + info.maxConnections +
') reached');
}
var nodeEl = element.find('.node' );
instance.draggable( nodeEl, {
grid: [20, 20],
drag: function(event, ui) {
var posX, posY;
if( typeof event.pos !== 'undefined') {
posX = event.pos[0];
posY = event.pos[1];
}
if (typeof ui !== 'undefined') {
posX = ui.position.left;
posY = ui.position.top;
}
$scope.node.x = posX;
$scope.node.y = posY;
$scope.$apply();
}
});
// suspend drawing and initialise.
instance.doWhileSuspended(function() {
var eps = element.find('.node-header');
//console.log(eps);
for (var i = 0; i < eps.length; i++) {
var e = eps[i],
p = e.parentNode;
console.log( 'e: %o', e );
console.log( 'e.parent: %o', p );
instance.makeSource(e, {
parent: p,
anchor: 'Continuous',
connector: ['StateMachine', {
curviness: 20
}],
connectorStyle: {
strokeStyle: '#5c96bc',
lineWidth: 2,
outlineColor: 'transparent',
outlineWidth: 4
},
maxConnections: 5,
onMaxConnections: displayMaxConnectionError
});
}
instance.makeTarget(nodeEl, {
dropOptions: {
hoverClass: 'dragHover'
},
anchor: 'Continuous',
allowLoopback: true
});
});
});
}
};
return def;
})
.directive('jsPlumbConnection', function($timeout) {
var def = {
restrict: 'E',
require: '^jsPlumbCanvas',
scope: {
ngClick: '&ngClick',
ngModel: '=ngModel'
},
link: function($scope, element, attrs, jsPlumbCanvas) {
var instance = jsPlumbCanvas.scope.jsPlumbInstance;
$timeout(function() {
if (typeof $scope.ngModel.conn === 'undefined') {
$scope.ngModel.conn = instance.connect({
uuids: [
$scope.ngModel.targetUUID,
$scope.ngModel.sourceUUID
],
overlays: [
['Label', {
label: '',
id: 'label'
}]
],
editable: true
});
}
var connection = $scope.ngModel.conn;
connection.bind('mouseenter', function(conn, originalEvent) {
$scope.ngModel.mouseover = true;
$scope.$apply();
});
connection.bind('mouseleave', function(conn, originalEvent) {
$scope.ngModel.mouseover = false;
$scope.$apply();
});
}, 1300);
$scope.$on('$destroy', function() {
instance.detach($scope.ngModel.conn);
});
}
};
return def;
})
;
I have also looked at the mrquincle/jsplumb-example from github. It works fine until I tried to load a more recent copy of jsPlumb, jquery, jquery-ui and angular. The issue with the updated libraries is that the connections are not attached between the two nodes.
i.imgur.com/VLtjWFy.png
Plunk
I am not tied to either the jsplumb-ng or the jsplumb-example approach. I'm just trying to get one to work.
Thanks ahead of time!

Related

AngularJS Smart Table : 'ng is not defined' error on smart table with filters

I am trying to implement angularjs table with filters using smart table (http://lorenzofox3.github.io/smart-table-website/#top)
To be precise, I am implementing a table with filters for date and numeric column. (On the link provided, it is available in 'Date and numeric filtering' section).
However, I am getting an error message as below,
angular.min.js:114 ReferenceError: ng is not defined
at link (http://localhost:39459/app/app.module.js:27:35)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:78:461
at ka (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:79:16)
at u (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:66:326)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:74:460
at https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:126:404
at m.$eval (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:141:47)
at m.$digest (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:138:140)
at m.$apply (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:141:341)
at g (https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js:94:139)
<st-date-range predicate="datetime" before="query.before" after="query.after" class="ng-isolate-scope">`enter code here`
Below is my code,
On _Layout.cshtml,I have the following scripts and styles imports,
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-route.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-animate.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-resource.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.0/angular-sanitize.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.2.5/ui-bootstrap-tpls.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-smart-table/2.1.8/smart-table.min.js"></script>
<link rel="stylesheet" type="text/css" href="http://angular-ui.github.com/ng-grid/css/ng-grid.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
<script src="~/app/components/expense/expenseController.js"></script>
<script src="~/app/app.module.js"></script>
<link href="~/assets/css/Site.css" rel="stylesheet" />
Below is my code in app.module.js.
var mainApp = angular.module('ExpenseManagerApp', ['ngRoute', 'ui.bootstrap', 'ui.bootstrap.tpls', 'smart-table'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.
when('/dashboard', { templateUrl: 'app/components/dashboard/dashboard.html', controller: 'dashboardController' }).
when('/expense', { templateUrl: 'app/components/expense/expense.html', controller: 'expenseController' }).
otherwise({ redirectTo: '/dashboard', templateUrl: 'app/components/dashboard/dashboard.html' });
}])
.controller('dashboardController', dashboardController)
.controller('expenseController', expenseController)
.directive('stDateRange', ['$timeout', function ($timeout) {
return {
restrict: 'E',
require: '^stTable',
scope: {
before: '=',
after: '='
},
templateUrl: '/app/shared/partials/stDateRange.html',
link: function (scope, element, attr, table) {
var inputs = element.find('input');
var inputBefore = ng.element(inputs[0]);
var inputAfter = ng.element(inputs[1]);
var predicateName = attr.predicate;
[inputBefore, inputAfter].forEach(function (input) {
input.bind('blur', function () {
var query = {};
if (!scope.isBeforeOpen && !scope.isAfterOpen) {
if (scope.before) {
query.before = scope.before;
}
if (scope.after) {
query.after = scope.after;
}
scope.$apply(function () {
table.search(query, predicateName);
})
}
});
});
function open(before) {
return function ($event) {
$event.preventDefault();
$event.stopPropagation();
if (before) {
scope.isBeforeOpen = true;
} else {
scope.isAfterOpen = true;
}
}
}
scope.openBefore = open(true);
scope.openAfter = open();
}
}
}])
.directive('stNumberRange', ['$timeout', function ($timeout) {
return {
restrict: 'E',
require: '^stTable',
scope: {
lower: '=',
higher: '='
},
templateUrl: '/app/shared/partials/stNumberRange.html',
link: function (scope, element, attr, table) {
var inputs = element.find('input');
var inputLower = ng.element(inputs[0]);
var inputHigher = ng.element(inputs[1]);
var predicateName = attr.predicate;
[inputLower, inputHigher].forEach(function (input, index) {
input.bind('blur', function () {
var query = {};
if (scope.lower) {
query.lower = scope.lower;
}
if (scope.higher) {
query.higher = scope.higher;
}
scope.$apply(function () {
table.search(query, predicateName)
});
});
});
}
};
}])
.filter('customFilter', ['$filter', function ($filter) {
var filterFilter = $filter('filter');
var standardComparator = function standardComparator(obj, text) {
text = ('' + text).toLowerCase();
return ('' + obj).toLowerCase().indexOf(text) > -1;
};
return function customFilter(array, expression) {
function customComparator(actual, expected) {
var isBeforeActivated = expected.before;
var isAfterActivated = expected.after;
var isLower = expected.lower;
var isHigher = expected.higher;
var higherLimit;
var lowerLimit;
var itemDate;
var queryDate;
if (ng.isObject(expected)) {
//date range
if (expected.before || expected.after) {
try {
if (isBeforeActivated) {
higherLimit = expected.before;
itemDate = new Date(actual);
queryDate = new Date(higherLimit);
if (itemDate > queryDate) {
return false;
}
}
if (isAfterActivated) {
lowerLimit = expected.after;
itemDate = new Date(actual);
queryDate = new Date(lowerLimit);
if (itemDate < queryDate) {
return false;
}
}
return true;
} catch (e) {
return false;
}
} else if (isLower || isHigher) {
//number range
if (isLower) {
higherLimit = expected.lower;
if (actual > higherLimit) {
return false;
}
}
if (isHigher) {
lowerLimit = expected.higher;
if (actual < lowerLimit) {
return false;
}
}
return true;
}
//etc
return true;
}
return standardComparator(actual, expected);
}
var output = filterFilter(array, expression, customComparator);
return output;
};
}]);
Below is my code in expenseController.js
var expenseController = function ($scope, $log) {
$scope.rowCollection = [{ id: 1, datetime: new Date().toLocaleString(), description: 'Credit Card Payment', category: 'Food', amount: '10.50' },
{ id: 2, datetime: new Date().toLocaleString(), description: 'Debit Card Payment', category: 'Clothes', amount: '22.50' },
{ id: 3, datetime: new Date().toLocaleString(), description: 'Net Banking Payment', category: 'Utensils', amount: '56.50' },
{ id: 4, datetime: new Date().toLocaleString(), description: 'Loan Payment', category: 'Food', amount: '10.50' }
];
};
expenseController.$inject = ['$scope', '$log'];
Rest everything is same as the sample code provided on the website of smart table.
Can anyone let me know what am I missing which is causing this error?

close multiselect dropdown directive on clicking outside dropdown

I have tried codes from this links
AngularJS dropdown directive hide when clicking outside,
http://plnkr.co/edit/ybYmHtFavHnN1oD8vsuw?p=preview,
closing dropdown single or multiselect when clicking outside.
But did not help.
<html>
<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css">
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script>
'use strict';
var app = angular.module('myApp', ['app.directives']);
app.controller('AppCtrl', function($scope){
$scope.roles = [
{"id": 1, "name": "Manager", "assignable": true},
{"id": 2, "name": "Developer", "assignable": true},
{"id": 3, "name": "Reporter", "assignable": true}
];
$scope.member = {roles: []};
$scope.selected_items = [];
});
var app_directives = angular.module('app.directives', []);
app_directives.directive('dropdownMultiselect', function($document){
return {
restrict: 'AE',
scope:{
model: '=',
options: '=',
pre_selected: '=preSelected'
},
template: "<div class='btn-group' data-ng-class='{open: open}'>"+
"<button class='btn btn-small'>Select</button>"+
"<button class='btn btn-small dropdown-toggle' data-ng-click='open=!open;openDropdown()'><span class='caret'></span></button>"+
"<ul class='dropdown-menu' aria-labelledby='dropdownMenu'>" +
"<li><a data-ng-click='selectAll()'><i class='icon-ok-sign'></i> Check All</a></li>" +
"<li><a data-ng-click='deselectAll();'><i class='icon-remove-sign'></i> Uncheck All</a></li>" +
"<li class='divider'></li>" +
"<li data-ng-repeat='option in options'> <a data-ng-click='setSelectedItem()'>{{option.name}}<span data-ng-class='isChecked(option.id)'></span></a></li>" +
"</ul>" +
"</div>" ,
link: function postLink(scope, element, attrs)
{
console.log("in on click");
var onClick = function (event) {
var isChild = element[0].contains(event.target);
var isSelf = element[0] == event.target;
var isInside = isChild || isSelf;
if (!isInside) {
scope.$apply(attrs.dropdownMultiselect)
}
}
scope.$watch(attrs.isActive, function(newValue, oldValue) {
if (newValue !== oldValue && newValue == true) {
$document.bind('click', onClick);
}
else if (newValue !== oldValue && newValue == false) {
$document.unbind('click', onClick);
}
});
},
controller: function($scope){
$scope.openDropdown = function(){
$scope.selected_items = [];
for(var i=0; i<$scope.pre_selected.length; i++){ $scope.selected_items.push($scope.pre_selected[i].id);
}
};
$scope.selectAll = function () {
$scope.model = _.pluck($scope.options, 'id');
console.log($scope.model);
};
$scope.deselectAll = function() {
$scope.model=[];
console.log($scope.model);
};
$scope.setSelectedItem = function(){
var id = this.option.id;
if (_.contains($scope.model, id)) {
$scope.model = _.without($scope.model, id);
} else {
$scope.model.push(id);
}
console.log($scope.model);
return false;
};
$scope.isChecked = function (id) {
if (_.contains($scope.model, id)) {
return 'icon-ok pull-right';
}
return false;
};
}
}
});
</script>
<body>
<div ng-app="myApp" ng-controller="AppCtrl">
<dropdown-multiselect pre-selected="member.roles" model="selected_items" options="roles" is-active="isDropdownOpen()"></dropdown-multiselect>
<pre>selected roles = {{selected_items | json}}</pre>
</div>
</body>
</html>
Please suggest changes for this.
I solved it finally using below code.
<html>
<link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css">
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js"></script>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script>
'use strict';
var app = angular.module('myApp', ['app.directives']);
app.controller('AppCtrl', function($scope){
$scope.roles = [
{"id": 1, "name": "Manager", "assignable": true},
{"id": 2, "name": "Developer", "assignable": true},
{"id": 3, "name": "Reporter", "assignable": true}
];
$scope.member = {roles: []};
$scope.selected_items = [];
});
var app_directives = angular.module('app.directives', []);
app_directives.directive('dropdownMultiselect', function($document){
return {
restrict: 'AE',
scope:{
model: '=',
options: '=',
pre_selected: '=preSelected'
},
template: "<div class='btn-group' data-ng-class='{open: open}'>"+
"<button class='btn btn-small'>Select</button>"+
"<button class='btn btn-small dropdown-toggle' data-ng-click='open=!open;openDropdown()' ><span class='caret'></span></button>"+
"<ul class='dropdown-menu' aria-labelledby='dropdownMenu' ng-show='open'>" +
"<li><a data-ng-click='selectAll()'><i class='icon-ok-sign'></i> Check All</a></li>" +
"<li><a data-ng-click='deselectAll();'><i class='icon-remove-sign'></i> Uncheck All</a></li>" +
"<li class='divider'></li>" +
"<li data-ng-repeat='option in options'> <a data-ng-click='setSelectedItem()'>{{option.name}}<span data-ng-class='isChecked(option.id)'></span></a></li>" +
"</ul>" +
"</div>" ,
link: function(scope, elem, attr, ctrl)
{
//console.log("in click");
elem.bind('click', function(e) {
console.log("in click");
// this part keeps it from firing the click on the document.
e.stopPropagation();
});
$document.bind('click', function() {
// magic here.
console.log("click in document");
scope.$apply(attr.dropdownMulti);
/*var myElement= document.getElementsByClassName('btn btn-small dropdown-toggle');
angular.element(myElement).triggerHandler('click');*/
})
},
controller: function($scope){
$scope.openDropdown = function(){
$scope.selected_items = [];
for(var i=0; i<$scope.pre_selected.length; i++){
$scope.selected_items.push($scope.pre_selected[i].id);
}
};
$scope.selectAll = function () {
$scope.model = _.pluck($scope.options, 'id');
console.log($scope.model);
};
$scope.deselectAll = function() {
$scope.model=[];
console.log($scope.model);
};
$scope.setSelectedItem = function(){
var id = this.option.id;
if (_.contains($scope.model, id)) {
$scope.model = _.without($scope.model, id);
} else {
$scope.model.push(id);
}
console.log($scope.model);
return false;
};
$scope.isChecked = function (id) {
if (_.contains($scope.model, id)) {
return 'icon-ok pull-right';
}
return false;
};
}
}
});
</script>
<body>
<div ng-app="myApp" ng-controller="AppCtrl">
<dropdown-multiselect pre-selected="member.roles" model="selected_items" options="roles" is-active="isDropdownOpen()" dropdown-multi="open=false"></dropdown-multiselect>
<pre>selected roles = {{selected_items | json}}</pre>
</div>
</body>
</html>

AngularJS template in directive not display text

I try to create directive in angularjs:
JavaScript and HTML code:
'use strict';
var app = angular.module('myApp', ['app.directives']);
app.controller('AppCtrl', function($scope){
$scope.roles = [
{"id": 1, "name": "Michael"},
{"id": 2, "name": "Max"},
{"id": 3, "name": "John"}
];
$scope.dummyData = "Dummy!!!";
$scope.member = {roles: []};
$scope.selected_items = [];
});
var app_directives = angular.module('app.directives', []);
app_directives.directive('dropdownMultiselect', function(){
return {
restrict: 'E',
scope:{
testElem: '=',
model: '=',
options: '=',
pre_selected: '=preSelected'
},
template: "<div class='btn-group' data-ng-class='{open: open}'>"+
"<button class='btn btn-small'>{{testElem}}</button>"+
"<button class='btn btn-small dropdown-toggle' data-ng-click='open=!open;openDropdown()'><span class='caret'></span></button>"+
"<ul class='dropdown-menu' aria-labelledby='dropdownMenu'>" +
"<li><a data-ng-click='selectAll()'><i class='icon-ok-sign'></i> Check All</a></li>" +
"<li><a data-ng-click='deselectAll();'><i class='icon-remove-sign'></i> Uncheck All</a></li>" +
"<li class='divider'></li>" +
"<li data-ng-repeat='option in options'> <a data-ng-click='setSelectedItem()'>{{option.name}}<span data-ng-class='isChecked(option.id)'></span></a></li>" +
"</ul>" +
"</div>" ,
controller: function($scope){
$scope.openDropdown = function(){
$scope.selected_items = [];
for(var i=0; i<$scope.pre_selected.length; i++){ $scope.selected_items.push($scope.pre_selected[i].id);
}
};
$scope.selectAll = function () {
$scope.model = _.pluck($scope.options, 'id');
console.log($scope.model);
};
$scope.deselectAll = function() {
$scope.model=[];
console.log($scope.model);
};
$scope.setSelectedItem = function(){
var id = this.option.id;
if (_.contains($scope.model, id)) {
$scope.model = _.without($scope.model, id);
} else {
$scope.model.push(id);
}
console.log($scope.model);
return false;
};
$scope.isChecked = function (id) {
if (_.contains($scope.model, id)) {
return 'icon-ok pull-right';
}
return false;
};
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.0/css/bootstrap-combined.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<link href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<div ng-app="myApp" ng-controller="AppCtrl">
<dropdown-multiselect pre-selected="member.roles" model="selected_items" testElem = "dummyData" options="roles"></dropdown-multiselect>
In template in the directive definition I have this row:
<button class='btn btn-small'>{{$scope.testElem}}</button>
As you can see with help of this row I try to display text inside $scope.testElem variable.
But the text does not appears.What I am missing? Why I don't see text on the declared button?

Angular.js - adding a class to an option with ng-options

Someone asked a similar question (How to use ng-class in select with ng-options), but I'm adding mine too, because it's related to the answer of the other guy's question.
The solution is awesome, but I don't quite understand it.
The answer was creating a directive - http://plnkr.co/edit/rbc4GWBffi4eFYhbvS6u?p=preview.
I would like do the same, but the class added should be the same as items.name. How do I do that?
console.clear();
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {
$scope.items = [
{ name: 'foo', id: 1, eligible: true },
{ name: 'bar', id: 2, eligible: false },
{ name: 'test', id: 3, eligible: true }
];
});
app.directive('optionsClass', function ($parse) {
return {
require: 'select',
link: function(scope, elem, attrs, ngSelect) {
// get the source for the items array that populates the select.
var optionsSourceStr = attrs.ngOptions.split(' ').pop(),
// use $parse to get a function from the options-class attribute
// that you can use to evaluate later.
getOptionsClass = $parse(attrs.optionsClass);
scope.$watch(optionsSourceStr, function(items) {
// when the options source changes loop through its items.
angular.forEach(items, function(item, index) {
// evaluate against the item to get a mapping object for
// for your classes.
var classes = getOptionsClass(item),
// also get the option you're going to need. This can be found
// by looking for the option with the appropriate index in the
// value attribute.
option = elem.find('option[value=' + index + ']');
// now loop through the key/value pairs in the mapping object
// and apply the classes that evaluated to be truthy.
angular.forEach(classes, function(add, className) {
if(add) {
angular.element(option).addClass(className);
}
});
});
});
}
};
});
/* CSS goes here */
.is-eligible {
color: green;
}
.not-eligible {
color: red;
}
<!DOCTYPE html>
<html ng-app="angularjs-starter">
<head lang="en">
<meta charset="utf-8">
<title>Custom Plunker</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js"></script>
<link rel="stylesheet" href="style.css">
<script>
document.write('<base href="' + document.location + '" />');
</script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<select ng-model="foo" ng-options="x.name for x in items"
options-class="{ 'is-eligible' : eligible, 'not-eligible': !eligible }"></select>
</body>
</html>
Thanks in advance
One way hard-coding this logic into the directive using option.text():
angular.element(option).addClass(option.text()); //
However, this would ignore the expression. http://plnkr.co/edit/46HndjYtg6HUbblnceNr?p=preview
Fix
app.directive('optionsClass', function ($parse) {
return {
require: 'select',
link: function(scope, elem, attrs, ngSelect) {
var optionsSourceStr = attrs.ngOptions.split(' ').pop(),
getOptionsClass = $parse(attrs.optionsClass);
scope.$watch(optionsSourceStr, function(items) {
var options = elem.find("option");
angular.forEach(items, function(item, index) {
var classes = getOptionsClass(item);
var option = options.eq(index);
angular.forEach(classes, function(add, className) {
if(add) {
angular.element(option).addClass(className);
}
});
});
});
}
};
});
https://jsfiddle.net/AndersBillLinden/ne0z9vwm/32/

Angularjs share to social networks

I'm using angularjs and wanted to add share buttons to my pages for different social networking sites. So far I've found Angular-socialshare and Socialitejs but don't know which one to choose or if other really good ones exist, that I don't even know of.
Here is an alternative to angular-socialshare (has share counter) that is currently the most popular/active on GitHub:
http://github.com/esvit/angular-social
Here is a simplified share button (no counter). This directive uses Font Awesome as a dependency.
http://github.com/tinacious/angular-easy-social-share
A note about metadata scraping and single page applications
Keep in mind that social network crawlers, which scrape HTML metadata for rich snippets (like OpenGraph links to images and descriptions), do not evaluate JavaScript. This can render certain meta tags in the header as {{bracketed}} expressions in a share box rather than dynamically loaded content... Assuming you have metadata that is loaded dynamically with AngularJS.
Check out this article about sharing rich snippets with Angular should you have that requirement: http://www.michaelbromley.co.uk/blog/171/enable-rich-social-sharing-in-your-angularjs-app
there is very easy solution that works fine with me,
you can rename you index.html to index.php
and include a php page that have a code to parse URL and based on the URL Parameters you can add your Title and description and image for twitter and facebook
here is the code of php page included in my index
<?
function curPageURL() {
$pageURL = 'http';
if ($_SERVER["HTTPS"] == "on") {$pageURL .= "s";}
$pageURL .= "://";
if ($_SERVER["SERVER_PORT"] != "80") {
$pageURL .= $_SERVER["SERVER_NAME"].":".$_SERVER["SERVER_PORT"].$_SERVER["REQUEST_URI"];
} else {
$pageURL .= $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"];
}
return $pageURL;
}
$parse = parse_url(curPageURL());
$urlArray = explode("/",$parse['path']);
$urlPage = $urlArray[2];
if($urlPage == "profile"){
$image = "profile-img.jpg";
$title = "Title of the page ";
$desc = "Description of Profile the page ";
$websiteUrl = "Http://example.com";
}elseif($urlPage == "news"){
$title = "Title of the News page ";
$desc = "Description of the page ";
$websiteUrl = "Http://example.com";
}
?>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title><? echo $title; ?></title>
<meta property="twitter:card" content="summary" />
<meta property="twitter:site" content="<? echo $websiteUrl; ?>" />
<meta property="twitter:title" content="<? echo $title; ?>" />
<meta property="twitter:description" content="<? echo $description; ?>" />
<meta property="twitter:image" content="<? echo $image; ?>" />
<meta property="twitter:url" content="<? echo curPageURL(); ?>" />
<meta property="og:title" content="<? echo $title; ?>" />
<meta property="og:description" content="<? echo $description; ?>" />
<meta property="og:image" content="<? echo $image; ?>" />
<meta property="og:type" content="article" />
<meta property="og:site_name" content="<? echo $website_name; ?>" />
<meta property="og:url" content="<? echo curPageURL(); ?>" />
Considering sinisterOrange's answer I'd like to add that there's some vital pieces of information about Michael Bromley's elegant solution.
In order to have multiple conditions for redirecting your urls into a script that will render the meta-tags correctly you must apply the condition multiple times for each rule.
And finally you'd have to make a negative condition for your app access.
<ifModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (facebookexternalhit/[0-9]|Twitterbot|Pinterest|Google.*snippet)
RewriteRule ^post/([A-Za-z0-9-]+)/([0-9-]+)$ http://www.example.com/api/renderMetaTags/post/$2 [P,L]
RewriteCond %{HTTP_USER_AGENT} (facebookexternalhit/[0-9]|Twitterbot|Pinterest|Google.*snippet)
RewriteRule ^location/([A-Za-z0-9-]+)/([0-9-]+)$ www.example.com/api/renderMetaTags/location/$2 [P,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !index
#this condition for crawlers not to interfere with normal access
RewriteCond %{HTTP_USER_AGENT} !(facebookexternalhit/[0-9]|Twitterbot|Pinterest|Google.*snippet)
#this rule for html5 mode and friendly urls without the #
RewriteRule ^(.*)$ /#/$1 [L]
</ifModule>
That'd be it. I know it's a little bit off-topic but I lost valuable time arriving at this solution.
Consider this solution as it worked for me using mvc 5 with angular js
In Index page copy this code
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.12/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<script src='test.js'></script>
<script src="http://platform.twitter.com/widgets.js"></script>
</head>
<body ng-app='testing'>
<div ng-controller='temp'>
<div facebook class="facebookShare" data-title='{{title}}' data-picture-url='http://i.imgur.com/WACv9Cl.jpg' data-url='{{url}}' data-shares='shares' data-callback='callback'>{{ shares }} </div>
<br />
<a twitter data-count='horizontal' data-url='{{url}}' data-size="medium" data-text='{{text}}'></a>
<br /><br />
<div class="linkedinShare" linkedin data-url='{{url}}' data-title='{{title}}' data-summary="{{text}}" data-shares='linkedinshares'>{{linkedinshares}}</div>
<br /><br />
<div gplus data-size="tall" data-annotation="bubble" data-href='{{url}}' data-action='share'></div>
</div>
</body>
</html>
In script.js write this below mentioned code
angular.module('testing', ['djds4rce.angular-socialshare'])
.run(function ($FB) {
$FB.init('Facebook App Id');
});
angular.module('testing').controller('temp', function ($scope, $timeout) {
$timeout(function () {
$scope.url = 'http://google.com';
$scope.text = 'testing share';
$scope.title = 'title1'
}, 1000)
$timeout(function () {
$scope.url = 'https://www.youtube.com/watch?v=wxkdilIURrU';
$scope.text = 'testing second share';
$scope.title = 'title2';
}, 1000)
$scope.callback = function (response) {
console.log(response);
}
});
This code is for the directive
angular.module('djds4rce.angular-socialshare', [])
.factory('$FB', ['$window', function ($window) {
return {
init: function (fbId) {
if (fbId) {
this.fbId = fbId;
$window.fbAsyncInit = function () {
FB.init({
appId: fbId,
channelUrl: 'app/channel.html',
status: true,
xfbml: true
});
};
(function (d) {
var js,
id = 'facebook-jssdk',
ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {
return;
}
js = d.createElement('script');
js.id = id;
js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
} else {
throw ("FB App Id Cannot be blank");
}
}
};
}]).directive('facebook', ['$http', function ($http) {
return {
scope: {
callback: '=',
shares: '='
},
transclude: true,
template: '<div class="facebookButton">' +
'<div class="pluginButton">' +
'<div class="pluginButtonContainer">' +
'<div class="pluginButtonImage">' +
'<button type="button">' +
'<i class="pluginButtonIcon img sp_plugin-button-2x sx_plugin-button-2x_favblue"></i>' +
'</button>' +
'</div>' +
'<span class="pluginButtonLabel">Share</span>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="facebookCount">' +
'<div class="pluginCountButton pluginCountNum">' +
'<span ng-transclude></span>' +
'</div>' +
'<div class="pluginCountButtonNub"><s></s><i></i></div>' +
'</div>',
link: function (scope, element, attr) {
attr.$observe('url', function () {
if (attr.shares && attr.url) {
$http.get('https://api.facebook.com/method/links.getStats?urls=' + attr.url + '&format=json').success(function (res) {
var count = res[0] ? res[0].total_count.toString() : 0;
var decimal = '';
if (count.length > 6) {
if (count.slice(-6, -5) != "0") {
decimal = '.' + count.slice(-6, -5);
}
count = count.slice(0, -6);
count = count + decimal + 'M';
} else if (count.length > 3) {
if (count.slice(-3, -2) != "0") {
decimal = '.' + count.slice(-3, -2);
}
count = count.slice(0, -3);
count = count + decimal + 'k';
}
scope.shares = count;
}).error(function () {
scope.shares = 0;
});
}
element.unbind();
element.bind('click', function (e) {
FB.ui({
method: 'share',
href: attr.url
}, function (response) {
if (scope.callback !== undefined && typeof scope.callback === "function") {
scope.callback(response);
}
});
e.preventDefault();
});
});
}
};
}]).directive('facebookFeedShare', ['$http', function ($http) {
return {
scope: {
callback: '=',
shares: '='
},
transclude: true,
template: '<div class="facebookButton">' +
'<div class="pluginButton">' +
'<div class="pluginButtonContainer">' +
'<div class="pluginButtonImage">' +
'<button type="button">' +
'<i class="pluginButtonIcon img sp_plugin-button-2x sx_plugin-button-2x_favblue"></i>' +
'</button>' +
'</div>' +
'<span class="pluginButtonLabel">Share</span>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="facebookCount">' +
'<div class="pluginCountButton pluginCountNum">' +
'<span ng-transclude></span>' +
'</div>' +
'<div class="pluginCountButtonNub"><s></s><i></i></div>' +
'</div>',
link: function (scope, element, attr) {
attr.$observe('url', function () {
if (attr.shares && attr.url) {
$http.get('https://api.facebook.com/method/links.getStats?urls=' + attr.url + '&format=json').success(function (res) {
var count = res[0] ? res[0].total_count.toString() : 0;
var decimal = '';
if (count.length > 6) {
if (count.slice(-6, -5) != "0") {
decimal = '.' + count.slice(-6, -5);
}
count = count.slice(0, -6);
count = count + decimal + 'M';
} else if (count.length > 3) {
if (count.slice(-3, -2) != "0") {
decimal = '.' + count.slice(-3, -2);
}
count = count.slice(0, -3);
count = count + decimal + 'k';
}
scope.shares = count;
}).error(function () {
scope.shares = 0;
});
}
element.unbind();
element.bind('click', function (e) {
FB.ui({
method: 'feed',
link: attr.url,
picture: attr.picture,
name: attr.name,
caption: attr.caption,
description: attr.description
}, function (response) {
if (scope.callback !== undefined && typeof scope.callback === "function") {
scope.callback(response);
}
});
e.preventDefault();
});
});
}
};
}]).directive('twitter', ['$timeout', function ($timeout) {
return {
link: function (scope, element, attr) {
var renderTwitterButton = debounce(function () {
if (attr.url) {
$timeout(function () {
element[0].innerHTML = '';
twttr.widgets.createShareButton(
attr.url,
element[0],
function () { }, {
count: attr.count,
text: attr.text,
via: attr.via,
size: attr.size
}
);
});
}
}, 75);
attr.$observe('url', renderTwitterButton);
attr.$observe('text', renderTwitterButton);
}
};
}]).directive('linkedin', ['$timeout', '$http', '$window', function ($timeout, $http, $window) {
return {
scope: {
shares: '='
},
transclude: true,
template: '<div class="linkedinButton">' +
'<div class="pluginButton">' +
'<div class="pluginButtonContainer">' +
'<div class="pluginButtonImage">in' +
'</div>' +
'<span class="pluginButtonLabel"><span>Share</span></span>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="linkedinCount">' +
'<div class="pluginCountButton">' +
'<div class="pluginCountButtonRight">' +
'<div class="pluginCountButtonLeft">' +
'<span ng-transclude></span>' +
'</div>' +
'</div>' +
'</div>' +
'</div>',
link: function (scope, element, attr) {
var renderLinkedinButton = debounce(function () {
if (attr.shares && attr.url) {
$http.jsonp('https://www.linkedin.com/countserv/count/share?url=' + attr.url + '&callback=JSON_CALLBACK&format=jsonp').success(function (res) {
scope.shares = res.count.toLocaleString();
}).error(function () {
scope.shares = 0;
});
}
$timeout(function () {
element.unbind();
element.bind('click', function () {
var url = encodeURIComponent(attr.url).replace(/'/g, "%27").replace(/"/g, "%22")
$window.open("//www.linkedin.com/shareArticle?mini=true&url=" + url + "&title=" + attr.title + "&summary=" + attr.summary);
});
});
}, 100);
attr.$observe('url', renderLinkedinButton);
attr.$observe('title', renderLinkedinButton);
attr.$observe('summary', renderLinkedinButton);
}
};
}]).directive('gplus', [function () {
return {
link: function (scope, element, attr) {
var googleShare = debounce(function () {
if (typeof gapi == "undefined") {
(function () {
var po = document.createElement('script');
po.type = 'text/javascript';
po.async = true;
po.src = 'https://apis.google.com/js/platform.js';
po.onload = renderGoogleButton;
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(po, s);
})();
} else {
renderGoogleButton();
}
}, 100);
//voodo magic
var renderGoogleButton = (function (ele, attr) {
return function () {
var googleButton = document.createElement('div');
var id = attr.id || randomString(5);
attr.id = id;
googleButton.setAttribute('id', id);
element.innerHTML = '';
element.append(googleButton);
if (attr.class && attr.class.indexOf('g-plusone') != -1) {
window.gapi.plusone.render(id, attr);
} else {
window.gapi.plus.render(id, attr);
}
}
}(element, attr));
attr.$observe('href', googleShare);
}
};
}]);
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this,
args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
function randomString(len, an) {
an = an && an.toLowerCase();
var str = "",
i = 0,
min = an == "a" ? 10 : 0,
max = an == "n" ? 10 : 62;
for (; i++ < len;) {
var r = Math.random() * (max - min) + min << 0;
str += String.fromCharCode(r += r > 9 ? r < 36 ? 55 : 61 : 48);
}
return str;
}

Resources