How can I disable deletion and focus on a BlockEmbed - quill

I have a special blot that extends BlockEmbed. I would like to disable focus and backspace deletion in this block. Is this possible?
import Quill from 'quill';
import { html, render } from 'lit-html'
let Block = Quill.import('blots/block');
let BlockEmbed = Quill.import('blots/block/embed');
const template = (text) => html`
<img src="https://prosemirror.net/img/dino/tyrannosaurus.png"/>
<span>${text}</span>`;
export class BlockActionBlot extends BlockEmbed {
static blotName = 'action';
static className = 'block-action';
static tagName = 'div';
static create({ id, text }) {
const node = super.create();
node.dataset.text = text;
node.dataset.id = id;
render(template(text), node)
return node;
}
static value(node) {
return {
id: node.dataset.id,
text: node.dataset.text
}
}
}

If you want your blot to be "static", the easiest way I found was to set node.contentEditable to false in the create method.
I hope this helps and the answer's not too late in your case (I ran into the very same issue today)...

To disable deletion you could override the deleteAt() method in the BlockActionBlot. Something like the following below. That should prevent deletion at the blot level.
export class BlockActionBlot extends BlockEmbed {
// ...
deleteAt() { }
}
See the blot methods for the signature of deleteAt and the other methods provided.
For disabling focus on the blot you could apply user-select: none and/or cursor: none to the blot. You'll probably want to add a class for the above css properties in the create method you have. You could do that with node.setAttribute('class', 'block-action') then apply the css to div.block-action in your loaded css.
Another option would be to try and intercept the delete key press. I think that approach, though doable, would be more complicated.

Step 1 (assumption). You have a custom blot class that extends something like BlockEmbed
Step 2.
Add the following to your custom blot class:
deleteAt() {
return false
}
to your custom blot class.
Step 3.
Set contenteditable to false when creating the blot in the create method
node.setAttribute('contenteditable', false);

Related

Typescript ReferenceError: google is not defined, but only in standalone files

I'm building a React app with an embedded Google Map.
I've got a custom menu element that I want to display on the map after a click. Google's docs instruct me to 'implement' (although I think in Typescript terms, they mean extend) the google.maps.OverlayView class in order to render elements over the map.
When I define the class ContextMenu extends google.maps.OverlayView class inline, the code compiles fine and my element shows up on click. I want to define this class in a separate file, using Typescript.
However, when I move ContextMenu to a separate file, React errors out with ReferenceError: google is not defined.
Any idea how to 'import the namespace' such that ContextMenu.ts knows where google is? It seems like I am missing something fundamental about Typescript here, but none of their documentation I've been able to find has discussed the practice of creating classes with external namespaces.
Or is extends the wrong way to do this here? Should I just follow Google's instructions, even in Typescript which exists to avoid messing with prototypes?
Inherit from this class by setting your overlay's prototype: MyOverlay.prototype = new google.maps.OverlayView();.
Working code:
// App.tsx
import React from 'react'
import { Wrapper } from '#googlemaps/react-wrapper'
// cannot define ContextMenu here
const App: React.VFC = () => {
// cannot define ContextMenu here
const onClick = (e: google.maps.MapMouseEvent) => {
// CAN define ContextMenu here
class ContextMenu extends google.maps.OverlayView {
private origin_: google.maps.LatLng
constructor(origin: google.maps.LatLng) {
super()
this.origin_ = origin
}
}
const menu = new ContextMenu(e.latLng)
}
return (
<Wrapper ...>
// Map goes here
</Wrapper>
)
}
Broken code:
// App.tsx as above, without ContextMenu defined.
// ContextMenu.ts
class ContextMenu extends google.maps.OverlayView {
// ...
}
It is not possible to directly extend a google.maps.* class since it actually isn't available (this might depend on tsconfig target, but I haven't tested). You can use the following pattern in TypeScript to delay.
export interface OverlayViewSafe extends google.maps.OverlayView {}
/**
* Extends an object's prototype by another's.
*
* #param type1 The Type to be extended.
* #param type2 The Type to extend with.
* #ignore
*/
// eslint-disable-next-line #typescript-eslint/no-explicit-any
function extend(type1: any, type2: any): void {
// eslint-disable-next-line prefer-const
for (let property in type2.prototype) {
type1.prototype[property] = type2.prototype[property];
}
}
/**
* #ignore
*/
export class OverlayViewSafe {
constructor() {
// We use the extend function for google.maps.OverlayView
// because it might not always be available when the code is defined.
extend(OverlayViewSafe, google.maps.OverlayView);
}
}

Is there a way to replace an Angular component by a component in a customer module like when using the decorator in AngularJS?

