CKEditor5: prevent nested same-type elements from automatically inheriting parent's attributes - ckeditor5

Is there a way to prevent nested same-type elements from automatically inheriting parent's attributes?
Eg:
<div class='border'>
<div class='shade'></div>
<div class='shade'></div>
</div>
is automatically translated to:
<div class='border'>
<div class='border shade'></div>
<div class='border shade'></div>
</div>
Excerpt from plugin:
editor.model.schema.register ('div', {
inheritAllFrom: '$root',
allowWhere: '$block',
isLimit: true,
allowAttributes: ['border', 'shade'],
} );
editor.conversion.elementToElement (
{ model: 'div', view: 'div', }
);
editor.conversion.attributeToAttribute ( { model: 'border', view: { key: 'class', value: 'border' } } );
editor.conversion.attributeToAttribute ( { model: 'shade', view: { key: 'class', value: 'shade' } } );

Related

Vue: Update part of an html string from data v-for loop with v-model input

Using Vue2 I have an array of objects in data which have an html string rendered in a v-for loop. Part of each string is a prop, which renders correctly initially. However, when the prop value is updated with v-model the data in the v-for loop is not updated.
jsfiddle: When the input is changed from "Bob" to "Sally" all instances should change, but those in the for-loop do not.
html
<div id="app">
<h2>Testing</h2>
<ul>
<li v-for="statement in statements" v-html="statement.text"></li>
</ul>
<input v-model="name" placeholder="edit name">
<p>Name is: {{ name }}</p>
<p class="italic">Outside loop: <b>{{name}}</b> likes dogs.</p>
</div>
vue
new Vue({
el: "#app",
data: function() {
return {
statements: [
{
id: 'food',
text: '<b>'+ this.name + '</b> likes to eat ice cream.',
},
{
id: 'fun',
text: 'Running is the favorite activity of <b>'+ this.name + '</b>',
},
],
}
},
props: {
name: {
type: String,
default: 'Bob',
},
},
})
The code has been simplified - the actual HTML strings have ~3 variables each that need to update, and are at different locations in each string, so I can't think of another way to replace the values when they are updated, while preserving the html tags. This is intended to be a single-page vue application, but is using Laravel and blade for some general page formatting.
name should be in data, not in props (it's not coming from a parent, it's just reactive data, which needs to be tracked for changes internally - inside this component).
statements should be in computed, because you want Vue to update it whenever its reactive references (e.g: this.name) change. Besides, this is not what you think it is inside the data function.
See it working:
new Vue({
el: "#app",
data: () => ({
name: 'Bob'
}),
computed: {
statements() {
return [
{
id: 'food',
text: '<b>'+ this.name + '</b> likes to eat ice cream.',
},
{
id: 'fun',
text: 'Runing is the favorite activity of <b>'+ this.name + '</b>',
},
]
}
},
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
p.italic {
font-style: italic;
}
<script src="https://v2.vuejs.org/js/vue.min.js"></script>
<div id="app">
<h2>Testing</h2>
<ul>
<li v-for="(statement, key) in statements" v-html="statement.text" :key="key"></li>
</ul>
<input v-model="name" placeholder="edit name">
<p>Name is: {{ name }}</p>
<p class="italic">Outside loop: <b>{{name}}</b> likes dogs.</p>
</div>
If you're trying to create a reusable component which takes in a person (with some values) and creates the statements based on those values and also allows editing the person's values, here's how to do it:
Vue.component('person-editor', {
template: '#person-editor-tpl',
props: {
person: {
type: Object,
default: () => ({})
}
},
data: () => ({
details: [
{ name: 'name', placeholder: 'Name' },
{ name: 'fun', placeholder: 'Running', label: 'Favorite activity' },
{ name: 'food', placeholder: 'pizza', label: 'Favorite food'}
]
}),
methods: {
update(payload) {
this.$emit('update:person', { ...this.person, ...payload })
},
getDetailLabel(d) {
return d.label || (d.name[0].toUpperCase() + d.name.slice(1))
}
}
});
Vue.component('person-details', {
template: '#person-details-tpl',
props: {
person: {
type: Object,
default: () => ({})
}
},
data: () => ({
statements: [
{ id: 'food', text: p => `<b>${p.name}</b> likes to eat ${p.food}.` },
{ id: 'fun', text: p => `${p.fun} is the favorite activity of <b>${p.name}</b>` }
]
})
})
new Vue({
el: "#app",
data: () => ({
persons: [
{ name: 'Jane', food: 'apples', fun: 'Hiking' },
{ name: 'Jack', food: 'pizza', fun: 'Sleeping' }
]
}),
methods: {
updatePerson(key, value) {
this.persons.splice(key, 1, value);
}
}
})
label {
display: flex;
}
label > span {
width: 150px;
}
<script src="https://v2.vuejs.org/js/vue.min.js"></script>
<div id="app">
<template v-for="(person, key) in persons">
<hr v-if="key" :key="`hr-${key}`">
<person-details :person="person"
:key="`details-${key}`"
></person-details>
<person-editor :person.sync="person"
:key="`editor-${key}`"
#update:person="updatePerson(key, person)"></person-editor>
</template>
</div>
<template id="person-editor-tpl">
<div>
<template v-for="detail in details">
<label :key="detail.name">
<span v-text="getDetailLabel(detail)"></span>
<input :value="person[detail.name]"
#input="e => update({ [detail.name]: e.target.value })">
</label>
</template>
</div>
</template>
<template id="person-details-tpl">
<ul>
<li v-for="(statement, key) in statements" v-html="statement.text(person)"></li>
</ul>
</template>
I separated the editor and the display in two separate components.
Because I had to define the components on the Vue instance it's a bit crammed in this example, but it looks a lot more elegant when using sfcs (and each component is a standalone .vue file).

Button component does not receive DataTable props

I am trying to add a button to my vuejs DataTable. I created a Button component and I can add it as a column in my DataTable, but I can't send it through props. I do not have full knowledge of vue, for sure I am doing something wrong.
Vue.component('edit-button', {
template: `
<button class="btn btn-xs btn-primary" #click="goToUpdatePage">Edit</button>
`,
props: ['data'],
methods: {
goToUpdatePage: function(){
alert(data)
}
}
});
export default {
data() {
return {
numeroPagina: 1,
tiposNatureza: [],
utisMoveis: [],
list: [],
filtros: {
dataInicial: new Date().toISOString().split('T')[0],
dataFinal: new Date().toISOString().split('T')[0],
selectedUtisMoveis: [],
selectedStatus: [],
selectedTipoNatureza: [],
selectedTipoPesquisa: ''
},
tabela: {
columns: [
{
// <a data-toggle="modal" data-target="#ExemploModalCentralizado">${row.DESCRICAO}</a>
label: 'Descrição', representedAs: function (row) {
if (row.DESCRICAO) return `<a data-toggle="modal" data-target="#ExemploModalCentralizado">${row.DESCRICAO}</a> <button onclick="alertMe('TESTE')">TESTE</button>`
else return '<b>Sem informação</b>'
}, interpolate: true
},
{
label: 'Quantidade', representedAs: function (row) {
return `<p><span class="label label-primary" style="font-size: 17px">${row.QTD}</span></p>`
}, interpolate: true
},
{
label: 'Action',
component: 'edit-button',
data: 'row',
component_data: {
path: 'contact',
action: 'update'
}
}
],
rows: []
}
}
}
<div class="row">
<div class="col-lg-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h2 v-if="quantidadeDeRegistros > 0">Foram encontrado(s) {{ quantidadeDeRegistros }}
resultado(s)</h2>
</div>
<div class="ibox-content no-padding" style="font-size: 15px">
<datatable :columns="tabela.columns" :data="tabela.rows"></datatable>
<datatable-pager v-model="numeroPagina" type="abbreviated" :per-page="8"></datatable-pager>
</div>
</div>
</div>
</div>
I need to create a button that takes the information from the selected row (row.DESCRIPTION) and sends it to a method, where I will do some dealing in a modal. Thank you very much in advance.
Try using this to reference the component scope.
alert(this.data)
And remove:
props: ['data'],
You do not want a passed in prop called data. You want the component's defined data property

Validating child components from parent using Vuelidate

I can't seem to find in the Vuelidate's documentation how can the whole model (including child components) be validated. Validating parent component and each of the child components isn't a problem, but I couldn't find a way to validate child components using the validations structure in the parent component.
I assume that something has to be passed as a prop to the child component.
I found a similar question on SO, but the answer doesn't seem to be complete and unfortunately it didn't help.
Parent component:
validations: {
payload: {
name: {
required,
minLength: minLength(5)
},
description: {
required,
minLength: minLength(20)
},
// I assume that this should work if I want to perform validation from the parent component
blocks: {
$each: {
priority: {
required,
integer,
between: between(-999, 999)
},
maxProcessed: {
minValue: minValue(1)
},
startTime: {
required,
isTime
}
}
}
}
}
Parent component template (parts of the code ommited for brevity)
<div class="message-body">
<block v-for="block in payload.blocks"
:key="block.id"
:type="'TEMPLATE'"
:block="block"
:selectedBlockId="selectedItem.block"
#selectBlock="selectBlock"
#removeBlock="removeBlock"></block>
</div>
Child component template (parts of the code ommited for brevity)
<div class="field">
<div class="control has-icons-left has-icons-right">
<input class="input is-small" type="text" placeholder="z.B. 300"
:class="{'is-danger':$v.block.priority.$invalid}" v-model="block.priority">
<span class="icon is-left">
<i class="fas fa-exclamation"></i>
</span>
</div>
<p class="help is-danger" v-if="!$v.block.priority.required">Priority is required</p>
<p class="help is-danger" v-if="!($v.block.priority.between && $v.block.priority.integer)">Priority has to be a number between -999 and 999</p>
</div>
Child component (with the validation logic within the child component)
props: {
block: {
type: Object,
required: true
}
},
validations: {
block: {
priority: {
required,
integer,
between: between(-999, 999)
},
maxProcessed: {
minValue: minValue(1)
},
startTime: {
required,
isTime
}
}
}

toggle class conditionally in Vue.js

Im trying to toggle a class of an element (the parent of the button being clicked) in Vue.js (which I've only started learning recently).
The idea is that the disabled class would be applied when included is not true.
I've created a toggleClass function, but even that is not being called it seems.
https://codepen.io/dagford/pen/NzQrJM
HTML
<div id="app">
<div class="item" v-for="item in items" :id="item.id" :key="item.id">
<span>{{item.amt}}</span>
<button v-on:click="item.included = !item.included" v-on:click="toggleClass()">exclude me</buton>
</div>
<br>
<div id="total">total: {{ itemTotal }}</div>
</div>
VUE
var app = new Vue({
el: "#app",
data: {
items: [
{
id: 'item1',
included: 'true',
amt: 10,
className: 'disabled'
},
{
id: 'item2',
included: 'true',
amt: 20,
className: 'disabled'
},
{
id: 'item3',
included: 'true',
amt: 30,
className: 'disabled'
}
]
},
methods: {
toggleClass: function () {
if(this.included) {
console.log('test');
// code to toggle the 'disabled' class here?
}
}
},
computed: {
itemTotal() {
return this.items.reduce(function (sum, item) {
if (item.included) {
return item.amt + sum;
}
return sum;
}, 0)
}
}
});
Managed to get it working thanks to Timothy's suggestion, that was almost there. It was just missing the .item portion.
So it should be
<div v-bind:class="{ disabled:!item.included }"></div>
<div v-bind:class="{ disabled:!included }"></div>
Maybe try using class binding :
doc

Aurelia js providing dynamic content to popover body

I am following the structure to implement tool tip from Sean Hunter Blog . Now i want provide tool-tip content as a dynamic html content i.e I want to show one html pattern inside content. How can I provide using Aurelia framework. In knockout JS using custom binding handler I am providing content as with id of division like below code.
Knockout Structure is
<button data-bind="tooltip: { template: 'ElementId', trigger: 'click', placement: 'right', container: 'body' }">Click Me</button>
<div id="ElementId" style="display: none;">
<div>Dynamic content will go here</div>
</div>
How to achieve same with Aurelia Structure:
<template>
<require from="templates/popover/tooltip"></require>
<button data-toggle="tooltip" tooltip="placement:bottom;trigger:click;html:true;template:'ElementId';title:tooltip Header">Click Me</button>
<div id="ElementId" style="display: none;">
<div>Dynamic content will go here</div>
</div>
</template>
Custom Attribute code
import {customAttribute, inject, bindable} from 'aurelia-framework';
import $ from 'bootstrap';
#customAttribute('tooltip')
#inject(Element)
export class Tooltip {
element: HTMLElement;
#bindable title: any;
#bindable placement: any;
#bindable content: any;
#bindable template: any;
constructor(element) {
this.element = element;
}
bind() {
if (this.content) {
$(this.element).tooltip({ title: this.title, placement: this.placement, content: this.content });
}
else {
$(this.element).tooltip({ title: this.title, placement: this.placement, template: $('#'+this.template).html() });
}
}
// gets fired when the provided value changes, although not needed in this example since the json from reddit is static
titleChanged(newValue) {
$(this.element).data('bs.tooltip').options.title = newValue;
}
contentChanged(newValue) {
if (this.content) {
$(this.element).data('bs.tooltip').options.content = newValue;
}
else {
$(this.element).data('bs.tooltip').options.template = newValue;
}
}
placementChanged(newValue) {
$(this.element).data('bs.tooltip').options.placement = newValue;
}
}
You would need to implement the rest of bootstrap's popover API in your custom attribute, and add some logic to turn a selector into a template.
Here's an example: https://gist.run?id=909c7aa984477a465510abe2fd25c8a1
Note: i've added the default values from bootstrap popovers for clarity
With a custom attribute:
src/app.html
<template>
<h1>${message}</h1>
<button class="btn btn-block btn-default" popover="title.bind: message; placement: top">Default popover</button>
<button class="btn btn-block btn-default" popover="title.bind: message; template-selector: #popoverTemplate; placement: bottom; html: true">Custom popover</button>
<div id="popoverTemplate">
<div class="popover" role="tooltip">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div>Some custom html</div>
</div>
</div>
</template>
src/app.ts
export class App {
message = "Hello world";
}
src/popover.ts
import {inject, customAttribute, bindable, DOM} from "aurelia-framework";
#customAttribute("popover")
#inject(DOM.Element)
export class Popover {
public element: HTMLElement;
constructor(element) {
this.element = element;
}
#bindable({defaultValue: true})
public animation: boolean;
#bindable({defaultValue: false})
public container: (string | false);
#bindable({defaultValue: 0})
public delay: (number | object);
#bindable({defaultValue: false})
public html: boolean;
#bindable({defaultValue: "right"})
public placement: (string | Function);
#bindable({defaultValue: false})
public selector: (string | false);
#bindable({defaultValue: `<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>`})
public template: string;
#bindable({defaultValue: false})
public templateSelector: (string | false);
#bindable({defaultValue: ""})
public title: (string | Function);
#bindable({defaultValue: "click"})
public trigger: string;
#bindable({defaultValue: { selector: "body", padding: 0 }})
public viewport: (string | Object | Function);
public attached(): void {
let template;
if (this.templateSelector) {
const templateElement = document.querySelector(this.templateSelector);
template = templateElement.innerHTML;
} else {
template = this.template;
}
$(this.element).popover({
animation: this.animation,
container: this.container,
delay: this.delay,
html: this.html,
placement: this.placement,
selector: this.selector,
template: template,
title: this.title,
trigger: this.trigger,
viewport: this.viewport
});
}
}
With a custom element:
This is in response to #Ashley Grant's comment. It could improve clarity if you used a custom element for this. I'm not sure of the implementation he had in mind, but this would be one way to make it work without really losing flexibility.
src/app.html
<template>
<h1>${message}</h1>
<popover-element title.bind="message" placement="bottom">
</popover-element>
<popover-element title.bind="message" placement="bottom">
<button slot="popoverTarget" class="btn btn-block btn-default">
Custom popover (custom element)
</button>
<div slot="popoverTemplate" class="popover" role="tooltip">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div>Some custom html</div>
<div>Message: ${message}</div>
</div>
</popover-element>
</template>
src/app.ts
export class App {
message = "Hello world";
}
src/popover-element.html
<template>
<div ref="target">
<slot name="popoverTarget">
<button class="btn btn-block btn-default">Default popover (custom element)</button>
</slot>
</div>
<div ref="template">
<slot name="popoverTemplate">
<div class="popover" role="tooltip">
<div class="arrow"></div>
<h3 class="popover-title"></h3>
<div class="popover-content"></div>
</div>
</slot>
</div>
</template>
src/popover-element.ts
import {customElement, bindable} from "aurelia-framework";
#customElement("popover-element")
export class PopoverElement {
public template: HTMLElement;
public target: HTMLElement;
#bindable({defaultValue: true})
public animation: boolean;
#bindable({defaultValue: false})
public container: (string | false);
#bindable({defaultValue: 0})
public delay: (number | object);
#bindable({defaultValue: false})
public html: boolean;
#bindable({defaultValue: "right"})
public placement: (string | Function);
#bindable({defaultValue: false})
public selector: (string | false);
#bindable({defaultValue: ""})
public title: (string | Function);
#bindable({defaultValue: "click"})
public trigger: string;
#bindable({defaultValue: { selector: "body", padding: 0 }})
public viewport: (string | Object | Function);
public attached(): void {
$(this.target.firstElementChild).popover({
animation: this.animation,
container: this.container,
delay: this.delay,
html: this.html,
placement: this.placement,
selector: this.selector,
template: this.template.firstElementChild.outerHTML,
title: this.title,
trigger: this.trigger,
viewport: this.viewport
});
}
}
You can remove the '.outerHTML' from this line template: this.template.firstElementChild.outerHTML, as template: this.template.firstElementChild, in order to get the model binding.