Triggering an Angular $broadcast from a factory - angularjs

I'm running a Dashboard system, where widgets on a page will render some grids and charts. I'm now setting up something we call "Widget Linking", where clicking on a grid will go off to a chart widget and display the data related to the grid row I just clicked on.
Example: "linking a treelist (parent widget) to a bar chart (child widget) "
In this example, I have Kendo treelist select event, which will pass the currently-selected row off to my widgetLinkingFactory factor. This factory should iterate through all available widgets on my page to see if any charts (child widgets) are linked to this treelist (parent widget).
I'd like to setup an Angular watcher, then trigger a $broadcast in my treelist select event. However, I keep running into this issue where I cannot inject $scope into a service or factory.
Factory code with treelist select event (see dataModelOptions.change):
'use strict';
angular.module('app')
.factory('TreeListDataModel', function (WidgetDataModel, widgetLinkingFactory) {
function TreeListDataModel() {
}
TreeListDataModel.prototype = Object.create(WidgetDataModel.prototype);
TreeListDataModel.prototype.constructor = WidgetDataModel;
angular.extend(TreeListDataModel.prototype, {
init: function () {
// Kendo datasource object is initialized here...
// KENDO TREELIST SELECT EVENT !!
this.dataModelOptions.change = function (e) {
var row = this.dataItem(this.select());
var parentObj = _.find(this.dataItems(), { id: row.parentId });
var dataModelOptions = {
row: row,
parentObj: parentObj,
dimensions: this.options.dimensions,
fieldTypes: this.options.fieldTypes
};
// CANNOT DO THIS HERE - $SCOPE NOT AVAILABLE !!!
$scope.$broadcast('refreshLinkedWidgets', dataModelOptions);
widgetLinkingFactory.linkCharts(row, parentObj, this.options.dimensions, this.options.fieldTypes);
},
this.updateScope(this.dataModelOptions);
},
updateScope: function (data) {
this.dataModelOptions = data;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return TreeListDataModel;
});
And in my main directive code, I have no problem setting up the watcher:
scope.$on('refreshLinkedWidgets', function (event, dataModelOptions) {
// call my widgetLinkingFactory update code
});
I'm experimenting with some ideas, so advice is appreciated.
regards,
Bob

Factory/service don't have scope. You can't do it on scope
But you can do it via injecting rooScope in your service.
Like this
Declare a method inside TreeListDataModel named refreshLinkedWidgets
then
$rootScope.$on("refreshLinkedWidgets", TreeListDataModel .refreshLinkedWidgets);
Sample code
angular.module('app')
.factory('TreeListDataModel', function (WidgetDataModel, widgetLinkingFactory,$rootScope) {
var TreeListDataModel={
refreshLinkedWidgets:refreshLinkedWidgets
};
function refreshLinkedWidgets(e,a){
// some code
}
$rootScope.$on("refreshLinkedWidgets", TreeListDataModel .refreshLinkedWidgets);
return TreeListDataModel;
}

Inject $rootScope and $broadcast from it.

Related

Widget toggle functionality with $compile

I need to implement toggle functionality for the widget. When the user clicks on the minimization button then widget should shrink and expand when click on maximize button respectively.
I'm trying to achieve this functionality with below piece of code.
Functionality working as expected but it is registering the event multiple times(I'm emitting the event and catching in the filterTemplate directive).
How can we stop registering the event multiple times ?
Or
Is there anyway to like compiling once and on toggle button bind the template/directive to DOM and to make it work rest of the functionality .
So could you please help me to fix this.
function bindFilterTemplate(minimize) {
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) { // filter template is custom
// directive like this
// "<widget></widget>"
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope); // Compiling with
// current scope on every time when user click on
// the minimization button.
});
}
} else {
$timeout(function () {
element.find('.cls-filter-body').empty();
});
}
}
bindFilterTemplate();
// Directive
app.directive('widget', function () {
return {
restrict: 'E',
controller: 'widgetController',
link: function ($scope, elem) {
// Some code
}
};
});
// Controller
app.controller('widgetController', function ($scope) {
// This event emitting from parent directive
// On every compile, the event is registering with scope.
// So it is triggering multiple times.
$scope.$on('evt.filer', function ($evt) {
// Server call
});
});
I fixed this issue by creating new scope with $scope.$new().
When user minimizes the widget destroying the scope.
Please let me know if you have any other solution to fix this.
function bindFilterTemplate(minimize) {
// Creating the new scope.
$scope.newChildScope = $scope.$new();
if ($scope.item && !minimize) {
if ($scope.item.filterTemplate) {
$timeout(function () {
var filterElement = angular.element($scope.item.filterTemplate);
var filterBody = element.find('.cls-filter-body');
filterElement.appendTo(filterBody);
$compile(filterElement)($scope.newChildScope);
});
}
} else {
$timeout(function () {
if ($scope.newChildScope) {
// Destroying the new scope
$scope.newChildScope.$destroy();
}
element.find('.cls-filter-body').empty();
});
}
}

