Plupload + AngularJS UI modal doesn't work in IE - angularjs

I've already seen a lot of articles about plupload and modal dialogs in old versions of IE, but any of them had a solution for my problem. I'm using AngularJS UI to open modals which contain the container div of plupload, and I need to do this work in this way.
I've tried all the solutions: uploader.refresh(), I've used require.js to load the plupload script when the dialog was already opened, but I still haven't found one that works.
Here's the function of the controller that calls the modal dialog:
$scope.EnviarNovoAnexoClick = function () {
var modalInstance = $modal.open({
templateUrl: '/Dialog/EnviarAnexo',
controller: 'EnviarAnexoDialogController',
resolve: {
documentoId: function () {
return $scope.documentoId;
}
}
});
modalInstance.result.then(function (anexo) {
$scope.documento.anexos.push(anexo);
}, function () {//dismiss callback
});
}
Here's the function that calls the uploader:
require(["/Scripts/plupload.full.js"], function (util) {
$scope.anexoUploader = new plupload.Uploader({
runtimes: 'gears,html5,flash,silverlight,browserplus,html4',
browse_button: 'anexoBtUpload',
container: 'anexoUploadDiv',
unique_names: true,
multi_selection: false,
max_file_size: '150mb',
chunk_size: '64kb',
url: '/Documento/Upload',
flash_swf_url: '/Scripts/plupload.flash.swf',
silverlight_xap_url: '/Scripts/plupload.silverlight.xap',
resize: { width: 320, height: 240, quality: 90 },
filters: [
{ title: "PDFs ", extensions: "pdf" },
{ title: "Imagens", extensions: "jpg,gif,png" },
{ title: "Zips", extensions: "zip" },
{ title: "Todos", extensions: "*" }
],
init: {
FilesAdded: function (up, files) {
if ($scope.uploadDocumento == null) {
$scope.showOrigemAnexo = false;
$scope.novoAnexo.upload = {};
$scope.InicializaUpload($scope.novoAnexo.upload);
$scope.uploadDocumento = $scope.novoAnexo.upload;
}
var fileName = $scope.anexoUploader.files[$scope.anexoUploader.files.length - 1].name;
$scope.uploadDocumento.nome = fileName;
$scope.novoAnexo.descricao = dotlessName(fileName);
$scope.$apply();
up.refresh(); // Reposition Flash/Silverlight
up.start();
},
UploadProgress: function (up, file) {
$scope.uploadDocumento.size = file.size;
$scope.uploadDocumento.percentage = file.percent;
$scope.$apply();
},
FileUploaded: function (up, file, response) {
$scope.uploadDocumento.id = file.id;
$scope.uploadDocumento.size = file.size;
$scope.$apply();
}
}
});
$scope.anexoUploader.init();
});
The file dialog is opening in Chrome, IE10 and Firefox, but I need that it works on IE9 and 8.
Thanks (:

This has something to do with caching and dynamically loaded script tag.
Solution that worked for me:
Add this directive:
.directive('cachedTemplate', ['$templateCache', function ($templateCache) {
"use strict";
return {
restrict: 'A',
terminal: true,
compile: function (element, attr) {
if (attr.type === 'text/ng-template') {
var templateUrl = attr.cachedTemplate,
text = element.html();
$templateCache.put(templateUrl, text);
}
}
};
}])
Declare your modal content in
<div data-cached-template="myInnerModalContent.html" type="text/ng-template">
<!-- content -->
</div>
You may need this style as well:
*[type="text/ng-template"]{
display: none;
}
In controller:
$scope.open = function() {
var modalInstance = $modal.open({
templateUrl: 'ModalContent.html',
controller: modalInstanceCtrl
});
};
Reference: http://blog.tomaszbialecki.info/ie8-angularjs-script-cache-problem/

Related

Mocking $uibModal's opened and closed promises

I am using Angular-UI's $uibModal to open a modal in my code. After calling the open method, I defined code to run in the opened.then() & closed.then() promises. All of this works fine, but when trying to test it (in Jasmine), I can't figure out how to resolve the promises for opened and closed.
here is the code I use to open the modal (in my controller):
function backButtonClick() {
var warningModal = $uibModal.open({
animation: true,
ariaLabelledBy: 'modal-warning-header',
ariaDescribedBy: 'modal-alert-body',
backdrop: 'static',
templateUrl: 'app/components/directives/modals/alertModal/alertModal.html',
controller: 'AlertModalController',
controllerAs: 'vm',
size: 'sm',
resolve: {
options: function() {
return {
title: stringsService.getString('WorkNotSavedTitle'),
message: stringsService.getString('WorkNotSavedMessage'),
modalHeaderClass: 'modal-warning-header',
modalHeaderIconClass: 'fa-warning modal-warning-alert-icon',
modalHeaderTitleClass: 'modal-warning-alert-title',
modalContentClass: 'modal-warning-content',
modalButtonsClass: 'modal-centered-buttons',
showModalHeader: true,
showPrimaryButton: true,
showSecondaryButton: false,
showTertiaryButton: true,
primaryButtonText: stringsService.getString('RemainInActivityButton'),
primaryButtonClick: function() { warningModal.dismiss(); },
tertiaryButtonText: stringsService.getString('LeaveActivityButton'),
tertiaryButtonClick: function() { warningModal.dismiss(); leaveActivity(); }
};
}
}
});
warningModal.opened.then(function() { vm.isWarningModalOpen = true; });
warningModal.closed.then(function() { vm.isWarningModalOpen = false; });
}
and the test I have so far:
it('should show the Warning modal if the back button is clicked', function() {
var modalServiceMock = {
open: function(options) {}
};
sinon.stub(modalServiceMock, 'open').returns({
dismiss: function() { return; },
opened: {
then: function(callback) { return callback(); }
},
closed: {
then: function(callback) { return callback(); }
}
});
var ctlr = $controller('BayServiceController', { $scope: this.$scope, $uibModal: modalServiceMock});
ctlr.backButtonClick();
//this line passes
expect(modalServiceMock.open).toHaveBeenCalled();
//this line fails
expect(ctlr.isWarningModalOpen).toBe(true);
});
Ok, so there may have been a better way about it, but this seems to work so here's what I came up with.
it('should show the Warning modal if the back button is clicked', function() {
modalServiceMock = {
open: function(modalOptions) {
var closedCallback;
return {
dismiss: function() { closedCallback(); },
opened: {
then: function(callback) { callback(); }
},
closed: {
then: function(callback) { closedCallback = callback; }
},
resolver: modalOptions.resolve
};
}
};
var ctlr = $controller('BayServiceController', { $scope: this.$scope, $uibModal: modalServiceMock});
ctlr.backButtonClick();
this.rootScope.$apply();
expect(ctlr.isWarningModalOpen).toBe(true); //this now passes
//to test closing the modal, I access the resolver property of the mock and run the given method for dismissing the modal
ctlr.warningModal.resolver.options().primaryButtonClick();
expect(ctlr.isWarningModalOpen).toBe(false);
});

Angular how to correctly destroy directive

I have a 'regionMap' directive that includes methods for rendering and destroying the map. The map is rendered inside of a modal and upon clicking the modal close button the 'regionMap' destroy method is called, which should remove the element and scope from the page. However, when returning to the modal page, that includes the 'region-map' element, the previous 'region-map' element is not removed, resulting in multiple maps being displayed. What is the correct way to remove the regionMap directive from the page when the modal is closed?
// directive
(function(){
'use strict';
angular.module('homeModule')
.directive('regionMap', regionMap);
function regionMap() {
var directive = {
restrict: 'E',
template: '',
replace: true,
link: link,
scope: {
regionItem: '=',
accessor: '='
}
}
return directive;
function link(scope, el, attrs, controller) {
if (scope.accessor) {
scope.accessor.renderMap = function(selectedRegion) {
var paper = Raphael(el[0], 665, 245);
paper.setViewBox(0, 0, 1100, 350, false);
paper.setStart();
for (var country in worldmap.shapes) {
paper.path(worldmap.shapes[country]).attr({
"font-size": 12,
"font-weight": "bold",
title: worldmap.names[country],
stroke: "none",
fill: '#EBE9E9',
"stroke-opacity": 1
}).data({'regionId': country});
}
paper.forEach(function(el) {
if (el.data('regionId') != selectedRegion.name) {
el.stop().attr({fill: '#ebe9e9'});
} else {
el.stop().attr({fill: '#06767e'});
}
});
}
scope.accessor.destroyMap = function() {
scope.$destroy();
el.remove();
}
}
}
}
})();
// controller template:
<region-map accessor="modalvm.accessor" region-item="modalvm.sregion"></region-map>
// controller:
vm.accessor = {};
...
function showMap() {
$rootScope.$on('$includeContentLoaded', function(event) {
if (vm.accessor.renderMap) {
vm.accessor.renderMap(vm.sregion);
}
});
function closeMap() {
if (vm.accessor.destroyMap) {
vm.accessor.destroyMap();
}
$modalInstance.dismiss('cancel');
}
The issue is related to loading a template with a directive inside of it. Fixed it by adding a var to check if the map has previously been rendered:
vm.accessor.mapRendered = false;
$rootScope.$on('$includeContentLoaded', function(event) {
if (vm.accessor.renderMap && !vm.accessor.mapRendered) {
vm.accessor.renderMap(vm.selectedRegions);
vm.accessor.mapRendered = true;
}
});

Can't get file upload to work in angular UI modal window

Here is a plunker example of something that i'm trying to do:
http://plnkr.co/edit/dlktEzrBeFshGaZsTmg7?p=preview
Basically i just want to use jquery file upload inside the modal window. as you can see in the plunker though, none of the callbacks are being called.
$('#fileupload').fileupload({
dataType: 'json',
done: function (e, data) {
$log.log("done accessed");
},
fail: function (e, data) {
$log.log("fail accessed");
},
progressall: function (e, data) {
$log.log("progressall");
},
//add: function(e,data){
//$log.log("add accessed");
//},
submit: function (e, data) {
var notetext = $("#descModal").val();
data.formData = { Description: notetext };
$log.log("submit accessed");
}
even the 'add' callback isn't being called when i add a file. This all works fine if i use angular strap, but i'd rather not use that for other reasons. I've investigated the modal initialization, and tried to override the windowTemplateURL
var theModal = $modal.open({ scope: $scope, templateUrl: modURL, controller: 'detailController', size: 'lg' });
(the default of which is here: https://github.com/angular-ui/bootstrap/blob/master/template/modal/window.html ),and it looks like the problem is the 'modal-transclude' attribute. Any ideas on getting past this?
Upgrade jQuery-ui to version 1.9.0 or higher.
Never use jQuery selectors in your controller, write a directive. E.g.
angular.module("myapp").directive("fileUpload", function($log, $parse) {
return {
restrict: "A",
link: function(scope, element, attrs) {
var options = $parse(attrs.fileUpload)(scope) || {};
element.fileupload({
dataType: "json",
url: "your url",
done: function(e, data) {
$log.log("done accessed");
},
fail: function(e, data) {
$log.log("fail accessed");
},
progress: function(e, data) {
options.progress = parseInt(data.loaded / data.total * 100, 10);
scope.$apply();
$log.log("progress");
},
submit: function(e, data) {
$log.log("notetext:", options.notetext);
data.formData = {
Description: options.notetext
};
$log.log("submit accessed");
}
});
}
}
And use it in this way:
<input file-upload="fileUploadOptions" type="file" multiple data-sequential-uploads="true" />
Here's an updated plunker: http://plnkr.co/edit/qLckEIlNLEcIfvwn4Q5x?p=preview
btw there is an example of the usage of this fileupload with angularjs: https://blueimp.github.io/jQuery-File-Upload/angularjs.html .

Adding a new data model to Malhar-Angular-Dashboard

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>
}
});

How to use $compile in angularjs with new element

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

Resources