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));
Related
I'm trying to store an array that holds DOM elemenst into localStorage. I can't append the elements I store in the array when storing/getting it from localStorage. But currently, my program works if I don't use localStorage, and I can append the array elements completely fine. Here's an example I just quickly wrote up which would lead to an error for me when using localStorage. What is the cause for this error?
**html**
<div class = "container">
<div class = "stuff"> Store this text </div>
</div>
<div class = "storeHere">
**index.js**
let arr = [];
if (localStorage.length == 0) localStorage.setItem("arr", JSON.stringify(arr));
else {
//psuedocode: Calling the imported method in "other.js"
}
**other.js**
method() {
let temp = [];
temp = JSON.parse(localStorage.getItem("arr"));
temp.push(document.querySelector(".stuff");
localStorage.setItem("arr", JSON.stringify(temp));
const storeHere = document.querySelector(".storeHere");
storeHere.appendChild(temp[0]); //Error, not of type Node
}
Put only serializable values into storage. Elements aren't serializable.
If you want to store Store this text, then store just that text. If the dynamic values include nested elements, either construct those nested elements when needed, or store the whole HTML string and set the .innerHTML of the container.
Here, the child of .stuff is only a plain single string, so just put that one string into storage.
// store string
localStorage.stuffText = document.querySelector('.stuff').textContent;
// retrieve string and put into DOM
if (localStorage.stuffText) {
document.querySelector('.stuff').textContent = localStorage.stuffText;
// or if you want to put it into a .storeHere element, use that selector instead
}
If you want to store an array of strings, then maybe something like
const storedArr = localStorage.arr ? JSON.parse(localStorage.arr) : [];
// store string
storedArr.push(document.querySelector('.stuff').textContent);
localStorage.stuffText = JSON.stringify(storedArr);
// retrieve string and put into DOM
if (localStorage.arr) {
const storedArr = JSON.parse(localStorage.arr);
for (const item of storedArr) {
document.querySelector('.stuff').textContent = item;
}
// or if you want to put it into a .storeHere element, use that selector instead
}
HTML Element is not serializable, so the localStorage cannot store it. You need to serialize it into string before save it. I recommend using element.outerHTML with JSON.stringify .
Example code in your case with array:
// Serialize
const arr = []
// convert to string
const elementAsString = document.querySelector('div').outerHTML
// add to your current array
arr.push(elementAsString)
// use JSON.stringify to keep correct array format in localStorage
localStorage.setItem('arr', JSON.stringify(arr))
// Deserialize
const saveArr = JSON.parse(localStorage.getItem('arr'))
// we need create an empty element first
const e = document.createElement('div')
// append element to a parent before set outerHTML
document.body.append(e)
// set HTML for it
e.outerHTML = saveArr[0]
You just need to replace these two line and your bugs will fix.
Inside other.js method() function :-
function method () {
//temp.push(document.querySelector(".stuff"));
temp.push(document.querySelector(".stuff").outerHTML);
//storeHere.appendChild(temp[0])
storeHere.innerHTML += temp[0];
}
Explanation
We cannot stringify a HTMLNode ( which is returned by the document.querySelector) because it is not a string, but we can get it as a string using its outerHTML property.
When you push the HTMLNode in the arr and set it to localStorage as string then localStorage.getItem("arr") return "[ '[object HTMLNode]' ]" as a string which you parsed as an array. It makes temp[0] = '[object HTMLNode]', which is a string ( not the HTMLNode ), which give you the error on appending it as a child Node ( that you mentioned in the comment ).
I was a bit late in answering because something came up when I was typing the answer 😅
Is there something that I'm missing that would allow item to log as an object with a parameter, but when I try to access that parameter, it's undefined?
What I've tried so far:
console.log(item) => { title: "foo", content: "bar" } , that's fine
console.log(typeof item) => object
console.log(item.title) => "undefined"
I'll include some of the context just in case it's relevant to the problem.
var TextController = function(myCollection) {
this.myCollection = myCollection
}
TextController.prototype.list = function(req, res, next) {
this.myCollection.find({}).exec(function(err, doc) {
var set = new Set([])
doc.forEach(function(item) {
console.log(item) // Here item shows the parameter
console.log(item.title) // "undefined"
set.add(item.title)
})
res.json(set.get());
})
}
Based on suggestion I dropped debugger before this line to check what item actually is via the node repl debugger. This is what I found : http://hastebin.com/qatireweni.sm
From this I tried console.log(item._doc.title) and it works just fine.. So, this seems more like a mongoose question now than anything.
There are questions similar to this, but they seem to be related to 'this' accessing of objects or they're trying to get the object outside the scope of the function. In this case, I don't think I'm doing either of those, but inform me if I'm wrong. Thanks
Solution
You can call the toObject method in order to access the fields. For example:
var itemObject = item.toObject();
console.log(itemObject.title); // "foo"
Why
As you point out that the real fields are stored in the _doc field of the document.
But why console.log(item) => { title: "foo", content: "bar" }?
From the source code of mongoose(document.js), we can find that the toString method of Document call the toObject method. So console.log will show fields 'correctly'. The source code is shown below:
var inspect = require('util').inspect;
...
/**
* Helper for console.log
*
* #api public
*/
Document.prototype.inspect = function(options) {
var isPOJO = options &&
utils.getFunctionName(options.constructor) === 'Object';
var opts;
if (isPOJO) {
opts = options;
} else if (this.schema.options.toObject) {
opts = clone(this.schema.options.toObject);
} else {
opts = {};
}
opts.minimize = false;
opts.retainKeyOrder = true;
return this.toObject(opts);
};
/**
* Helper for console.log
*
* #api public
* #method toString
*/
Document.prototype.toString = function() {
return inspect(this.inspect());
};
Make sure that you have defined title in your schema:
var MyCollectionSchema = new mongoose.Schema({
_id: String,
title: String
});
Try performing a for in loop over item and see if you can access values.
for (var k in item) {
console.log(item[k]);
}
If it works, it would mean your keys have some non-printable characters or something like this.
From what you said in the comments, it looks like somehow item is an instance of a String primitive wrapper.
E.g.
var s = new String('test');
typeof s; //object
s instanceof String; //true
To verify this theory, try this:
eval('(' + item + ')').title;
It could also be that item is an object that has a toString method that displays what you see.
EDIT: To identify these issues quickly, you can use console.dir instead of console.log, since it display an interactive list of the object properties. You can also but a breakpoint and add a watch.
Use findOne() instead of find().
The find() method returns an array of values, even if you have only one possible result, you'll need to use item[0] to get it.
The findOne method returns one object or none, then you'll be able to access its properties with no issues.
Old question, but since I had a problem with this too, I'll answer it.
This probably happened because you're using find() instead of findOne(). So in the end, you're calling a method for an array of documents instead of a document, resulting in finding an array and not a single document. Using findOne() will let you get access the object normally.
A better way to tackle an issue like this is using doc.toObject() like this
doc.toObject({ getters: true })
other options include:
getters: apply all getters (path and virtual getters)
virtuals: apply virtual getters (can override getters option)
minimize: remove empty objects (defaults to true)
transform: a transform function to apply to the resulting document before returning
depopulate: depopulate any populated paths, replacing them with their original refs (defaults to false)
versionKey: whether to include the version key (defaults to true)
so for example you can say
Model.findOne().exec((err, doc) => {
if (!err) {
doc.toObject({ getters: true })
console.log('doc _id:', doc._id) // or title
}
})
and now it will work
You don't have whitespace or funny characters in ' title', do you? They can be defined if you've quoted identifiers into the object/map definition. For example:
var problem = {
' title': 'Foo',
'content': 'Bar'
};
That might cause console.log(item) to display similar to what you're expecting, but cause your undefined problem when you access the title property without it's preceding space.
I think using 'find' method returns an array of Documents.I tried this and I was able to print the title
for (var i = 0; i < doc.length; i++) {
console.log("iteration " + i);
console.log('ID:' + docs[i]._id);
console.log(docs[i].title);
}
If you only want to get the info without all mongoose benefits, save i.e., you can use .lean() in your query. It will get your info quicker and you'll can use it as an object directly.
https://mongoosejs.com/docs/api.html#query_Query-lean
As says in docs, this is the best to read-only scenarios.
Are you initializing your object?
function MyObject()
{
this.Title = "";
this.Content = "";
}
var myo1 = new MyObject();
If you do not initialize or have not set a title. You will get undefined.
When you make tue query, use .lean() E.g
const order = await Order.findId("84578437").lean()
find returns an array of object , so to access element use indexing, like
doc[0].title
I need to create an empty JQuery element to be the outer scope list of a loop, for example :
const forms_data = ['Name', 'Email'];
const form = $();
for (const input_name of forms_data){
form.insertAfter(`<input placeholder="${input_name}" / >`)
form.append(`<input placeholder="${input_name}" / >`)
form.after(`<input placeholder="${input_name}" / >`)
}
$('form').append(form);
But none of the ways of inserting new elements into it worked for me, according to other answers here, $() seems to be the correct way.
I don't want an outer div, I want all the inserted element to be siblings.
What am I doing wrong?
jsFiddle : https://jsfiddle.net/Lusfektu/16/
You should create form element like this $('<form>) and then you can use append method.
const forms_data = ['Name', 'Email'];
const form = $('<form>');
forms_data.forEach(name => {
form.append($('<input>', {placeholder: name}))
})
$('body').append(form);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Update: You could use reduce to make array of inputs or jquery objects and then append that array to form element at once.
const forms_data = ['Name', 'Email'];
const inputs = forms_data.reduce((r, name) => {
r.push($('<input>', {placeholder: name}))
return r;
}, [])
$('form').append(inputs)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form action=""></form>
If all you are trying to do is create the inputs and append them to the form, you can use a simple array, or map the elements. $.map can be used to loop over an array, create new elements, and return them as a list at the end. Using this we can create the array, and then append them all at once to the form.
const forms_data = ['Name', 'Email'];
$('form').append($.map(forms_data, function(element){
return `<input placeholder="${element}" / >`;
}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<form></form>
You're right to use $(), but those APIs won't work. Instead use .add():
form = form.add($(`<input placeholder="${input_name}" / >`));
As the other answer notes, if you initialize the element to a <form> element then you can use the other APIs, but if you've already got a <form> on the page then that will be problematic. You could in that case initialize the container to something harmless like a <div> or a <span>.
This should do it:
const form = $('<form>');
Then simply use append on it like you did on the second line of your sample.
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
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