I am currently using the Durandal framework to build a simple site (my first with Durandal) and have a question on how to go about adding a simple javascript function. I want to call the function after the DOM loads, but am not sure how to attach it to the current viewmodel. The problem I am having is the function is being called before the DOM loads and the div ID hasn't been created yet, which in this case is "sb-search".
I then tried adding the function to the viewmodel:
define(["plugins/router"], function (router) {
var vm = {
viewAttached: viewAttached
};
return {
router: router
};
function attached() {
new UISearch(document.getElementById('sb-search'));
}
return vm;
});
but, to no avail.
Any help would be greatly appreciated. Even a simple tutorial on how to "document.write('Hello World')" would be helpful. Thanks!
This is not strictly related, but I'd like to add something to what was said in the comments: you most likely shouldn't scan the global document in the attached handler. When it's called, composed views may not be, well, composed/attached themselves yet, and in general it's a good idea not to make assumptions about the global state. Also, you can gain performance by not scanning the whole DOM.
When calling attached, Durandal passes the root DOM element of the view bound to the view model as the first argument to the function. Use it to restrict search. If it's in a child/composed view, use the compositionComplete handler, called after all composition in complete (the event "bubbles up"). If it's in a parent view, use the second argument passed to these functions. If it really sounds too complicated, consider that your design might be flawed itself, look for MVVM guidance.
For completeness:
The comments mention that
You must export the right function (attached != viewAttached),
If you indeed intended to define an attached handler called by Durandal, know that viewAttached is deprecated in favor of attached.
And I'd also add that you return an anonymous object containing a router property before you return your vm (view model for sure), although that might be a left-over from some tests you did and copy-pasted here by mistake.
Related
I'm interested in being able to determine whether or not a class method is being executed within the class itself or from outside, and having different functionality based that.
What's a way of determining whether or not a class method is being executed within the class itself?
This is a quick and dirty example of what I'm interested in:
class Example {
internal = false
meow () {
console.log(this.internal);
return 'meow'
}
makeSound () {
return this.meow.bind({...this, internal: true})();
}
}
const example = new Example();
console.log(example.meow())
console.log(example.makeSound())
Here, Example#meow runs two times, one from within Example#makeSound and once on it's own. I would like a way of knowing from within Example#meow when the method has been executed from within the class, versus from outside the class, as in example.meow().
One way to do this, is with binding all internal calls to methods, if I add a property internal true, to this bound copy of this, then I can check from within Example#meow where the call is coming from.
This is only a proof of concept, but it's not great to try and clone this, and seems to be a real pain.
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));
I'm attempting to build an Ember app without prototype extensions and the Ember docs give examples of how to do this, but they don't include the example of when I want my observer to run on init.
So currently if my code were written like this:
fullNameChanged: function() {
// deal with the change
}.observes('fullName').on('init')
The only example I can find to write it is like this:
Person.reopen({
fullNameChanged: Ember.observer('fullName', function() {
// deal with the change
})
});
So how would I tell this code to run on init?
May be you are looking for this
Person.reopen({
fullNameChanged: Ember.on('init', Ember.observer('fullName', function () {
// deal with the change
}))
});
OR (this won't fire handler if change happens on init, use above on that case)
Person.reopen({
init: function(){
Ember.observer('fullName', function() {
// deal with the change
});
}
});
Alright, this edit for answer the mistakes(?) mentioned below
Well, sometimes it might be necessary to fire the observer on initialization time.
Ember.observer is an Ember namespace method, not part of Ember.Object's prototype. Therefore this.observer never exists, but addObserver() does.
There is no need to invoke the handler, Ember runtime will invoke the handler when the property changes
calling this._super is unnecessary unless it really does matter. In this case, if Person just extends Ember.Object calling super doesn't do anything.
By default, does nothing unless it is overridden during class definition.
It's contextual, and as long as OP didn't specify anything about class definition it's beyond the scope of answering.
Nothing better explains than an example
The accepted answer actually contains five separate mistakes, of varying degrees of severity.
It unnecessarily places setting up the observer in the init hook.
It sets up the observer inside the init hook incorrectly, using Ember.observer instead of this.observer, which won't even work.
It fails to invoke (as opposed to setting up) the handler at init time.
It fails to call init on the superclass.
It unnecessarily uses reopen.
1. No need to set up observer in init hook
You do not need any procedural "call" or "invocation" in an init hook to set up an observer. Either of the two following forms will set them up automatically when the object is instantiated.
fullNameChanged: function() { } . observes('fullName')
observeFullNameChanged: Ember.observer('fullName', this.fullNameChanged.bind(this))
2. Use object.observer for procedural setup of observers.
If you did want to set up the observer procedurally, then you call object.observer, not Ember.observer, which is defined for use as above. Calling Ember.observer procedurally will accomplish nothing; Ember will have no idea of what object the property to observe lies. In this case, it would be this.observer('fullName', ...) (although as mentioned above you actually don't need to do this at all; instead use the approach of point 1).
3. Invoke handler on init
But you also want to invoke the handler at init time. There are three ways:
init: function() { this.fullNameChanged(); /* call super */ }
initFullNameChanged: Ember.on('init', this.fullNameChanged.bind(this))
fullNameChanged: function() { ... }.on('init')
where the third option uses the prototype extensions you don't want.
4. Calling super from init
If you are going to have an init hook, even though it's not needed, you need to call super, or things will break down horribly:
init: function() {
...
this._super.apply(this, arguments);
}
5 No need for reopen
reopen accomplishes nothing here. Just put the above properties into the class definition itself.
Solution
The correct answer to what is the equivalent of
fullNameChanged: function observer() { }.observes('fullName').on('init')
is therefore
fullNameChanged: function() { },
observeFullNameChanged: Ember.observer('fullName', this.fullNameChanged.bind(this)),
initFullNameChanged: Ember.on('init', this.fullNameChanged.bind(this))
It would be equivalent, and possibly more readable, to do this:
initFullNameChanged: Ember.on('init', function() {
// define and execute handler
(function fullNameChanged() { ... }());
// set up obsever
this.observe('fullName, fullNameChanged);
})
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.
I have a click event that happens outside the scope of my custom directive, so instead of using the "ng-click" attribute, I am using a jQuery.click() listener and calling a function inside my scope like so:
$('html').click(function(e) {
scope.close();
);
close() is a simple function that looks like this:
scope.close = function() {
scope.isOpen = false;
}
In my view, I have an element with "ng-show" bound to isOpen like this:
<div ng-show="isOpen">My Div</div>
When debugging, I am finding that close() is being called, isOpen is being updated to false, but the AngularJS view is not updating. Is there a way I can manually tell Angular to update the view? Or is there a more "Angular" approach to solving this problem that I am not seeing?
The solution was to call...
$scope.$apply();
...in my jQuery event callback.
Why $apply should be called?
TL;DR:
$apply should be called whenever you want to apply changes made outside of Angular world.
Just to update #Dustin's answer, here is an explanation of what $apply exactly does and why it works.
$apply() is used to execute an expression in AngularJS from outside of
the AngularJS framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the AngularJS framework we need to perform proper scope life cycle of
exception handling, executing watches.
Angular allows any value to be used as a binding target. Then at the end of any JavaScript code turn, it checks to see if the value has changed.
That step that checks to see if any binding values have changed actually has a method, $scope.$digest()1. We almost never call it directly, as we use $scope.$apply() instead (which will call $scope.$digest).
Angular only monitors variables used in expressions and anything inside of a $watch living inside the scope. So if you are changing the model outside of the Angular context, you will need to call $scope.$apply() for those changes to be propagated, otherwise Angular will not know that they have been changed thus the binding will not be updated2.
Use
$route.reload();
remember to inject $route to your controller.
While the following did work for me:
$scope.$apply();
it required a lot more setup and the use of both .$on and .$broadcast to work or potentially $.watch.
However, the following required much less code and worked like a charm.
$timeout(function() {});
Adding a timeout right after the update to the scope variable allowed AngularJS to realize there was an update and apply it by itself.