Getting jQuery.data functionality without jQuery - javascript

You can quite easily set a data attribute of any element with jquery using $('#elid').data('key', value). You can do the same using document.querySelector('#elid').setAttribute('data-key', value)
However, jQuery gives you a special ability that querySelector doesn't - the ability to add attributes of an arbitrary type (including functions, and I think promises, which is what I need).
So if you were to do $('#elid').data('key', function(){console.log('yes')}) with jQuery, and then $('#elid').data('key')(), it would log 'yes' to the console -- we can just assign a function to the element as a data attribute and run it whenever.
But we can't do the same with 'setAttribute' -- when we do it, it apparently just assigns a stringified form of the function to the data attribute, rather than the actual function.
I've provided example code here:
https://jsfiddle.net/8e1wyL41/
So how can I apply data to elements with plain javascript, just like jQuery, including the ability to have arbitrary functions or javascript objects as data attribute values?

jQuery#data() uses an internal object to keep track of data values. It does not update the element to have new or changed data-* attributes when setting data values. When retrieving a data value, if the internal object does not have a set value it will attempt to get it from the data-* attributes.
A overly simplified way of doing this without jQuery would be to just use an object and store your data on that
var element = document.querySelector("div");
element.customData = {};
//get data example, check if customData has a value first, if not use dataset
var someData = element.customData["somedata"] || element.dataset["somedata"];
//set
element.customData["somedata"] = function(){};
If you don't want to contaminate the element with arbitrary properties you could use a WeakMap, pending on browser support, to associate a data object with the element. This also allows for using a single object to maintain other element data objects as well. The key to the data object is the element object itself. And the data object will get deleted from the map automatically once the element is garbage collected
var dataMap = new WeakMap();
var element = document.querySelector('div');
var elementData = dataMap.get(element);
if(!elementData){
dataMap.set(element, elementData = {});
}
//get data example, check if data object has a value first, if not use dataset
var someData = elementData["somedata"] || element.dataset["somedata"];
//set
elementData["somedata"] = function(){};

.dataset sets or gets a DOMString of HTML data-*, though you can use Function() to call the function stored as string at HTMLElement.dataset
document.documentElement.dataset.fn = function fn(...args) {console.log(args)};
new Function("return " + document.documentElement.dataset.fn)()("yes");

Related

How to pass dynamic variable to opener.window.form?

How do I use a dynamic parameter name when accessing a variable?
Example:
opener.document.form.namerow_3.value = this.cells[0].innerHTML;
opener.window.form.{varaible}.value=this.cells[0].innerHTML;
In this case, the variable would be namerow_3, which will change based on user selection.
How do I solve the problem?
If I understand your question correctly, you're trying to access a dynamic property of the form object. You can do that by accessing the object like this:
// Base example of how to access namerow_3
opener.document.form.namerow_3.value = this.cells[0].innerHTML;
// Example with static accessor name
opener.document.form["namerow_3"].value = this.cells[0].innerHTML;
// Example with variable
var propName = "namerow_3";
opener.document.form[propName].value = this.cells[0].innerHTML;
Since most regular objects in JavaScript are basically a hashmap, you can usually access an objects property by specifying it's key like you would an array's index (in this case, form["namerow_3"]).

.add() in firebase cloud firestore nested objects

The firebase documentation says this about updating in nested objects: https://firebase.google.com/docs/firestore/manage-data/add-data#update_fields_in_nested_objects
My structure
let ref = db.collections('projects').doc('blabla')
ref.set({
objOfObjects: {}
})
I would like to add an object(value) with a randomly generated key to objOfObjects, much like the regular add function. I can't tie the key to any of the values of the object. Is there a way to add to objOfObjects or do I have to restructure my data?
There is no built-in function add a field with a randomly generated name to a document. But you can generate the random name yourself and add it. You can even use collection.add() to generate the random name, since (as Doug says here) that doesn't actually create a document yet.
var newId = store.collection('projects').doc().id;
let ref = db.collections('projects').doc('blabla');
var updates = {};
updates[id] = { ... }
ref.update(updates)
The tricks used here:
Get a new unique id, as explained before.
Use [] notation to use the value of that id as the property name.
Call update() to update a document, instead of replacing it.

