I'm trying to write a directive for HighCharts in AngularJS which supports two way data binding as well as click events on charts.
Directive:
app.directive('highchart', function () {
return {
restrict: 'E',
template: '<div></div>',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(scope.example_chart, function() {
var chart = JSON.parse(attrs.chart)
element.highcharts(chart);
}
}
}
});
Now, when I write my HTML like this:
<div>
<highchart chart='example_chart'></highchart>
</div>
It supports the click event, but not two way data binding.
And, when it is passed as an expression:
<div>
<highchart chart='{{example_chart}}'></highchart>
</div>
It supports two way data binding but the function written in JSON of example_chart for click event doesn't get parsed and hence not functioning.
So, suggest a way to handle both the cases in AngularJS way.
highcharts-ng
You can use highcharts-ng directive, See usage here: Fiddle
Also you can use custom directive:
Custom
See demo in Fiddle
Actually there is nothing special here, pretty simple isolate scope directive with watcher on highChart configuration (defined as JSON).
I my case I used several watchers on specific fields to improve perforamnce but you can run deep watch on all config object
HTML
<high-chart config="chartConfig"> </high-chart>
JS
myapp.directive('highChart',
function () {
return {
restrict: 'E',
replace:true,
scope: {
config: '='
},
template: '<div id="container" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
//var mergedOptions = getMergedOptions(scope, element, config);
chart = new Highcharts.Chart(config);
if(config.loading) {
chart.showLoading();
}
};
initChart();
scope.$watch('config.loadRandomData', function (value) {
if(value == false){return;}
chart.series[0].setData(scope.config.series[0].data);
scope.config.loadRandomData = false;
}, true);
scope.$watch('config.loading', function (loading) {
if(loading) {
chart.showLoading();
} else {
chart.hideLoading();
}
});
scope.$watch('config.series[0].type', function (type) {
chart.series[0].update({type: type});
});
scope.$watch('config.series[0].dataLabels.enabled', function (enableDataLabels) {
chart.series[0].update({dataLabels: {enabled: enableDataLabels}});
});
}//end watch
}
}) ;
Related
I am working on an application that has same directives but the content change and template depends on the id of a controller in a module.
app.directive('glTranslate', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
var key = element.text();
attrs.$observe('options', function(value) {
var options;
try {
options= JSON.parse(value);
element.text( I18n.t( key, options ) );
} catch (err) {
// SyntaxError maybe thrown as the options string is dynamically updated through Angular bindings
// and the options string may be momentarily be syntactically incorrect
console.log("Error parsing options: " + value + "\nError:"+ err.message + "\n");
}
});
}
};
});
If I understand you correctly, to set a directive's template dynamically based on some condition, you can use ng-include inside the directive. The pseudocode below works. It includes an example directive with dynamic template chosen based attribute passed to it and simple HTML usage. Needless to say, although the template contents vary, here they use the same directive controller and its accompanying functionality. Hope this helps.
.directive("dynaDirective", function() {
return {
template: '<ng-include src="getTemplateUrl()"/>',
scope: {
someData: '='
},
restrict: 'E',
controller: function($scope) {
//function used on the ng-include to resolve the template
$scope.getTemplateUrl = function() {
//basic handling
return "templateName" + $scope.someData.type;
}
$scope.doSomething1 = {
//......
}
$scope.doSomething2 = {
//......
}
}
};
});
<dyna-directive some-data="someObject"></dyna-directive>
I have tried using k-content-editable and well as just the generic data-ng-disabled but neither of these worked. Looking at the documentation it's not even clear to me there is a way to disable the control.
You can do this by creating a custom directive:
.directive("kNgDisabled", function() {
return {
restrict: "A",
link: function(scope, element, attr) {
scope.$on("kendoWidgetCreated", function(e, widget) {
var value = scope.$eval(attr.kNgDisabled);
$(widget.body).attr("contenteditable", !value);
scope.$watch(attr.kNgDisabled, function(value) {
$(widget.body).attr("contenteditable", !value);
});
})
}
}
});
Then use it like this:
<textarea kendo-editor k-ng-disabled="disabled"></textarea>
Here is a live demo: http://dojo.telerik.com/#korchev/AdApu
Add following code in your Angular controller->
var x = document.getElementById("myForm");
x.addEventListener("focus", myFocusFunction, true);
function myFocusFunction() {
$($('#keFinding').data().kendoEditor.body).attr('contenteditable', false);
}
Is it possible to apply two way binding to a <textarea></textarea> that has had TinyMCE applied to it for Rich Text Formatting.
I can't get it to work! I can get TinyMCE to load the content of my model, but when I update the text in TinyMCE, my model does not auto update!
Is there a way?
You can do this by creating your own directive.
What you need to do is to let your directive sync your model when something in the TinyMCE editor changes. I have not used TinyMCE, but Wysihtml5. I think you can remake this to use TinyMCE instead.
angular.module('directives').directive('wysihtml5', ['$timeout',
function ($timeout) {
return {
restrict: 'E',
require: 'ngModel',
template: "<textarea></textarea>", // A template you create as a HTML file (use templateURL) or something else...
link: function ($scope, $element, attrs, ngModel) {
// Find the textarea defined in your Template
var textarea = $element.find("textarea");
// When your model changes from the outside, use ngModel.$render to update the value in the textarea
ngModel.$render = function () {
textarea.val(ngModel.$viewValue);
};
// Create the editor itself, use TinyMCE in your case
var editor = new wysihtml5.Editor(textarea[0],
{
stylesheets: ["/style.css"],
parserRules: wysihtml5ParserRules,
toolbar: true,
autoLink: true,
useLineBreaks: false,
});
// Ensure editor is rendered before binding to the change event
$timeout(function () {
// On every change in the editor, get the value from the editor (textarea in case of Wysihtml5)
// and set your model
editor.on('change', function () {
var newValue = textarea.val();
if (!$scope.$$phase) {
$scope.$apply(function () {
ngModel.$setViewValue(newValue);
});
}
});
}, 500);
}
};
}]);
Then you can use the directive in your html page like this:
<wysihtml5 ng-model="model.text" />
Here's a link if you need more info on creating your own directive: http://docs.angularjs.org/guide/directive
Also compare the render function from the directive above to this render function from angular-ui-tinymce ( https://github.com/angular-ui/ui-tinymce )
ngModel.$render = function() {
if (!tinyInstance) {
tinyInstance = tinymce.get(attrs.id);
}
if (tinyInstance) {
tinyInstance.setContent(ngModel.$viewValue || '');
}
Plnkr: http://plnkr.co/edit/04AFkp?p=preview
However depending on the timing of the loading of your DOM you may need to set the priority on your directive upwards. :-)
Here is my solution using a custom angular directive.
You'll need to use jQuery with angularJS, TinyMCE 4 and their jQuery plugin.
myApp.directive('tinymce', function() {
return {
restrict: 'C',
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
element.tinymce({
setup: function (e) {
e.on("change", function() {
modelCtrl.$setViewValue(element.val());
scope.$apply();
}
}
});
}
}
}
Then in your HTML:
<textarea class="tinymce" ng-model="data"></textarea>
That's it, have fun.
I'm trying to use prettyprint plugin for my angularjs app.
But cannot make it works. I create a simple directive and call method prettyPrint(), but the code is not formatted.
FIDDLE: http://jsfiddle.net/Tropicalista/yAv4f/2/
App.directive('test', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$(element).prettyPrint()
}
};
});
I modified your code and i'll update here:
http://jsfiddle.net/yAv4f/6/
html:
<div ng-app="Knob" ng-controller="myCtrl">
<pre class="prettyprint linemus"></pre>
<pre class="prettyprint linemus"><!DOCTYPE html><html lang="en"></html></pre>
</div>
javascript:
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.dom = '<!DOCTYPE html><html lang="en"></html>'
})
App.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(scope.dom));
}
};
});
Basically, you need to use the file prettify.js to control the execution of the prettify() function, with prettyPrintOne() you can execute it in a specific html text.
And to simplify the use of the directive, like prettify stlyle, i'll suggest restric to 'C' a class and change the the directive name to 'prettyprint'
I've expanded on the previous answers and created a jsfiddle with a working directive that responds in realtime to model changes:
http://jsfiddle.net/smithkl42/cwrgLd0L/27/
HTML:
<div ng-app="prettifyTest" ng-controller="myCtrl">
<div>
<input type="text" ng-model="organization.message" />
</div>
<prettify target="organization"><pre><code class="prettyprint">console.log('{{target.message}}');
</code>
</pre>
</prettify>
</div>
JS:
var App = angular.module('prettifyTest', []);
App.controller('myCtrl', function ($scope) {
$scope.organization = {
message: 'Hello, world!'
};
});
App.directive('prettify', ['$compile', '$timeout', function ($compile, $timeout) {
return {
restrict: 'E',
scope: {
target: '='
},
link: function (scope, element, attrs) {
var template = element.html();
var templateFn = $compile(template);
var update = function(){
$timeout(function () {
var compiled = templateFn(scope).html();
var prettified = prettyPrintOne(compiled);
element.html(prettified);
}, 0);
}
scope.$watch('target', function () {
update();
}, true);
update();
}
};
}]);
h/t to #DanielSchaffer (see Template always compiles with old scope value in directive).
Angular already has this filter built-in for JSON:
<pre>
{{data | json}}
</pre>
If you want to make your own directive, you can use the JSON object directly:
app.filter('prettyJSON', function () {
function syntaxHighlight(json) {
return JSON ? JSON.stringify(json, null, ' ') : 'your browser doesnt support JSON so cant pretty print';
}
return syntaxHighlight;
});
With markup
<pre>
{{data | prettyJSON}}
</pre>
I would like to make a small addition to the directive by #carlosmantilla
You can achieve the same thing without creating the scope variable. I have added this correction on github
This should work properly I assume.
http://jsfiddle.net/yAv4f/143/
var App = angular.module('Knob', []);
App.controller('myCtrl', function($scope) {
$scope.text = "function f1(){int a;}";
})
function replaceText(str)
{
var str1 = String(str);
return str1.replace(/\n/g,"<br/>");
}
app.directive('prettyprint', function() {
return {
restrict: 'C',
link: function postLink(scope, element, attrs) {
element.html(prettyPrintOne(replaceText(element.html()),'',true));
}
};
});
I struggled with this issue for quite a while and wanted to chime in here, albeit much later than everyone else (for real though, who's still using AngularJS in late 2017? This guy.) My specific use-case was where I have code (xml) being dynamically loaded on the page which needed to be pretty printed over and over again.
This directive will take in your code as an attribute, remove the prettyprinted class that's added to the element right after you run prettyPrint(). It will watch for changes on the inputted code from the parent's scope and run the code again when changes occur.
Only dependency is that you have Google's code-prettify. I had it self-hosted, hence the PR.prettyPrint() (as instructed in the docs as of sept 2017).
The directive fully encapsulates the needed Google code-prettify functionality for dynamic content.
angular.module('acrSelect.portal.directives')
.directive('prettyPrint', ['$timeout', function($timeout) {
return {
restrict: 'E',
scope: {
'code': '=',
},
template: '<pre ng-class="{prettyprint: code}">{{ code }}</pre>',
link: function (scope, element, attr) {
scope.$watch('code',function(){
$timeout(function() {
//DOM has finished rendering
PR.prettyPrint();
element.find(".prettyprint").removeClass("prettyprinted");
});
});
}
}
}
]);
The html in the parent template might look
<pretty-print code="selectedCode" ng-show="codeIsSelected"></pretty-print>
Hope this helps another poor soul!
I'm trying to acheive databinding to a value returned from a service inside a directive.
I have it working, but I'm jumping through hoops, and I suspect there's a better way.
For example:
<img my-avatar>
Which is a directive synonymous to:
<img src="{{user.avatarUrl}}" class="avatar">
Where user is:
$scope.user = CurrentUserService.getCurrentUser();
Here's the directive I'm using to get this to work:
.directive('myAvatar', function(CurrentUser) {
return {
link: function(scope, elm, attrs) {
scope.user = CurrentUser.getCurrentUser();
// Use a function to watch if the username changes,
// since it appears that $scope.$watch('user.username') isn't working
var watchUserName = function(scope) {
return scope.user.username;
};
scope.$watch(watchUserName, function (newUserName,oldUserName, scope) {
elm.attr('src',CurrentUser.getCurrentUser().avatarUrl);
}, true);
elm.attr('class','avatar');
}
};
Is there a more succinct, 'angular' way to achieve the same outcome?
How about this ? plunker
The main idea of your directive is like
.directive('myAvatar', function (CurrentUserService) {
"use strict";
return {
restrict: 'A',
replace: true,
template: '<img class="avatar" ng-src="{{url}}" alt="{{url}}" title="{{url}}"> ',
controller: function ($scope, CurrentUserService) {
$scope.url = CurrentUserService.getCurrentUser().avatarUrl;
}
};
});