Call function on html page from controller in AngularJS - javascript

Long story short:
I have the following file structure:
class RandomCtrl {
constructor(randomService) {
this.randomService = randomService;
...
}
$onInit() {
getData.call(null, this);
}
...
}
updateLegendChart(){
RandomCtrl.chartStuff.chart.unload("ID-1234");
}
function getData(RandomCtrl) {
RandomCtrl.ChartDataService.getData(DemandCtrl.dataParams).then(result => {
RandomCtrl.result = result.data;
RandomCtrl.siteNames = result.data.map(element => element.SiteName);
RandomCtrl.keys = Object.keys(result.data);
RandomCtrl.chartStuff = getChart(result.data);
RandomCtrl.chartStuff.chart.unload("ID-1234"); ////<-HERE IT WORKS!!!
}).catch((e) => {
console.log(e);
});
}
function getChart(data) {
const chartOptions = getWeekHourlyOptions(data);
const allCols = [].concat(chartOptions.dataColumns);
...
return {allCols, chart};
}
...
RandomCtrl.$inject = ['randomService'];
export const Random = {
bindings: {
data: '<',
siteNames: '<'
},
templateUrl: randomPageHtml,
controller: RandomCtrl
};
I have a chart containing multiple lines each of them representing a site, I want to remove or add them when I click on their name in a legend section.
I do this by using load and unload methods of Billboard.js.
If a write it inside getData(), the line with HERE IT WORKS, it works but it does it every time I run the code, I want to do it only when I click a button.
The problem is that I cannot glue this functionality to an ng-click into an html page.
This is the html page:
<div class="demand page">
<div class="chart-legend-container">
<div ng-repeat="site in $ctrl.keys">
<chart-legend site="$ctrl.siteNames[site]" keys= "$ctrl.keys"></chart-legend>
<button ng-click="$ctrl.updateLegendChart()">CLICK ME</button>
</div>
<div>
</div>
My approach was to use updateLegendChart() which is a method on the controller which should be called when ng-click is triggered.
The method is in the controller and looks like this:
updateLegendChart(){
RandomCtrl.chartStuff.chart.unload("ID-1234");
}
The error says:
TypeError: Cannot read property 'chart' of undefined
Any idea how to call that function properly?

Inside $onInit hook 'this' keyword refers to the $onInit context , not to RandomCtrl
$onInit() {
getData.call(null, this);
}
Probably something you don't want to do, because then you're appending all those properties (result, chartStuff, etc.) to the wrong object.
..
//here RandomCtrl is still $onInit context, and not the the class context
RandomCtrl.chartStuff = getChart(result.data);
As a consequence when you invoke updateLegendChart(), RandomCtrl doesn't have any chartStuff field, thus you get the exception "TypeError: Cannot read property 'chart' of undefined"
updateLegendChart(){
RandomCtrl.chartStuff.chart.unload("ID-1234");
}
if you try passing RandomCtrl directly you should be fine.
$onInit() {
getData.call(null, RandomCtrl);
}

To make it work as expected it should be replaced RandomCtrl with this inside updateLegendChart() method like this:
updateLegendChart(siteNames){
this.chartStuff.chart.unload(siteNames);
}
It doesn't need to modify $onInit() method, it should be let as it is

Related

How to subscribe a control in a template to event listener?

I've added a tag to some of the components in my template like this.
<div>...</div>
<div #blopp>...</div>
<div>...</div>
<div #blopp>...</div>
<div #blopp>...</div>
<div>...</div>
I also do this in the class definition.
export class NavBar {
#ViewChildren("blopp", { read: ElementRef }) blopps: QueryList<ElementRef>;
constructor() { console.log("NavBar created"); }
ngAfterViewInit() { debugger; }
}
I can iterate over the elements in the debugger using the following script. However, I can't just use on(event,action) as I get the error that such method doesn't exist there.
blopps.forEach((element)=>{...});
Googling gave me something about blopp.listener(...) but it seems that my query list doesn't have that method neither. At the moment I feel less than well-oriented so it might be something rather obvious. What am I missing and how can I add events to my template's controls?
Field blopp has type QueryList, it means that it should be processed as in this example:
constructor(private renderer:Renderer){}
//AfterViewInit interface
ngAfterViewInit() {
blopps.forEach(elementRef => {
this.renderer.listen(elementRef.nativeElement, 'click', (e) => console.log(e));
});
}
You need to subscribe to changes and specify a type. If your element is native you can do it like this:
this.blopps.changes.subscribe(children => {
//note that children is a collection so you can do foreach here
children.last.nativeElement.focus();
// console.log(children.first['_results'][0].nativeElement);
console.log(children.first);
// Do Stuff or wire with referenced element here...
});
You need to call toArray() to get an array that you can iterate over:
blopps.toArray().forEach((element)=>{...});

