Aurelia Templating Engine compose - javascript

I'm trying to compose component dynamically through code (not with <compose>) and add it to the DOM from a service (similar to a popup).
From my understanding, the best way is to use the TemplatingEngine via .compose method.
I cannot find a decent documentation or small sample on how this is used exactly.
This is what I have so far
constructor(
loggerFactory: LoggerFactory,
private bindingEngine: BindingEngine,
private templatingEngine: TemplatingEngine,
private container: Container,
private viewResources: ViewResources
) {
const hostee = this.templatingEngine.compose({
host: document.body,
container: this.container,
bindingContext: {
},
viewModel: SnackbarHostElement,
viewResources: this.viewResources,
viewSlot: new ViewSlot(document.body, true)
});
hostee.then(x => {
x.attached();
});
I'm getting no errors and .then is being triggered, however, the component doesn't seem to be rendering. Any help would be appreciated!

This is the final code (thanks #Fabio)
async init(): Promise<View | Controller> {
const viewController = await this.templatingEngine.compose({
host: document.body,
container: this.container,
bindingContext: {
},
viewModel: this.container.get(SnackbarHostElement),
viewResources: this.viewResources,
viewSlot: new ViewSlot(document.body, true)
});
viewController.attached();
return viewController;
}

I just use the method like this, wishes to help you!
const View = await this.templatingEngine.compose({
host: this.element,
container: this.container,
bindingContext: this.bindingContext,
overrideContext: this.overrideContext,
viewModel: this.container.get(CustomComponent),
viewResources: this.viewResources,
viewSlot: new ViewSlot(this.element, true),
});

Related

How to import js library properly in dart? Flutter web application

I want to render a YooKassa widget in flutter app, so I've written this code to implement it
https://yookassa.ru/checkout-widget/v1/checkout-widget.js
#JS()
library checkout-widget.js;
import 'package:js/js.dart';
#JS('YooMoneyCheckoutWidget')
class YooMoneyCheckoutWidget {
external YooMoneyCheckoutWidget(YooMoneyCheckoutWidgetParams params);
external render(String viewID);
}
#JS()
#anonymous
class YooMoneyCheckoutWidgetParams {
external factory YooMoneyCheckoutWidgetParams({
String confirmation_token,
String return_url,
Map<String, dynamic> customization,
Function error_callback
});
}
class YooKassa {
YooMoneyCheckoutWidget _checkoutWidget;
YooKassa({String confirmation_token,String return_url}):_checkoutWidget = YooMoneyCheckoutWidget(YooMoneyCheckoutWidgetParams(confirmation_token: confirmation_token, return_url: return_url, customization: {'colors': {'controlPrimary':'#00BF96'}},error_callback: null));
void render(String viewID) => _checkoutWidget.render(viewID);
}
then trying to call the constructor for a JS code
final widget = YooKassa(confirmation_token: 'ct-2834a83d-000f-5000-9000-1316627a4610',return_url: 'https://app.moneyhat.ru/');
and then render it in DivElement in a HTMLElement
final wrapper = html.DivElement()
..style.width = '100%'
..style.height = '100%'
..id = 'pay-form';
ui.platformViewRegistry.registerViewFactory(
viewID,
(int viewId) => wrapper,
);
widget.render('pay-form');
return Scaffold(
body: SizedBox(
height: 500,
child: HtmlElementView(
viewType: viewID,
),
));
}
What's wrong here? Flutter raises an error - YooMoneyCheckoutWidget is not a constuctor

Using Google One Tap in Angular

I'd like to use Google One Tap in my Angular 11 app. Following the documentation I added <script async defer src="https://accounts.google.com/gsi/client"></script> to my html and then used the following code in my app.component.html:
<div id="g_id_onload"
data-client_id="MY_GOOGLE_CLIENT_ID"
data-callback="handleCredentialResponse",
data-cancel_on_tap_outside="false">
</div>
The popup works fine, though I can't seem to log in. If I create a function handleCredentialResponse in app.component.ts, I get the following error: [GSI_LOGGER]: The value of 'callback' is not a function. Configuration ignored.
If I instead try to use the JavaScript API, Typescript throws the following error: Property 'accounts' does not exist on type 'typeof google'
What should I do to be able to using Google One Tap in Angular?
I had a similar problem when I used the HTML API approach, so I ended up using the JavaScript API instead.
Here's what I did:
First, make sure to install the #types/google-one-tap package.
As you mentioned, I'm also importing the script in my index.html file, like so:
<body>
<script src="https://accounts.google.com/gsi/client" async defer></script>
<app-root></app-root>
</body>
Now, moving on to your main component which in my case is app.component.ts, import the following first:
import { CredentialResponse, PromptMomentNotification } from 'google-one-tap';
Then, you can add this on the ngOnInit(). Make sure to read the documentation to get more details on the onGoogleLibraryLoad event:
// #ts-ignore
window.onGoogleLibraryLoad = () => {
console.log('Google\'s One-tap sign in script loaded!');
// #ts-ignore
google.accounts.id.initialize({
// Ref: https://developers.google.com/identity/gsi/web/reference/js-reference#IdConfiguration
client_id: 'XXXXXXXX',
callback: this.handleCredentialResponse.bind(this), // Whatever function you want to trigger...
auto_select: true,
cancel_on_tap_outside: false
});
// OPTIONAL: In my case I want to redirect the user to an specific path.
// #ts-ignore
google.accounts.id.prompt((notification: PromptMomentNotification) => {
console.log('Google prompt event triggered...');
if (notification.getDismissedReason() === 'credential_returned') {
this.ngZone.run(() => {
this.router.navigate(['myapp/somewhere'], { replaceUrl: true });
console.log('Welcome back!');
});
}
});
};
Then, the handleCredentialResponse function is where you handle the actual response with the user's credential. In my case, I wanted to decode it first. Check this out to get more details on how the credential looks once it has been decoded: https://developers.google.com/identity/gsi/web/reference/js-reference#credential
handleCredentialResponse(response: CredentialResponse) {
// Decoding JWT token...
let decodedToken: any | null = null;
try {
decodedToken = JSON.parse(atob(response?.credential.split('.')[1]));
} catch (e) {
console.error('Error while trying to decode token', e);
}
console.log('decodedToken', decodedToken);
}
I too had the same problem in adding the function to the angular component.
Then i found a solution by adding JS function in appComponent like this:
(window as any).handleCredentialResponse = (response) => {
/* your code here for handling response.credential */
}
Hope this help!
set the div in template to be rendered in ngOnInit
`<div id="loginBtn" > </div>`
dynamically inject script tag in your login.ts as follows
constructor(private _renderer2: Renderer2, #Inject(DOCUMENT) private _document: Document){}
ngAfterViewInit() {
const script1 = this._renderer2.createElement('script');
script1.src = `https://accounts.google.com/gsi/client`;
script1.async = `true`;
script1.defer = `true`;
this._renderer2.appendChild(this._document.body, script1);
}
ngOnInit(): void {
// #ts-ignore
window.onGoogleLibraryLoad = () => {
// #ts-ignore
google.accounts.id.initialize({
client_id: '335422918527-fd2d9vpim8fpvbcgbv19aiv98hjmo7c5.apps.googleusercontent.com',
callback: this.googleResponse.bind(this),
auto_select: false,
cancel_on_tap_outside: true,
})
// #ts-ignore
google.accounts!.id.renderButton( document!.getElementById('loginBtn')!, { theme: 'outline', size: 'large', width: 200 } )
// #ts-ignore
google.accounts.id.prompt();
}
}
async googleResponse(response: google.CredentialResponse) {
// your logic goes here
}
Google One Tap js library tries to find callback in the global scope and can't find it, because your callback function is scoped somewhere inside of your app, so you can attach your callback to window, like window.callback = function(data) {...}.
Also, since you are attaching it to window, it's better to give the function a less generic name.

Monitoring variable change done by jQuery inside component

I know this isn't optimal, but I have regular javascript code changing a variable that is property in my component.
The reason for this is that I have dynamic javascript (that comes from Google Blockly) being run (eval) in my component.
Here's the component template:
<input #textInput
[attr.max-value]="maxValue"
type="number"
style="width: 495px;"
(change)="onChange($event)"
[id]="key"
class="form-control"
[disabled]="disabled"
value="{{value}}" />
And the component code:
export class NumberStepComponent extends BaseStepComponent {
/** number-step ctor */
constructor() {
super();
}
#ViewChild("textInput") textInput: ElementRef;
private _maxValue: string;
get maxValue(): string {
return this._maxValue;
}
set maxValue(val: string) {
this._maxValue = val;
console.log("Changed ** : " + val);
}
}
When I change maxValue from Angular, I do get the setter code triggered. But when jQuery does it, that setter isn't triggered.
ie:
var code = `$('#ID').attr('max-value', "5")`;
eval(code);
I've tried executing the code "in the zone" thinking this would keep Angular up to date:
this.ngZone.run(() => {
var code = `$('#ID').attr('max-value', "5")`;
eval(code);
});
Doesn't trigger the setter either. Any ways to achieve this ?
You should not use $('#ID').attr('max-value', "5"), since the value is binded (so handled by Angular).
If you want to change the value, update the model (this.maxValue = '5'), not the view. Angular will update the view for you.
It's hard to guess what you can/cannot do but you can try to add a MutationObserver. For example :
const node = document.querySelector('.some-element') // or this.textInput.nativeElement;
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => console.log(mutation));
});
observer.observe(node, {
attributes: true,
childList: true,
characterData: true
});