ng-table going to first page when data changes

I'm working on a meteor app that uses angular-meteor and ng-table. The dataset of the ng-table is from a collection. You can add rows to the table dynamically via a modal form. The dataset is reactive and changes the view once there's a change in the collection. I'm watching for status changes of the item (QUEUED, PROCESSING, CREATED).
Thing is, if the table is on page 2 and the status changes (any change in the dataset), the table is redrawn and goes back to page 2. Is there a way to make it stay on the current page even with dataset changes?
Here's a sample of what I have so far:
(function () {
'use strict';
function myController($scope, $reactive, ngTableParams, ngTableEventsChannel, myService) {
$reactive(this).attach($scope);
this.subscribe('myCollection');
this.currentPage = 1;
this.helpers({
tableParams: () => {
return new ngTableParams({
page: this.currentPage
}, {
//this is just a simple collection call
dataset: myService.getItems().fetch()
});
}
});
$scope.$watch('vm.tableParams', (params) => {
//not triggered :(
ngTableEventsChannel.onDatasetChanged(() => {
console.log('data changed');
tableParams.page($scope.currentPage);
}, $scope, tableParams);
ngTableEventsChannel.onPagesChanged(() => {
$scope.currentPage = tableParams.page();
}, $scope, tableParams);
});
}
angular.module('MyApp').controller('MyController', myController);
})();
The onDatasetChanged event isn't being triggered. I was going to change the page there but I'm not sure if that's a good idea anyway.

calling a function when AngularUI Bootstrap modal has been dismissed and animation has finished executing

I'm using the Angular UI bootstrap modal and I ran into a bit of a problem.
I want to call a function when the bootstrap modal dismiss animation is finished. The code block below will call the cancel() function as soon as the modal starts to be dismissed - and NOT when the modal dismiss animation has finished.
Angular UI does not use events, so there is no 'hidden.bs.modal' event being fired (at least, not to my knowledge).
var instance = $modal.open({...});
instance.result.then(function(data) {
return success(data);
}, function() {
return cancel();
})
The cancel() block immediately runs when the modal starts to close. I need code to execute when the closing animation for the Bootstrap modal finishes.
How can I achieve this with angular UI?
Component for reference:
https://angular-ui.github.io/bootstrap/#/modal
Thanks!
A little late but hope it still helps! You can hijack the uib-modal-window directive and check when its scope gets destroyed (it is an isolated scope directive). The scope is destroyed when the modal is finally removed from the document. I would also use a service to encapsulate the functionality:
Service
app.service('Modals', function ($uibModal, $q) {
var service = this,
// Unique class prefix
WINDOW_CLASS_PREFIX = 'modal-window-interceptor-',
// Map to save created modal instances (key is unique class)
openedWindows = {};
this.open = function (options) {
// create unique class
var windowClass = _.uniqueId(WINDOW_CLASS_PREFIX);
// check if we already have a defined class
if (options.windowClass) {
options.windowClass += ' ' + windowClass;
} else {
options.windowClass = windowClass;
}
// create new modal instance
var instance = $uibModal.open(options);
// attach a new promise which will be resolved when the modal is removed
var removedDeferred = $q.defer();
instance.removed = removedDeferred.promise;
// remember instance in internal map
openedWindows[windowClass] = {
instance: instance,
removedDeferred: removedDeferred
};
return instance;
};
this.afterRemove = function (modalElement) {
// get the unique window class assigned to the modal
var windowClass = _.find(_.keys(openedWindows), function (windowClass) {
return modalElement.hasClass(windowClass);
});
// check if we have found a valid class
if (!windowClass || !openedWindows[windowClass]) {
return;
}
// get the deferred object, resolve and clean up
var removedDeferred = openedWindows[windowClass].removedDeferred;
removedDeferred.resolve();
delete openedWindows[windowClass];
};
return this;
});
Directive
app.directive('uibModalWindow', function (Modals) {
return {
link: function (scope, element) {
scope.$on('$destroy', function () {
Modals.afterRemove(element);
});
}
}
});
And use it in your controller as follows:
app.controller('MainCtrl', function ($scope, Modals) {
$scope.openModal = function () {
var instance = Modals.open({
template: '<div class="modal-body">Close Me</div>' +
'<div class="modal-footer"><a class="btn btn-default" ng-click="$close()">Close</a></div>'
});
instance.result.finally(function () {
alert('result');
});
instance.removed.then(function () {
alert('closed');
});
};
});
I also wrote a blog post about it here.

