MathJax not Rendering With Angular - javascript

The issue I have is this:
On my website, I pull my blog content from an external source which, the first time it's called upon to display, has to use a HTTP request to get. Also, the blog posts are written in Markdown, and I have to parse this to HTML.
I have this controller that goes out and gets the posts from github, decodes them, and parses them into HTML:
app.controller('content', function ($scope, github, html) {
github.getAllContent().then(function (res) {
var files = [];
res.data.forEach(function (obj) {
github.getFile(obj.path).then(function (res) {
res.data.content = marked(window.atob(res.data.content));
res.data.name = res.data.name.slice(0, res.data.name.indexOf('.'));
files.push(res.data);
})
});
$scope.files = files;
});
$scope.renderHtml = html.renderHtml;
});
html is this service
app.service('html', function ($sce) {
this.renderHtml = function (string) {
return $sce.trustAsHtml(string);
}
});
that allows me to insert the HTML into each HTML element like this: <elem>ng-bind-html="renderHtml(info) </elem>".
Whenever I do this, however, LaTeX content isn't rendered. I have configured MathJax to recognize $ ... $ as delimiters, but no matter what happens, I can't seem to get anything to render. I have even called the MathJax.Hub.Typeset() function or set the typeset callback in the MathJax.Hub.Queue function and it doesn't work. Is this because of the markdown parser I use or how the data is encoded? Or is it just a matter of typesetting the MathJax at the right time?
In this project, I use Angular ui-router, if that has anything to do with it.

I have used this (mathjaxBind.directive.js) directive in my project for MathJax and its working for me:
Plunker
mathjaxBind.directive.js
'use strict';
MathJax.HTML.Cookie.Set("menu", {});
MathJax.Hub.Config({
skipStartupTypeset: true,
messageStyle: "none",
extensions: ["tex2jax.js", "mml2jax.js", "MathML/content-mathml.js", "MathML/mml3.js"],
jax: ["input/MathML", "input/TeX", "output/SVG", "output/HTML-CSS", "output/NativeMML", "output/CommonHTML"],
"HTML-CSS": {
availableFonts: [],
styles: {".MathJax_Preview": {visibility: "hidden"}},
showMathMenu: false
},
"SVG": {
availableFonts: [],
styles: {".MathJax_Preview": {visibility: "hidden"}},
showMathMenu: false
},
"NativeMML": {
availableFonts: [],
styles: {".MathJax_Preview": {visibility: "hidden"}},
showMathMenu: false
},
"CommonHTML": {
availableFonts: [],
styles: {".MathJax_Preview": {visibility: "hidden"}},
showMathMenu: false
}
});
MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready", function () {
var FONT = MathJax.OutputJax["HTML-CSS"].Font;
FONT.loadError = function (font) {
MathJax.Message.Set("Can't load web font TeX/" + font.directory, null, 2000);
document.getElementById("noWebFont").style.display = "";
};
FONT.firefoxFontError = function (font) {
MathJax.Message.Set("Firefox can't load web fonts from a remote host", null, 3000);
document.getElementById("ffWebFont").style.display = "";
};
});
(function (HUB) {
var MINVERSION = {
Firefox: 3.0,
Opera: 9.52,
MSIE: 6.0,
Chrome: 0.3,
Safari: 2.0,
Konqueror: 4.0,
Unknown: 10000.0 // always disable unknown browsers
};
if (!HUB.Browser.versionAtLeast(MINVERSION[HUB.Browser] || 0.0)) {
HUB.Config({
jax: [], // don't load any Jax
extensions: [], // don't load any extensions
"v1.0-compatible": false // skip warning message due to no jax
});
setTimeout('document.getElementById("badBrowser").style.display = ""', 0);
}
})(MathJax.Hub);
MathJax.Hub.Register.StartupHook("End", function () {
var HTMLCSS = MathJax.OutputJax["HTML-CSS"];
if (HTMLCSS && HTMLCSS.imgFonts) {
document.getElementById("imageFonts").style.display = ""
}
});
angular.module('fsaApp')
.directive('mathjaxBind', function () {
return {
restrict: "A",
controller: ["$scope", "$element", "$attrs", function ($scope, $element, $attrs) {
$scope.$watch($attrs.mathjaxBind, function (value) {
//$($element).parent().find('math').wrap("<script type='math/mml'></script>");
$element.html(value);
MathJax.Hub.Queue(["Typeset", MathJax.Hub]);
});
}]
};
});
Controller:
app.controller('MainCtrl', function($scope) {
$scope.info='<script type=\"math/mml\"><math>\n<mstyle displaystyle=\"true\">\n<mtext> N </mtext>\n<msub>\n<mrow>\n<mtext> O </mtext>\n</mrow>\n<mrow>\n<mn> 2 </mn>\n</mrow>\n</msub>\n</mstyle>\n</math></script>';
$scope.info2='<script type=\"math/mml\"><math>\n<mstyle displaystyle=\"true\">\n<mtext> C </mtext>\n<msub>\n<mrow>\n<mtext> H </mtext>\n</mrow>\n<mrow>\n<mn> 4 </mn>\n</mrow>\n</msub>\n</mstyle>\n</math></script>';
});
Library:
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
in HTML:
<div mathjax-bind="info"></div>
<div mathjax-bind="info2"></div>

