I'm trying to create an element from string, required for an external library (Trix editor by Bootcamp) to be implemented as a Vue component.
Therefore, I've found the following snippet in the Trix issues:
let _ = require('lodash');
Vue.component('wysiwyg', {
props: ['value'],
template: '<div></div>',
data() {
return {
trix: null,
id: ''
}
},
mounted() {
this.id = _.sampleSize('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 5).join('');
this.trix = $(`<trix-editor input="${this.id}"></trix-editor>`);
let self = this;
this.trix.on('trix-change', (e) => {
self.$emit('input', e.currentTarget.innerHTML)
});
this.$watch('value', function(value) {
value = value === undefined ? '' : value;
if (self.trix[0].innerHTML !== value) {
self.trix[0].editor.loadHTML(value);
}
});
this.trix.insertAfter(this.$el);
},
});
They use jQuery to create the element, which I'd like to avoid. Using template tags or a DOMParser, the library won't load - using $(template) though, everything works smoothly.
What is the difference between the following snippets and how to get things right?
I've tried several methods that fail:
With createContextualFragment:
let el = document
.createRange()
.createContextualFragment('<div class="foo"></div>')
With template tags:
let template = document.createElement('template');
template.innerHTML = '<div class="foo"></div>';
let el = template.content.firstChild;
With DOMParser:
let el = (new DOMParser())
.parseFromString('<div class="foo"></div>', 'text/html')
.body.childNodes[0];
All of the above create the element correctly, the node is inserted into the DOM but the editor library won't start.
With jQuery, the element is inserted and the editor library starts:
let el = $('<div class="foo"></div>')
To sum things up, I'm not looking for advice on implementing the Trix library but I'd like to know what jQuery does different when it creates an element.
Basically, jQuery uses RegExp and other tricks to parse the relevant parts from the string. For the example you gave, it would get an element of type div with a class attribute that has a value of foo.
Then it uses that data to create the element and add the properties corresponding to the attributes:
var element = document.createElement("div");
element.className = "foo"; // className is the DOM property equivalent to the class attribute
And that's it for this particular example, since the element doesn't have any child elements indicated by the HTML string
Related
I am trying to create an HTML element in JS using jQuery from an Array of Objects have information of element tag, class, and other attributes or style.
Why Use jQuery instead HTML?
You can think of sort of dynamically append small components like buttons, inputs at certain place in html. This helps user to interact with webpage better.
Code
let array = [{el:'div',class:'card',attr:{id:'abc'},css:{display:'block'}}];
let array2 = [{el:'div'}]
const element = (array) => {
let el = [];
Object.values(array).forEach((val)=>{
el.push($(`<${val.el}>`).addClass(val.class).attr(val.attr).css(val.css))
})
return el;
}
element(array) // Works fine;
element(array2) //Throws error because we are not feeding any value to .addClass ...
it works fine when all keys are available in the array but it doesn't work when any of them missing.
Error >
Uncaught TypeError: Cannot read property 'nodeType' of undefined
how do I fix this? thanks
All you need is to decouple the tagName el from the other Object data and return that jQuery Element instance Object $:
const $element = ({el, ...rest}) => $(`<${el}/>`, rest);
const el1 = {el:'div', class:'card', id:'abc', text:"Hello", css:{color:'red'}, appendTo: "body"};
const el2 = {el:'span', class:'test', text:" World!", css:{color:'blue'}, appendTo: "body"};
const $el1 = $element(el1);
const $el2 = $element(el2);
$el1.css({fontSize: "2em"});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
From the DOCS
As of jQuery 1.8, any jQuery instance method (a method of jQuery.fn) can be used as a property of the object passed to the second parameter:
$("<tagName/>", {properties})
For an Array of data, here's how to return an Array of jQuery instances by simply passing the function $element as the Array.prototype.map() argument:
const $element = ({el, ...rest}) => $(`<${el}/>`, rest);
const els = [
{el:'div', class:'card', id:'abc', text:"Hello", css:{color:'red'}, appendTo: "body"},
{el:'span', class:'test', text:" World!", css:{color:'blue'}, appendTo: "body"}
];
const $els = els.map($element);
console.log($els);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
You have written the wrong syntax to access object item values. try below-mentioned code to access object values directly
let stringfyObject=JSON.parse(JSON.stringify(array));
el.push((`<${stringfyObject.el}>`).addClass(stringfyObject.class).attr(stringfyObject.attr).css(stringfyObject.css));
There is an exercise in a bootcamp I am attending which tasks one to create a jQuery like selector, here is part of the test:
describe("selectorTypeMatcher", function() {
it("should return the 'id' type for an id selector", function() {
var type = selectorTypeMatcher('#pagetitle');
expect(type).toEqual("id");
});
it("should return the 'class' type for a class selector", function() {
var type = selectorTypeMatcher('.image');
expect(type).toEqual("class");
});
it("should return the 'tag.class' type for a tag.class selector", function() {
var type = selectorTypeMatcher('img.thumbnail');
expect(type).toEqual("tag.class");
});
it("should return the 'tag' type for a tag selector", function() {
var type = selectorTypeMatcher('div');
expect(type).toEqual("tag");
});
});
The following is part of function which I created as described in the test spec.
var selectorTypeMatcher = function(selector) {
if (selector.includes('#')) return 'id';
if (selector.indexOf('.') == 0) return 'class';
if (/<[a-z][\s\S]*>/i.test() && selector.includes('.')) return 'tag.class';
};
I am stuck at the conditional which would check for a tag and class e.g. div.foo
I thought of created an array which would contain all existing tags...
var tags = ["a", "div", "span", "form", "h1", "h2", "h3", "h4"];
And then loop over them and see if that value was followed by an . for a class but that would be a lot of elements...
I thought of leveraging document.querySelectorAll('*') but that just...
Returns a list of the elements within the document (using depth-first
pre-order traversal of the document's nodes) that match the specified
group of selectors. The object returned is a NodeList.
But like it says Returns a list of the elements within the document...
So is there an API that will return all of the existing html elements?
html, head, body, div, p, span etc.
Merci!
You can use HTMLUnknownElement object to check for a valid tag by specification:
if (tagIsValid(selector))
return 'tag';
and tagIsValid definition would be:
function tagIsValid(tag) {
tagChecked = document.createElement(tag).toString();
return tagChecked != "[object HTMLUnknownElement]";
}
if (selector.indexOf('.') > 0) return 'tag.class';
return 'tag';
I think you can end it with that.
What I'm trying to do is quite easy at first however I get an (obviously completely useless) error from webpack and I'm wondering how it can be fixed, I want a simple "custom" tag to be rendered by React, the code is as follows:
let htmlTag = "h" + ele.title.importance;
let htmlTagEnd = "/h" + ele.title.importance;
return(
<{htmlTag} key={elementNumber}>{ele.title.content}<{htmlTagEnd}>
);
Basically instead of having a predefined tag I want to have my own {template} tag, I know in this situation there would be work arounds for this (e.g. defining a className with my "importance" value and adding some css for that), but for the sake of science I'd like to know how (and if) this can be done in react/jsx.
JSX doesn't allow you to use dynamic HTML tags (dynamic components would work). That's because whenever you use something like <sometag ... />, an HTML element with tag name sometag is created. sometag is not resolved as a variable.
You also can't do what you have shown above. JSX expressions are not valid in place of a tag name.
Instead, you have to call React.createElement directly:
return React.createElement(
"h" + ele.title.importance,
{
key: elementNumber,
},
ele.title.content
);
Edit
My initial answer was not correct, you cannot use a variable directly and would need to use the createElement method described in Felix's answer. As noted below, and utilised in the blog post I originally linked, you can use object properties, so I've made an example of this, which hopefully will be useful as an answer to the question.
class Hello extends React.Component {
constructor() {
super();
this.state = {
tagName: "h1"
};
}
sizeChange(i) {
this.setState({
tagName: 'h' + i
});
}
changeButtons() {
var buttons = [];
for (let i=1; i<=6; i++) {
buttons.push(<button onClick={() => this.sizeChange(i)}>H{i}</button>);
}
return buttons;
}
render() {
return (
<div>
{this.changeButtons()}
<this.state.tagName>
Change Me
</this.state.tagName>
</div>
);
}
}
JSFiddle here
Original Answer
It can be done, although I don't think it is officially supported so may break in the future without warning. The caveat to this approach is that the variable name you choose for your tag cannot be the same as an HTML element.
var Demo = React.createClass({
render: function() {
const elementTag = 'h' + ele.title.importance;
return(
<elementTag>
Header x contents
</elementTag>
);
}
});
More explanation and a fuller example can be found here
What is the best way to view all jQuery data key-value pairs across every element (in jQuery 2.x)?
A selection-oriented approach ( e.g. $('*').data() ) obviously does not work, because the return value is tied to a single element.
I know that I can iterate over every element, checking each for data:
var allData = [];
$('html *').each(function() {
if($.hasData(this)) {
allData.push({ el: this, data: $(this).data() })
}
})
JSFiddle
This does produce the expected output, but iterating over each possible data key feels like a backwards approach to this problem.
Is there some way to find all element data directly?
N.B. I'm interested for debugging, not production code.
You could select every element within the body with $("body *") and apply jQuery's .filter() to it. Working example:
var $elementsContainingData $("body *").filter(function() {
if($.hasData(this)) return this;
});
console.log($elementsContainingData);
Edit
As #spokey mentioned before, there's an internal variable named "cache" within the jQuery object: $.cache.
This variable consists of a bunch of objects which contain keys like "data" or "events":
5: Object
data: Object
events: Object
handle: function (a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)}
__proto__: Object
You can iterate through that object and filter for the data:
var filteredCache = $.each($.cache,function() {
if(typeof this["data"] === "object") return this;
});
Here's an working example plus a function to merge that stuff into a single and more handy object consisting only of dataKey => dataValue pairings: Fiddle
Edit
As mentioned in comments this solution does not work in jQuery version 2.x since $.cache is deprecated.
My last suggestion is creating a hook for jQuerys data function in order to extend an own object$.dataCache = {}; each time data() is called.
Extending, replacing or adding jQuerys functions is done by accessing $.fn.functionName:
$.fn.data = function(fn,hook) {
return function() {
hook.apply(this,arguments);
return fn.apply(this,arguments);
}
}($.fn.data,function(key,value) {
var objReturn = {};
objReturn[key] = value;
$.extend($.dataCache,objReturn);
});
This also works great in jQuery version 2: Fiddle
Alright, I've got this blank array of objects.
I am dynamically finding every node in a web page and each node is going to have it's own object and properties.
I need a way to throw the values I need into their respective objects property
So, for example, I find the body node. I now have a special little object for this node. I need to throw pretty much everything about this little guy into his object's properties.
So I pretty much need it to render like this:
Turning this:
<html>
<body style="margin:0; padding:0;" title="My Title">
<p>some text</p>
<div class="wrapper"></div>
<footer></footer>
</body>
</html>
Into this:
this.nodesInfo = [ // All nodes in the page's DOM
{
type: 'body', // ex: body, section, aside, div, etc.
id: 'myID', // the Id of that element
class: ['myClass1', 'myClass2'], // the class/class' of that element
depth: '2', // the level in the page's DOM in which that element sits, this will be an integer
parent: 'html', // that elements direct parent Node
children:['div.wrapper', 'p', 'footer'], // any child Nodes that, that element may be a parent to
text: '', // the text inside that element if any exists
attributes: ["style=margin:0; padding:0;", "title='My Title'"] // all attributes of this node
}
];
It would of course cycle through each node it discovered and do this for each node accordingly, until it ran out of nodes.
The class, children, and attributes properties are arrays for the simple possibility of multiples of any of these. Everything else is just a string since a node can't have more than one ID, title, or direct parent tag.
If a node does not contain some of these properties then that property would remain blank/null/undefined.
My question is simple. Is this possible, if not would I instead have to create each object individually and the push them into my nodesInfo array?
I think the easiest way to go about this would be making an object of each Node and then pushing them all (once they are all created) into an array.
I was building something like this the other night. This should work and you can add more stuff easily. http://jsfiddle.net/elclanrs/UHbMa/
$.fn.buildTree = function() {
var tree = {};
this.find('*').andSelf().each(function(i, v) {
var parents = $(this).parents().length - 1,
depth = 0;
while (depth < parents) {
depth++;
}
tree[v.tagName.toLowerCase() + '(' + i + ')'] = {
id: (v.id) ? '#' + v.id : '',
className: (v.className) ? '.' + v.className.replace(' ', '.') : '',
depth: depth
};
});
return tree;
};
// Then you can do this...
var tree = $('#element').buildTree();
for (var tag in tree) {
// Get your variables
var tag.match(/\w+/), // Get rid of `(n)`
id = tree[tag].id,
className = tree[tag].className,
depth = tree[tag].depth;
html = 'something';
// Bla bla
}