Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add $().data for handling data-* #47

Open
gingerchew opened this issue Jan 16, 2024 · 7 comments
Open

Add $().data for handling data-* #47

gingerchew opened this issue Jan 16, 2024 · 7 comments

Comments

@gingerchew
Copy link

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.

$('div').attr('data-user', {
  fname: 'argyle',
  lname: 'ink'
});

$('div').attr('user') // "[object Object]"

// data-user="{"fname":"ginger","lname":"chew"}"
$('div').data('user', {
  fname: 'ginger',
  lname: 'chew'
});

$('div').data('user') // { fname: 'ginger', lname: 'chew' }

// Needing to set `data-` like this on an object is frustrating
$('div').attr({
  'data-fname': 'argyle',
  'data-lname': 'ink'
  'data-user-id': 1
});

// the `data-` is assumed and don't need to wrap the keys in quotes
// and converting camelCase to kebab-case is handled automatically
$('div').data({
  fname: 'ginger',
  lname: 'chew',
  userId: 2
})

The mockup handles set/get/delete, so accepting an object as the first argument like in .attr() isn't there yet.

@argyleink
Copy link
Owner

lots of great ideas in here! here's all the new stuff I spot:

  • automatic prepending of a namespace
  • normalize attr api to also work like the proposed data api
  • data() api would interface with the .dataset api
  • accept objects are attributes or data (and i assume attributes set it on the node and not try to create names?)

this 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

gingerchew added a commit to gingerchew/blingblingjs that referenced this issue Jan 17, 2024
@gingerchew
Copy link
Author

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.

@argyleink
Copy link
Owner

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):

  • add option to attr() function to convert camelCase keys

thoughts? does the boil down appropriately? does dataset cover what you're looking for?

@gingerchew
Copy link
Author

gingerchew commented Jan 18, 2024

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.

Warning: Potential Nerd Snipe

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

@argyleink
Copy link
Owner

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 #47 (comment), 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.

@gingerchew
Copy link
Author

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

@argyleink
Copy link
Owner

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:

  • only new thing to learn or know is that you can use .data() like you do .attr()
  • very small addition to the code
  • isnt creating exceptions or niche details
  • just a workflow normalizer for bulk sets

I dunno if this would be the exact thing you're looking for, but it'd move the needle a bit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants