How to use $compile in angularjs with new element - angularjs

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

Related

read file directive with controller as

I need some help. I build the following directve to translate docx file into html string.
(function(){
'use strict';
angular
.module('app.core')
.directive('uploadFile', uploadFile);
function uploadFile($rootScope, $parse){
var directive = {
restrict: "A",
scope:{result : '='},
controller: 'refertazioneController',
controllerAs: "vm",
link: linkFunction,
};
function linkFunction(scope, element, attrs, controller){
document.getElementById("document")
.addEventListener("change", handleFileSelect, false);
function handleFileSelect(event) {
readFileInputEventAsArrayBuffer(event, function(arrayBuffer) {
mammoth.convertToHtml({arrayBuffer: arrayBuffer})
.then(displayResult)
.done();
});
}
function displayResult(result) {
scope.vm.result = resutl.value;
/* document.getElementById("output").innerHTML = result.value;
var messageHtml = result.messages.map(function(message) {
return '<li class="' + message.type + '">' + escapeHtml(message.message) + "</li>";
}).join("");
document.getElementById("messages").innerHTML = "<ul>" + messageHtml + "</ul>";*/
}
function readFileInputEventAsArrayBuffer(event, callback) {
var file = event.target.files[0];
var reader = new FileReader();
reader.onload = function(loadEvent) {
var arrayBuffer = loadEvent.target.result;
callback(arrayBuffer);
};
reader.readAsArrayBuffer(file);
}
function escapeHtml(value) {
return value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>');
}
};
return directive;
}
})();
the problem is that i'm not able to retrivie the translate string in the controller, defined as follows:
(function(){
'use strict';
angular
.module('app.core')
.controller('refertazioneController', refertazioneController);
function refertazioneController($stateParams, refertationService, $window, examinationService, dataService, $scope){
var vm = this;
vm.prova="refertazioneController";
vm.tinymceModel = '';
vm.sospeso=true;
vm.datiDaRefertare = $stateParams;
vm.paziente = dataService.getPatient(vm.datiDaRefertare.patientId);
examinationService.getPatientExamsDef(vm.datiDaRefertare.patientId).then(function(r){
vm.subjectExam = r.data[0].data;
})
console.log(vm.paziente);
vm.currentUser = sessionStorage;
vm.tinymceOptions = {
onChange: function(e) {
// put logic here for keypress and cut/paste changes
},
inline: false,
slector: 'textarea',
// toolbar: 'undo redo | styleselect | bold italic | link image | print save cancel',
height: 500,
plugins : 'advlist autolink link image lists charmap print preview template save paste',
skin: 'lightgray',
theme : 'modern',
language:'it',
statusbar: false,
templates:[ {title: 'Titolo1', description: 'Descrizione1', content: '<p style="text-align: center;">'+
'<strong>A.S.L. 02 LANCIANO-VASTO-CHIETI</strong>'+
'</p>'},
{title: 'Titolo2', description: 'Secondo referto', url: 'sections/refertazione/referto1.html'}
]
};
vm.html = {};
//vm.html.content = '<p>qui per esempio ci va il template che mi ridà il back end</p><h2>altra roba</h2>';
refertationService.openRefert(1,2);
refertationService.closeRefert(1,2);
refertationService.saveRefert(1,2);
/* vm.testoHtml = "";*/
}
})();
I thought that the line : scope.vm.result = result.value was able to bind the string to my controller and then that i was able to render it in the view as refertazione.result (refertazione is the name of my controller). But this not works, where I'm wrong?
A slightly better pattern that relies on events. You could pull this same pattern off with a scope variable that is two way.
Idea is you use an event to tell the controller data has changed.
function uploadFile($rootScope, $parse) {
var directive = {
restrict: "A",
scope: {},
link: linkFunction,
};
function linkFunction(scope, element, attrs) {
var fn = $parse(attrs.uploadFile);
element.on('change', function(onChangeEvent) {
var reader = new FileReader();
console.log(reader);
reader.onload = function(onLoadEvent) {
$rootScope.$broadcast('fileupdate', onLoadEvent.target.result);
};
reader.readAsText((onChangeEvent.srcElement || onChangeEvent.target).files[0]);
});
}
return directive;
}
Inside of your controller you would listen for the fileupdate event.
//inside your controller:
$scope.$on('fileupdate', showContent);
function showContent(event, $fileContent){
vm.content = $fileContent;
}

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