Angular8 and Full Calendar component

I am trying to make full calendar working in my Angular8 application. I tried few different ways of implementation but all let me down so far.
Most tutorials and examples suggests to use "jquery approach" but I think it is outdated, when I tried it, something like this:
$("#calendar").fullCalendar(
this.defaultConfigurations
);
I have an runtime error that fullCalendar is not a function.
The only way I could make calendar to work is this approach:
export class EventCalendarViewComponent extends BaseClass implements OnInit {
#ViewChild('calendar', null) calendar: FullCalendarComponent;
calendarPlugins = [dayGridPlugin];
calendarEvents = [];
addDialogRef: MatDialogRef<AddEventComponent>;
editDialogRef: MatDialogRef<EditEventComponent>;
constructor(private dialog: MatDialog, protected snackBar: MatSnackBar, private eventService: EventService) {
super(snackBar);
}
protected getData() {
this.eventService.getAllEvents().subscribe(res => {
res.forEach(event => {
const calendarEvent: CalendarEvent = this.schoolEventToCalendarEvent(event);
this.calendarEvents.push(calendarEvent);
});
});
}
ngOnInit(): void {
this.getData();
}
private schoolEventToCalendarEvent(event: SchoolEvent): CalendarEvent {
return {
id: event.id,
title: event.title,
start: moment(event.startDate).format('YYYY-MM-DD'),
end: moment(event.endDate).format('YYYY-MM-DD')
}
} ...
and html looks like this then:
<full-calendar #calendar
defaultView="dayGridMonth"
[plugins]="calendarPlugins"
[events]="calendarEvents"
[editable]="true"
[allDayDefault]="true"
[timeZone]="'local'"
></full-calendar>
However events populates with getData() method do not show on the calendar. The only way to see any events is static population of its variable:
calendarEvents = [{id: 1, title: 'static entry', start: '2019-09-05'}];
What is the trick here? I cannot find the way to refresh the calendar after db call is comleted.
Well the trick is (at least in my case) to declare the component like this:
<full-calendar #calendar
defaultView="dayGridMonth"
deepChangeDetection="true"
[plugins]="calendarPlugins"
[events]="calendarEvents"
[refetchResourcesOnNavigate]="true"
[editable]="true"
[allDayDefault]="true"
[timeZone]="'local'"
></full-calendar>
So I think that
deepChangeDetection="true"
is the the property which make the difference.
refetchResourcesOnNavigate which is there as well do not change anything. Since my data comes from the the database another property:
rerenderDelay
might be useful here, especially that deepChangeDetection might affect the performance.

Angular2+ e2e testing - Cannot use by.id

I'm new to Angular2 and haven't developed the Angular components to be tested. However, I'm supposed to write some some UI (e2e) tests but I'm not even able to input text in an input field.
My problem is that
element(by.id('username')).sendKeys('test')
is not working. (Same with Button elements and so on)
I'm sure that it is only a small thing but I'm not able to find out what it is.
I have the following configuration:
Proctractor: 5.1.2
chrome driver: 58.0.3029.110
OS: Windows NT 6.1.7601 SP1 x86_64
Spec file:
import { LoginPage } from './login.po';
describe('login tests', function() {
let page: LoginPage;
beforeEach(() => {
page = new LoginPage();
});
it('Demo', () => {
page.navigateTo();
page.getUsernameInput().sendKeys('test');
});
});
Page Object file:
import { browser, element, by } from 'protractor';
export class LoginPage {
navigateTo() {
return browser.get('/login');
}
getParagraphText() {
return element(by.class('app-root h1')).getText();
}
getUsernameInput() {
return element(by.id('username'));
}
}
The HTML template:
....
<div>
<input
id="username"
name="username"
ngModel
type="text"
placeholder="{{something}}"
autocomplete="off"
class="input-text"
required>
</div>
...
Proctractor config
var SpecReporter = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 120000,
getPageTimeout: 120000,
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
seleniumAddress: 'http://localhost:4444/wd/hub',
baseUrl: 'http://localhost:8001',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
useAllAngular2AppRoots: true,
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e'
});
},
onPrepare: function() {
jasmine.getEnv().addReporter(new SpecReporter());
}
};
Any help is highly appreciated.
EDIT:
None of the solutions worked in my case. I ended up using browser.driver.findElement(by.id('username')); instead of element(by.id('username')); This is unsatisfying because I still don't understand why this doesn't work. I'd be thankful if someone could give me a hint or explanation.
I think your problem is timing.
What happens if you do:
it('Demo', () => {
// wait for page to change to /login
return page.navigateTo().then(() => {
// then look for user input and write 'test'
return page.getUsernameInput().sendKeys('test');
});
});
Edit:
Sounds odd to me that browser.driver.findElement(by.id('username')) works since element(by.id('username')) should be equivalent.
I use a helper class for a lot of the browser interactions, perhaps worth a shot.
Snippets I use for finding element and sending keystrokes:
public static async getElement(locator: By | Function, waitMs?: number): Promise<ElementFinder | any> {
await BrowserHelper.waitForVisibilityOf(locator, waitMs | 1000);
return element(locator);
}
public static sendKeys(locator: By | Function, keys: string, clear?: boolean, waitMs?: number): Promise<void> {
return BrowserHelper.getElement(locator, waitMs).then((element: ElementFinder) => {
if (!clear) {
return element;
}
return element.clear().then(() => element);
}).then((element: ElementFinder) => {
return element.sendKeys(keys)
});
}
public static async waitForVisibilityOf(locator: By | Function, waitMs?: number): Promise<any> {
await browser.wait(EC.presenceOf(element(locator)), waitMs || 5000).then(() => {
// visible
}, (error) => {
console.error('timeout at waitForVisibilityOf', locator, error);
});
}
I believe this is due to your getUsernameInput() method returning not the locator in this case. As per Protractor documentation,
The element() function returns an ElementFinder object. The ElementFinder knows how to locate the DOM element using the locator you passed in as a parameter, but it has not actually done so yet. It will not contact the browser until an action method has been called.
You can try this modified code
getUsernameInput() {
element(by.id('username')).sendKeys('text');
}
}
and then using
it('Demo', () => {
page.navigateTo();
page.getUsernameInput();
});
});
Also, I'm not sure your getText() would return the text, because getText() returns a Promise, which you would have to resolve. This has been explained here.

Categories

Resources