material-components / material-web

Material Design Web Components
https://material-web.dev
Apache License 2.0
9.36k stars 898 forks source link

[Suggestion] Dynamically load select content #1613

Closed robertofabrizi closed 4 years ago

robertofabrizi commented 4 years ago

Dear friends, I'd love to switch to using this great implementation of Web Components with the beaufitul Material Design, I'm especially tempted by a frameworkless approach and website. What would be your recommended way to dynamically fill content in these components, for example ask to the backend for the values of a select?

Thank you and have a nice day, Robert

dfreedm commented 4 years ago

Select should already be able to handle dynamically added options. Can you add an example jsbin of what you've tried so far?

robertofabrizi commented 4 years ago

Dear @dfreedm , I'm sorry if my post was misleading, I didn't want to give any suggestion, but rather to receive one. If this isn't the right place to ask for it I'll promptly close the thread / try to remove it. My question was, in an attempt to get away from frameworks and custom libraries towards an approach made of standard web components / building blocks, what is the preferred way of retrieving data dynamically from the backend and update the components with it?

In theory, I could use a familiar JQuery feature like this:

$.get( "ajax/test.html", function( data ) {
  $( ".result" ).html( data );
  alert( "Load was performed." );
});

or in case of a React custom Component, override componentDidMount (assuming that data is loaded on first component rendering, just as an example):

componentDidMount() {
    fetch('https://some-api.com/harry-potter')
    .then((response) => response.json())
    .then(booksList => {
        this.setState({ books: booksList });
    });
}

to invoke a backend API passing some parameters to it, retrieve an array of values and fill the select with those. I was wondering if such ability to invoke an URL and update the web component data with its invocation could be something backed in the components themselves.

e111077 commented 4 years ago

Hello, the best place to ask this may be in the Polymer slack group where people are always happy to answer questions on web components and how they interact with or without frameworks. You may also want to look into the #material channel on there:

https://www.polymer-project.org/slack-invite

Though shooting from the hip, webcomponents are just like any normal native element. A question is how would you handle this with a normal <select> element without a framework?

An example might go like this:

<select id="selectEl">
  <option></option> <!-- empty option for deselection -->
</select>

(using typescript for type clarity)

const createOption = (text: string, value: string) => {
  const opt = document.createElement('option');
  opt.innerText = text;
  opt.value = value;
  return opt;
}

main() => {
  const select = document.querySelector('#selectEl') as HTMLSelectElement;
  fetch('https://some-api.com/harry-potter')
    .then(res => res.json())
    .then(booksList => {
      booksList.foreach(book => {
        const opt = createOption(book.title, book.isbn);
        select.appendChild(opt);
      }); 
    });
}

Now the way you would do it with webcomponents would be the same as how you'd treat a native <select>:

<mwc-select id="selectEl">
  <mwc-list-item></mwc-list-item> <!-- empty option for deselection -->
</mwc-select>
import {Select} from '@material/mwc-select';

const createListItem = (text: string, value: string) => {
  const li = document.createElement('mwc-list-item');
  li.innerText = text;
  li.value = value;
  return li;
}

main() => {
  const select = document.querySelector('#selectEl') as Select;
  fetch('https://some-api.com/harry-potter')
    .then(res => res.json())
    .then(booksList => {
      booksList.foreach(book => {
        const li = createListItem(book.title, book.isbn);
        select.appendChild(li);
      }); 
    });
}
robertofabrizi commented 4 years ago

Thank you @e111077 , exactly the feedback I was looking for and the way I thought would be best to achieve it.

My main doubt was, assuming that a common data model could be agreed on beforehand, let's say a json with two fields, one called innerText and one called value, wouldn't it be nicer to add the functionality right inside the web component itself to query a remote location for its content? It marries its philosophy of self containment well, just like I don't have to do a document.querySelector to retrieve it to set its color, I'd also like to skip it to init its content.

In other words, give the web component a property to set the remote endpoint (in your example (https://some-api.com/harry-potter) and maybe the occasion to invoke it (onload, etc).

e111077 commented 4 years ago

I think this is out of scope for this component. We try to mirror the native control elements as best we can, and adding fetch functionality to a select seems too complex to call it a simple select.

Additionally, we have considered allowing for an array of values to be passed in e.g.

select.options = [{text: 'Option 1', value: 'option1'}, {text: 'Option 2', value: 'option2'}];

but the DX for those wanting to simply assign it without using javascript or a rendering library that can bind to properties is pretty bad. e.g.

<mwc-select
  options="[{text: 'Option 1', value: 'option1'}, {text: 'Option 2', value: 'option2'}]">
</mwc-select>

The common way the browser handles lists of data is via lists of nodes e.g.

<mwc-select>
  <mwc-list-item value="option1">Option 1</mwc-list-item>
  <mwc-list-item value="option2">Option 2</mwc-list-item>
</mwc-select>

Following the browser model helps out with devX as it comes with affordances: people know how to use <select><option></option></select>, thus it is the same with <mwc-select>