What I want is very simple, I want the Expand All button to be auto clicked when I open this pluralsight course page. Its HTML is:
<a id="expandAll"
ng-click="expandAllModules()"
ng-hide="allModulesExpanded()">
Expand All
</a>
So it seems easy and we just need to call the function expandAllModules(). However I don't know why it give me undefined when I check its type:
typeof expandAllModules
=> "undefined"
Generally typeof a function should give me "function" like this:
function a(){}
=> undefined
typeof a
=> "function"
Since the function expandAllModules() is not available, I can't call it. Anyone can give me a hand on this issue?
Edit
Perhaps I need to elaborate on my question. I'm not the author of that page. I just want to make a simple greasemonkey or tempermonkey script and expand the modules automatically when I enter the page.
The Problem
The reason calling just expandAllModules() doesn't work is because this function belongs to one of Angular's scopes and isn't a method assigned to window. This function is defined in Plural Sight's table-of-contents-controller-v9.js like so:
"use strict";
pluralsightModule
.controller("TableOfContentsController", ['$scope', ..., function ($scope, ...) {
...
$scope.expandAllModules = function() {
_.each($scope.courseModules, function (module) { module.visible= true; });
};
...
}])
The Solution
In order for us to call this function ourselves, we have to go through this scope.
scope is an object that refers to the application model. It is an execution context for expressions. Scopes are arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can watch expressions and propagate events. – AngularJS: Developer Guide
The scope is part of the element which triggers the function. We can access this particular scope by passing the element's id attribute into angular.element(), then calling scope() on that object:
angular.element('#expandAll').scope()
This will give us the following data, where we can see the expandAllModules() function:
Unfortunately AngularJS doesn't let us simply execute scope().expandAllModules(); instead we have to go through it's $apply and $eval methods:
var scope = angular.element('#expandAll').scope();
scope.$apply(function() {
scope.$eval(scope.expandAllModules())
});
We can now also collapse the modules as well by calling:
scope.$apply(function() {
scope.$eval(scope.collapseAllModules())
});
I apologize if I am off-base here. Are you trying to "link" into that page and 'force' the page to "expand all", or do you have access to the page, and want to trigger the click with some code on the page, by you inserting the code? Just doing something like this seems to work from commandline.
jQuery(function(){
jQuery('#expandAll').trigger('click');
});
Since I do not know your need, my thought is that this is a bit simplistic and not what you are looking for. From the responses of others, it appears you want to create your own directive to initiate the click?
I might have some typos -- but the idea is there.
angular.element(document.body).ready(function() {
var el = angular.element( document.getElementById('expandAll') );
var scope = el.scope();
scope.expandAllModules();
scope.$digest(); <--- might not be needed, but when i check your site, it needs to have this
});
updates
if it was just 'onclick' instead of 'ng-click', you do not need to get the scope; and just call the function directly.
updates
I have tried this on your site, you need to have scope.$digest(). When I tried it, i was using the developer console.
see the developer console below
I was playing with it on your site.
Related
Coming from a C++ background, trying to work with an OO language that doesn't have explicit typing is a little more than a headache.
So I have dynamic elements for a webpage that are "controlled" by objects since there are tons of stuff I need to manage on each for it to work. The element is just the visual output of the data inside of the object itself, that's all I really need it for.
Except that I need the object to perform an internal function when it's clicked. That seems to be the biggest source of my headache thus far.
Javascript:
function onClick(file) //The external onClick function I use to try to get it to call from.
{
file.state = INUSE;
file.checkState();
}
function fileObject () { //The file object itself
this.element;
this.newElement();
//initialize stuff for the object
}
fileObject.prototype.newElement = function() { //creates a new element and sets its event listener
this.element.click(function() {onClick(this)});
}
fileObject.prototype.checkState = function() {/*does stuff*/} //apparently this is "not a function"
The error I get exactly is "file.checkState is not a function" from Firefox's console panel.
I'm still new to javascript, but after doing some debugging, I've come to find out that it's explicitly the onClick(this) function that is causing all of the errors. When used with something else, the onClick function works perfectly, but for some reason, the this keyword doesn't appear to actually be sending the reference to the fileObject since all checks show file being undefined when inside of the onClick scope.
Is there something fundamentally wrong about the way I'm trying to do this or am I just missing a step (or adding something that I don't need) that will help get this snippet working.
So you know, your initial problem isn't actually handling the action, but listening to it. click will trigger a synthetic click event, rather than liste for one.
You want ... .element.addEventListener("click", callback); that said, you face a second problem, immediately thereafter.
I will leave my example code as you've written it to not confuse the matter...
But when you see click( ) know that I mean subscribing with addEventListener, if element really does mean a browser DOM element. If it's not a standard browser element, and your own API, then ignore the previous portion, and carry on.
this is dynamically bound at the invocation time of the function (not at definition time).
The nearest function, scoped above, is your callback function that you are passing into .click( ... ).
Which is entirely different than the this which you mean outside of the callback.
Whatever is on the left-hand side of the dot is the this context for the duration of that particular invocation.
Needless to say, click() doesn't know enough to bind the this you mean, to the left-hand side of your callback.
The solution (or one of many) is to use lexical scoping and/or closure to retain the value of the object you mean.
// easy but messier
var fileObject = this;
... .click(function () { onClick(fileObject); });
// Cleaner with thunks:
function clickHandler (onClick, obj) {
return function () { onClick(obj); };
}
... .click(clickHandler(this));
Coming from c++ the way Javascript handles this will seem a little crazy, it looks like here you need to tell the function you've defined what this is - like so:
this.element.click(function() {onClick(this)}.bind(this));
My goal is to create an Angular module that displays popup dialog messages. This module contains a directive (HTML, CSS and JavaScript) containing the internal logic (and markup and styles). Plus there's a service (factory) which acts as an API that can be used by other services.
Now this service of course has an openDialog() function which should insert the dialog directive into the DOM and present it to the user.
All solutions to this problem I have found so far make use of the $compile function. But it needs scope as a parameter. In a service where there's no scope though. They only exist in controller or link functions.
The reason I chose this implementation is for separation of concerns (directive's link and controller for internal usage, factory for external usage because it can be dependency injected). I know I could pass the scope when calling the function like this:
popupDialogService.openDialog({ /* options */ }, $scope);
But I don't see the point. It doesn't feel right. What if I call that function from inside another service which doesn't use scope either?
Is there a way to easily put the directive into the DOM from inside the service function or is there a better way to solve this problem?
Another solution I'm thinking about is calling a function of the directive's controller from inside the directive's factory. Is that possible?
Code
popupDialog.directive.js
angular.module('popupDialog').directive('popupDialog', directive);
function directive() {
return { ... };
}
popupDialog.service.js
angular.module('popupDialog').factory('popupDialogService', factory);
function factory() {
return { openDialog, closeDialog }; // *ES2015
function openDialog(options) {
// this function should put the `popupDialog` directive into the DOM
}
function closeDialog() {
// and this one should remove it
}
}
some.random.service.js
angular.module('myApp').factory('someRandomService', factory);
factory.$inject = ['popupDialogService'];
function factory(popupDialogService) {
return { clickedButton };
function clickedButton() {
popupDialogService.openDialog({ /* options */ });
// Sample implementation.
// It shouldn't matter where this function is beeing called in the end.
}
}
I know I could pass the scope when calling the function ... And it doesn't feel right.
Well you anyway need scope for dialog HTML content, Angular needs to compile and render it in some scope, right? So you have to provide scope object for your template somehow.
I suggest you to take a look at popular modal implementations like they do it, for example Angular UI Bootstrap's $modal or this simple one I was creating for my needs. The common pattern is passing scope parameter with modal initialization or use new child scope of the $rootScope for dialog. This is the most flexible way that should work for your both cases.
After all, it's not necessarily has to be real scope instance. You can even make your service accept plain javascript object and use it to extend new $rootScope.$new() object with.
Similar to this question, I would like to break on a variable change in Chrome. However, I'm using Angular, so the variable I would like to break on is only defined in the HTML. Its essentially something like this -
<div ng-click="show = !show">...<div>
<div ng-class="{expanded : show}">...
With no code in the controller. So how can I get Chrome to pause when show is changed? I know that I can change the code to a function call that wraps show and then put a breakpoint there, but its an inefficient use of time and I would like to know if there is a way to directly break on variable change.
Well, you can achive the desired feature via console:
First get access to the scope, on which show property is defined. Here
is an explanation of how you can do this.
Then you may observe changes of this scope object via Chrome's Object.observe.
To simplify this process you may define global function like this one:
function breakOn(property, object) {
Object.observe(object, function (changes) {
changes.forEach(function (change) {
if (change.name === property) {
console.log("Property " + property + " changed");
console.log(change);
debugger;
}
});
});
}
And then after step 1 type in the console: breakOn('show', $scope);
I am trying to create namespaces in JavaScript as in the following script:
var hlAdmin = hlAdmin || {};
hlAdmin.editCompany = function (src) {
// function script
}
Then I call the function in HTML:
onclick="hlAdmin.editCompany(123)"
I get a reference error: Cannot find "editCompany".
Anyone know why?
Based on your comments I assume the following:
The equivalent script (and scoping is like):
<html><head>
</script>
var hlAdmin = hlAdmin || {};
hlAdmin.editCompany = function (src) {
// error in this script
}
</script>
</head></body>
<button onclick="hlAdmin.editCompany(123)">Caption</button>
</body></html>
In this example hlAdmin is indeed in the global scope (the root-scope of the host, called window in browsers).
If (in this example) you get reference error: Cannot find "editCompany", then one should look at other error-messages in your (browser's) error-log, because when there is a fatal error in the function for hlAdmin.editCompany, then that function will not be created (hence .editCompany becomes a property that points to undefined instead of a method that points to the function OR .editCompany doesn't even exist (depending on engine/error)).
To investigate if you indeed have a scoping-problem you could test this by: window['hlAdmin'] || (window['hlAdmin']={}); (or some equivalent variant). If that made the code work, then it seems you have some scoping-problem.
Hope these steps help someone in the future.
It's generally considered bad form to mix inline javascript and non-inline. The preferred way to do this would be to keep all the javascript in one place using an event handler:
window.hlAdmin = window.hlAdmin || {};
window.hlAdmin.editCompany = function (src) {
// function script
}
document.getElementById('yourElementId').onclick = function() {
hlAdmin.editCompany(123);
};
To more specifically address the issue: One thing that could cause this issue is if the hlAdmin object is not ending up in the global scope. You stated that this declaration is "at the top of the JavaScript file", but if it's in any kind of function (such as a function set to window.onload, or the jQuery $(function() { ... });) it would not end up in the global scope when declared as a var. A variable declared with var will only end up globally scoped if it's in the root scope, outside of any kind of function. If rather than using var hlAdmin you instead use window.hlAdmin, this will make sure that even if you're inside a document ready function or something similar, you're creating your hlAdmin in the global context, which will fix the problem if it is in fact an issue of scope.
I found the problem.
The browsers (at least Aurora and Chrome) are dropping the namespace in the onclick attribute. When you look at the browser html the namespace has just disappeared from the markup.
UPDATE: The back-end service was powered by an ASP.Net AJAX Web Service proxy.
1) The main page has two global objects, one for the back end connections (Svc) and another for handling the DOM (Main). It also dynamically loads documents into an iframe.
2) These iframes need to access services provided by Svc, and also supply a callback function.
3) The problem - passing a function created in the iframe to the parent frame, it's treated as an object not a function and cannot be invoked.
Can anyone provide a better solution than what I've got currently in the iframe:
var Main = parent.Main,
Svc = parent.Svc;
Svc.method(data, Main.createCallback(
function(response) {}
));
and in the parent frame:
Main.createCallback = function(func) {
return function() {
func.apply(func, arguments);
}
}
if you override the iFrame's function from the main, the main scope will then be used.
The inverse problem can be seen here, in your case, you just override the frame's function itself i.e:
document.getElementById('yourFrameID').contentWindow.targetFunctionInFrame = targetFunctionInMain;
Bonus: if you can modify the iFrame's code, I would suggest to:
In the frame:
make a placeholder function callbackParent() {}
add a call to this function into your iframe code, so that you just have to override the callbackParent from your main.
In the main:
make the function which should be invoked function doStuff() {}
override the function as described above document.getElementById('yourFrameID').contentWindow.callBackParent = doStuff;
I use iframes to modularize my app too.They are a kind of includes embedding all CSS, HTML and JS for a module.
My first attempts were by returning a function too, but then I found it quite hard for sharing scopes.
Now I make directly a reference to the main parent object in the iframe.
eg:
var Svc = parent.Svc, JSON = parent.JSON, $ = parent.$;
Svc.module1 = {
method1:function(arg){
...
},
...
}
The global var JSON and jQuery references are here to have them available inside the methods.
My guest is that Svc.method is making some checks to see if the callback has some criteria before calling it. This criteria might be that the callback function must created by the same framework (here it's ASP.Net). You have to find what that criteria is. if "Main.createCallback" works, it's because it's meeting that criteria.
Sorry but your all wrong... add this....
const event = new CustomEvent('MSGSent', { detail: "fff variable" });
Call it like this....use a global variable for detail... like an array []
window.dispatchEvent(event);
Now after the iframe loads add this code and you get an Object back in the main page....
iframe.onload = function() {
try {
iframe.contentWindow.addEventListener('MSGSent',function(e){
alert(e.detail);
});
} catch (error) {
}
};
The problem is ASP.Net AJAX Web Service proxies, which don't appear to support calling the web service from an iframe with an inline callback function.