How to pass HTML slot in vue component? - javascript

I want to pass default slot as VNode element as DOM Tag and text property undefined. currently i am getting VNode as text means when i log slot default then give VNode property text have HTML content and other properties are undefined i am passing like given below code:
<passage-question>{{ itemData(item).title }}</passage-question>
and PassageQuestion have code like this:
<script>
import { isArray } from 'lodash'
import PassageReference from './passage-reference.vue'
const PASSAGE_REF_MARK = '#passage-'
function isPassageRefAsAnchorNode(node, keyword) {
return node.tag === 'a' && node?.data?.attrs?.href?.startsWith(keyword)
}
function replacePassageRefAsAnchorNode(createElement, nodes) {
return (
nodes &&
nodes.map((node) => {
if (isPassageRefAsAnchorNode(node, PASSAGE_REF_MARK)) {
const refId = node.data.attrs.href.substr(PASSAGE_REF_MARK.length)
if (refId) {
return createElement(PassageReference, {
props: { refId },
})
}
}
if (node.children && isArray(node.children)) {
node.children = replacePassageRefAsAnchorNode(createElement, node.children)
}
return node
})
)}
export default {
components: {
PassageReference,
},
render(createElement) {
const nodes = Array.from(this.$slots.default || [])
const resultNodes = replacePassageRefAsAnchorNode(createElement, nodes)
return createElement(
'div',
{
attrs: { class: 'passage-question' },
},
resultNodes
)},
}
</script>
please help me. Thank You.

Related

react2angular issue when binding second directive to the same data object (they control each other values)

