I am trying to use AngularJS two-way binding text which includes Latex style equations. I would like to call MathJax to format the equations, but I'm not sure of the best way to ensure that MathJax is called after AngularJS finishes changing the model. I think I need a callback. Here is my JavaScript:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.Update = function() {
$scope.Expression = 'Evaluate: \\( \\frac{9}{4} \\div \\frac{1}{6} \\)';
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
$scope.Expression = 'Evaluate: \\( \\frac{5}{4} \\div \\frac{1}{6} \\)';
}
And here is my HTML:
<div ng-controller="MyCtrl">
<button ng-click="Update()">Update</button>
{{Expression}}
</div>
Fiddle is here: http://jsfiddle.net/LukasHalim/UVjTD/1/. You'll notice that on the fiddle the original expression isn't removed even after you click the update button twice - seems like a bug or conflict.
Having wasted many days (and maybe weeks) fighting MathJax, I'm all too familiar with its various quirks with updating math expressions on the fly. I'm brand new to Angular but this gave me a good chance to dive in and I ended up with a solution which solves my problems -- hopefully it'll solve yours as well.
Live demo: jsfiddle
Instead of using the plain interpolation that Angular provides, I created a new directive based on ng-bind called mathjax-bind.
If expression is a variable containing math code, then instead of \( {{expression}} \) you can write:
<span mathjax-bind="expression"></span>
and everything will be typeset and updated at the appropriate times.
The supporting code for the directive follows:
myApp.directive("mathjaxBind", function() {
return {
restrict: "A",
controller: ["$scope", "$element", "$attrs",
function($scope, $element, $attrs) {
$scope.$watch($attrs.mathjaxBind, function(texExpression) {
var texScript = angular.element("<script type='math/tex'>")
.html(texExpression ? texExpression : "");
$element.html("");
$element.append(texScript);
MathJax.Hub.Queue(["Reprocess", MathJax.Hub, $element[0]]);
});
}]
};
});
Simplest, fastest and most stable solution:
$rootScope.$watch(function(){
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
return true;
});
Advantages:
Easy to setup, just copy this code.
Everything on your page is typeset.
It renders much faster than the other solutions. This is because it can render the page in one go. Other answers here wait for one item to finish, until they typeset the next one. That makes rendering veeeery slow if there are for example multiple mathjax-bind directives (as another answer suggests). This point is the reason I was looking for a different answer.
You can still easily exclude elements using the option “ignoreClass” in your mathjax settings.
Benchmarking:
100 mathjax-bind directives took 63 seconds, while with this method it took 1.5 second to render the page. I know that this function will be executed a lot since it's called on every digest cycle, however, it doesn't noticeably slow down the page.
I created a simple fiddle expanding on Ben Alpert's answer. Here's the fiddle and plunk.
Specifically If a text has only a part of it to be converted by Mathjax, you can use this.
For inline mathjax you must surround the text by $, and for block display you must surround the block by $$. (You can use any format you like if you create the corresponding regex)
app.js
MathJax.Hub.Config({
skipStartupTypeset: true,
messageStyle: "none",
"HTML-CSS": {
showMathMenu: false
}
});
MathJax.Hub.Configured();
var myApp = angular.module("myApp", []);
myApp.directive("mathjaxBind", function() {
return {
restrict: "A",
scope:{
text: "#mathjaxBind"
},
controller: ["$scope", "$element", "$attrs", function($scope, $element, $attrs) {
$scope.$watch('text', function(value) {
var $script = angular.element("<script type='math/tex'>")
.html(value == undefined ? "" : value);
$element.html("");
$element.append($script);
MathJax.Hub.Queue(["Reprocess", MathJax.Hub, $element[0]]);
});
}]
};
});
myApp.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace: true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function(html) {
html = html.replace(/\$\$([^$]+)\$\$/g, "<span class=\"blue\" mathjax-bind=\"$1\"></span>");
html = html.replace(/\$([^$]+)\$/g, "<span class=\"red\" mathjax-bind=\"$1\"></span>");
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
function MyCtrl($scope, $element) {
$scope.html = "A coin of is $ \\frac{5}{4} $ thrown $$\\frac{1}{6}$$ dfv";
}
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<script src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML&delayStartupUntil=configured&dummy=.js"></script>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.2.x" src="http://code.angularjs.org/1.2.7/angular.js" data-semver="1.2.7"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="MyCtrl">
<input type="text" ng-model="html"/><br/>
<div dynamic="html"></div>
</div>
</body>
style.css
input[type="text"] {
width: 800px;
}
.red{
color:red;
display:inline-block;
}
.blue{
color:blue;
display:block;
}
Take a look at http://jsfiddle.net/pz5Jc/
In your template:
{{Label}} <span id="mathElement">{{Expression}}</span>
In your controller:
$scope.Update = function() {
$scope.Expression = '\\frac{9}{4} \\div \\frac{1}{6}';
$scope.Label = 'Updated Expression:'
var math = MathJax.Hub.getAllJax("mathElement")[0];
math.Text('\\frac{4}{4} \\div \\frac{2}{6}');
}
Couple of points:
I'm not too familiar with mathjax, but:
Splitting the label out from the expression allows you to work with the expression directly.
You need to manually pick up a DOM element to force a refresh of the expression. This isn't a very 'angular' way to do things unfortunately - but when mathjax parses the expression (and inserts it's own DOM elements), it pushes those elements outside the angular bindings.
Fix here is to specifically select the correct mathjax element and call a text change function to update the expression.
Here's a directive that lets you use double curly markup inside the expression (and doesn't require setting an expression variable on the scope). It's based on this blog post, except I only support MathJax, and I save the compiled DOM, so that it updates on changes to scope variables.
As Alex Osborn said, it's best to separate non-math from math.
Usage:
<p>This is inline math: <latex>x^{ {{power}} }</latex>,
and this is display math: <div latex> y^{ {{power}} } .</div></p>
In a snippet:
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.power = "\\sin(x^2)";
})
.directive('latex', function() {
return {
restrict: 'AE',
link: function(scope, element) {
var newDom = element.clone();
element.replaceWith(newDom);
var pre = "\\(",
post = "\\)";
if (element[0].tagName === 'DIV') {
pre = "\\[";
post = "\\]";
}
scope.$watch(function() {
return element.html();
}, function() {
console.log(element);
newDom.html(pre + element.html() + post);
MathJax.Hub.Typeset(newDom[0]);
});
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<div ng-app="app" ng-controller="ctrl">
<p>Power:
<input ng-model="power" />
</p>
<p>This is the inline latex,
<latex>x^{ {{power}} }</latex>, followed by some display mode latex
<div latex>y^{ {{power}} } = {{power}}.</div>And that's it!
</p>
</div>
A simple solution is to use $timeout to put MathJax.Hub.Queue(["Typeset", MathJax.Hub]) in the browser event queue (see Run a directive after the DOM has finished rendering).
Something like this:
var app = angular.module('myApp', []);
app.controller('myController', function ($scope, $timeout) {
controller = this;
$scope.Update = function () {
$scope.value = " \\( \\frac{5}{4} \\div \\frac{1}{6} \\)";
$timeout(controller.updateMathJax, 0);
}
this.updateMathJax = function () {
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
});
You can try with my modifications http://jsfiddle.net/bmma8/4/
modify input or click on button will update your expression.
js:
MathJax.Hub.Config({
extensions: ["tex2jax.js"],
jax: ["input/TeX","output/HTML-CSS"],
tex2jax: {inlineMath: [["$","$"],["\\(","\\)"]]}
});
var myApp = angular.module('myApp',[]);
function MyCtrl($scope, $log) {
var QUEUE = MathJax.Hub.queue; // shorthand for the queue
$scope.Update = function() {
QUEUE.Push(["Text",MathJax.Hub.getAllJax("MathOutput")[0],"\\displaystyle{"+ $scope.Expression+"}"]);
//$scope.Expression = 'Updated Expression: \\( \\frac{9}{4} \\div \\frac{1}{6} \\)';
//MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
}
$scope.Expression = 'Original Expression: \\( \\frac{5}{4} \\div \\fra
and html:
<div ng-controller="MyCtrl">
<button ng-click="Update()">Update</button>
<input ng-model="Expression" ng-change="Update()">
<div id="MathOutput">
You typed: ${}$
</div>
</div>
Alexandre
I actually thought of another solution. When you render some angular and math you do this:
ANGULAR CONTROLLER
$scope x = 5;
HTML
<h3> {{ '$ Multiplication = '+ x + ' * 2 =' + (x*2) + '$'}} </h3>
Formated Math Jax result
Multiplication = 5 * 2 = 10
The key is to include the dollar signs inside the brackets as text. When Angular renders them, the dollar signs will appear as plain text, but when the Math Jax format comes into action it will recognize the dollar signs and do the magic.
I Build a directive for this....
FIDDLE: http://jsfiddle.net/8YkUS/1/
HTML
p data-math-exp data-value="math">
JAVASCRIPT
appFlipped.directive("mathExp", function () {
return {
scope: {
value: "="
},
link: function (scope, el) {
var domEl = el[0];
scope.$watch("value", function (newValue) {
//nothing to do here
if (newValue == null || window.MathJax == null)return;
//update the dom with the new value and pass the hub for styling the equation
domEl.innerHTML = '`' + newValue + '`';
MathJax.Hub.Queue(["Typeset", MathJax.Hub, domEl]);
});
}
}
});
I fiddled a bit more on Roney's solution. The display math should be displayed in display mode; with
<script type="math/tex; mode=display">
I added an attribute to the generated span to indicate that.
Fiddle is here http://jsfiddle.net/repa/aheujhfq/8/
Related
I can't figure out what's wrong with my code. I'm following the example of this fiddle in order to bind the two inputs. What I'm trying to do is just a little more involved- I'd like to load a certain template based on an attribute passed into the directive. I can't figure out what's wrong over here.
This is my html
<!-- template -->
<script type="text/ng-template" id="X-template.html">
<button ng-click='clickMe()'><b>Check model</b></button>
Directive: <input ng-model="this.test"></input>
</script>
<div ng-controller="MyCtrl">
<my-directive type="X"></my-directive>
No Directive:<input ng-model="this.test"></input>
{{this.test}}
</div>
And my JS:
var app = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.this= {test : "asdf"};
$scope.clickMe = function() {
alert($scope.this.test);
}
}
app.directive('myDirective', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
element.append('<div ng-include="\'' + attrs.type + '-template.html\'"></div>');
}
}
});
The issue is with trying to use this, which is a reserved keyword in JavaScript, as a property name.
Try using a different property name. I changed your example to use foo in the link below.
http://jsfiddle.net/01h3ne4y/
I am creating a custom checkbox directive for my app, however, tab skips right over it. How can I force the tab key to stop at my directive?
Code: (Note: Plunker seems to be on the fritz right now...)
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div ng-controller='TestCtrl'>
<input type='text' />
<test-checkbox ng-model='isChecked'></test-checkbox>
<input type='text' />
</div>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.min.js"></script>
<script>
var app = angular.module("testApp", []);
app.controller('TestCtrl', ['$scope', function($scope) {
$scope.isChecked = true;
}]);
app.directive('testCheckbox', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
isChecked: "=ngModel"
},
link: function (scope, element) {
var html =
"<div class='slgCheckbox' ng-click='onClick(isChecked = !isChecked)'>" +
$(element).html() +
"<i class='checkboxIcon glyphicon' ng-class='{\"glyphicon-check\" : isChecked === true, \"glyphicon-unchecked\" : isChecked === false }'></i>" +
"</div>";
var compiledHtml = $compile(html)(scope);
$(element).replaceWith(compiledHtml);
}
}
}]);
</script>
</body>
</html>
You can add a tabindex attribute to the div element and that will make it selectable.
In your template above, you can do something like this:
var html =
"<div tabindex='1' class='slgCheckbox' ng-click='onClick(isChecked = !isChecked)'>" +
$(element).html() +
"<i class='checkboxIcon glyphicon' ng-class='{\"glyphicon-check\" : isChecked === true, \"glyphicon-unchecked\" : isChecked === false }'></i>" +
"</div>";
Note that the tabindex attribute must go on the div element.
Thought, this is a bit of a simplistic answer for 2 reasons:
You will not always want a tabindex of 1. It will need to be relative to other elements of the page.
Just because it can be given focus, pressing space will not cause the onclick handler to be invoked. You will need some custom logic for that through using the onkeypress handler.
Your best bet is to use an input element as best as you can so that you can leverage native select handling.
For more information see msdn.
I’m new to Angularjs and the Dojo-Toolkit so please forgive my newbieness.
I have an element in my page that I’m able to bind to my model without any problem: <input type="text" ng-model="startDateRange"></input> This works as expected.
When I update the element so it uses the Dojo-Toolkit the binding appears to be broken: <input type="text" ng-model="startDateRange" data-dojo-type="dijit/form/DateTextBox"></input> The binding to the model no longer works.
I’m not sure what I’m doing wrong. Any help would be appreciated. Thanks.
AngularJS binding works upon DOM nodes, if you move, delete or replace a DOM node that AngularJS is watching, then the code will no longer work.
As Thomas Kagan said, Dojo widgets will replace the DOM node with the data-dojo-type on it by the DOM nodes provided in the template of those widgets. This simply erases your binding as if it didn't exist.
A proper AngularJS solution would be to wrap the Dojo DateTextBox inside a directive, so AngularJS knows that this is encapsulated and AngularJS should only access the directive through an API (the scope of a directive).
For example:
myApp.directive("dateTextBox", function($timeout) {
var link = function(scope, elem, attr) {
require(["dijit/form/DateTextBox"], function(DateTextBox) {
var dateTxtBox = new DateTextBox({});
dateTxtBox.set('value', scope.date);
dateTxtBox.on("change", function(date) {
$timeout(function() {
scope.date = date;
});
});
elem.append(dateTxtBox.domNode);
});
};
return {
restrict: 'E',
scope: {
date: "="
},
link: link
};
});
This is just a basic example, I also made a demo, which you can view by running the snippet below.
angular.module("myApp", []).controller("TestCtrl", function($scope) {
$scope.date = new Date();
})
.directive("dateTextBox", function($timeout) {
var link = function(scope, elem, attr) {
require(["dijit/form/DateTextBox"], function(DateTextBox) {
var dateTxtBox = new DateTextBox({});
dateTxtBox.set('value', scope.date);
dateTxtBox.on("change", function(date) {
$timeout(function() {
scope.date = date;
});
});
elem.append(dateTxtBox.domNode);
});
};
return {
restrict: 'E',
scope: {
date: "="
},
link: link
};
});
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dijit/themes/claro/claro.css" />
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.10.1/dojo/dojo.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="myApp" class="claro">
<div ng-controller="TestCtrl">
<date-text-box date="date"></date-text-box><br />
{{date | date}}
</div>
</body>
</html>
When the dojo parser runs it will identify the input element as a dijit widget and destroy your input element replacing it with a dojo widget so the ng-model property will no longer be present. I recommend using Dojo's observable module for data-binding instead of trying to mix in angular
I am trying to modify an Angular template before any other directive is triggered, in particular interpolation. I am doing this through the compile option in the directive definition.
Here is my test code:
<!doctype html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.js"></script>
<script>
angular.module('soQuestion', [])
.directive('example', function () {
return {
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
tElement.attr('foo', 'prefix-' + tAttrs.foo);
console.log(tElement.attr('foo'));
}
}
};
})
.controller('controller', function($scope) {
$scope.value = 'something';
});
</script>
</head>
<body ng-app="soQuestion">
<div ng-controller="controller">
<div example foo="keyword_{{value}}"></div>
</div>
</body>
</html>
However, the final result that I get is <div foo="keyword_something"></div> instead of <div foo="prefix-keyword_something"></div>, even if the compile function was triggered properly. What is going on here?
Its a directive priority issue, and admittedly I still don't completely understand. But don't for get that {{}} is itself a directive. Its getting applied in some order with yours, and overwriting your manipulation. If its terminal and high priority, it clears up.
DEMO
angular.module('soQuestion', [])
.directive('example', function () {
return {
priority: 1000,
terminal : true,
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
tElement.attr('foo', 'prefix-' + tAttrs.foo);
console.log(tElement.attr('foo'), tElement[0]);
}
return function(){};
}
};
})
.controller('controller', function($scope) {
$scope.value = 'something';
});
because this breaks the {{}} I would consider compiling the attr manually as well.
angular.module('soQuestion', [])
.directive('example', function () {
return {
compile: function (tElement, tAttrs) {
if (tAttrs.foo && tAttrs.foo.match(/^keyword/)) {
// Change the tAttrs hash instead of the element itself
tAttrs.foo = 'prefix-' + tAttrs.foo;
// Change the element too, in case no interpolation is present
tElement.attr('foo', tAttrs.foo);
console.log(tElement.attr('foo'));
}
}
};
})
Explanation: the interpolate directive does check for changes in the attribute value. However it doesn't look again on the DOM node itself but on the tAttrs hash.
Old pessimistic answer:
I don't think it's possible to achieve the desired result.
Looking at Angular's source, collectDirectives puts together the list of directives that affect a certain node. Their respective compile functions are collected and then sorted by priority. The problem is that the compile function of {{}} is then bound to keyword_{{value}} and is not affected by the example directive.
I'm trying to write an animation directive, that changes the width of an element and makes a change in the model afterwords. Here is my code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<div ng-app="myApp" ng-controller="MyCtrl">
<button ng-init="speed=20000" ng-click="model.width = model.width + 100;"> + </button>
<button ng-click="model.width = model.width - 100;"> - </button>
<div animate-to-width="model.width" speed="{{speed}}" done="model.done()" style="background-color: #f00; width: 100px;">w:{{model.width}}|a:{{model.a}}|b:{{model.b}}</div>
</div>
<script src="components/jquery/jquery.js"></script>
<script src="components/angular-unstable/angular.js"></script>
<script>
var myApp = angular.module('myApp',[]);
myApp.directive('animateToWidth', function() {
return {
'restrict': 'A',
'link' : {
'post': function(scope, element, attrs) {
scope.$watch(
attrs.animateToWidth,
function (newValue) {
element.animate(
{'width': newValue + 'px'},
attrs.speed,
function () {
scope.model.a++;
//scope[attrs.done]();
}
);
}
);
}
}
};
});
function MyCtrl($scope) {
$scope.model = {};
$scope.model.width = 100;
$scope.model.a = 0;
$scope.model.b = 0;
$scope.model.done = function () { $scope.model.b++; };
}
</script>
</body>
</html>
When I run this code the second parameter of the jQuery .animate() function seems not to affect the animation speeds and the callback (third parameter) will be called immediately instead after the animation has been finished.
My second problem is, that I would want to pass a callback from the controller into the directive and I don't know how to achieve this.
EDIT
Here is the solution (thanks to #banana-in-black):
http://plnkr.co/edit/D9TJHBYjtnxTve0xZpBS?p=preview
And here without those width values in the controller:
http://plnkr.co/edit/eiy99Crh57Jc78RhAsRt?p=preview
What you get from attrs.speed is String, and there's no effect if you set duration as String to jQuery.animate(). So make it a Number could solve the speed issue.
The callback after jQuery.animate() is called outside "the angular world", so you have to use $apply to make sure angular knows what happened to models.
If you didn't assign scope to directive, it will use the existing scope on the element. In this case, the div[animate-to-width] uses the same scope as MyCtrl does. You can just call your function which is set to scope in your controller.
scope.$watch(
attrs.animateToWidth,
function (newValue) {
element.animate(
{'width': newValue + 'px'},
attrs.speed * 1,
function () {
scope.$apply(function() {
scope.model.a++;
scope.model.done();
});
}
);
}
);
Example in Plunker