I'm modeling a web page with a lot of items on it. Coming from a Ruby background, I had one class for each, say, large item and its subitems on the page. For instance, a navbar would be its own class:
import { Selector, t } from 'testcafe';
export class NavBar {
constructor () {
this.home = Selector('#home')
this.intro = Selector('#intro')
...
}
}
export class HeaderSection {
...
}
Questions:
Do I need a default class? My IDE is complaining, but the test work. I believe, the answer is no, but it's a good practice(?)
What's the recommended way to write a complex page model in JavaScript? I'm leaning to have one-page class, say index and then have multiple child classes (Navbar and HeaderSection on my example) that inherit from the index class
This is what I think it should be:
import { Selector, t } from 'testcafe';
export default class Index {
}
export class NavBar extends Index {
constructor () {
super ();
this.home = Selector('#home')
this.intro = Selector('#intro')
...
}
}
export class HeaderSection extends Index {
constructor () {
super ();
...
}
}
so when I import the page model into my test case, I can call import Index from ../pages/index_page.js
Do I need a default class? My IDE is complaining, but the test work. I believe, the answer is no, but it's a good practice(?)
It's not necessary. The default keyword determines the way of the export in JavaScript. You can organize page objects as you like.
What's the recommended way to write a complex page model in JavaScript? I'm leaning to have one page class, say index and then have multiple child classes (Navbar and HeaderSection on my example) that inherit from the index class
It depends on page complexity. If test page is simple then one page object class for one page is enough. If test page is complex, creating separate classes for complex controls is a good approach.
Related
I have been learning squish to automate a desktop application and I'm using Javascript at language to write my tests.
So I have these three following classes in my code : Actions, Button and AppElements. And AppElement class creates an instance of Button elements which is inheriting Actions class.
import * as names from 'names.js';
class Actions{
constructor(appObject){
this.object=(appObject);
}
doubleclick(){
doubleClick(waitForObjectExists(this.object));
}
}
class Button extends Actions{
constructor(anElement){
this.object=anElement;
super(anElement);
}
}
class AppElements {
get dashboardButton() { return new Button(names.contentButtonDashboardWcPushButton);}
}
export
{
Actions,
AppElements,
Button
};
import * as names from 'names.js';
function main() {
// attach the desktop application
let dash= new test.AppElements();
dash.dashboardButton.doubleclick();
}
But when the property dashboardButton() is being called from the test script(from the main function) then it first goes to line constructor(appObject){ but does not enter in the constructor block and gives following error
error Detail Attempted to access uninitialized this value. Forgot to call super() in derived class?
And because of that the Actions class can never be called and I cannot access any methods of the Actions class.
Would it be great if i could get some help to fix the problem
Here it says...
When used in a constructor, the super keyword appears alone and must be used before the this keyword is used. The super keyword can also be used to call functions on a parent object.
So try:
class Button extends Actions {
constructor(anElement) {
super(anElement);
this.object = anElement;
}
}
Related information:
JavaScript extensions in Squish 6.6
Super
I have two views on the same page. View A’s view-model needs to call a method in view B’s view-model. Is that possible with Aurelia?
Also, is this the best approach? Would it be better to use the EventAggregator (pub/sub) to communicate between the view-models?
----- More Details -----
To be much more specific, there is a nav bar used in my app.html file like this:
<template>
<require from="nav-bar-view"></require>
<nav-bar-view></nav-bar-view>
<router-view></router-view>
</template>
View-models within the router-view need to be able to change the nav bar's title and button text.
My initial design was to use pub/sub to communicate the changes to the nav bar view-model. Since that seemed a bit messy and overcomplicated, I wanted to come up with a simpler approach.
My latest idea is to create a singleton NavBar class that is injected into the NavBarView class and the "consumer" view-models.
The following is a simplified version of the code:
nav-bar-view.html:
<template>
<div class="center">${navBar.title}</div>
</template>
nav-bar-view.js:
import {inject} from 'aurelia-framework';
import {NavBar} from 'nav-bar';
#inject(NavBar)
export class NavBarView {
constructor(navBar) {
this.navBar = navBar;
}
}
nav-bar.js:
import {singleton} from 'aurelia-framework';
#singleton()
export class NavBar {
constructor() {
this.title = '';
}
}
view.js (a consumer of the nav bar):
import {inject} from 'aurelia-framework';
import {NavBar} from 'nav-bar';
#inject(NavBar)
export class View {
constructor(navBar) {
this.navBar = navBar;
}
attached() {
this.navBar.title = 'This View's Title';
}
}
Again, this is much simpler than the actual code, but it servers to illustrate the idea.
I've tried it out and it works fine. Does this make sense? Is there a better way?
pub/sub would work but I suspect you're looking for something a little more targeted than that.
The preferred way to pass something into a custom element is via a bindable property. Assuming you have component-a and component-b and A needs to call a method on B's view-model. Here's what you could do:
get a reference to B's view-model so we can bind to it's properties and methods:
<component-b view-model.ref="b"></component-b>
Add bindable property to component A so we can give component A the reference to B's method.
import {bindable} from 'aurelia-framework';
export class ComponentA {
#bindable sayHello;
}
Bind component A's sayHello property to B's sayHello method.
<component-a say-hello.call="b.sayHello()"></component-a>
Here's a runnable example: https://gist.run/?id=91269472d4e6509e32123ca2a63dd9ca
Edit
Based on the updated information in the question, here's what I would recommend:
1. Create a class that contain's your nav-bar state
export class NavState {
title = 'some default title';
}
2. Take a dependency on the NavState in your nav-bar component
#inject(NavState)
export class NavBar {
constructor(state) {
this.state = state; // now you can bind to "state.title" in nav-bar.html
}
...
}
3. Take a dependency on the NavState in components that need to change the title.
#inject(NavState)
export class MyComponentThatChangesTheTitle {
constructor(state) {
this.state.title = 'something else';
}
...
}
This will be more flexible than passing around a component's viewmodel as state. For example, with this model, you can configure the title before the nav-bar is even instantiated.
Though this topic has already been discussed in other posts like this:
Dynamically loading a typescript class (reflection for typescript)
I'm not able to find an answer to my specific issue. So, pardon me if this is duplicated.
I'm trying to create a very simple directive in Angular 2 (using Typescript), which allows dynamic addition or removal of a set of controls represented by a Type. For example, if the type is:
class Stone{
constructor(
public nameOfStone?: string,
public typeOfStone?: string
){}
}
the UI would have something like this:
I'm able to get this working with a specific Type (ex: Stone). But, given that the directive's objective is just to add this dynamic add/remove feature, I felt that it would make sense to parameterise the type to be created and use this for different type definitions. I tried something like this in the Component class:
import {Component} from 'angular2/core';
import {NgForm} from 'angular2/common';
import {ControlGroup, Control, FormBuilder, FORM_DIRECTIVES} from 'angular2/common'
#Component({
selector: 'stone-details',
templateUrl: '../stones/stone-details.component.html',
directives: [FORM_DIRECTIVES]
})
export class StoneComponent {
type = 'Stone';
Stones = new Array<Stone>();
addBtnClicked(){
let Stone = Object.create(window['Stone'].prototype);
//let Stone = new Stone('', '');
this.Stones.push(Stone);
}
removeBtnClicked(index: number){
if(index >= this.Stones.length){
alert('Not a valid index');
}else if(confirm('Remove this Stone?')){
this.Stones.splice(index, 1);
}
}
}
class Stone{
constructor(
public nameOfDeity?: string,
public typeOfDeity?: string
){}
}
When I use the commented line
let Stone = new Stone('', '');
the component works perfectly, but if I use
let Stone = Object.create(window['Stone'].prototype);
it doesn't seem to work and the error I see is
angular2.dev.js:23941 ORIGINAL EXCEPTION: TypeError: Cannot read property 'prototype' of undefined.
I initially thought exporting the Stone class would help, but none of the crazy variations (exporting the class, trying to refer to the class as window['StoneComponent'].export_1['Stone']) helped. I understand the component isn't directly visible under the window component, but I'm not sure what I'm missing. Is there an alternate way to doing this? Am I missing something? Please advise.
P.S: I'm using the latest version of Angular 2 and Typescript (I started this application a couple of days back).
The problem with your code is definition order.
Specifically, class definitions are not hoisted like function definitions are. The tricky part is that the type Stone is hoisted, which is perfectly valid, but the value Stone, the constructor function, is not.
To get around this just move the definition of Stone above the component or extract it into a separate module and import it.
Do not try to shove it into a global variable, say window. That is a very poor practice and will lead to bugs and name collisions faster than one might think. It also defeats the benefits of modules.
In short, what you need is
class Stone {
constructor(
readonly nameOfDeity?: string,
readonly typeOfDeity?: string
) {}
}
export class StoneComponent {
kind = 'Stone';
stones: Stone[] = [];
addBtnClicked() {
const stone = new Stone();
this.stones.push(stone);
}
removeBtnClicked(index: number) {
if (index >= this.stones.length) {
alert('Not a valid index');
} else if (confirm('Remove this Stone?')){
this.stones.splice(index, 1);
}
}
}
UPDATE
Since in the original question you state that this will be a generic component and you will have multiple classes where the actual class is selected by a kind property of the component class. You may want to consider the following pattern rather.
component-field-kinds.ts
export class Stone { ... }
export class Widget { ... }
export class Gizmo { ... }
generic-component.ts
import * as kinds from './component-field-kinds';
type Kind = keyof typeof kinds;
export class GenericComponent {
#Input() kind: Kind;
values: typeof kinds[Kind][] = [];
addBtnClicked() {
const value = new kinds[this.kind]();
this.values.push(value);
}
Note, for what it is worth, JavaScript, and therefore TypeScript has no such thing as a dynamic class loader. This is just how the language works all the time and the whole structure is first class.
This is not Java.
Since class is simple function you could create instance using new func(), but you still have to pass func from outer code.
I guess the most efficient solution is the following:
export class StoneComponent<T> {
addBtnClicked(){
let item:T = <T>{}
this.items.push(item);
}
}
types will match as long as objects have the same set of properties.
This question already has an answer here:
ES6 classes : what about instrospection?
(1 answer)
Closed 6 years ago.
I just can't find out where the references to declared ES6 classes are stored, I would have expected them in the window Object, bit they don't appear there.
I don't think it's a duplicate of ES6 classes : what about instrospection? since he is asking for a existance check of a class, what I want is a list of available classes.
For example:
class Test {
constructor() {
}
}
window.Test // undefined
What I want is a list of all classes that extend a class of mine
To clarify that I have a structure that looks something like this:
class Base {
constructor(components) {
for(let component of components) {
window[component](); // window.Test2 not defined
}
}
start() {
new this();
}
}
class Test2 extends Base {
constructor() {
super();
}
}
class Test extends Base {
constructor() {
super(['Test2','Test2']);
}
}
Test.start();
That's just an abstraction of my structure, in short I have to use strings at super(['Test2', 'Test2'])
At the moment I'm doing something like this
Base.register(Test2);
for every class and I want to get rid of that.
You can use Class expressions to store them in some sort of array, although I probably wouldn't do it, if I were you.
var Test = class Test {
constructor() {
}
}
allClasses.push(Test);
JavaScript classes are introduced in ECMAScript 6 and are syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax is not introducing a new object-oriented inheritance model to JavaScript. JavaScript classes provide a much simpler and clearer syntax to create objects and deal with inheritance.
Basically ES6 classes are compiled to plain old-fashioned Javascript functions. You may "store" them at the window object but this is a major pitfall as you are killing the whole module patter ES6 introduced.
If you want sort of a "module" of classes, you could theoretically do something like this:
// some-class.js
export default class SomeClass {}
Then:
// another-class.js
export default class AnotherClass {}
And your entry file:
// index.js
import SomeClass from './some-class.js' // extensions optional; here just for clarity
import AnotherClass from './another-class.js'
export default {
SomeClass,
AnotherClass
}
If you have all of those embedded in the same directory (we'll call the directory example), you can just import that entire module wherever you need it:
// something-that-requires-your-module.js
// this will by default enter through the `index.js` file of your `example` directory
import Example from './example';
console.log(Example.SomeClass);
console.log(Example.AnotherClass);
This is part ES6 question part React question. I'm trying to use namespaced components in React with ES6 classes and Babel. So I guess the real question is how to name space es6 classes so I can do what is explained here: https://facebook.github.io/react/docs/jsx-in-depth.html#namespaced-components
Since I get an unexpected token error:
class Headline extends Component { ... }
class Headline.Primary extends Component { ...
^
The ECMAScript-6 class declaration syntax expects a standard BindingIdentifer as the class name. A dot is not a valid character inside an identifier name.
In the context used in the link in OP, the "namespace" is an object, and properties are added to that object one by one using the dot notation for property access.
You could replicate that by using a class expression instead:
'use strict'
var ns = {}
ns.MyClass = class {
constructor() {
console.log('in constructor')
}
}
new ns.MyClass()
This doesn't really change with ES6, you still will have to do an assignment:
Headline.Primary = class Primary extends Component { … };
However, using classes like Headline as namespaces is getting pretty deprecated with ES6 (and has previously been a questionable practice anyway), you should instead leverage the new module system. Export Primary as a named export, and instead of importing the Headline class rather do import * as headlines from ….
This link also relates to this question.
In the Module objects section, it is described that you can do something like this:
// headline.js file
export {Headline, Primary}
class Headline {}
class Primary {}
// In another module...
import * as Headline from "headline";
let h = new Headline.Headline();
let hp = new Headline.Primary();
It's not exactly what you are trying to do, but is an alternative.
Another way of doing it is almost like #Bergi has already pointed out, but I'm just clarifying it further:
let Headline = class Headline extends Component { }
Headline.Primary = class Primary extends Component { }
export {Headline as default}
// in another module:
import Headline from 'headline';
let headline = new Headline();
let primary = new Headline.Primary();