This works fine, but whenever i split these 2 components:"Editor" and "EditorToolbar" (in the link i update values same way for both) editor becomes very slow and at the same time nor editor-toolbar works ( this code works fine if i have 1 react component which holds 1 state for both but now i don't "state" i have to update value in angular so that it works same way i unite these 2 components as 1 holding state inside react(that's what i'm trying to achieve)
I assume problem is inside link how i assign values to both components
1 week trying and dying to solve this.
Editor
<kmp-note-editor has-toolbar="hasToolbar" ng-model="editorState.content" ng-change="handleDocumentNoteChange()" style="background-color: white; border: solid 1px #D2D6DE;" ng-disabled="(documentNote.status.id_name==='signed' && !enableSignedDocument) || encounterIsClosed"></kmp-note-editor>
Toolbar
<kmp-note-editor-toolbar ng-model="editorState.content" ng-change="handleToolbarChange()" ng-disabled="(documentNote.status.id_name==='signed' && !enableSignedDocument) || encounterIsClosed"></kmp-note-editor-toolbar>
React2Angular code
.directive("kmpNoteEditor", [
"$timeout",
($timeout) => ({
restrict: "E",
require: "ngModel",
template:
'<kmp-note-editor-with-toolbar-raw attr="1" disabled="disabled" value="viewValue" on-change="handleChange"></kmp-note-editor-with-toolbar-raw>',
link: {
pre: (scope, element, attrs, ngModelCtrl) => {
scope.disabled = attrs.disabled;
ngModelCtrl.$render = () => {
render(ngModelCtrl.$viewValue);
};
function render(value) {
scope.viewValue = value;
}
scope.handleChange = (change) => {
console.log("CH");
console.log(change);
ngModelCtrl.$setViewValue(change.value);
/* Anti Pattern!
* https://github.com/angular/angular.js/wiki/Dev-Guide%3A-Anti-Patterns
* Fixes bug (part 3): https://vabaco.atlassian.net/browse/EVXBL-7736
*/
if (mobileAndTabletcheck()) {
// ngModelCtrl.$setViewValue(change);
// ngModelCtrl.$render();
} else {
if (scope.$$phase) {
$timeout(function() {
fn();
});
} else {
$timeout(function() {
scope.$apply(fn);
});
}
}
function fn() {
$timeout(function() {
if (!mobileAndTabletcheck()) {
if (
change.operations.every(
(operation) => operation.type === "set_selection"
)
) {
// render(change.value);
} else {
// ngModelCtrl.$setViewValue(change.value);
// ngModelCtrl.$render();
}
}
});
}
};
if (!mobileAndTabletcheck()) {
ngModelCtrl.$formatters.push((html) => {
return mobileAndTabletcheck()
? html
: htmlSerializer.deserialize(html || "");
});
ngModelCtrl.$parsers.push((value) => {
return mobileAndTabletcheck()
? value
: htmlSerializer.serialize(value);
});
ngModelCtrl.$viewChangeListeners.push(() => {});
}
},
},
}),
])
React code
import htmlSerializer from "#components/NoteEditor/htmlSerializer";
import * as React from "react";
import NoteEditor from "../NoteEditor";
import NoteEditorToolbar from "../NoteEditorToolbar";
interface NoteEditorWithToolbarProps {
value: Object | string;
onChange?: (Object) => void;
disabled?: boolean;
hasToolbar:boolean;
}
const createDOMPurify = require("dompurify");
const DOMPurify = createDOMPurify(window);
interface NoteEditorWithToolbarState {}
const NoteEditorWithToolbar = (props:NoteEditorWithToolbarProps) => {
const [value, setValue] = React.useState(props.value);
console.log(props)
React.useEffect(() => {
setValue(props?.value);
console.log("sss")
}, [props.value]);
const changeHandler = (event) => {
if (!event.operations.some((operation) => operation.type === "set_value")) {
setValue(event.value);
props.onChange(event);
}
};
return (
<div className="kmp__NoteEditorWithToolbar" id="kmp__NoteEditorWithToolbar">
{props.hasToolbar && (
<NoteEditorToolbar
value={value}
onChange={changeHandler}
disabled={props.disabled}
/>
)}
<NoteEditor
value={value}
onChange={changeHandler}
disabled={props.disabled}
/>
</div>
);
};
export default NoteEditorWithToolbar;

Vue mixin render HTML and append to $ref

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

Cant Render React Componet, Error In my understanding or code?

So I have built a graph generator and have all the correct data on the props the problem is when I go to render the build component It logs
Object { "$$typeof": Symbol(react.element), type: createElement(), key: "1", ref: null, props: {…}, _owner: null, _store: {…}, … }
Here is the Code I am sure its something silly I am not understanding about the render.
i
mport React, { Component } from 'react'
import * as action from '../../actions'
import { connect } from 'react-redux'
import jsx from 'react-jsx'
import { bindActionCreators } from 'redux'
import {Modal} from './Modal'
#connect(
(flux, props) => {
return {
filters: flux.FilterStore,
ready: flux.FilterStore.ready,
presets: flux.DataStore.preSelectList,
graphs: flux.GraphStore.graphList
}
},
(dispatch, props) => {
dispatch(action.fetchGraphList())
return {
addDataReportGraphDetails: bindActionCreators(action.addDataReportGraphDetails, dispatch)
}
}
)
class RenderGraphPreview extends Component {
constructor(props) {
super(props)
this.state = {
running: {},
show:false,
graph:{}
}
this.data = 0;
}
//Error function for shorthand errors
throwError = (string = "Error", err = null) => {
throw new Error(`${string}:${err}`)
}
//simple print function to print objects and strings
p = (string, variable) => {
typeof variable === `object` ? variable = JSON.stringify(variable) : variable
console.log(`${string}:${variable}`)
}
showModal = e => {
this.state.show = true
}
generateGraph = ({ presets, filters, graphDetails }) => {
var reportProps = {
wasRunning: true,
companyName: filters.company_name,
companyVertical: filters.company_vertical,
graphTitle: jsx.client(`<div>${graphDetails.title}</div>`)(reportProps).props.children
}
this.data++
if (typeof reportProps.graphTitle == "object") {
reportProps.graphTitle = reportProps.graphTitle.join("")
}
if (!this.state.running) {
reportProps.wasRunning = false
this.state.running = true
}
if (graphDetails.graph) {
var Graph = React.createFactory(require(`../graphs/${graphDetails.graph}`).default);
var newGraphProps = {}
var graphPropKeys = Object.keys(graphDetails.props || {})
graphPropKeys.map((graphKey) => {
if (graphDetails.props[graphKey] && graphDetails.props[graphKey].toString().length > 0)
newGraphProps[graphKey] = graphDetails.props[graphKey]
})
if (graphDetails.timeframe) {
newGraphProps[timeframe] = graphDetails[timeframe]
}
if (graphDetails.props.attackIndexFilterPreset) {
let preset;
for (let j = 0, jEnd = presets.length; j < jEnd; j++) {
if (presets[j]._id == graphDetails.props.attackIndexFilterPreset) {
return preset = presets[j]
}
}
if (preset) {
console.log(`In presents`)
newGraphProps = { ...preset, ...newGraphProps }
}
}
}
// console.log(<Graph key={this.state.count++} isDocument={true} reportKey={graphDetails.key} onImageCreated={this.props.addDataReportGraphDetails} {...filters} {...reportProps} {...newGraphProps}/>)
return (
<Graph key={this.data} isDocument={true} reportKey={graphDetails.key} onImageCreated={this.props.addDataReportGraphDetails} {...filters} {...reportProps} {...newGraphProps}/>
)
}
//Verifies we have the correct data to build the graph
startGraphGeneration = async (e,{ props }) => {
e.preventDefault()
let require = this.props.filters && this.props.presets && props
if (!require) {
this.throwError()
}
let graphProps = {
presets: this.props.presets,
filters: this.props.filters,
graphDetails: props,
}
let g = await this.generateGraph(graphProps)
this.setState({
graph: g
});
console.log(g)
}
render() {
var x = this.state.graph
return (
<div>
<button onClick={(e) => this.startGraphGeneration(e,this.props)}>Preview Graph</button>
{this.state.graph ? <x/> : `Doing Noting`}
</div>
)
}
}
export default connect()(RenderGraphPreview)
In your render method you use this.state.graph. You set this variable to the value returned from generateGraph function, which returns rendered node, not a component
. And then you try to render this node as a component(<x/>), which doesn't work. Also in
generateGraph function console.log(g) shows you rendered component. So just return x in you render method instead:
render() {
var x = this.state.graph
return (
<div>
<button onClick={(e) => this.startGraphGeneration(e,this.props)}>Preview Graph</button>
{this.state.graph ? x : `Doing Noting`}
</div>
)
}