I am developing an application for a wide customer range. Different customers tend to have different needs for customization in their UI. Therefore, we would like to replace those components by customer-specific components. Unfortunately, this seems to be impossible. Could anyone be of help?
The situation we would like:
Module 'Base'
Component 'Scheduler' - displaying a couple of components from template
Component 'SchedulerEvent' (tag 'scheduler-event') with some basic data
Module 'Customer'
Component 'CustomerSchedulerEvent' (tag 'scheduler-event') with customer-specific data
In this situation, we would like to have the CustomerSchedulerEvent displayed instead of the normal SchedulerEvent. Although the code compiles properly this way, still the SchedulerEvent is displayed.
In old AngularJS code, there was the decorator concept which could replace entire directives/components, which is being described here: https://docs.angularjs.org/guide/decorators#directive-decorator-example.
Is there a possibility to get kind-of this behavior working in modern Angular as well?!
Although quite cumbersome, at least there appears to be a workaround:
Make sure you have a component host directive. We will use that one later.
#Directive({
selector: '[componentHost]',
})
export class ComponentHostDirective {
constructor(readonly $viewContainerRef: ViewContainerRef) { }
}
Create a service returning the component type to render:
import { Injectable, Type } from '#angular/core';
[...]
#Injectable()
export class TemplateComponentService extends TemplateComponentBaseService {
getTemplate(): Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent> {
// console.log('Our EventInformationTemplateService...');
return LaneSubscriberSchedulingEventInformationTemplateComponent;
}
}
which you register as follows in your base module:
#NgModule({
...
providers: [
EventInformationTemplateService,
{ provide: EventInformationTemplateBaseService, useExisting: EventInformationTemplateService }
]
})
export class BaseModule {
}
Your component that could be replaced, should look like:
import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, Type, ViewChild } from "#angular/core";
import { ComponentHostDirective } from "src/common/directives/component-host.directive";
import { AggregateServiceFactory } from "src/common/services/aggregates";
import { LaneSubscriberSchedulingEventInformationTemplateBaseComponent } from "src/production-planning/components/lane-subscriber-scheduling/event-information-template/event-information-template-base.component";
import { EventInformationTemplateBaseService } from "src/production-planning/components/lane-subscriber-scheduling/event-information-template/event-information-template-base.service";
import { moduleName } from "src/production-planning/production-planning.states";
#Component({
selector: 'app-template',
templateUrl: './template.component.html',
})
export class TemplateComponent extends TemplateBaseComponent implements AfterViewInit {
componentType: Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent>;
customComponent: boolean;
#ViewChild(ComponentHostDirective, { static: true }) private _componentHost: ComponentHostDirective;
constructor(
$element: ElementRef,
private readonly $componentFactoryResolver: ComponentFactoryResolver,
private readonly _templateComponentService: TemplateComponentBaseService) {
this.componentType = this._templateComponentService.getComponent();
this.customComponent = !isNull(this.componentType) && this.componentType !== TemplateComponent;
// console.group('TemplateComponentService.getComponent()');
// console.log('Component type', this.componentType);
// console.log('Component custom?', this.customComponent);
// console.groupEnd();
}
// Component lifecycle events
ngAfterViewInit(): void {
if (this.customComponent === true) {
const componentFactory = this.$componentFactoryResolver.resolveComponentFactory(this.componentType);
this._componentHost.$viewContainerRef.clear();
const componentRef = this._componentHost.$viewContainerRef.createComponent<LaneSubscriberSchedulingEventInformationTemplateBaseComponent>(componentFactory);
componentRef.instance.event = this.event;
}
}
}
and its template file like:
<ng-container *ngIf="customComponent != true">
<!-- TODO Display more information -->
<strong>{{ event.title }}</strong>
</ng-container>
<ng-template componentHost></ng-template>
Create another service like the one above and register it the same way in another module in your app.
As you can see, we hide the original component content using *ngIf and use the component host on the ng-template to render the replacing component in its place when the service returns another type than the current type. The reason to opt for this strange path, is that the tag will directly map to our base template component and is not replaceable.
A drawback for this scenario is that the component host directive, being a ViewChild, is only available after view init, which is quite late. For very complex scenarios, this workaround could therefore lead to some unwanted timing issues and such...
I hope anyone could provide a better solution?

How to export a function from a class, so to be able to import it into another class to use