Getting access to a DOM element after closing Angular-Bootstrap $modal instance

I'm using Malhar Dashboard widget framework at the moment, and struggling with an issue in the close event of the Bootstrap Modal.
FYI: https://github.com/DataTorrent/malhar-angular-dashboard
Specifically, when I close the modal and arrive back to the $modalInstance.result.then function, I need to have access to the parent container which launched the modal in the first place. This proves to be difficult.
For example, when certain click events are triggered, I can get access to the Kendo UI chart element as follows :
var chart = widget.find('.k-chart').data("kendoChart");
chart.resize($(".k-chart"));
But after having launched the $modal like this :
var modalInstance = $modal.open(options);
I want to close the $modal, and still get access to that Kendo chart widget in the modalInstance.result.then() section - but widget is undefined:
modalInstance.result.then(
function (result) {
var chart = widget.find('.k-chart').data("kendoChart"); // widget UNDEFINED !!!
}
)
**** UPDATE - Two things: ****
1) On a positive note, I can get access to the parent node of .modal-body; however, I don't think my solution is very elegenant with the use of el.parent().parent().parent().parent().find() .
2) On the downside, although I can call the kendo.resize() function, it has no effect.
Help is appreciated.
// Set resolve and reject callbacks for the result promise
modalInstance.result.then(
function (result) {
// Call the close callback
onClose(result, widget, scope);
if (!widget.gadgetConfigured) {
widget.gadgetConfigured = true;
widget.setHeight(widget.fixedSize.height);
widget.setWidth(widget.fixedSize.width);
}
scope.$broadcast('widgetSettingsClosed', widget, result);
// attempt to refresh kendo chart - 04/30/2015 BM:
var el = $('.modal-body');
var chartElem = el.parent().parent().parent().parent().find('.k-chart').data("kendoChart");
if (chartElem != undefined) {
chartElem.setOptions({ chartArea: { width: chartElem.getSize().width * .95, height: chartElem.getSize().height * .90 } });
chartElem.resize($(".k-chart"));
}
scope.$emit('widgetChanged', widget);
},
function (reason) {
// Call the dismiss callback
onDismiss(reason, scope);
}
);

Best way to retrieve selected item from Kendo treeview within Angular JS framework

I'm integrating the Kendo UI treeview widget into my Angular based app (using asp.net mvc4 framework).
I'm seeking advice on the best way to retrieve the tree's selected item, as I'm not sure if the way I'm doing it - e.sender._current.text(); - is best practice.
In my html div below you'll notice k-on-change="vm.onTreeSelect(kendoEvent)" , and the associated js event in function onTreeSelect(e) .
I find the current documentation a little weak at http://kendo-labs.github.io/angular-kendo/#/TreeView , so what I'm looking for is two things at the moment:
1) The best way to get the currently-selected item from the treeview.
2) How do I know when I've reached the bottom leaf of my tree.
Thank you ahead of time of your advice, and please find some code snippets below...
My html snippet is :
<div class="widget-content text-left text-info">
Selected: {{vm.selected}}
<span id="treeview" kendo-tree-view="tree"
k-options="vm.treeOptions"
k-data-source="vm.hierarchy"
k-on-change="vm.onTreeSelect(kendoEvent)">
</span>
</div>
and a snippet from my javascript code is :
(function () {
'use strict';
var controllerId = 'dashboard';
angular.module('app').controller(controllerId, ['common', 'datacontext', dashboard]);
vm.hierarchy = [];
vm.onTreeSelect = onTreeSelect;
vm.treeOptions = {
checkboxes: {
checkChildren: true
}
};
vm.selected = null;
activate();
function activate() {
var promises = [getHierarchy(), getCountries()];
common.activateController(promises, controllerId)
.then(function () { log('Activated Dashboard View'); });
}
function dashboard(common, datacontext) {
function onTreeSelect(e) {
vm.selected = e.sender._current.text();
}
})();
the best way to get the data item would be:
var dataItem = e.sender.dataItem(e.sender.select());
This will give you the item with all data attached. Make sure you call it on change event. this would work because e.sender is actually the kendo-tree-view in angular and the other methods are standard. also pass the kendoEvent as event argument.

Resources