I've been exploring Aurelia and so far have loved what I've seen. I've come accross an issue that I'm not really sure how to solve. I used jquery datatables for large results in my current app with angular, using server side fetches. Datatables has a function you can call whenever a new row is added to the table (fnRowCallback - http://legacy.datatables.net/ref#fnRowCallback, or "createdRow" - https://datatables.net/examples/advanced_init/row_callback.html#) - This is really handy as you can recompile the dom after each row (costly I know).
This enables you to reference functions that exist in the current scope (or viewModel) that the datatable exists in. For example:
In my view model:
export class DataTableTest{
test(){
alert('this is a test');
}
}
In the return results from a datatable fetch:
{name:'blah',age:40,actions:"<a click.delegate='test();'>Test</a>"}
For some reason I can't seem to figure out how to recompile an element once it has been added to the dom.
Does anyone have any ideas how you could do this?
UPDATE:
These are the original options I pass to datatables:
var options = {
"fnRowCallback": function (nRow) {
$compile($(nRow).contents())(scope);
}
};
I've tried the following after injecting that compiler service:
"fnRowCallback": function (nRow) {
this.compiler.compile($(nRow).contents()).fragment.innerHTML;
},
But I always get Uncaught TypeError: Cannot read property 'compile' of undefined - I do this in the "attached" function.. If I console.log(this.compiler) outside of these options, it's available. Also, we don't need to return html back to datatables, just run the compile on the contents. Many thanks for all your help!
You can use a compiler service to compile the element:
import {inject, ViewCompiler, ViewResources, Container} from 'aurelia-framework';
/**
* Compiler service
*
* compiles an HTML element with aurelia
*/
#inject(ViewCompiler, ViewResources, Container)
export class Compiler {
viewCompiler: any;
resources: any;
container: any;
constructor(viewCompiler, resources, container) {
this.viewCompiler = viewCompiler;
this.resources = resources;
this.container = container;
}
compile(templateOrFragment, ctx = null, viewSlot = null):any {
if (typeof templateOrFragment === "string") {
var temp = document.createElement('span');
temp.innerHTML = templateOrFragment;
templateOrFragment = temp;
}
var view = this.viewCompiler.compile(templateOrFragment, this.resources).create(this.container, ctx);
return view;
}
}
I use this in Kendo in the cell template callback function (it lets you return a string that will become the cell contents)
function(dataItem) {
var cellctx = { "$item": dataItem, "$parent": ctx };
return this.compiler.compile(templateString, cellctx).fragment.innerHTML;
}
(this happens in Aurelia's bind callback so the ctx is the executionContext)
I just wrap the current data item up in a context and alias it as $item so I can work with it.
Looks something like this:
<kendo-grid>
<kendo-grid-col title="Main Office" field="IsMainOffice">
<kendo-template><img if.bind="$item.IsMainOffice" src="/content/img/accept.png" /></kendo-template>
</kendo-grid-col>
</kendo-grid>
Related
I'm updating a web app from using Knockout.js Templates to Vue 3 Components. This issue is not in the components but in the JS where I'm creating objects to be used in the components.
I'm running into strange behaviour around using refs where an object member is not always being set as a ref. I haven't been able to recreate this in an example but a simplified version of the structure is that we have an attribute that returns an object that contains the ref value.
function Attribute(val) {
var value = ref(val);
return {
value: value
}
}
We have a builder that is used to add labels and other items such permissions and classes to the UI.
function UIBuilder() {
var self = this;
var newElement = {};
function newText(value) {
newElement.inputType = 'TEXT';
newElement.value = value;
return self;
}
function addLabel(lbl) {
newElement.label = lbl;
return self;
}
function build() {
return newElement;
}
this.newText = newText;
this.addLabel = addLabel;
this.build = build;
}
Finally there is a function that returns an object that contains everything that the component needs.
function TextInput(initValue) {
var self = this;
self.label = initValue.label;
self.value = initValue.value;
var textInput = {
label: self.label,
value: self.value
};
return textInput;
}
Then I create an object to be passed to the component.
var attr = new Attribute(5225);
var textBox = new TextInput(new UIBuilder().newText(attr).addLabel('Description').build());
This structure works using Knockout and I'm trying to keep as much of the existing code as possible.
Most of the time this works but there are some occasions where the value is coming in as a string rather then a ref. I haven't been able to isolate why this is happening. While debugging most of the time the values for initValue.value and self.value look like this in the watch on VS Code.
On some occasions it changes to string values even though the objects were created using the same functions.
I checked the initValue object when taking the screenshot and it appeared to be a ref
As far as I can see the value should stay as a ref. I'm not unwrapping it in code. Why is this happening? What should I look for?
I am using the server-side of Blazor.
I want to change the CSS of the body.
In Jquery I can write the code like this easily:
$("body").css("overflow-y","hidden");
However, with this tutorial(Blazor Change Validation default css class names) said, it seems I can only change the CSS by changing the class name.
It is so complex while crossing the component, especially the body is at the top of all the components.
I wonder whether there is a way can changes CSS directly in Blazor. Thank you.
There are several ways of getting out of the "blazor way" of doing things and accomplishing css modification of an element.
Simplest: Just like you can use the class attribute, use the style attribute
<element style=#myStyle></element>
#code {
string myStyle;
void MyMethod() {
myStyle="overflow-y: hidden;"
}
}
Advanced: Use JS interop
a. In the main view (index.html or Pages/_Host.cshtml depending on project type), create a js endpoint for your component
<script>
window.applyStyleForElement = function(styleOp) {
document.getElementById(styleOp.id).style[styleOp.attrib] = styleOp.value;
}
</script>
b. In razor file:
#Inject IJRRuntime JSRuntime
<element id=#myId></element>
#code {
string myId = Guid.NewGuid().ToString("n");
async Task MyMethod() {
await JSRuntime.InvokeAsync("applyStyleForElement",
new { id = myId, attrib = "overflowY", value = "hidden" });
}
}
Finally, applying to your special case with body element ("advanced" method above).
a. In the main view (index.html or Pages/_Host.cshtml depending on project type), create a js endpoint
<script>
window.applyStyleForBody = function(style) {
document.body.style[style.attrib] = style.value;
}
</script>
b. In razor file:
#Inject IJRRuntime JSRuntime
(...)
#code {
async Task MyMethod() {
await JSRuntime.InvokeAsync("applyStyleForBody",
new { attrib = "overflowY", value = "hidden" });
}
}
Well, Blazor does not support direct css modification yet, since Web Assembly doesn't. Anyway heads up, it is on the road-map for Web Assembly/Blazor.
Therefor your best bet is, changing the class name with variables. At least for now.
I'M NOT SURE IT'S THE RECOMMENDED WAY BUT it works!
For one of my project, I include a in the page html itself, using params :
<style>
html
{
background-color:#_ColorCss;
}
</style>
//html stuff here
#code
{
public string Color{ get; set; } = "white";
string _ColorCss => $"{Color}"; //use this in case of formatting (ex : add 'px' or that kind of things)
//code stuff here
}
not the very sexiest way but it works
have fun !
Well, actually there is a way to do that and it works really good (it might suffer a little delay though).
I know this answer is a little bit late but it might help other people who face the same challenge.
We need to create some JS code that includes the wanted files:
function includeLeftStyle() {
appendStyle("left.css");
}
function includeRightStyle() {
appendStyle("right.css");
}
function appendStyle(path) {
var element = document.createElement("link");
element.setAttribute("rel", "stylesheet");
element.setAttribute("type", "text/css");
element.setAttribute("href", path);
document.getElementsByTagName("head")[0].appendChild(element);
}
The wished CSS can be called according to the language (any other coditions) in the MainLayout:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (// is left)
{
await JSRuntime.InvokeAsync<object>("includeLeftStyle");
}
else
{
await JSRuntime.InvokeAsync<object>("includeRightStyle");
}
}
}
Happy coding! :)
Beginnig from the #Tewr answer, we can also change the whole class:
a) In the main view (index.html or Pages/_Host.cshtml depending on project type), create a js endpoint
<script>
window.applyStyleForElement = function (styleOp) {
if (styleOp != null) {
document.getElementById(styleOp.id).className = styleOp.value;
}
}
</script>
b) Then in the razor file
async Task MyMethod(string sortColumn)
{
await JsRuntime.InvokeVoidAsync("applyStyleForElement",
new { id = sortColumn, value = "newClassName" });
}
I am transitioning from Selenium to WebdriverIO and I'm running into some difficulty regarding function reusability. Let me demonstrate with an example:
<nav>
<div><a>Clients</a></div>
<div><a>Accounts</a></div>
<div><a>Packages</a></div>
</nav>
lets say I have a navigation bar with 3 links above. When I land on this page, I want to check if each link exists. My function may look something like this:
class LoginPage extends Page {
get clientsLink() { return $('//a[contains(., "Clients")]'); }
isTabDisplayed() {
if (this.clientsLink.isDisplayed()) {
return true;
} else {
false
}
}
}
this is fine except I would have to write 2 more getters for Accounts and Packages and so my class would look like this:
class LoginPage extends Page {
get clientsLink() { return $('//a[contains(., "Clients")]'); }
get accountsLink() { return $('//a[contains(., "Accounts")]'); }
get packagesLink() { return $('//a[contains(., "Packages")]'); }
isClientTabDisplayed(tab) {
if (this.clientsLink.isDisplayed()) {
return true;
} else {
false
}
}
isAccountsTabDisplayed(tab) {
if (this.accountsLink.isDisplayed()) {
return true;
} else {
false
}
}
isPackagesTabDisplayed(tab) {
if (this.packagesLink.isDisplayed()) {
return true;
} else {
false
}
}
}
at this point, my anxiety kicks in and I start to think of ways I can reuse the isTabDisplayed function where I can pass a string to the getter with my tab name, or something along the lines of that.
Unfortunately, getters do not accept parameters and so far I have not found any resources on google that can help me to solve this issue (most common being Page Object Model which doesn't seem to address this problem)
Is my thought process out of line that I am striving for reusable code in UI testing or am I not googling for correct patterns?
Page Objects in WebdriverIO are just plain ES6 classes. Have a look through the documentation on ES6 classes to understand how you can create functions that you can pass arguments in to.
Now, that being said, what you're doing here isn't necessary. Instead of creating a function which references a getter, why not just reference that getter directly in your test?
const login = new LoginPage();
const isAccountsTabDisplayed = login.accountsLink.isDisplayed();
There's really no reason to create a wrapper function around this.
hi I am trying to get city name from google api but getting that error below is my code
appcomponent class
import {Component, OnInit} from 'angular2/core';
import {marketComponent} from './market.component';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {introComponent} from './intro.component';
import {geoService} from './service.geo';
import {JSONP_PROVIDERS} from 'angular2/http';
declare var google: any;
#Component({
selector: 'my-app',
templateUrl: 'app/app.component.html',
directives: [ROUTER_DIRECTIVES],
providers: [JSONP_PROVIDERS, geoService]
})
#RouteConfig([
{ path: '/intro', name: 'Intro', component: introComponent, useAsDefault: true },
{ path: '/market', name: 'Market', component: marketComponent },
])
export class AppComponent {
constructor(private _http: geoService) { }
public maps;
public cat_error: Boolean = false;
public xml_Latitude :string;
public xml_Lang: string;
ngOnInit() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPosition);
} else {
alert("Geolocation is not supported by this browser.");
}
var input: any = document.getElementById('google_places_ac');
var autocomplete = new google.maps.places.Autocomplete(input, {});
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var place = autocomplete.getPlace();
console.log(place)
});
}
showPosition(position) {
this.xml_Latitude = position.coords.latitude;
this.xml_Lang = position.coords.longitude;
this._http.getPlaces(this.xml_Latitude, this.xml_Lang).subscribe(
data => { this.maps = data },
err => { this.cat_error = true }
);
var result = this.maps.results;
var city = result[0].address_components[4].long_name + "," + result[0].address_components[6].long_name;
alert(city);
}
}
and geoservice file
import {Injectable} from 'angular2/core';
import { Response, Jsonp} from 'angular2/http';
import 'rxjs/add/operator/map';
#Injectable()
export class geoService {
constructor(private http: Jsonp) { }
public xml_Latitude: string;
public xml_Lang: string;
public getPlaces(xml_Latitude, xml_Lang) {
return this.http.get(`http://maps.googleapis.com/maps/api/geocode/json?latlng=
'${this.xml_Latitude}','${this.xml_Lang}'&sensor=true`)
.map((res: Response) => res.json())
.catch(this.handleError);
}
private handleError(error: Response) {
console.error(error);
return error.json().error || 'Server error';
}
}
error also says getplaces is not a function, I think I am missing something but don't know what....
In addition to the callback ordering problem identified by Thierry, you have a lost this context on this line:
navigator.geolocation.getCurrentPosition(this.showPosition);
The Problem
You have the classic JavaScript problem known as the incorrect this context.
The this keyword in JavaScript behaves differently than in does in other languages like C# and Java.
How this works
The this keyword, in a function, is determined as follows:
* If the function was created through a call to .bind, the this value is the argument provided to bind
* If the function was invoked through a method call, e.g. expr.func(args), then this is expr
* Otherwise
* If the code is in strict mode, this is undefined
* Otherwise, this is window (in a browser)
Let's look at how this works in practice:
class Foo {
value = 10;
doSomething() {
// Prints 'undefined', not '10'
console.log(this.value);
}
}
let f = new Foo();
window.setTimeout(f.doSomething, 100);
This code will print undefined (or, in strict mode, throw an exception).
This is because we ended up in the last branch of the decision tree above.
The doSomething function was invoked, the function wasn't a result of a bind call, and it wasn't invoked in a method syntax position.
We can't see the code for setTimeout to see what its invocation looks like, but we don't need to.
Something to realize is that all doSomething methods point to the same function object.
In other words:
let f1 = new Foo();
let f2 = new Foo();
// 'true'
console.log(f1.doSomething === f2.doSomething);
We know that setTimeout can only see the function we passed it, so when it invokes that function,
there's no way for it to know which this to provide.
The this context has been lost due to our referencing the method without invoking it.
The Red Flag
Once you know about this problems, they're easy to spot:
class Foo {
value = 10;
method1() {
doSomething(this.method2); // DANGER, method reference without invocation
}
method2() {
console.log(this.value);
}
}
The Solution
You have a few options here, each with its own trade-offs.
The best option depends on how often the method in question is invoked from differing call sites.
Arrow Function in Class Definition
Instead of using the normal method syntax, use an arrow function to initialize a per-instance member.
class DemonstrateScopingProblems {
private status = "blah";
public run = () => {
// OK
console.log(this.status);
}
}
let d = new DemonstrateScopingProblems();
window.setTimeout(d.run); // OK
Good/bad: This creates an additional closure per method per instance of your class. If this method is usually only used in regular method calls, this is overkill. However, if it's used a lot in callback positions, it's more efficient for the class instance to capture the this context instead of each call site creating a new closure upon invoke.
Good: Impossible for external callers to forget to handle this context
Good: Typesafe in TypeScript
Good: No extra work if the function has parameters
Bad: Derived classes can't call base class methods written this way using super.
Bad: The exact semantics of which methods are "pre-bound" and which aren't create an additional non-typesafe contract between your class and its consumers.
Function Expression at Reference Site
Shown here with some dummy parameters for explanatory reasons:
class DemonstrateScopingProblems {
private status = "blah";
public something() {
console.log(this.status);
}
public run(x: any, y: any) {
// OK
console.log(this.status + ': ' + x + ',' + y);
}
}
let d = new DemonstrateScopingProblems();
// With parameters
someCallback((n, m) => d.run(n, m));
// Without parameters
window.setTimeout(() => d.something(), 100);
Good/bad: Opposite memory/performance trade-off compared to the first method
Good: In TypeScript, this has 100% type safety
Good: Works in ECMAScript 3
Good: You only have to type the instance name once
Bad: You'll have to type the parameters twice
Bad: Doesn't easily work with variadic parameters
I think that you should move the result block into the subscribe callback associated the getPlaces method call:
showPosition(position) {
this.xml_Latitude = position.coords.latitude;
this.xml_Lang = position.coords.longitude;
this._http.getPlaces(this.xml_Latitude, this.xml_Lang).subscribe(
data => {
this.maps = data;
var result = this.maps.results; // <----------
var city = result[0].address_components[4].long_name + "," + result[0].address_components[6].long_name;
alert(city);
},
err => { this.cat_error = true }
);
}
It's because this.maps is undefined before the callback is called. And you try tyo get the result attribute before (this.maps.results).
Edit
I also see a problem at the line navigator.geolocation.getCurrentPosition. You could refactor your code this way:
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => { // <----
this.showPosition(position);
});
} else {
alert("Geolocation is not supported by this browser.");
}
Is there a way to define a property in a TypeScript module?
None of these compile:
module My {
// doesnt work
get Value(): number { return 42; }
// doesn't work either
get function Value(): number { return 42; }
// nope
function get Value(): number { return 42; }
}
Right now I'm forced to use this:
module My {
declare var Value: number;
Object.defineProperty(My, "Value", {
get: () => 42
});
}
The second form seems messy to me and the code hinting doesn't really treat it as a read-only property but as a plain variable.
Is there any standard way of defining properties directly inside modules?
No, there's not a way to declare a property on a module in TypeScript using any documented language features.
You can do it in several slightly round-about techniques.
A module can extend an existing class or function. So, I've created a class with a static property, and then later created a module that uses the same name as the class.
class My
{
static get Value():Number {
return 42;
}
}
module My {
var works: boolean = true;
}
alert(My.Value);
It does generate one oddity in the JavaScript generated code that you wouldn't do manually (and should be removed by most optimizers anyway) ... it will redeclare the variable My when the module is created. This does not cause a run-time issue as the variable was already lifted in JavaScript and will not conflict with the first usage.
Here's another option:
module Global {
class Inner {
get Value():Number {
return 42;
}
}
export var My;
My = new Inner();
}
var My = Global.My;
alert(My.Value);
While it presents an extra namespace, you can manipulate it however you'd like and use the inner class or change it as needed. This way, the My variable is global, just like it would be as a module.
Instead of using the module keyword, consider instead using export, which will allow you to do what you want to do, treating the file itself as a module (which is how CommonJS and AMD both work).
// in My.ts
var My = {
get value() {
return 42;
}
};
export = My;
// in foo.ts
import My = require('My');
console.log(My.value);
I describe this in greater detail in a blog post, The Definitive Guide to TypeScript.
I tried the singleton
let My = {
get value() {
return 42;
}
}
export My
but ran into an issue where the emitted JS still said get value() and didn't work on older versions of Node. I tried Object.defineProperty but then lost TypeScript compatibility. Here's my bridge that fixes both cases:
interface My {
value: number
}
// type assertion fixes TypeScript usage
let my = <My>{}
// defineProperty fixes JS usage
Object.defineProperty(my, 'value', {
get: () => 42
});
export = my;
It's used like a module in typescript
import * as my from './my'
my.property // returns 42
// my.property = doesn't work
I know it's a "little" late for this but using typescript 4.8 you can do this:
export module MyModule {
export var myVariable: string = "test";
}
then use it like:
MyModule.myVariable = "something else";