I'm trying to create custom plugin that creates simple anchor. I used code from documentation (https://ckeditor.com/docs/ckeditor5/latest/framework/guides/tutorials/implementing-a-block-widget.html) and now I'am trying to add ID attribude from prompt.
It is simple empty element with class (for ck-editor css usage) and id for reference.
Everything is working fine, but I`m not able to add the ID attribude obtained from prompt -> no id attribute is added to the div element.
Any idea?
export default class Anchor extends Plugin {
static get requires() {
return [ AnchorEditing, AnchorUI ]
}
}
Anchor UI class
class AnchorUI extends Plugin {
init() {
const editor = this.editor
editor.ui.componentFactory.add( 'Anchor', locale => {
const command = editor.commands.get( 'insertAnchor' )
const buttonView = new ButtonView( locale )
buttonView.set( {
label: "Vložit kotvu",
tooltip: true,
icon
} )
buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' )
this.listenTo( buttonView, 'execute', () => {
const id = prompt("Zadejte ID")
if(id){
editor.execute( 'insertAnchor', {id})
}
} )
return buttonView
} )
}
}
AnchorEditing class
class AnchorEditing extends Plugin {
static get requires() {
return [ Widget ]
}
init() {
this._defineSchema()
this._defineConverters()
this.editor.commands.add( 'insertAnchor', new InsertAnchorCommand( this.editor ))
}
_defineSchema() {
const schema = this.editor.model.schema
schema.register( 'Anchor', {
isObject: true,
allowWhere: '$block'
})
}
_defineConverters() {
const conversion = this.editor.conversion
conversion.for( 'upcast' ).elementToElement( {
model: 'Anchor',
view: {
name: 'div',
attributes: {
id: "anchor",
'data-class': "anchor"
}
}
} )
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'Anchor',
view: {
name: 'div',
attributes: {
id: "anchor",
'data-class': "anchor"
}
}
} )
conversion.for( 'editingDowncast' ).elementToElement( {
model: 'Anchor',
view: ( modelElement, { writer: viewWriter } ) => {
const section = viewWriter.createContainerElement( 'div', { 'data-class': 'anchor' } )
return toWidget( section, viewWriter, { label: 'anchor widget' } )
}
} )
conversion.attributeToAttribute({
model: {
name: "div",
key: 'id'
},
view: {
name: "div",
key: "id"
}
})
}
}
InsertAnchorCommand class
class InsertAnchorCommand extends Command {
execute({id}) {
this.editor.model.change( writer => {
this.editor.model.insertContent( createAnchor( writer, id ) )
} )
}
refresh() {
const model = this.editor.model
const selection = model.document.selection
const allowedIn = model.schema.findAllowedParent( selection.getFirstPosition(), 'Anchor' )
this.isEnabled = allowedIn !== null
}
}
createAnchor function
function createAnchor( writer, id ) {
const Anchor = writer.createElement( 'Anchor' )
writer.setAttribute('id', id, Anchor)
console.log(Anchor)
writer.append( {}, Anchor )
return Anchor
}
expected result: 'div data-class="anchor" id="from-prompt"> '
actual result: 'div data-class="anchor" id="anchor"> '
Related
I'm trying to create react components dynamically based on my JSON structure using a recursive function. Here is my implementation.
components.js
import React from "react";
import Text from "./components/Text";
import Image from "./components/Image";
const Components = {
text: Text,
image: Image
};
export default (block) => {
if (typeof Components[block.type] !== "undefined") {
return React.createElement(Components[block.type], {
key: block.id,
block: block
});
}
return React.createElement(
() => <div>The component {block.type} has not been created yet.</div>,
{ key: block.id }
);
};
dummy data
const data = {
userId: 123123,
pages: [
{
id: 1,
type: "div",
data: { text: "hello" },
content: [
{
id: 123,
type: "text",
data: { text: "hello" }
},
{
id: 456,
type: "image",
data: { source: "url", link: "url" }
}
]
}
]
};
There is a third argument in the React.createElement which is to render the children. Extended your Components component like below to do the recursive rendering:
const ComponentsMap = {
div: Div,
text: Text,
image: Image
};
const Components = (block) => {
if (typeof ComponentsMap[block.type] !== "undefined") {
return React.createElement(
ComponentsMap[block.type],
{
key: block.id,
block: block
},
block.content ? block.content.map((subComp) => Components(subComp)) : null
);
}
return React.createElement(
() => <div>The component {block.type} has not been created yet.</div>,
{ key: block.id }
);
};
export default Components;
Working Demo:
Let's see we have the simple component ToggleButton:
const ButtonComponent = Vue.component('ButtonComponent', {
props: {
value: Boolean
},
methods: {
handleClick() {
this.$emit('toggle');
}
},
template: `
<button
:class="value ? 'on' : 'off'"
#click="handleClick"
>
Toggle
</button>`
});
And the story for that component:
import ToggleButton from './ToggleButton.vue';
export default {
title: 'ToggleButton',
component: ToggleButton,
argTypes: {
onToggle: {
action: 'toggle' // <-- instead of logging "toggle" I'd like to mutate `args.value` here
}
}
};
export const Default = (_args, { argTypes }) => ({
components: { ToggleButton },
props: Object.keys(argTypes),
template: `
<ToggleButton
:value="value"
:toggle="onToggle"
/>
`
});
Default.args = {
value: false
}
What I want to achieve is to handle toggle action inside the story and change value that I've used in Default.args object to change the button style by changing the class name from .off to .on.
I had the same exact issue, and kept looking for days, till I stumbled upon this github post:
https://github.com/storybookjs/storybook/issues/12006
Currently in my React (am sure vue approach will be similar), I do following:
import React from 'react';
import CheckboxGroupElement from '../CheckboxGroup';
import { STORYBOOK_CATEGORIES } from 'elements/storybook.categories';
import { useArgs } from '#storybook/client-api';
export default {
component: CheckboxGroupElement,
title: 'Components/CheckboxGroup',
argTypes: {
onChange: {
control: 'func',
table: {
category: STORYBOOK_CATEGORIES.EVENTS,
},
},
},
parameters: { actions: { argTypesRegex: '^on.*' } },
};
const Template = (args) => {
const [_, updateArgs] = useArgs();
const handle = (e, f) => {
// inside this function I am updating arguments, but you can call it anywhere according to your demand, the key solution here is using `useArgs()`
// As you see I am updating list of options with new state here
console.log(e, f);
updateArgs({ ...args, options: e });
};
return <CheckboxGroupElement {...args} onChange={handle} />;
};
export const CheckboxGroup = Template.bind({});
CheckboxGroup.storyName = 'CheckboxGroup';
CheckboxGroup.args = {
//Here you define default args for your story (initial ones)
controller: { label: 'Group controller' },
options: [
{ label: 'option 1', checked: true },
{ label: 'option 2', checked: false },
{ label: 'option 3', checked: false },
],
mode: 'nested',
};
I want to use a mixin to find a referenced Node and then append some HTML to it rendered using Vue, so I can pass data into it.
const Tutorial = guide => ({
mounted() {
this.guide = guide;
this.html = Vue.compile(`<p>Test</p>`).render;
guide['add-location'].forEach(step => {
this.$refs[step.ref].appendChild(this.html);
})
},
data: function() {
return {
guide: null,
html: null
}
}
});
export default Tutorial;
This is what I have at the moment, it gets the ref correctly, just can't append the HTML as I don't think i'm using Vue.compile correctly.
Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'
In my opinion, It's better if we can avoid mutate DOM directly. What about replace ref with v-html?
const tutorial = guide => ({
mounted() {
guide['add-location'].forEach(step => {
this[step.ref] += this.html;
})
},
data: function() {
return {
...guide['add-location'].reduce((result, step) => {
result[step.ref] = ''
return result
}, {}),
html: `<p>Test</p>`
}
}
});
const Foo = {
template: `
<div>
<div v-html='foo'></div>
<div v-html='bar'></div>
</div>
`,
mixins: [tutorial({
'add-location': [
{ ref: 'foo' },
{ ref: 'bar' }
]
})]
}
Another idea is using wrapper component to wrap target or if your target is a component then you create a wrapper as mixin too.
Using with html property:
<wrapper ref='foo'>
<div>Foo</div>
</wrapper>
const Wrapper = {
props: ['html'],
render(h) {
return h('div', [this.$slots.default, h('div', {
domProps: {
innerHTML: this.html
}
})])
}
}
...
this.$refs.foo.html = '<h1>Hello Foo</h1>'
Example
Or using with custom appendChild method:
const Wrapper = {
data: () => ({
children: []
}),
methods: {
appendChild(child) {
this.children.push(child)
}
},
render(h) {
return h('div', [
this.$slots.default,
...this.children.map(child => h('div', {
domProps: {
innerHTML: child
}
}))
])
}
}
...
this.$refs.foo.appendChild('<h1>Hello Foo</h1>')
this.$refs.foo.appendChild('<h1>Hello Bar</h1>')
Example
Or using with Vue.compile in case that html is not plain html:
const Wrapper = {
data: () => ({
template: '',
context: {}
}),
methods: {
setChild(template, context) {
this.template = template
this.context = context
}
},
render(h) {
let res = Vue.compile(this.template)
return h('div', [
this.$slots.default,
h({
data: () => this.context,
render: res.render,
staticRenderFns: res.staticRenderFns
})
])
}
}
...
this.$refs.foo.setChild('<h1>Hello {{ name }}</h1>', {
name: 'Foo'
})
Example
I have two categories "A" and "B". On Click any button of category "A" removes the button and must move to category "B", On Click any button of category "B" adds the button to category "A" and must move from category "B".
export const LauncherButtons = [
{
linked: false,
type: 'ABC',
name: 'ReactJs'
},
{
linked: false,
type: 'ABC',
name: 'VueJS'
},
{
linked: true,
type: 'XYZ',
name: 'Angular'
},
{
linked: true,
type: 'XYZ',
name: 'Javascript'
}
];
This is what I am rendering for category "A".
{ LauncherButtons.map((button,index) => {
return(
button.linked === true &&
<LauncherActionButton
text={button.name}
onClick = {this.removeAction}/>
);
})}
Rendering category "B".
{ LauncherButtons.map((button,index) => {
return(
button.linked !== true &&
<LauncherActionButtonAdd
textAdd={button.name}
onClick = {this.addAction}/>
);
})}
So basically, when I click on a button of category "A" (True) it should move to category "B" and become false, similarly, when I click on a button of category "B" (False) it should become true and move to category "A".
Try something like this: https://codesandbox.io/s/holy-leftpad-hw1oe
I've laid out two sections, an active and inactive section. By clicking on a button, you move it to the opposite side. I don't know what your LauncherActionButton component looks like so consider this like a bare-bones template.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
export const LauncherButtons = [
{
linked: false,
type: "ABC",
name: "ReactJs"
},
{
linked: false,
type: "ABC",
name: "VueJS"
},
{
linked: true,
type: "XYZ",
name: "Angular"
},
{
linked: true,
type: "XYZ",
name: "Javascript"
}
];
class App extends React.Component {
state = {
buttons: LauncherButtons
};
createActiveButtons = () => {
const { buttons } = this.state;
return buttons
.filter(button => {
return button.linked;
})
.map(activeButton => {
return (
<button onClick={this.handleOnClick} name={activeButton.name}>
{activeButton.name}
</button>
);
});
};
createInactiveButtons = () => {
const { buttons } = this.state;
return buttons
.filter(button => {
return !button.linked;
})
.map(inactiveButton => {
return (
<button onClick={this.handleOnClick} name={inactiveButton.name}>
{inactiveButton.name}
</button>
);
});
};
handleOnClick = event => {
const { buttons } = this.state;
const { name } = event.target;
let updatedButtons = buttons.map(button => {
if (button.name === name) {
return {
...button,
linked: !button.linked
};
} else {
return button;
}
});
this.setState({
buttons: updatedButtons
});
};
render() {
return (
<div style={{ display: "flex" }}>
<div style={{ width: "50%", background: "green", height: "300px" }}>
{this.createActiveButtons()}
</div>
<div style={{ width: "50%", background: "red", height: "300px" }}>
{this.createInactiveButtons()}
</div>
</div>
);
}
}
What about using the item as a parameter? for example:
removeAction(button) {
// change button however you like
}
// in the render method
{
LauncherButtons.map((button, index) => {
return (
button.linked &&
<LauncherActionButton
text={button.name}
onClick={() => removeAction(button)}/>
);
})
}
I have started working on react typescript. I am creating a drop down component using semantic ui. The problem is that semantic Ui provides code in java script format which is easier to understand. I need to convert the below code to typescript. I am successful in doing some of it but having problem converting handleAddition while adding new value to memberOptions.
Below is the code of JS.
I am not sure if I can use setState in typescript.
const memberOptions = [
{
text: 'Bruce',
value: 'bruce'
},
{
text: 'Clark',
value: 'clark'
},
{
text: 'Diana',
value: 'diana'
},
{
text: 'Peter',
value: 'peter'
}
];
class DropdownExampleAllowAdditions extends Component {
state = { memberOptions }
handleAddition = (e, { value }) => {
this.setState({
memberOptions: [{ text: value, value },
...this.state.memberOptions],
})
}
handleChange = (e, { value }) => this.setState({ currentValues: value })
render() {
const { currentValues } = this.state
return (
<Dropdown
options={this.state.options}
placeholder='Choose Languages'
value={currentValues}
onAddItem={this.handleAddition}
onChange={this.handleChange}
/>`enter code here`
)
}
}
I need to convert handleAddition to typescript. are there some rules regarding them?
Here is the solution. The trick is how you manage the state.
const options = [
{
text: 'Bruce',
value: 'bruce'
},
{
text: 'Clark',
value: 'clark'
},
{
text: 'Diana',
value: 'diana'
},
{
text: 'Peter',
value: 'peter'
}
];
interface Props {
options? : any
}
export default class DropdownExampleAllowAdditions extends React.PureComponent<Props> {
constructor(props: Props) {
super(props);
this.state = {options:{}};
}`enter code here`
const handleAddition = (e: {}, {value}: {value:string}) => {
this.setState({
options: [{ text: value, value }, ...this.state.options],
})
}
const handleChange = (e: {}, { value }: { value: string }) =>
form.setFieldValue(fieldName, value);
render() {
const { options } = this.props;
return (
<Dropdown
options={this.state.options}
placeholder='Choose Languages'
value={currentValues}
onAddItem={handleAddition}
onChange={handleChange}
/>`enter code here`
)
}
}