I have a [seemingly] trivial dust.js template. The context object I am using to render the template contains a handler which references another item in the context object. I also include a toString handler, which also references another item in the context object.
Template:
{error}
<pre>
{#error.getStackTrace}
{.}{~n}
{/error.getStackTrace}
</pre>
Context:
{
error: {
st: ['a','b','c'],
msg: 'This is an error message',
getStackTrace: function () {
return this.st;
},
toString: function () {
return this.msg;
}
}
}
Rendered:
This is an error message<pre></pre>
If I reference {#error.st} directly, it renders correctly:
This is an error message<pre>a
b
c
</pre>
If I inspect 'this' inside of the getStackTrace() handler, it is pointing back to DOMWindow. It is interesting, however, that invoking toString() implicitly, it is scoped correctly. If I explicitly invoke toString() {error.toString}, then the scope jumps back to DOMWindow.
The only reason this is a problem, (why I cannot access error.st directly) is because the st array is actually stored in a Qooxdoo property, and I only have access to the generated getter. The above example mimics the actual object as simply as I can.
Is this a bug in dust.js? Is it losing the correct scope in handlers? Or am I missing something in the dust.js docs to retain scope?
you could use it in this way:
{
error: {
st: 'a,b,d',
msg: 'This is an error message',
getStackTrace: function (chunk, context) {
return context.current().error.st;
},
toString: function () {
return this.msg;
}
}
}
this is Javascript isn't always obvious, especially when you are returning functions.
When Dust resolves references like {error.st} which is a function. It calls that function, but it does not set a scope for it; so it defaults to the global scope which in your browser is window.
Look at this line: https://github.com/akdubya/dustjs/blob/master/lib/dust.js#L319
Here is what is sorta happening:
var current_context = {
st: 'a,b,d',
msg: 'This is an error message',
getStackTrace: function (chunk, context) {
return error.st;
},
toString: function () {
return this.msg;
}
}
current_context.st(); // outputs correctly
var elem = current_context.st; // here is your reference {.}
elem(); // Dust tries to resolve your reference but it doesn't set the scope
elem.call(current_context); // if we pass the scope you'll get what you want.
Is this a bug in Dust? Probably not since you have the context via context.current().
Does it make sense for this to point to window. No, but when you use Dust on the server side I imagine, we'll have a better use for this.
Related
I'm trying to create a (pure) constructor function and a QUnit test for it:
//app.js
function myFunc(param1, param2) {
this.param1 = param1;
this.param2 = param2;
return this;
}
//test.js
QUnit.test("should return a string and a number", function(assert) {
assert.ok(myFunc("some-string", 4545435234), "a string and a number were returned");
});
The code runs and passes my test until I add "use strict" to app.js. Then QUnit displays the following fail message:
1. Died on test #1 at http://some/url/:1:1: Cannot set property 'param1' of undefined
Source: TypeError: Cannot set property 'param1' of undefined
I can get both the code to work and the test to pass if I return the myFunc parameters as an array:
function myFunc(param1, param2)) {
return [param1, param2];
}
But that just doesn't seem right. I get that this has something to do with var hoisting but I'm not clear about it.
Thanks in advance.
...
In strict mode JavaScript functions are not given the default context (this), thus you must provide the context. Once way to do this is through the new keyword. If you change your assertion to the following I think this will work:
assert.ok(new myFunc("some-string", 4545435234), "a string and a number were returned");
I'm using the native Bluetooth serial library and trying to mock data for testing in the browser. By experimentation (and a little reading) it seems that the way to do this is to check for the 'cordova' platform:
export class BluetoothServiceWrapper implements OnDestroy, OnChanges {
...
private isEmulated:boolean = true;
...
constructor(platform:Platform) {
platform.ready().then(() => {
this.isEmulated = !platform.is('cordova');
});
}
The strange thing is that this works in some parts:
connect(device:BluetoothDevice) {
return Observable.create(observer => {
...
if (!this.isEmulated) {
...
}else{
... // this is executed in the browser
}
}
}
But in other parts the this.isEmulated is undefined:
write(data:any):Promise<any> {
if (!this.isEmulated) {
return BluetoothSerial.write(data);
} else {
.... // this never gets executed
}
}
Am I overcomplicating this and there is an easier way to check if we are using browser/emulation? Or is there some error in the way the context is being passed over?
I should mention that both methods get the same members when accessing 'this' i.e. the BluetoothServiceWrapper members. In the case of the 'write' function though the isEmulated variable is hidden/undefined.
Ok, this was a bit of a trap. The important piece of information that was missing from the original post was that I had another component/service perform the following:
if (!this.isConnected && (!this.isConnecting)) {
this.bluetoothServiceWrapper.connect(device).subscribe(data => this.tuningModuleService.onData(data), console.error);
this.tuningModuleService.setOutputFunction(this.bluetoothServiceWrapper.write);
}
Inside the service above I would be calling this.write('somedata'), using the function above given as reference.
The service:
outputToSerialFn: any;
constructor(applicationRef: ApplicationRef, platform: Platform) {
...
// default (mock) output function
this.outputToSerialFn = function (data) {
return new Promise((resolve, reject) => {
console.log('Mock BT OUT', data);
})
};
}
setOutputFunction(outputToSerialFn: any) {
this.outputToSerialFn = outputToSerialFn;
}
The problem is that during calls the write function would get the scope of the Service using it instead of the BluetoothWrapper service.
One solution is to replace the call above with:
this.tuningModuleService.setOutputFunction(this.bluetoothServiceWrapper.write.bind(this.bluetoothServiceWrapper));
The key word is bind.
This is probably not the best pattern but might help someone who is also struggling with this. The lesson here is that passing functions as parameters overrides the original function scope.
I'm assuming this isn't possible, but wanted to see if anyone knew any better.
With ES6, you can get the name of a function. For instance:
function foo() { return true; }
function bar() { return true; }
const functionContainer = foo;
foo.name; // 'foo'
bar.name; // 'bar'
functionContainer.name; // 'foo'
In Ember, you can pass an action into an action helper. For instance:
export default Ember.Component.extend({
actions: {
bar() {
return true;
}
}
});
And the template:
{{foo-component foo=(action "bar")}}
Within foo-component, is there some way to do this:
export default Ember.Component.extend({
doFoo: Ember.on('didRecieveAttrs', function() {
console.log(this.attrs.foo.name); // 'bar'
})
});
When I try, I just get a blank string. This makes sense, since it looks like the bar action is getting wrapped in a nameless function by ember-metal.
Anyone know of a way to grab that name? It would make a huge difference for a project I'm working on.
Nope, you can't do exactly what you want < insert technical discussion about closures here >, but you can kinda fake it by just adding another param to your component, like so:
{{foo-component foo=(action "bar") actionName="bar"}}
then in your component.js you can access
this.attrs.actionName // "bar"
I'm using Meteor 1.0.
I have a Template.*name*.rendered function that makes a number of calculations. At the end of the calculations, I would like the output to make its way into a Template.*name*.helpers so I can use it in the corresponding html page.
Here's a simplified version of the code:
Template.myTemplate.rendered = function () {
var x = Math.random();
Template.otherTemplate.helpers({
randomNum: x
});
}
When I call {{randomNum}} in otherTemplate, nothing happens.
I have also tried putting the Template.*name*.helpers outside of Template.*name*.rendered, in which case, I get the error:
Uncaught ReferenceError: x is not defined
Thoughts?
This isn't really the right way of going about things as the way Meteor works is by compiling templates before the application starts, rather than at run-time. Whilst something along these lines may be possible (for example by using Template.registerHelper), it would be much better to set a reactive variable to a specific value in the rendered callback and have the helper set to return that instead:
Session.setDefault('randomNum', 0);
Template.myTemplate.rendered = function () {
Session.set('randomNum', Math.random());
}
Template.otherTemplate.helpers({
randomNum: Session.get('randomNum')
});
If you'd rather use a private variable for the randomNum, have a look at ReactiveVar. It could be any reactive data source and it would work.
You used to create helpers as an object of the template but since Meteor has deprecated that you now have to create the helpers within the helper function.
Now in order to call the helper via javascript you must use this function
Template.*TemplateName*.__helpers.get('*HelperName*')(*Params*);
Its a pretty simple way of doing this and it keeps the functions out of the global scope so its pretty clean.
Here is an example of how I am using this
~~~
Template.home.events({
'click .pair': function(event) {
var _this = $(event.currentTarget);
Template.home.__helpers.get('pairDevice')(_this);
}
});
Template.home.helpers({
'devices' : function() {
return Session.get('devices');
},
'pairDevice' : function(elm) {
elm.fadeOut();
$('.home-page').addClass('paired');
var deviceList = [
{
'name' : 'Patrick\'s Phone',
'UUID' : '234123,4n123k4nc1l2k3n4 l1k23n4l12k3nc4l12'
},
{
'name' : 'Mike\'s Phone',
'UUID' : '734k23k4l2k34l2k34l2k34l2k3m'
},
{
'name' : 'Edgar\'s Phone',
'UUID' : '567k56l7k4l56k7l5k46l74k56l74k5'
}
];
Session.set('devices', deviceList);
}
});
~~~
Here is my html:
<a href="#modal{{screencast.id}}" role="button" class=" btn" data-toggle="modal"
ng-click="fetch_comments(screencast.id)" ng-video url="match_url(screencast.video_url)">Play</a>
My directive:
'use strict';
App.directive('ngVideo', [function () {
return {
restrict: 'A',
scope: { url: '='},
link: function (scope, elem, attrs) {
elem.bind('click', function () {
console.log(scope.url);
});
}
}
}]);
When I refresh page in href="#modal{{screencast.id}}" i have only href="#modal". When I remove scope: { url: '='} from directive it works fine, and href has value of screencast.id.
What i'm doing wrong?
I am going to assume the HTML snippet you posted is placed inside an ng-video element in that case (it is not clear from your message but what you describes seems to indicate this).
When you add scope: { url: '='} to your directive, you create an isolate scope, which means a new scope is created and all the elements inside this directive will live inside this new scope, disconnected from the parent scope. In that case, your {{screencast.id}} binding won't be able to access the screencast object if it was located in the parent scope.
I think for your situation, the best solution would be to remove scope: { url: '='} since you are only using it to read a single attribute and use the attrs parameter instead.
Your link function could look like:
link: function (scope, elem, attrs) {
var urlAttr;
//watch url attribute (we have to wait for the binding to be evaluated, hence the $observe)
attrs.$observe('ngModel', function(value) {
urlAttr = value;
});
elem.bind('click', function () {
if(urlAttr){
console.log(urlAttr);
}
});
}