Why is 'this' losing context in new Angular 1.5 components?

Got a very odd issue coming up here with the new components. When we had a 1.4 directive we had the following code...
(function () {
'use strict';
angular.module('app.board').directive('dcCb', dcClipboardCopy);
function dcCb() {
return {
link : function(scope, elem) {
var clipboard = new Clipboard(elem[0]);
elem.on('$destroy', function() {
clipboard.destroy();
});
}
};
}
})();
Inside the clipboard.destroy() function is the following...
Clipboard.prototype.destroy = function(){
this.listeners.destroy();
}
In 1.4 this is the same as the element so...
<button class="btn btn-sm btn-menu-outline copy-button" ...
So this worked fine as the button element seemed to have the listeners property which could be invoked.
However after the upgrade to 1.5 and now we have a component like this....
(function() {
'use strict';
angular.module('app.board').component('dcCb', {
...
controller: [ '$element','$scope',function($element,$scope) {
var self = this;
self.$postLink = postLink;
function postLink(){
var clipboard = new Clipboard($element[0]);
...
$element.on('$destroy', clipboard.destroy);
}
}]
});
})();
this (when inside the destroy function of the Clipboard) is now the controller object. So trying to call this.listeners throws an error.
First Question :
I understand that this in new components is the component scope but in 1.4 it was the button element. Surely in both the button element should be $element? Were we doing something wrong in 1.4?
Second Question :
Shouldn't var clipboard = new Clipboard($element[0]) force the context of this inside the clipboard to always be the clipboard itself (due to the new keyword)?
You're handing a function, which is arbitrarily defined on a class, off to the window and event listeners to be executed in a different context than the instance of Clipboard:
$element.on('$destroy', clipboard.destroy);
This is a fundamental concept of execution context in javascript, and I'd recommend reading up on it. But you can easily solve your current problem by simply binding the context of the function you are passing:
$element.on('$destroy', clipboard.destroy.bind(clipboard));

Why my applyBindings doesn't work? Knockout