Using different controllers on the same directive in AngularJS

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.

angularjs + typeahead input field initial vaule

I have this directive, which is basicly wrapper for typeahead plugin from bootstrap. Everything is working like a charm. But now I have to set initial vaule in typeahead's input field. The value is passed as a string in attrs.init. But I don't know how to insert it into text field.
angular.module('rcApp')
.directive('rcAutocomplete', ['$injector', function ($injector) {
return {
scope: {
model: '#',
search: '#',
key: '#',
show: '#',
init: '#',
ngModel: '='
},
template: '<input type="text">',
replace: true,
restrict: 'E',
require: 'ngModel',
link: function (scope, element, attrs) {
// inject model service
var service = $injector.get(attrs.model);
// define search function
var searchFunction = attrs.search;
// holds picked object id
scope.id = 0;
// holds objects that matched query, mapped by "show" value
scope.map = {};
element.on('focusout ac.itempicked', function () {
scope.$apply(function () {
scope.ngModel = scope.id;
});
});
// launch typehead plugin
element.typeahead(
{
source: function (query, process) {
// clear cache
scope.id = 0;
scope.map = {};
service[searchFunction](query).then(function (result) {
var dataValues = [];
var fieldsToShow = scope.show.split('|');
$.each(result.data, function (index, dataItem) {
// generate key-show string
var valueHash = '';
for (var i = 0; i < fieldsToShow.length; i++) {
valueHash += dataItem[fieldsToShow[i]] + ' ';
}
valueHash = $.trim(valueHash);
// map results
scope.map[valueHash] = dataItem;
// prepare return strings
dataValues.push(valueHash);
});
// return content
process(dataValues);
});
},
updater: function (item) {
if (typeof scope.key === 'undefined') {
scope.id = scope.map[item];
}
else {
scope.id = scope.map[item][scope.key];
}
element.trigger('ac.itempicked');
return item;
}
}
);
}
};
}]);
** UPDATE **
Solution, that worked for me is adding code like this to link function:
// init value
if (typeof attrs.init !== 'undefined') {
window.setTimeout(function () {
element.val(attrs.init);
scope.$apply();
}, 10);
}
But still I don't quite understand why "element.val(attrs.init);" don't updated the view, and calling scope.$apply() did, but throw an "$digest already in progress" error. Wrapping it in window.setTimeout helped, but this also is a hack for me....
It's got to be a way to make it cleaner/simpler...

AngularJS - Setting a variable on scope from directive

I'm trying to set a variable, selected.child, on the $scope so that I can use it elsewhere. I'm still new to scopes in Angular, but not sure why I can't set something on the scope from within the directive. I can call scope functions.
I have a JSfiddle for it and code is posted below.
Thanks for the help in advance.
The HTML:
<div ng-controller="DashCtrl">
<h3>{{selected.child}}<h3>
<div ng-repeat="child in children" select={{child}}>
{{child.username}}
</div>
</div>
The javascript:
var dash = angular.module('dash', []);
dash.directive('select', function () {
return {
restrict: "A",
scope: false,
link: function (scope, element, attrs) {
element.bind('click', function () {
scope.selected.child = jQuery.parseJSON(attrs.select); //Neither this
scope.setSelected(jQuery.parseJSON(attrs.select)); //Nor this is working
if (attrs.select != scope.selected) {
other_elements = angular.element(document.querySelectorAll('[select]'));
for (var i = 0; i < other_elements.length; i++) {
elm = jQuery(other_elements[i]);
elm.css('background', 'none');
}
element.css('background', '#F3E2A9');
}
});
}
};
});
dash.controller('DashCtrl', function ($scope) {
$scope.setSelected = function (child) {
$scope.selected.child = child;
};
$scope.children = [{
"age_group": "6-8",
"username": "my_child"
}, {
"age_group": "8-10",
"username": "another_child"
}];
$scope.selected = {
child: "none"
};
});
You are missing a call to $apply
Just modify your code as below
scope.selected.child = jQuery.parseJSON(attrs.select); //Neither this
//scope.setSelected(jQuery.parseJSON(attrs.select)); //Nor this is working
scope.$apply();

Resources