Related

Polymer1: addEventListener for Notify Not Working as Expected

I am trying to call window.addEventListener on my custom behavior however im not having any luck getting it to work.
test-bahvior.html
<script>
"use strict";
window.MyTest = window.MyTest || {};
MyTest.Test = {
properties: {
globals: {
type: Boolean,
notify: true,
value: false
}
},
ready: function() {
setTimeout(() => {
this.globals = true;
console.log('changed val ' + this.globals);
}, 5000);
},
};
</script>
i am then trying to callwindow.addEventListener("globals-changed", this._test); in the ready: function() of another html file (myapp.html) however this._test doesnt seem to fire despite the setTimeout causing the value change.
I have been following the Polymer 1 docs:
https://polymer-library.polymer-project.org/1.0/docs/devguide/properties#notify
Help is much appreciated.
TIA

Cannot read property 'data' of undefined on a not-even started $http.get request

I have a web application, started by a previous company, written in Angular.JS.
The application exposes a request towards the back-end (written in Node.JS+Express) to gather some data required to fill a table.
Specifically, this is the request that the application sends everytime the user enters in the page that holds the table (The config variable holds the access token).
define(['app'], function (app) {
app.factory('AdvService', AdvService);
AdvService.$inject = ['BasicService'];
function AdvService(BasicService) {
var service = angular.extend(BasicService, {});
var $http = service.$http;
var API = service.API
var handleSuccess = service.handleSuccess;
var handleError = service.handleError;
var config = {
params: {
'token': JSON.parse(window.sessionStorage.getItem('session')).token
}
};
service.searchByCriteria = function (criteria) {
debugger;
config.params.pageNumber = criteria.pageNumber;
config.params.pageSize = criteria.pageSize;
return $http.get(API + '/api/v2/admin/ads/getAdsByCriteria', config).then(handleSuccess, handleError);
};
service.createAd = function (ad) {
debugger;
return $http.post(API + '/api/v2/admin/ads/insertNewAd', ad, config).then(handleSuccess, handleError);
};
return service;
}
});
handleSuccess and handleError are so defined
define(['app'], function (app) {
app.factory('BasicService', BasicService);
BasicService.$inject = ['CONF', '$http', '$window', '$q'];
function BasicService(CONF, $http, $window, $q) {
$http.defaults.headers.common['x-access-token'] = JSON.parse($window.sessionStorage.getItem('session')).token;
return {
$http: $http,
API: CONF.API_URL,
handleSuccess: function (res) {
debugger;
var deferred = $q.defer();
res.data.success ? deferred.resolve(res.data) : deferred.reject(res.data.message);
return res.data;
},
handleError: function (error) {
debugger;
return {
success: false,
message: error
};
}
}
}
})
and this is the only point of the application that calls that service
($scope.search = function () {
debugger;
AdvService.searchByCriteria($scope.searchCriteria).then(function (res) {
debugger;
$scope.searchRes = res.data.docs;
//$scope.gridOptions.totalItems = res.data.total;
}, function (err) {
console.log(err);
});
})();
Being a CORS request (the front-end is at port 8010 and the back-end in another one), I see from Chrome's Network Monitoring System that the $http.get part gets executed twice, but here's my problem: even before starting handling on the back-end the first call, the front-end generates the error
angular.js:14961 TypeError: Cannot read property 'data' of undefined
at Object.<anonymous> (ui-grid.js:3291)
at Object.invoke (angular.js:5117)
at $controllerInit (angular.js:11139)
at nodeLinkFn (angular.js:10002)
at angular.js:10410
at processQueue (angular.js:17330)
at angular.js:17378
at Scope.$digest (angular.js:18515)
at Scope.scopePrototype.$digest (hint.js:1495)
at Scope.$apply (angular.js:18903)
and even tough the request does return data, having crashed, Angular cannot correctly render everything.
The only thing I tried was to use the Await/Async mechanism to try to wait and see what could have happened but this always resolves in the above error.
Does anybody have any clue of what's going on? I'm 100% positive that the code that I've posted here is the only one that gets called during this process and I can't honestly understand why should the process fail if both requests return data
So, as correctly pointed out by Alon Eitan in the comments, the error was somehow related with ui-grid and that, probably, it might have been that the gridOptions were not correctly called like the HTML was excepting.
IN FACT ...
HTML
<div class="alerts container-fluid" ng-controller="AdsController">
<div class="row">
WORK IN PROGRESS!
<!-- <span>PubblicitĂ  attive: <b>{{activeAdsCounter}}</b> - </span>-->
<!-- <span>PubblicitĂ  totali: <b>{{totalAdsCounter}}</b> - </span>-->
</div>
<button ng-click="openCreateNewAdModal()"><i class="material-icons">library_add</i></button>
<div class="row">
<div class="col-md-12">
<h3>PubblicitĂ  Nel Sistema</h3>
<div class="grid" ui-grid="adsGridOptions" ui-grid-pagination
ui-grid-auto-resize></div>
</div>
</div>
</div>
Controller
define(['app', 'moment'], function (app, moment) {
app.controller('AdsController', AdsController);
AdsController.$inject = ['$scope', '$mdDialog', 'AdvService'];
function AdsController($scope, $mdDialog, AdvService) {
debugger;
$scope.rowHeight = 30;
$scope.gridOptions = {
data: 'searchRes',
paginationPageSizes: [25, 50, 75],
paginationPageSize: 25,
enableSorting: true,
enablePaginationControls: true,
enableColumnMenus: false,
useExternalPagination: true,
rowHeight: $scope.rowHeight,
columnDefs: [
{
name: 'Cliente',
field: 'customerName',
}, {
name: 'Durata',
field: 'duration',
}, {
name: 'Euro Spesi',
field: 'spentEuros',
}, {
name: 'Data inserimento',
field: 'createdAt',
type: 'date',
width: '130',
cellFilter: "date:'dd-MM-yyyy'",
}, {
name: 'Email Referente',
field: 'referralEmail',
}, {
name: 'Nome Referente',
field: 'referralPerson',
}, {
name: 'Numero Referente',
field: 'referralNumber',
},
],
onRegisterApi: function (gridApi) {
$scope.gridApi = gridApi;
$scope.gridApi.core.on.sortChanged($scope, function (grid, sortColumns) {
if (sortColumns[0]) {
console.log(sortColumns[0].sort);
$scope.searchCriteria.sort = {};
$scope.searchCriteria.sort[sortColumns[0].field] = sortColumns[0].sort.direction;
}
});
$scope.gridApi.pagination.on.paginationChanged($scope, function (pageNum, pageSize) {
$scope.searchCriteria.pageNumber = pageNum;
$scope.searchCriteria.pageSize = pageSize;
$scope.search();
});
}
};
The controller had the options called as "gridOptions", while the HTML had them called "adsGridOptions". Changing the name to "gridOptions" to both sides solved the issue.
Thank you, Alon Eitan!

Error accessing AngularJS factory methods/arrays

I'm dividing my functions/objects into service and factory methods, and injecting them into my controller patentTab. I had a code for a tab panel which I originally placed within the controller patentTab that worked.
Now I have placed this code in a factory method and for some reason the content isn't loading. Console log shows no errors, and when I click the relative tab the correct URL is loaded, but the content doesn't change. Is there an issue with my array in the factory? If not, what is the reason?
Orginal code
app.controller('patentTab', function($scope, $http){
$scope.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
$http.get('../json/patent-info.json').then(function(response){
$scope.patentData = response.data.patentInfo;
})
$scope.currentTab = 'patent-info.htm';
$scope.onClickTab = function (tab) {
$scope.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
}
$scope.isActiveTab = function(tabUrl) {
return tabUrl == $scope.currentTab;
}
});
New code (with issue)
app.controller('patentCtrl', ['$scope', '$http', 'patentTabFactory', function($scope, $http, patentTabFactory) {
$http.get('http://localhost:8080/Sprint002b/restpatent/').then(function(response) {
$scope.patents = response.data;
});
$scope.loadPatentItem = function(url) {
$scope.patentItem = url;
}
$scope.tabs = patentTabFactory.tabs;
$scope.currentTab = patentTabFactory.currentTab;
$scope.onClickTab = patentTabFactory.onClickTab;
$scope.isActiveTab = patentTabFactory.isActiveTab;
}]);
app.factory('patentTabFactory', function() {
var factory = {};
factory.tabs = [{
title: 'Patent Information',
url: 'patent-info.htm'
}, {
title: 'Cost Analysis',
url: 'cost-analysis.htm'
}, {
title: 'Renewal History',
url: 'renewal-history.htm'
}];
factory.currentTab = 'patent-info.htm';
factory.onClickTab = function (tab) {
factory.currentTab = tab.url; //the tabs array is passed as a parameter from the view. The function returns the url property value from the array of objects.
console.log(tab.url);
}
factory.isActiveTab = function(tabUrl) {
return tabUrl == factory.currentTab; //for styling purposes
}
return factory;
});
You not calling factory.onClickTab() method from your controller.
It should be like :
$scope.onClickTab = function(currentTab) {
patentTabFactory.onClickTab(currentTab);
$scope.currentTab = patentTabFactory.currentTab;
};
and, for isActiveTab, Like :
$scope.isActiveTab = patentTabFactory.isActiveTab(currentTab);
Here is a plunker where I am using a factory. The only changes I have done are:
1. Place the factory file before the app script file.
2. Use a separate declaration for factories and then inject it in the app.
var factories = angular.module('plunker.factory', []);
factories.factory('patentTabFactory', function() {
// Factory bits
};
I have injected the factories in the app.
var app = angular.module('plunker', ['plunker.factory']);
Here is a working plunker for that. PlunkR

Disable items in ng-repeat list on click of one other item

Here is my plnkr with my progress so far: http://plnkr.co/edit/iEHMUMlASZaqdMQUeF7J?p=preview
I'm having problems implementing the following functionality however.
When an item on the list is clicked, I need to disable the remaining items on the list. ie, another request should not take place, and these remaining items' colour should change to indicate the disabled state.
Once the request has taken place, then the entire list should go back to the original state.
Edit: I've made some progress. Although a bit messy it's getting me a bit closer. My problem is the following line:
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
This prevents the click event running more than once at a time. However it's stopping the click event from running all together once its run for the first time. I would like to be able to re-run the process once it is complete an unlimited amount of times.
Directive:
app.directive('listItem', function (ListService, $timeout, $location) {
return {
restrict: 'ACE',
controller : 'ItemController',
template: '<p>{{item}} {{foo}}</p>',
link: function (scope, element, attrs) {
$(element).bind('click', function (e) {
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
$(this).addClass('pending');
var elem = $(this);
$timeout(function () {
ListService
.selectItem(scope.item)
.then( function () {
console.log('success');
elem.removeClass('pending').addClass('success');
//$location.path('foo.html')
scope.foo = 'not bar';
}, function () {
console.log('error');
elem.removeClass('pending').addClass('error');
elem.parent().removeClass('item-selected');
});
;
}, 2000);
});
}
};
});
The entire app code including directive:
var app = angular.module('listtestApp', []);
app.service('ListService', function ($http) {
var data = [
'alpha',
'bravo',
'charlie',
'delta',
'foxtrot'
];
return {
getData : function () {
return data;
},
selectItem : function () {
return $http({ method: 'GET', url : '/data/list.json'});
}
}
});
app.controller('ListController', function ($scope, ListService) {
$scope.list = ListService.getData();
$scope.foo = 'Bar';
});
app.controller('ItemController', function ($scope, ListService) {
});
app.directive('listItem', function (ListService, $timeout, $location) {
return {
restrict: 'ACE',
controller : 'ItemController',
template: '<p>{{item}} {{foo}}</p>',
link: function (scope, element, attrs) {
$(element).bind('click', function (e) {
$(this).parent().addClass('item-selected').children().unbind('click').removeClass('pending');
$(this).addClass('pending');
var elem = $(this);
$timeout(function () {
ListService
.selectItem(scope.item)
.then( function () {
console.log('success');
elem.removeClass('pending').addClass('success');
//$location.path('foo.html')
scope.foo = 'not bar';
}, function () {
console.log('error');
elem.removeClass('pending').addClass('error');
});
;
}, 2000);
});
}
};
});
html markup below:
<body ng-app="listtestApp">
<div ng-controller="ListController">
<div ng-repeat="item in list" list-item>
</div>
</div>
</body>
You have several solutions at your disposal :
Check that any element has the pending or success or error class
use your function scope to store it in a variable
EDIT : if you want to re-enable selection after the request has been posted, you could use something like this (variant of version #1)

How to produce a highchart (using angular js) with json data coming from an Ajax request

My question is related to the link How to handle Highcharts events from an AngularJS directive?. What if I want to have the highchart generated from dynamic data? my chart object is defined/configured as below,
chart: {
type: 'bar'
},
series: [{
name: 'A,B,C,D',
score: [1,2,2,3]
}],
legend: {
enabled: false
}
I want to feed the corresponding 'name' and 'score' data dynamically from json string obtained from Ajax request and is of the form,
[{"Name":"A","Score":1},{"Name":"B","Score":2}]
Please let me know if i need to provide any other details.
Many Thanks.
Re-framing the question:
I want to create a highchart using angular js. My javascript file is
var app = angular.module('charts', []);
app.directive('highchart', function () {
return {
restrict: 'E',
template: '<div></div>',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(function () { return attrs.chart; }, function () {
if (!attrs.chart) return;
var charts = JSON.parse(attrs.chart);
$(element[0]).highcharts(charts);
});
}
};
});
app.controller('Ctrl', function ($scope, $http, $timeout) {
$scope.overSpeedWorstRecords = [];
$scope.handleOverSpeedWorstRecords = function (data, status) {
$scope.overSpeedWorstRecords = data;
}
$http.get('http://localhost:12345/abc/pqr').success($scope.handleOverSpeedWorstRecords).error("error message");
$timeout($scope.fetch, 1000);
$scope.renderChart = {
chart: {
type: 'bar'
},
series: [{
name: 'A,B,C,D',
score: [1,2,2,3]
}],
legend: {
enabled: false
}
};
});
I am getting my json data in overSpeedWorstRecords through an Ajax query ($http.get). Additionally, I have defined a chart object with 'name' and 'score' hardcoded. With this setup I am having the highchart loaded with hardcoded data and I am getting the json data as well which I can access in the HTML as follows,
<!DOCTYPE>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Dashboard Application</title>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="http://code.highcharts.com/highcharts.js"></script>
<script type="text/javascript" src="Scripts/DashboardCtrl.js"></script>
</head>
<body>
<section ng-app="charts">
<div ng-controller="Ctrl">
<highchart chart='{{renderChart}}'></highchart>
<table>
<tr ng-repeat="record in overSpeedWorstRecords">
<td>{{record.Name}}</td>
<td>{{record.Score}}</td>
</tr>
</table>
</div>
</section>
</body>
</html>
However, I want to feed the json data which I am getting through the Ajax call, to the chart object to create the bar chart dynamically.
Please let me know if I need to elaborate the problem further.
Solution to the Problem:-
I solved the problem by myself. I am posting this solution for my future reference and in case it helps somebody having the same requirement.
The javascript was modified so that the Name and Score could be accessed from the json data. They were stored in the 'name' and 'score' arrays which were passed to the chart option object in order to render the highchart.
var app = angular.module('charts', []);
app.directive('highchart', function () {
return {
restrict: 'E',
template: '<div></div>',
replace: true,
link: function (scope, element, attrs) {
scope.$watch(function () { return attrs.chart; }, function () {
if (!attrs.chart) return;
var charts = JSON.parse(attrs.chart);
$(element[0]).highcharts(charts);
});
}
};
});
app.controller('Ctrl', function ($scope, $http, $timeout) {
$http.get('http://localhost:1234/abc/pqr').success(function (data, status) {
var score = [];
for (var i = 0; i < data.length; i++) {
score.push(data[i].Score);
}
var name = [];
for (var i = 0; i < data.length; i++) {
name.push(data[i].Name);
}
$scope.renderChart = {
chart: {
type: 'bar'
},
xAxis: {
categories: name
},
series: [{
data: score
}],
legend: {
enabled: false
}
};
}).error("error message");
$timeout($scope.fetch, 1000);
});

Categories