Hello I am trying simply to create input and iframe and when I paste the YouTube link the iframe should change with the new src. I have done this so far
<div class="heading">id <input data-bind="text: youtubeLink"/></div>
<iframe id="player" type="text/html" width="444" height="250" frameborder="0" data-bind="attr: { src: linkEmbed }"></iframe>
And in the script:
function MyViewModel() {
this.youtubeLink = ko.observable('https://www.youtube.com/watch?v=4UNkmlCKw9M');
this.linkEmbed = ko.pureComputed({
read: function () {
var extract = this.youtubeLink().replace("/watch?v=", "/embed/");
console.log(extract)
return extract;
},
write: function (value) {
this.youtubeLink();
},
owner: this
});
}
ko.applyBindings(MyViewModel());
This works exactly as I want but the video wont change if I paste another link in the input.
I am using this from knockout documentation: http://knockoutjs.com/documentation/computed-writable.html
You have several problems:
You don't call new on your model, but you wrote it as a constructor
You use text binding instead of value binding for your input
Your computed's write doesn't assign, but you don't need it anyway
Once you correct those, it works.
function MyViewModel() {
var model = {};
model.youtubeLink = ko.observable('https://www.youtube.com/watch?v=4UNkmlCKw9M');
model.linkEmbed = ko.pureComputed(function () {
var result = model.youtubeLink().replace("/watch?v=", "/embed/")
return result;
});
return model;
}
ko.applyBindings(MyViewModel());
http://jsfiddle.net/ueoob7ne/2/
TLDR: jQuery hides knockout bind errors.
Another thing that breaks it....
jQuery is known to catch exceptions and hide them. I had to step through knockout-debug.js AND THEN jquery.js until i got to a part that looks like this (around line 3600)
// Only normal processors (resolve) catch and reject exceptions
process = special ?
mightThrow :
function() {
try {
mightThrow();
} catch ( e ) {
wouldn't you know it... I put a watch on (e) an here was what I found hidden in there:
Error: Unable to process binding "text: function(){return ko.toJSON(vm.model(),null,2) }"
Message: Multiple bindings (if and text) are trying to control descendant bindings of the same element

How do you make the meteorjs reactive-froala editor update when its value changes?

I'm using meteorjs and the froala-reactive editor.
In my router I return the collection data to the template, which works fine.
But I need the ability to update the contents of editor. What is the best way to update _value?
The template code:
{{> froalaReactive _onbeforeSave=doSave inlineMode=false _value=getText}}
The router.js code:
Router.route('admin/pages/:_id', function () {
this.render('Page', {
data: function () {
Session.set('editorContent', 'editor content here');
return Pages.findOne({_id: this.params._id})
}});
});
Helper function:
Template.Page.helpers({
getText: function () {
var self = this;
return function (e, editor, data) {
return Session.get("editorContent");
};
}
});
I expect that when the session variable editorContent changes the displayed content in the editor updates, but this is not working.
Your helper function should simply return the Session value, instead of a function.
Like this:
getText: function () {
return Session.get('editorContent');
}
Here's a working example that you can clone and play around with.

Understanding synthetic events in ReactJS

I need some help with understanding the so-called synthetic events in ReactJS. I wrote the following toy program that has a Video component and a VideoList component. When a video in the rendered list of videos is clicked, I would print out what video gets clicked in the console.
I don't understand how the event onVideoSelected() gets defined. Is it replaced by the onClick() event in the rendered Video component?
Thanks!
var Video = React.createClass({
handleClick: function() {
this.props.onVideoSelected(this.props.title);
},
render: function() {
return <li><div onClick={this.handleClick} className="bg-success">{this.props.title}</div></li>;
}
});
var VideoList = React.createClass({
propTypes: {
data: React.PropTypes.array.isRequired
},
handleVideoSelected: function(title) {
console.log('selected Video title is: ' + title);
},
render: function() {
return (
<div className="panel panel-default"><div className="panel-heading">List of Videos</div><ul>
{data.map(function (v) {
return <Video onVideoSelected={this.handleVideoSelected} key={v.title} title={v.title} />;
},this)}
</ul></div>
);
}
});
var data = [
{title: 'video title 1', link: 'http://www.youtube.com/1'},
{title: 'video title 2', link: 'http://www.youtube.com/2'},
{title: 'video title 3', link: 'http://www.youtube.com/3'}
];
React.render(<VideoList data={data} />, document.getElementById('videolist'));
There's actually no magic going on here, just passing functions around. onVideoSelected is a function reference that you passed into the Video component via a property; said another way, the flow goes like this:
What happens when you click the div? Call this.handleClick.
What happens when you call handleClick? Call this.props.onVideoSelected.
How is onVideoSelected defined? It got passed into the component, just like any other property.
What was passed in to the onVideoSelected property? A reference to the VideoList's handleVideoSelected function.
It may help to compare it to some sorta-similar, simplified jQuery code:
function handleVideoSelected(title) {
console.log('selected Video title is: ' + title);
}
function createVideoDiv(onVideoSelected, title) {
var div = $("<div className="bg-success"></div>").text(title).appendTo(...);
div.on("click", function() {
// call the function that was passed to us
onVideoSelected(title);
});
}
$.each(videos, function(idx, video) {
createVideoDiv(handleVideoSelected, video.title);
});
In the jQuery version, you pass handleVideoSelected into createVideoDiv; similarly, in the React version, you pass handleVideoSelected into Video via props.
After your onClick handler is called in the Video component you are no longer dealing with events; these are plain old function calls.
To keep a reference to the video title, pass a curried version of handleVideoSelected with the title as the first arg by using Function.prototype.bind:
{this.props.data.map(function (v) {
return <Video onVideoSelected={this.handleVideoSelected.bind(this, v.title)} key={v.title} title={v.title} />;
}, this)}
(I also prepended this.props to data. Looks like a typo in your code.)
This is how individual Todos are identified in the "Expose Component Functions" doc.

Categories