To be clear, I am learning TypeScript and React for spfx developments. I have read and taken part in tutorials and other answers on various sources and Stack Overflow and haven't found them enough to help me.
Here is my function in the EvalReqNewForm class (getGrades()):
export default class EvalReqNewForm extends React.Component<IEvalReqNewProps, IEvalReqNewState> {
constructor(props){
super(props);
this.state = {
EvalType: null,
JobTitReportTo: null,
JobTitReportToNum: null,
PropGradeList: [],
SelectedGrade: undefined,
CompPos: null,
ContextNewJobCode: null
};
this._onJobTitReportToChange = this._onJobTitReportToChange.bind(this);
this._onJobTitReportToNumChange = this._onJobTitReportToNumChange.bind(this);
this._onPropGradeChange = this._onPropGradeChange.bind(this);
}
...
public _getGrades() {
pnp.sp.web.lists.getByTitle("Grades").items.get().then((items: any[]) =>{
let returnedGrades:IDropdownOption[]= items.map((item) =>{return {key:item.Title, text:item.Title};});
this.setState({PropGradeList : returnedGrades});
});
}
And I want to use the _getGrades() function from the other class to use in a ComponentDidMount() in order to the 'Grades' from the SP list.
Does it involve using props? Or can the function be simply exported and imported into the class where I want to use it?
Please understand I'm learning the basics here!
TL;DR
Create a function that only depends on it's parameters and export/import it.
Or can the function be simply exported and imported into the class where I want to use it?
What you can do is create a function that depends only on it's parameters.
So it would be something like
export function getGrades(something){
// do what ever you want with something
return somethingYouDid
}
And then you import it to other files and use it like
import { getGrades } from '...'
...
public _getGrades() {
const results = getGrades(someDataFromSomeWhere)
this.setState({PropGradeList : results});
}
I also see that you are using promises, so maybe you will need to use async/await in your case.
Edit:
As said in the comments
I can't seem to use export within a class
You should use it out side of the class, you will have to functions.
One out side of the class that have all the logic and other inside that class that only calls the outside function.
export function getGrades(something){
// do what ever you want with something
return somethingYouDid
}
class ... {
...
public _getGrades() {
const results = getGrades(someDataFromSomeWhere)
this.setState({PropGradeList : results});
}
}

React binding through constructor - possible to automate?

As per recommendations from others, I have been binding class methods in the constructor in React, for example:
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
I have components with many methods, and I am binding all of these methods to this. Argh, what a pain! To avoid repetitively maintaining this pattern, I built a function that would be called in the constructor in place of all the individual calls; it binds all the methods specific to that class, while a parent class would take care of its own methods moving up the classes. For example:
function bindClassMethodsToThis(classPrototype, obj) {
Object.getOwnPropertyNames(classPrototype).forEach(prop => {
if (obj[prop] instanceof Function && prop !== 'constructor') {
obj[prop] = obj[prop].bind(obj);
console.log(`${classPrototype.constructor.name} class binding ${prop} to object`);
}
});
}
class A {
constructor() {
bindClassMethodsToThis(A.prototype, this);
}
cat() {
console.log('cat method');
}
}
class B extends A {
constructor() {
super();
bindClassMethodsToThis(B.prototype, this);
}
dog() {
console.log('dog method');
}
}
let b = new B();
So, React and ES6 gurus, is this a reasonable approach, or I am doing something wrong here? Should I stick to the individual bindings to this?
Your strategy seems sound, though there are some edge cases that you may end up wanting to tackle. A library like react-autobind, which Alexander mentioned, takes care of some of these things for you, and if I were to use this strategy I would probably use a library like this one (or take a look into the source code to get an idea for what it does).
For completeness, some alternative approaches are:
Use class properties and arrow functions (along with any necessary Babel transforms) to create pre-bound methods:
class MyComponent extends React.Component {
handleChange = () => { /* ... */ }
}
Use a decorator, like the autobind decorator from core-decorators, along with any necessary Babel transforms (this was the strategy I used previously):
import { autobind } from 'core-decorators'
class MyComponent extends React.Component {
#autobind
handleChange() { /* ... */ }
}
Explore the use of Hooks (currently in alpha) to avoid the problem of binding all together (since state values and setters exists as local variables to be closed over). This is the strategy I've been preferring very recently, but please note it is still in the proposal state and may change. :)
Assuming you have Babel setup for it, you can also use arrow functions instead, avoiding the need to bind this:
class Foo extends React.Component {
handleClick = (event) => {
event.preventDefault()
}
render() {
return <div onClick={this.handleClick}>Click me</div>
}
}
One way to resolve this is to call bind in render:
onChange={this.handleChange.bind(this)}

react component method binding with arrow function (class properties)

I heard that this is the cool way to define function instead of binding them in constructor:
class Comp extends React.Component {
delete = id => {
}
render() {}
}
but when I try to build I get:
Module build failed: SyntaxError: Unexpected token (7:15)
pointing to equal sign after delete
what I am missing?
To define class properties the way you are doing it, you need to activate the experimental babel feature transform-class-properties. This allows you to assign expressions like arrrow functions to class properties:
class Bork {
//Property initializer syntax
instanceProperty = "bork";
boundFunction = () => {
return this.instanceProperty;
}
//Static class properties
static staticProperty = "babelIsCool";
static staticFunction = function() {
return Bork.staticProperty;
}
}
Note that this feature is not yet part of the ECMAScript specification and may change in the future.
You need an additional babel plugin for this feature:
https://www.npmjs.com/package/babel-plugin-transform-class-properties

Resources