I'm just testing typescript in VisualStudio 2012 and have a problem with its type system. My html site has a canvas tag with the id "mycanvas". I'm trying to draw a rectangle on this canvas. Here's the code
var canvas = document.getElementById("mycanvas");
var ctx: CanvasRenderingContext2D = canvas.getContext("2d");
ctx.fillStyle = "#00FF00";
ctx.fillRect(0, 0, 100, 100);
Unfortunately VisualStudio complains that
the property 'getContext' does no exist on value of type
'HTMLElement'
It marks the second line as an error. I thought this would be merely a warning but the code does not compile. VisualStudio says that
there were build errors. Would you like to continue and run the last
successful build ?
I didn't like this error at all. Why is there no dynamic method invocation ? After all the method getContext definitely exists on my canvas element. However I thought this problem would be easy to solve. I just added a type annotiation for canvas:
var canvas : HTMLCanvasElement = document.getElementById("mycanvas");
var ctx: CanvasRenderingContext2D = canvas.getContext("2d");
ctx.fillStyle = "#00FF00";
ctx.fillRect(0, 0, 100, 100);
But the type system still wasn't satisfied. Here's the new error message, this time in the first line:
Cannot convert 'HTMLElement' to 'HTMLCanvasElement': Type
'HTMLElement' is missing property 'toDataURL' from type
'HTMLCanvasElement'
Well, I'm all out for static typing but this makes the language unusable. What does the type system want me to do ?
UPDATE:
Typescript has indeed no support for dynamic invocation and my problem can be solved with typecasts. My question is basically a duplicate of this one TypeScript: casting HTMLElement
var canvas = <HTMLCanvasElement> document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");
or using dynamic lookup with the any type (no typechecking):
var canvas : any = document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");
You can look at the different types in lib.d.ts.
const canvas = document.getElementById('stage') as HTMLCanvasElement;
While other answers promote type assertions (that's what they are — TypeScript doesn't have type casts that actually change the type; they are merely a way of suppressing type checking errors), the intellectually honest way to approach your problem is to listen to the error messages.
In your case, there are 3 things that can go wrong:
document.getElementById("mycanvas") might return null, because no node of that id is found (it might have been renamed, not injected to the document yet, someone might have tried running your function in an environment without access to DOM)
document.getElementById("mycanvas") might return a reference to a DOM element, but this DOM element is not a HTMLCanvasElement
document.getElementById("mycanvas") did return a valid HTMLElement, it is indeed an HTMLCanvasElement, but the CanvasRenderingContext2D is not supported by the browser.
Instead of telling the compiler to shut up (and possibly finding yourself in a situation where a useless error message like Cannot read property 'getContext' of null is thrown), I recommend taking control over your application boundaries.
Make sure the element contains a HTMLCanvasElement
const getCanvasElementById = (id: string): HTMLCanvasElement => {
const canvas = document.getElementById(id);
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error(`The element of id "${id}" is not a HTMLCanvasElement. Make sure a <canvas id="${id}""> element is present in the document.`);
}
return canvas;
}
Make sure the rendering context is supported by the browser
const getCanvasRenderingContext2D = (canvas: HTMLCanvasElement): CanvasRenderingContext2D => {
const context = canvas.getContext('2d');
if (context === null) {
throw new Error('This browser does not support 2-dimensional canvas rendering contexts.');
}
return context;
}
Usage:
const ctx: CanvasRenderingContext2D = getCanvasRenderingContext2D(getCanvasElementById('mycanvas'))
ctx.fillStyle = "#00FF00";
ctx.fillRect(0, 0, 100, 100);
See TypeScript Playground.
It seems this is being corrected in the .9 version of TypeScript:
http://blogs.msdn.com/b/typescript/archive/2013/03/25/working-on-typescript-0-9-generics-overload-on-constants-and-compiler-performance.aspx
See the section on "Overload on Constants" where the canvas tag is explicitly shown.
I had the same problem, but with SVGSVGElement instead of HTMLCanvasElement. Casting to SVGSVGElement gave a compile-time error:
var mySvg = <SVGSVGElement>document.getElementById('mySvg');
Cannot convert 'HTMLElement' to 'SVGSVGElement':
Type 'HTMLElement' is missing property 'width' from type 'SVGSVGElement'.
Type 'SVGSVGElement' is missing property 'onmouseleave' from type 'HTMLElement'.
If fixed it by first casting to 'any':
var mySvg = <SVGSVGElement><any>document.getElementById('mySvg');
or this way (it has the identical effect)
var mySvg: SVGSVGElement = <any>document.getElementById('mySvg');
Now mySvg is strongly typed as SVGSVGElement.
You may have to add DOM to compilerOptions.lib in your tsconfig.json.
// 'tsconfig.json'
{
"compilerOptions": {
"target": "ES2017",
"module": "commonjs",
"lib": [
"es5",
"DOM",
"esnext"
]
}
}
This is an old topic... maybe dead to 2012, but exciting and new to VS Code and typescript.
I had to do the following to get this to work in VS Code with the following package references.
const demoCanvas: HTMLCanvasElement = document.getElementById('rfrnCanvas') as any;
if(demoCanvas.getContext) {
const context = demoCanvas.getContext('2d');
if(context) {
reactangle(context);
}
}
Typescript Version:
{
"#typescript-eslint/eslint-plugin": "^2.29.0",
"#typescript-eslint/parser": "^2.29.0",
"typescript": "^3.7.5"
}
I'd recommend
let canvas = document.getElementById('canvas') as
HTMLCanvasElement;
Related
I would like to change the pitch of a sound file using the HTML 5 Audio node.
I had a suggestion to use the setVelocity property and I have found this is a function of the Panner Node
I have the following code in which I have tried changing the call parameters, but with no discernible result.
Does anyone have any ideas, please?
I have the folowing code:
var gAudioContext = new AudioContext()
var gAudioBuffer;
var playAudioFile = function (gAudioBuffer) {
var panner = gAudioContext.createPanner();
gAudioContext.listener.dopplerFactor = 1000
source.connect(panner);
panner.setVelocity(0,2000,0);
panner.connect(gainNode);
gainNode.connect(gAudioContext.destination);
gainNode.gain.value = 0.5
source.start(0); // Play sound
};
var loadAudioFile = (function (url) {
var request = new XMLHttpRequest();
request.open('get', 'Sounds/English.wav', true);
request.responseType = 'arraybuffer';
request.onload = function () {
gAudioContext.decodeAudioData(request.response,
function(incomingBuffer) {
playAudioFile(incomingBuffer);
}
);
};
request.send();
}());
I'm trying to achieve something similar and I also failed at using PannerNode.setVelocity().
One working technique I found is by using the following package (example included in the README): https://www.npmjs.com/package/soundbank-pitch-shift
It is also possible with a biquad filter, available natively. See an example here: http://codepen.io/qur2/pen/emVQwW
I didn't find a simple sound to make it obvious (CORS restriction with ajax loading).
You can read more at http://chimera.labs.oreilly.com/books/1234000001552/ch04.html and https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode.
Hope this helps!
I hear that UnityScript is pretty close to JavaScript. Does UnityScript support the Object Literal syntax of JavaScript?
e.g.
var x = {
y: 12
};
Strangely, searching google for UnityScript "Object Literal" yields no useful results.
UnityScript versus JavaScript have very different semantics.
Creating a new JavaScript file in Unity with this syntax will not compile.
#pragma strict
var x = {
y: 12 // error: BCE0005
};
function Start () {
Debug.Log (x.y); // error: BCE0019
}
This will give build errors:
NewBehaviourScript(4,5): BCE0005: unknown identifier: 'y'.
NewBehaviourScript(8,17): BCE0019: 'y' is not a member of 'Boo.Lang.Hash'.
However, you could implement a Hashtable:
#pragma strict
var x:Hashtable = new Hashtable();
x["y"] = 12;
function Start () {
Debug.Log (x["y"]);
}
I have a variable
var qstAccessCode:String = "default";
and a loader with URLRequest
var qst:XML;
var qstLoader:URLLoader = new URLLoader();
qstLoader.load(new URLRequest("http://dl.dropbox.com/u/44181313/Qaaps/Audio/" + qstAccessCode + ".qst"));
qstLoader.addEventListener(Event.COMPLETE, processQST);
function processQST(e:Event):void {
qst = new XML(e.target.data);
trace("QST loading");
}
I would like to use the value of qstAccessCode to complete the URL (so I can change the URL based on user input - if no input then use "default") but I get an error:
"1120: Access of undefined property qstAccessCode"
Is this to do with scoping? How can I complete the URL? Thanks in advance.
Edit: I haven't been able to get clear on this, so I'm also going to look at generating the complete URL from the user-input function and see if I get the URLRequest to pick it up as a variable. If there are any further comments on the original idea I will be very grateful to read them. Cheers.
Edit: #Moorthy I have qstAccessCode defined like this:
var qatAccessCode:String = "default";
var stageText:StageText = new StageText();
stageText.returnKeyLabel = ReturnKeyLabel.GO;
stageText.stage = this.stage;
stageText.viewPort = new Rectangle(225, 765, 200, 35 );
stageText.addEventListener(Event.CHANGE, onChange);
function onChange(e:Event):void
{
qatAccessCode = stageText.text;
trace(qatAccessCode);
}
It traces keyboard entry when I test movie (Air 3.2 for Android).
qstAccessCode should be defined in the same scope as the URLRequest.
You must defined property qstAccessCode like:
var qstAccessCode:string;
qstAccessCode's value is your url address.
I'm writing a display class in Javascript (using jQuery) which may be instantiated before a web page has loaded. If the page isn't ready when the constructor is called, the instance is added to a static instances field for the class, which is iterated over when the page has loaded:
function MemDisplay(ready_callback) {
this.readyCallback = ready_callback;
if (MemDisplay.ready) {
this.linkToPage();
} else {
MemDislay.instances.push(this);
}
}
//this makes sure that the ready callback can be sent when the page has loaded
MemDisplay.ready = false;
MemDisplay.instances = [];
$(document).ready(function () {
var i;
MemDisplay.ready = true;
for (i = 0; i < MemDisplay.instances.length; i += 1) {
MemDisplay.instances[i].linkToPage();
} });
//example truncated for brevity
When I run this through JSLint, I get this error:
Problem at line 25 character 9:
'MemDislay' is not defined.
MemDislay.instances.push(this);
I need to reference MemDisplay.instances in the constructor, but the constructor is where MemDisplay is defined, so I'm puzzled about how to make this work while fitting within JSLint's guidelines. Is there a better way to do this? Should I just ignore JSLint in this instance?
JSLint here is actually highlighting a broader issue with the code without saying so.
You are referencing a class (MemDisplay) but never instantiating it as an object. I.e. you are treating the class like an already-instantiated object.
I've created a very simple equivalent to what you are trying to achieve (also at this JSFiddle)
function MyClass(p1, p2){
this.param1 = p1; //class member/property - use this to access internally.
if (this.param1 === 1){ //you might want to consider doing this as part of some setter method
alert("test");
}
this.MyMethod = function(){ //class method/function
alert("MyMethod Called");
};
}
var myObj = new MyClass(1,2); //instantiate
alert(myObj.param1); //get value of object member (you can set as well)
myObj.MyMethod(); //call a method
It'll take a bit of reorgansiation, but by declaring the values up front, you can get make JSLint happy.
My brain must have figured this out while I slept: the trick is to attach the field to the prototype, which seems pretty obvious now that I've thought of it, since that's what you have to do to define class methods.
The following checks out in JSLint, and demonstrates the sharing of a field between all instances of MyClass (or see this code on jsfiddle):
/*global alert */
function MyClass(name) {
this.name = name;
MyClass.prototype.field += 1;
}
MyClass.prototype.field = 0;
MyClass.prototype.myMethod = function () {
alert(this.name + "'s class's field is " + MyClass.prototype.field);
};
var myObj = new MyClass("first");
myObj.myMethod();
var myOtherObj = new MyClass("second");
myObj.myMethod();
myOtherObj.myMethod();
I'm not sure if there's a prettier way to do it, as having 'prototype' all over the place feels a bit excessive, on the other hand it could be a good thing because it makes it clear that prototype.field does not belong to the instance.
Can someone tell me what the difference is between the 2 JSON parsers?
https://github.com/douglascrockford/JSON-js/blob/master/json.js
https://github.com/douglascrockford/JSON-js/blob/master/json2.js
I have a JSON file from 2007-04-13 (It has methods such as parseJSON). I don't see these methods in any of the new versions.
From their code:
// Augment the basic prototypes if they have not already been augmented.
// These forms are obsolete. It is recommended that JSON.stringify and
// JSON.parse be used instead.
if (!Object.prototype.toJSONString) {
Object.prototype.toJSONString = function (filter) {
return JSON.stringify(this, filter);
};
Object.prototype.parseJSON = function (filter) {
return JSON.parse(this, filter);
};
}
I guess parseJSON is obsolete, therefore the new version (json2) doesn't even use it anymore. However if your code uses parseJSON a lot you could just add this piece of code somewhere to make it work again:
Object.prototype.parseJSON = function (filter) {
return JSON.parse(this, filter);
};
Quoting here:
"JSON2.js - Late last year Crockford quietly released a new version of his JSON API that replaced his existing API. The important difference was that it used a single base object."
I also noticed that json2 stringified arrays differently than json2007.
In json2007:
var array = [];
array[1] = "apple";
array[2] = "orange";
alert(array.toJSONString()); // Output: ["apple", "orange"].
In json2:
var array = [];
array[1] = "apple";
array[2] = "orange";
alert(JSON.stringify(array)); // Output: [null, "apple", "orange"].