Open gingerchew opened 10 months ago
lots of great ideas in here! here's all the new stuff I spot:
.dataset
apithis lib is currently very tiny, do you think there's a way to fit these new features and apis in without doubling or tripling the lib? the ideas here are good, and implementing them is totally plausible, but i'm worried it's more work than it sounds like
One way to ensure the package size stays small would be to add an upper limit using something like size-limit. I pulled together a draft PR at #48 to put the normalize attribute aspect down on paper.
size-limit is a great suggestion, but I'm mostly considering size like how much code to maintain.
thanks for the PR and tests 🙂 hopefully that didn't give you much issue to setup. "converting camelCase to kebab-case is handled automatically" wasnt clear to me until your PR! maybe that becomes an option of .attr()
, because i think nearly everything you're articulating here for .data()
is covered by dataset? even the camel casing: element.dataset.camelCase = 'fun'
sets the appropriate attribute on the element data-camel-case="fun"
.
now i'm thinking it's less features! (if .data()
is basically covered via dataset
):
thoughts? does the boil down appropriately? does dataset cover what you're looking for?
I think that minimizing the ideas in complexity is a good idea. If I go through the original shortlist, this is where I fall on lib concern vs user concern:
automatic prepending of a namespace
This is probably the solution for my data-*
woes, since adding the dataset interface/proxy may add too much complexity.
// as a user concern
const ns = 'mynames-';
$('div').attr(ns+'firstName', 'ginger');
// as a lib concern
$('div').attr('firstName', 'ginger', 'data-');
// Allows for other attribute characters
$('div').attr('lastName', 'chew', ':user.');
// but how does that work when retrieving
// namespaced data?
$('div').attr('lastName', null, ':user.') // 'chew'
normalize attr api to also work like the proposed data api + addendum: add option to
.attr()
function to convert camelCase keys
This is frustrating, .setAttribute('camelCase', true)
is supposed to end up as camelcase="true"
on the element. Normalizing the attr name to be like a data-*
attribute as an option to the .attr()
seems kind of silly to me, meaning it moves to the user concern column
data() api would interface with the .dataset api
The idea with the .data()
was more for storing complex data and interface with data-*
. This would be different from the original $.data()
didn't actually interface with data-*
at all. This would transfer the $.data()
from inside of the jQuery context to data-*
attributes. Here is a vanilla implementation of $.data()
for reference. But that can easily be moved to the user concern column when you consider the example below.
$('.user-card').data('complex', { fname: "ginger", lname: "chew" });
// as a user concern would be
$('.user-card').attr('data-complex', JSON.stringify({ fname: "ginger", lname: "chew" }));
accept objects are attributes or data (and I assume attributes set it on the node and not try to create names?)
I don't think I understand what you mean by this one.
At the end of writing all of this, I'm noticing that a lot of the things that a .data()
method would give can be considered a user concern, and outside the perview of blingbling. But adding some sort of namespace would be nice.
I thought, "this feels like something that could work as a plugin instead of a core api". I'm sure it is, but I couldn't figure it out. Instead, heres to add plugin functions to $ similar to how jQuery.fn did for 15 bytes
At the end of writing all of this, I'm noticing that a lot of the things that a .data() method would give can be considered a user concern, and outside the perview of blingbling. But adding some sort of namespace would be nice.
that's pretty much the same conclusion I can to in https://github.com/argyleink/blingblingjs/issues/47#issuecomment-1896605826, that this request is kinda boiling down to add option to attr() function to convert camelCase keys
. I'm curious the reason for putting so much data into attributes? you could just stick it on nodes as a property and value, then you dont have to do any transformations, and retrieval of the data is just as easy as setting it? no need to stringify or convert camel cases:
node.myData = { whatEver: 'you', want: [1,2,3,4,5] }
console.log(node.myData) // { whatEver: 'you', want: [1,2,3,4,5] }
modifying the set attribute with a namespace feels like middleware, or maybe just a wrapping function around the object provided to blingbling?
$('div').attr(camelCase({
'test-camel': 'foo',
'hi-case': 'bye',
}))
but then you'd need one to undo it.. yeah, this is sticky, but i totally understand where you're coming from.
this is fun to discuss to btw 🙂
Nerd Snipe
excellent nerd snipe lol. that is indeed a neat way to make this lib able to feature plugins.
The main reason I like using the data-*
instead of on the element object is that it "exists" somewhere.
<div>Does this div have data attached to it?</div>
<ul>
<li>Or did you put it in this list?</li>
</ul>
<!-- Or wait was it on a comment? -->
When you use the data-*
you can see it in the dom. Its also nice when bringing data from a server for a little progressive enhancement:
<div class="user-card" data-user='{"eventsInterested":[{},{},{},{}],"id":"thatAdamGuy"}'>
<img class="user-profile" />
<ul class="user-events">Use the data-user information to generate a calendar</ul>
</div>
<div class="user-card" data-user='{"eventsInterested":[{}],"id":"gingerWhatsErName"}'>
<img class="user-profile" />
<ul class="user-events"></ul>
</div>
const users = $('.user-card');
users.forEach($user => {
const { eventsInterested, id } = $user.data('user');
for (const event of events) {
$user.insertAdjacentHTML('beforeend', `<li data-id="${event.id}">${event.name}</li>`);
}
});
A lot of the work I do ends up needing the old JSON inside a script tag to bypass processing and evaluating a giant object of server generated data, somewhere between "this is a lot of data for one object" and "maybe this should be an API". citing my sources for this "playground rumor" sounding performance tip.
<script id="$wayMoreDataThanIsReasonable" type="application/json">{ ... }</script>
let data = JSON.parse($wayMoreDataThanIsResonable.innerHTML.trim());
data = { /* the same giant object could cause issues with reducing evaluation time */ };
modifying the set attribute with a namespace feels like middleware, or maybe just a wrapping function around the object provided to blingbling?
What I have done in the past is use a Proxy to... proxy dataset.
const $data = (el) => new Proxy(el.dataset, {
get(target, key) {
let v = target[key];
try {
if (+v === +v) v = +v;
v = JSON.parse(v);
} finally {
return v
}
},
set(target, key, value) {
target[key] = JSON.stringify(value);
return true;
},
deleteProperty(target, key) {
return delete target[key];
}
});
$data(el).user = { /* mixed data object */ };
console.log($data(el).user) // same object, as an object
delete $data(el).user; // true
console.log($data(el).user) // undefined
But what about passing a reviver/replacer function? Complexity threshold crossed.
this is fun to discuss to btw 🙂
I love aimlessly theory crafting code haha
i'm thinking that what this lib can do, is the same thing it did for attribute, which is allow bulk sets (not gets and no transforms on keys):
// before blingbling, only one set at a time, very annoying
element.setAttribute('foo', 'bar') // <div foo='bar'>
element.dataset.foo = 'bar' // <div data-foo='bar'>
// after blingbling
$element.attr({
foo: 'bar',
baz: 'qux',
})
// <div foo='bar' baz='qux'>
$element.data({
foo: 'bar',
baz: 'qux',
})
// <div data-foo='bar' data-baz='qux'>
// browser converts casing https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset#dash-style
// this wouldnt be needed if element.dataset = {foo:'bar',baz:'qux'} worked
bulk gets are still on the author:
$element.map(el => ({
foo: el.attr('foo'),
baz: el.attr('baz'),
}))
$element.map(el => ({
foo: el.data('foo'),
baz: el.data('baz'),
}))
// ^ but this is also dorky, cuz you can just do
$element[0].dataset // { foo: 'bar', baz: 'qux' }
// i dont even think we should support $element[0].data()
// i'd rather folks learn the API that's already there
removing stuff is the same as .attr()
also:
$element.attr('foo', null)
$element.data('foo', null)
Things I like about this:
.data()
like you do .attr()
I dunno if this would be the exact thing you're looking for, but it'd move the needle a bit
I coded up a mockup of the idea here. While there is overlap with
.attr()
I think there could still be a place for it. especially when handling data that can't be coerced to a string.The mockup handles set/get/delete, so accepting an object as the first argument like in
.attr()
isn't there yet.