React Testing: Cannot read property 'toLowerCase' of undefined

I am testing a react component using Mocha, Chai and Enzyme. The component is
TodoList.js
export class TodoList extends Component {
render() {
var {todos, searchText, showCompleted, isFetching} = this.props;
var renderTodos = () => {
if(isFetching){
return (
<div className='container__message'>
<PulseLoader color="#bbb" size="6px" margin="1.5px" />
</div>
);
}
if(todos.length === 0){
return <p className='container__message'>Nothing to show</p>
}
return TodoAPI.filterTodos(todos, showCompleted, searchText).map((todo) => {
return (
<Todo key={todo.id} {...todo} />
)
});
}
return (
<div>
{renderTodos()}
</div>
);
}
}
export default connect(
(state) => {
return state;
}
)(TodoList);
This component uses another function which is
TodoAPI.js
import $ from 'jquery';
module.exports = {
filterTodos: function(todos, showCompleted, searchText){
var filteredTodos = todos;
filteredTodos = filteredTodos.filter((todo) => {
return !todo.completed || showCompleted; // todo is not completed or showCompleted is toggled
});
console.log(filteredTodos);
filteredTodos = filteredTodos.filter((todo) => {
console.log(todo.text);
return todo.text.toLowerCase().indexOf(searchText.toLowerCase()) !== -1;
});
filteredTodos.sort((a, b) => {
if(!a.completed && b.completed){
return -1;
} else if(a.completed && !b.completed){
return 1;
} else {
return 0;
}
});
return filteredTodos;
}
};
The test which I have written tests that TodoList.js renders 2 Todo components as I have provided an array of two objects.
TodoList.spec.js
import React from 'react';
import ConnectedTodoList, {TodoList} from '../../src/components/TodoList';
describe('TodoList', function(){
let todos = [
{
id: 1,
text: 'some dummy text',
},
{
id: 2,
text: 'some more dummy text',
}
];
beforeEach(function(){
this.wrapper = shallow(<TodoList todos={todos} />);
});
it('should exist', function(){
expect(this.wrapper).to.exist;
});
it('should display 2 Todos', function(){
expect(this.wrapper.find('Todo')).to.have.lengthOf(2);
});
})
But when I execute this test I get an error which says
1) TodoList "before each" hook for "should exist":
TypeError: Cannot read property 'toLowerCase' of undefined
at F:/Study Material/Web/React Projects/ReactTodoApp/src/api/TodoAPI.js:16:43
Your issues stems from this line in TodoList.js:
var {todos, searchText, showCompleted, isFetching} = this.props;
This is expecting all of these values to be passed as props to the TodoList component. As searchText is not provided in the tests, it has the value undefined when it gets passed to filterTodos where searchText.toLowerCase() is eventually called, causing the error.
Changing the beforeEach section of your tests to:
beforeEach(function(){
this.wrapper = shallow(<TodoList todos={todos} searchText='dummy' />);
});
should solve the issue. You should probably also provide showCompleted and isFetching so that you aren't relying on defaults.
Best guess without running the code myself is that searchText is undefined and so when you call toLowerCase on it in the TodoAPI the function cannot be called.
The only other place you have used toLowerCase is on the todo text itself which you provide through a prop.

How to use React.cloneElement to pass a function property with a return object?

I'm using react-router which forces me to use React.cloneElement to pass down properties to my Children. I can pass down objects and functions, but my issue is where one of my functions has a return object back up to the parent, which is always undefined. The function triggers in the parent, but it doesn't receive the object I'm passing it from the child.
Here is a jsFiddle of the below example code if anyone wants to edit it https://jsfiddle.net/conor909/gqdfwg6p/
import React from "react";
import ReactDom from "react-dom";
const App = React.createClass({
render() {
return (
<div>
{this.getChildrenWithProps()}
</div>
)
},
getChildrenWithProps() {
return React.Children.map(this.props.children, (child) => {
return React.cloneElement(child, {
myFunction: this.myFunction
});
});
},
// NOTE:
// the idea is that the variable 'newForm' should be sent back up to App, I can log out 'newForm' in the Child, but here in App, it is undefined.
myFunction(newForm) {
console.log(newForm); // => undefined object
}
});
const Child = React.createClass({
propTypes: {
myFunction: React.PropTypes.func,
myForm: React.PropTypes.object
},
render() {
return (
<form className="col-sm-12">
<MyForm
changeForm={this.onChangeForm}
form={this.props.myForm} />
</form>
)
},
onChangeForm(formChanges) {
let newForm = {
...this.props.myForm,
...formChanges
}
// console.log(newForm); => here my newForm object looks fine
this.props.myFunction(newForm);
}
});
const MyForm = React.createClass({
propTypes: {
changeForm: React.PropTypes.func.isRequired
},
render() {
return (
<div>
<Input onChange={this.onChangeForm}>
</div>
)
},
onChangeForm(value) {
this.props.changeForm({ something: value });
}
});

Categories

Resources