I have created a directive and service that combine to create a pop-up modal in our application. I can call the pop-up service which sets up a scope variable and causes a modal directive to become visible. The problem is I cannot figure out where the best place is to put the logic that I need for the individual instances of a pop-up modal. Every modal will have a different view and therefore different logic to control the modal.
Is there a way to tell the directive to use a specific controller dynamically so I can control of code is ran while the different modals are visible to the user?
Here is my overlay Service which sets up the modal
.factory("Overlay", ["$rootScope", "$window", '$q', function ($rootScope, $window, $q) {
var overlay = {};
var el;
var gears;
var scope = {};
var isActive = false;
function _displayBackground() {
$rootScope.overlayVisible = true;
}
function _hideBackground() {
$rootScope.overlayVisible = false;
}
overlay.getScope = function(){
return scope;
}
overlay.Gears = {
show: function(msg, feedback){
_displayBackground();
var def1 = $q.deferred();
var def2 = $q.deferred();
$timeout(function(){
def1.resolve({
result: {
Complete: true,
Success: true
}
});
}, Math.random() * 5000);
$timeout(function(){
def2.resolve({
result: {
Complete: true,
Success: false
}
})
}, Math.random() * 5000);
feedback = [
{
Label: 'Item 1',
Complete: false,
Promise: def1.promise
}, {
Label: 'Item 2',
Complete: false,
Promise: def2.promise
}
];
scope.feedback = feedback;
scope.message = msg;
scope.url = '/partials/Common/gears.html';
$rootScope.modalVisible = true;
},
hide: function(){
_hideBackground();
$rootScope.modalVisible = false;
}
};
overlay.Alert = {
show: function (title, content, hint, posCallback, btnText, negCallback) {
_displayBackground();
// el = angular.element(
}
}
return overlay;
}])
And here is my modal directive
app.directive('skModal', function($window, Overlay){
return {
restrict:'EA',
template: '<div ng-include="modal.url" onload="setPosition()"></div>',
link: function(scope, elem, attrs){
var top, left;
scope.modal = {};
scope.setPosition = function(){
top = ($window.innerHeight - elem.outerHeight()) / 2;
left = ($window.innerWidth - elem.outerWidth()) / 2;
elem.css({ 'top': top, 'left': left });
}
scope.$watch(attrs.skModal, function(newVal){
if(newVal) {
angular.extend(scope.modal, Overlay.getScope());
}else{
elem.css({ 'top': -9999, 'left': -9999 });
scope.modal.url = '';
}
});
}
};
});
I basically link the scope I want to use from the Overlay service using getScope but I have no way to process the Promises I wanted to use in this particular modal in order to show feedback of 1-many API calls.
Related
I need in a angularjs single page application a google-places autocomplete input, that shall run as a service and shall be initialized once at runtime. In case of navigation, the with goolge-places initialized element and the appropriate scope are destroyed.
I will re-use the places input field after navigate to the page containing the places autocomplete input field. With the method element.replaceWith() it works well.
After replacing the element, I can not reset the input by the "reset" button. How can I bind the new generated scope to the "reset" button and the old scope variables. Because the old scope and elements are destroyed by the navigation event?
.factory('myService', function() {
var gPlace;
var s, e;
var options = {
types: [],
componentRestrictions: {country: 'in'}
};
function init() {
}
function set(element, scope) {
console.log('set');
if (!gPlace) {
e = element;
gPlace = new google.maps.places.Autocomplete(element[0], options);
google.maps.event.addListener(gPlace, 'place_changed', function() {
scope.$apply(function() {
scope.place.chosenPlace = element.val();
});
});
} else {
element.replaceWith(e);
}
}
init();
return {
'init':init,
'set':set
};
});
the navigation (element and scope destroying) will be simulated in this plunk by the ng-if directive that will be triggered by the "remove" button.
see here plunk
If you want you can create a service that holds the last selected place and shares it among controllers and directives:
.service('myPlaceService', function(){
var _place;
this.setPlace = function(place){
_place = place;
}
this.getPlace = function(){
return _place;
}
return this;
});
Then create a directive that uses this service:
.directive('googlePlaces', function(myPlaceService) {
return {
restrict: 'E',
scope: {
types: '=',
options: '=',
place: '=',
reset: '='
},
template: '<div>' +
'<input id="gPlaces" type="text"> <button ng-click="resetPlace()">Reset</button>' +
'</div>',
link: function(scope, el, attr){
var input = document.querySelector('#gPlaces');
var jqEl = angular.element(input);
var gPlace = new google.maps.places.Autocomplete(input, scope.options || {});
var listener = google.maps.event.addListener(gPlace, 'place_changed', function() {
var place = autocomplete.getPlace();
scope.$apply(function() {
scope.place.chosenPlace = jqEl.val();
//Whenever place changes, update the service.
//For a more robust solution you could emit an event using scope.$broadcast
//then catch the event where updates are needed.
//Alternatively you can $scope.$watch(myPlaceService.getPlace, function() {...})
myPlaceService.setPlace(jqEl.val());
});
scope.reset = function(){
scope.place.chosenPlace = null;
jqEl.val("");
}
scope.$on('$destroy', function(){
if(listener)
google.maps.event.removeListener(listener);
});
});
}
}
});
Now you can use it like so:
<google-places place="vm.place" options="vm.gPlacesOpts"/>
Where:
vm.gPlacesOpts = {types: [], componentRestrictions: {country: 'in'}}
I am trying to detect idle user (such as no mouse or touch events) and display a pop up warning. I have one service of detecting timeout and another to display the pop up. Time out service is being called from app.run. I have almost completed it, have the following two issues,
pop up is being displayed in same page, not as a modal.
pop up is being displayed multiple time.
Here is my code.
/*** App ****/
var myapp = angular.module("myapp", ['ui.bootstrap']);
myapp.run(function ($rootScope, timeoutService) {
timeoutService.setup();
});
/**** time out sevice ****/
myapp.factory('timeoutService', function ($window, modalService) {
var timeoutID;
var startTimer = function () {
// wait 2 seconds before calling goInactive
timeoutID = $window.setTimeout(goInactive, 2000);
}
var resetTimer =function(e) {
$window.clearTimeout(timeoutID);
goActive();
}
var goInactive = function () {
modalService.showModal({}, modalOptions).then(function (result) {
alert("Hello");
});
}
var goActive=function() {
startTimer();
}
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Delete Customer',
headerText: 'Delete ?',
bodyText: 'Are you sure you want to delete this customer?'
};
var timeOutService = {};
timeOutService.setup = function () {
$window.addEventListener("mousemove", resetTimer, false);
$window.addEventListener("mousedown", resetTimer, false);
$window.addEventListener("keypress", resetTimer, false);
$window.addEventListener("DOMMouseScroll", resetTimer, false);
$window.addEventListener("mousewheel", resetTimer, false);
$window.addEventListener("touchmove", resetTimer, false);
$window.addEventListener("MSPointerMove", resetTimer, false);
startTimer();
};
return timeOutService;
});
/****** Modal Service ******/
myapp.service('modalService', ['$modal', function ($modal) {
var modalDefaults = {
backdrop: true,
keyboard: true,
modalFade: true,
templateUrl:'../Modal.html'
};
var modalOptions = {
closeButtonText: 'Close',
actionButtonText: 'OK,',
headerText: 'Proceed?',
bodyText: 'Perform this action?'
};
this.showModal = function (customModalDefaults, customModalOptions) {
if (!customModalDefaults) customModalDefaults = {};
customModalDefaults.backdrop = 'static';
return this.show(customModalDefaults, customModalOptions);
};
this.show = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
if (!tempModalDefaults.controller) {
tempModalDefaults.controller = function ($scope, $modalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function (result) {
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
$modalInstance.dismiss('cancel');
};
}
}
return $modal.open(tempModalDefaults).result;
};
}]);
You must create a global controller that functions defined beginning , this driver can be used on the homepage or send calls from any page of the document .
Reference should be made to assign the controller html < div ng - controller = " SesionCtrl "> < / div >
app.controller('SesionCtrl',function($scope,Idle,Keepalive,$log){
$Scope.$On('IdleStart',function(){
//Open Modal code
});
$ Scope . $ On ( ' IdleTimeout ', function () {
// The end of the timeout Logout
window.location = " logout.php " ;
});
} ) ;
<html lang="en"ng-app="myApp">
<body ng-app="myApp">
<div ng-controller="SesionCtrl"></div><!--Instance Controller-->
</body>
</html>
Im' working on the Malhar Angular Dashboard, based on this github project https://github.com/DataTorrent/malhar-angular-dashboard.
As per the documentation in the link post just above, under the 'dataModelType' heading 1/2 way down:
`The best way to provide data to a widget is to specify a dataModelType in the Widget Definition Object (above). This function is used as a constructor whenever a new widget is instantiated on the page.`
And when setting up the Widget Definition Objects, there are various options to choose from :
templateUrl - URL of template to use for widget content
template - String template (ignored if templateUrl is present)
directive - HTML-injectable directive name (eg. "ng-show")
So when I add my own widget definition column chart, I attempt to use the 'template' option; however it does NOT inject the {{value}} scope variable I'm setting.
Using the original datamodel sample widget def, it works fine using the 'directive' option. If I mimic this method on my column chart definition then it works ! But it doesn't work using the template option.
Here's the 'widgetDefinitions' factory code :
(function () {
'use strict';
angular.module('rage')
.factory('widgetDefinitions', ['RandomDataModel','GadgetDataModel', widgetDefinitions])
function widgetDefinitions(RandomDataModel, GadgetDataModel) {
return [
{
name: 'datamodel',
directive: 'wt-scope-watch',
dataAttrName: 'value',
dataModelType: RandomDataModel // GOTTA FIGURE THIS OUT !! -BM:
},
{
name: 'column chart',
title: 'Column Chart',
template: '<div>Chart Gadget Here {{value}}</div>',
dataAttrName: 'value',
size: {width: '40%',height: '200px'},
dataModelType: ColumnChartDataModel
},
];
}
})();
and here are the factories:
'use strict';
angular.module('rage')
.factory('TreeGridDataModel', function (WidgetDataModel, gadgetInitService) {
function TreeGridDataModel() {
}
TreeGridDataModel.prototype = Object.create(WidgetDataModel.prototype);
TreeGridDataModel.prototype.constructor = WidgetDataModel;
angular.extend(TreeGridDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
//this.treeGridOptions = {};
this.updateScope('THIS IS A TreeGridDataModel...'); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return TreeGridDataModel;
});
'use strict';
angular.module('rage')
.factory('ColumnChartDataModel', function (WidgetDataModel) {
function ColumnChartDataModel() {
}
ColumnChartDataModel.prototype = Object.create(WidgetDataModel.prototype);
ColumnChartDataModel.prototype.constructor = WidgetDataModel;
angular.extend(ColumnChartDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
var value = 'THIS IS A ColChartDataModel...';
//$scope.value = value;
this.updateScope(value); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return ColumnChartDataModel;
});
and finally the directives:
'use strict';
angular.module('rage')
.directive('wtTime', function ($interval) {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<div>Time<div class="alert alert-success">{{time}}</div></div>',
link: function (scope) {
function update() {
scope.time = new Date().toLocaleTimeString();
}
update();
var promise = $interval(update, 500);
scope.$on('$destroy', function () {
$interval.cancel(promise);
});
}
};
})
.directive('wtScopeWatch', function () {
return {
restrict: 'A',
replace: true,
template: '<div>Value<div class="alert alert-info">{{value}}</div></div>',
scope: {
value: '=value'
}
};
})
.directive('wtFluid', function () {
return {
restrict: 'A',
replace: true,
templateUrl: 'app/views/template2/fluid.html',
scope: true,
controller: function ($scope) {
$scope.$on('widgetResized', function (event, size) {
$scope.width = size.width || $scope.width;
$scope.height = size.height || $scope.height;
});
}
};
});
I'd like to know why ONLY the directive option will update the wigdet's data and not the template option.
thank you,
Bob
I believe I see the problem. The dataAttrName setting and updateScope method are actually doing something other than what you're expecting.
Look at the makeTemplateString function here. This is what ultimately builds your widget's template. You should notice that if you supply a template, the dataAttrName does not even get used.
Next, take a look at what updateScope does, and keep in mind that you can override this function in your own data model to do what you really want, a la:
angular.extend(TreeGridDataModel.prototype, {
init: function() {...},
destroy: function() {...},
updateScope: function(data) {
// I don't see this "main" object defined anywhere, I'm just going
// off your treegrid.html template, which has jqx-settings="main.treeGridOptions"
this.widgetScope.main = { treeGridOptions: data };
// Doing it without main, you could just do:
// this.widgetScope.treeGridOptions = data;
// And then update your treegrid.html file to be:
// <div id="treeGrid" jqx-tree-grid jqx-settings="treeGridOptions"></div>
}
});
I am trying to develop a friendly dialog plugin with angularjs & bootstrap.
I found dialog based in directive was not that easy to use, add a html tag first and define controller and variable, that is too complicated.
So I intend to add a method to angular, create new element dynamic and set variable to root scope, but there seems to be some problems about compile.
Here is mainly code:
var defaultOption = {
title: 'Title',
content: 'Content',
backdrop: false,
buttons: [],
$dom: null
};
var prefix = '__dialog',
index = 0;
var newId = function (scope) {
var id = prefix + index;
if (!scope[id]) return id;
index++;
return arguments.callee(scope);
};
var app = angular.module("app", []);
app.directive('bootstrapModal', ['$compile', function ($compile) {
return {
restrict: 'A',
replace: true,
scope: {
bootstrapModal: '='
},
link: function (scope, $ele) {
var $dom = $(defaultTemplate),
body = $ele.children();
if (body.length) $dom.find('.modal-body').html(body);
$ele.replaceWith($dom);
$compile($dom)(scope);
scope.bootstrapModal.$dom = $dom;
}
};
}]);
angular.ui = {};
angular.ui.dialog = function (args) {
var $body = angular.element(document.body),
$rootScope = $body.scope().$root,
option = $.extend({}, defaultOption, args);
option.id = option.id || newId($rootScope);
option.show = function () {
this.$dom.modal('show');
};
option.hide = function () {
this.$dom.modal('hide');
};
$body.injector().invoke(function ($compile) {
$rootScope[option.id] = option;
var dialog = '<div bootstrap-modal="{0}"></<div>'.format(option.id);
var html = $compile(dialog)($rootScope);
$('body').append(html);
});
return option;
};
$(function () {
angular.ui.dialog({ //test
title: 'Alert',
content: 'test content for alert',
buttons: [{
name: 'Close',
focus: true
}]
}).show();
});
The entire code is too long, so I put it in JSFIDDLE
Thanks!
Put a $rootScope.$apply(function() { ... }) around your code where you compile your dialog in injector().invoke(...).
Updated fiddle
How can i be notified when a directive is resized?
i have tried
element[0].onresize = function() {
console.log(element[0].offsetWidth + " " + element[0].offsetHeight);
}
but its not calling the function
(function() {
'use strict';
// Define the directive on the module.
// Inject the dependencies.
// Point to the directive definition function.
angular.module('app').directive('nvLayout', ['$window', '$compile', layoutDirective]);
function layoutDirective($window, $compile) {
// Usage:
//
// Creates:
//
var directive = {
link: link,
restrict: 'EA',
scope: {
layoutEntries: "=",
selected: "&onSelected"
},
template: "<div></div>",
controller: controller
};
return directive;
function link(scope, element, attrs) {
var elementCol = [];
var onSelectedHandler = scope.selected();
element.on("resize", function () {
console.log("resized.");
});
$(window).on("resize",scope.sizeNotifier);
scope.$on("$destroy", function () {
$(window).off("resize", $scope.sizeNotifier);
});
scope.sizeNotifier = function() {
alert("windows is being resized...");
};
scope.onselected = function(id) {
onSelectedHandler(id);
};
scope.$watch(function () {
return scope.layoutEntries.length;
},
function (value) {
//layout was changed
activateLayout(scope.layoutEntries);
});
function activateLayout(layoutEntries) {
for (var i = 0; i < layoutEntries.length; i++) {
if (elementCol[layoutEntries[i].id]) {
continue;
}
var div = "<nv-single-layout-entry id=slot" + layoutEntries[i].id + " on-selected='onselected' style=\"position:absolute;";
div = div + "top:" + layoutEntries[i].position.top + "%;";
div = div + "left:" + layoutEntries[i].position.left + "%;";
div = div + "height:" + layoutEntries[i].size.height + "%;";
div = div + "width:" + layoutEntries[i].size.width + "%;";
div = div + "\"></nv-single-layout-entry>";
var el = $compile(div)(scope);
element.append(el);
elementCol[layoutEntries[i].id] = 1;
}
};
}
function controller($scope, $element) {
}
}
})();
Use scope.$watch with a custom watch function:
scope.$watch(
function () {
return [element[0].offsetWidth, element[0].offsetHeight].join('x');
},
function (value) {
console.log('directive got resized:', value.split('x'));
}
)
You would typically want to watch the element's offsetWidth and offsetHeight properties. With more recent versions of AngularJS, you can use $scope.$watchGroup in your link function:
app.directive('myDirective', [function() {
function link($scope, element) {
var container = element[0];
$scope.$watchGroup([
function() { return container.offsetWidth; },
function() { return container.offsetHeight; }
], function(values) {
// Handle resize event ...
});
}
// Return directive definition ...
}]);
However, you may find that updates are quite slow when watching the element properties directly in this manner.
To make your directive more responsive, you could moderate the refresh rate by using $interval. Here's an example of a reusable service for watching element sizes at a configurable millisecond rate:
app.factory('sizeWatcher', ['$interval', function($interval) {
return function (element, rate) {
var self = this;
(self.update = function() { self.dimensions = [element.offsetWidth, element.offsetHeight]; })();
self.monitor = $interval(self.update, rate);
self.group = [function() { return self.dimensions[0]; }, function() { return self.dimensions[1]; }];
self.cancel = function() { $interval.cancel(self.monitor); };
};
}]);
A directive using such a service would look something like this:
app.directive('myDirective', ['sizeWatcher', function(sizeWatcher) {
function link($scope, element) {
var container = element[0],
watcher = new sizeWatcher(container, 200);
$scope.$watchGroup(watcher.group, function(values) {
// Handle resize event ...
});
$scope.$on('$destroy', watcher.cancel);
}
// Return directive definition ...
}]);
Note the call to watcher.cancel() in the $scope.$destroy event handler; this ensures that the $interval instance is destroyed when no longer required.
A JSFiddle example can be found here.
Here a sample code of what you need to do:
APP.directive('nvLayout', function ($window) {
return {
template: "<div></div>",
restrict: 'EA',
link: function postLink(scope, element, attrs) {
scope.onResizeFunction = function() {
scope.windowHeight = $window.innerHeight;
scope.windowWidth = $window.innerWidth;
console.log(scope.windowHeight+"-"+scope.windowWidth)
};
// Call to the function when the page is first loaded
scope.onResizeFunction();
angular.element($window).bind('resize', function() {
scope.onResizeFunction();
scope.$apply();
});
}
};
});
The only way you would be able to detect size/position changes on an element using $watch is if you constantly updated your scope using something like $interval or $timeout. While possible, it can become an expensive operation, and really slow your app down.
One way you could detect a change on an element is by calling
requestAnimationFrame.
var previousPosition = element[0].getBoundingClientRect();
onFrame();
function onFrame() {
var currentPosition = element[0].getBoundingClientRect();
if (!angular.equals(previousPosition, currentPosition)) {
resiszeNotifier();
}
previousPosition = currentPosition;
requestAnimationFrame(onFrame);
}
function resiszeNotifier() {
// Notify...
}
Here's a Plunk demonstrating this. As long as you're moving the box around, it will stay red.
http://plnkr.co/edit/qiMJaeipE9DgFsYd0sfr?p=preview
A slight variation on Eliel's answer worked for me. In the directive.js:
$scope.onResizeFunction = function() {
};
// Call to the function when the page is first loaded
$scope.onResizeFunction();
angular.element($(window)).bind('resize', function() {
$scope.onResizeFunction();
$scope.$apply();
});
I call
$(window).resize();
from within my app.js. The directive's d3 chart now resizes to fill the container.
Here is my take on this directive (using Webpack as bundler):
module.exports = (ngModule) ->
ngModule.directive 'onResize', ['Callback', (Callback) ->
restrict: 'A'
scope:
onResize: '#'
onResizeDebounce: '#'
link: (scope, element) ->
container = element[0]
eventName = scope.onResize || 'onResize'
delay = scope.onResizeDebounce || 1000
scope.$watchGroup [
-> container.offsetWidth ,
-> container.offsetHeight
], _.debounce (values) ->
Callback.event(eventName, values)
, delay
]