Direct reference from DOM object to my object / data?

// my object
function myObject(id) {
this.id = id;
}
var parent = document.getElementById('parent');
var myObjects = [];
for(i = 0; i < someCount; i++) {
var id = someData[i].id; // unique value
var oobject = new myObject(id);
myObjects.push(oobject);
// div element
var div = document.createElement('div');
div.id = id; // note how div id is same as id property - these two are in "pairs" a.k.a the data from that object is meant for that div
div.className = 'my-div';
parent.appendChild(div);
}
What are my options? How to make a direct reference from DOM object to my object?
This doesn't even have to be a separate object, I just need to store some data that I could access directly / O(1) via DOM element.
Data is changed dynamically and often which rules out localStorage in my opinion
There's too much data for data- attribute and I'm worried that it causes some issues
Works in all modern browsers (also stealth mode) and mobiles - not a big plus for localStorage
I need to be able to get/set my data when DOM element is clicked without any nasty array iterations or searching for the correct object using only some property's value (like id in the example above).
I looked into adding my own properties/data to DOM object itself
but all I could find was that this is a very bad idea. These posts were old (between 2008 and 2011), how are things in 2016? There is extremely little information about this. This would be the easiest and I would only need 1 line: myDiv.addEventListener('click', function(e){ var id = e.currentTarget.myObject.id; }
I could also assign DOM object to my own object but as far as I know and I couldn't find any information stating otherwise: there's no way to directly get an object by its property a.k.a move up in the object's hierarchy.
One way to do this in O(1) and without altering DOM nodes is to use an object literal as a Map (hash) linking the node to an object.
You are already setting an id attribute on your DOM elements, so you can use each node's id as the key to connect to the appropriate myObject:
var map = {}; // create the map. map[domNodeID] = appropriate myObject
for(i = 0; i < someCount; i++) {
var id = someData[i].id;
var oobject = new myObject(id);
// div element
var div = document.createElement('div');
div.id = id;
div.className = 'my-div';
parent.appendChild(div);
// link node and object through the map
map[id] = oobject;
}
Then you can just reference the object using the map:
myDiv.addEventListener('click', function(e) {
var oobject = map[this.id]; // O(1) lookup
// use your object
}
There is also a built-in object for this purpose called Map, which might prove better than using an object literal but it depends on you needed browser support. Map does allow using objects as keys as well as strings so you can technically link a node to an object directly, without the need for an id:
map.set(node, oobject)
Additionally, instead of using direct access like map[key] = value, perhaps you can add methods to your custom map that use the same names as Map methods, such as map.set(key, value) to ensure an easy switch to a Map later on.

Can the HTML data attribute hold a reference to a DOM element?

Is it possible with the HTML data- attributes to hold a reference to another DOM element? For example, I could do this with jQuery:
var domel1 = document.getElementById("#mydiv")
var domel2 = document.getElementById("#mydiv2")
$(domEl1).attr('data-domel', domel2)
Then later on, with jQuery I would do:
var domel1 = document.getElementById("#mydiv")
var domel2 = $(domel2).data('domel')
$(domel2).html("blahblahblah")
This might seem like a trivial example because I could just reference domel2 with the same id like I did at first, but there are cases where this could be useful for representing relationships between <div>s.
Yes and no. You cannot store a reference to a DOM element in a data- attribute. However, you can associated a reference to a DOM element to another element using jQuery .data(), which are already using:
$someElement.data('name', someOtherElement);
From the jQuery documentation:
The .data() method allows us to attach data of any type to DOM
elements in a way that is safe from circular references and therefore
from memory leaks.
Note that using .data() to set data will add it to the data store but not add it as a data- attribute in the DOM. However, using .data() to read data will check the data store as well as the data- attribute (if one exists and there's no data store value with the given key).
Not directly. data-* attributes are just attributes, so you can only store a string in them.
But, of course, you can store the id or class of your target element, in order to retrieve it later.
Or you could also store a reference to the element in a property, since properties can have any value.
Not legal, since attributes should be text strings. But since you're using jQuery you could use the .data() method instead.
jQuery .data() basically does everything for you.
But if you cannot use jQuery, or have to implement something more case-specific, you can hold indexes to a global object that holds the actual data.
This way you can support any type of data you need, references, objects, functions (even binded functions).
Here's a vanilla implementation of data, though I'm not sure what are your limitations - you will probably want to change some bits of the code below.
Note that elements in this code are identified by using their id.
// Set data with: elem.data('key',anything)
// Get data with: elem.data('key')
// Remove data (kind of) with: elem.data('key',undefined)
// This will generate random id on element if id is missing
Node.prototype.force_id = function() {
return this.id || (this.id = ('' + Math.random()).replace('0.', 'id-'));
}
// Our own data implementation
window.DATAOFDOM = {}; // I like naming globals as ALLCAPS
Node.prototype.data = function(k, v) {
if (arguments.length == 1) {
// getter
if (window.DATAOFDOM[this.id]) {
return window.DATAOFDOM[this.id][k]; // returns undefined when k isn't there
}
// else: implicitly returns undefined when there's no data for this element
} else {
// setter
this.force_id();
if (!window.DATAOFDOM[this.id])
window.DATAOFDOM[this.id] = {};
return window.DATAOFDOM[this.id][k] = v;
}
}
https://jsfiddle.net/oriadam/63zn9qtd/
This is not an answer because no element can store DOM element as an attribute value. Here is a small polyfill to do the same
If I have the same requirement I would follow this approach,
var DataDOM = (function(){
function DataDOM(){}
var elements = {}, counter = 0;
DataDOM.prototype.set = function(ele){
elements['ele' + counter] = ele;
counter += 1;
return ele + (counter - 1);
}
DataDOM.prototype.get = function(eleRef){
return elements[eleRef];
}
return DataDOM;
})();
Use like below
var dDOM = new DataDOM();
For example if if I want to set DOM reference to a element data attribute
var div = document.getElementById('someId');
var attr = dDOM.set(div);
Then set attr as data to some element
then while retrieving use below method to get it back
var referedElement = dDOM.get(someElement.attr('data'));
Because there is no direct way to store elements as Data AFAIK.

localStorage methods

function Todo(id, task, who, dueDate) {
this.id = id;
this.task = task;
this.who = who;
this.dueDate = dueDate;
this.done = false;
}
function updateDone(e) {
var spanClicked = e.target;
var id = spanClicked.parentElement.id;
var done = spanClicked.parentElement.done;
spanClicked.innerHTML = " ✔ ";
spanClicked.setAttribute("class", "done");
var key = "todos" + done;
localStorage.setItem(key, "true");
console.log(key);
}
In the second part of the last function I'm trying to change the value of the done property to "true" when the object I'm targeting is clicked for localStorage. What happens instead though is when I check localStorage in the web console the value of that object doesn't change and instead another object is added to the localStorage called "todosundefined: 'true'". I don't understand why when I click an object something is added to the localStorage as a separate, undefined object instead of changing the value of the object I clicked on. Any suggestions to modify it so that I change the done value of the object I click on for localStorage?
As mentioned in the comments above, there's no done property on DOM objects. I'm not entirely sure what you're trying to do, but in order to get the contents of the class attribute of a DOM object, you should use the className property. E.g:
var done = spanClicked.parentElement.className;
Then again, that doesn't appear to make any sense based on the code you posted. I'm assuming you want to use local storage to keep track which items on a list of todo's are done. In that case, wouldn't it be better to just store an array of id's of all items that are done (or the reverse)? You can't store an array directly in local storage, but you can use, for example, JSON.stringify() to get a string representation of the array that you can then store.

Categories