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

Find a place for prose #43

Open
curran opened this issue Mar 6, 2017 · 1 comment
Open

Find a place for prose #43

curran opened this issue Mar 6, 2017 · 1 comment

Comments

@curran
Copy link
Owner

curran commented Mar 6, 2017

The following feels like it does not belong in the README. Maybe a Medium post would be better? Or in a separate .md file? Stashing it here for now.

Composing Components

Components can use other components in their update functions. Some useful patterns for component composition are:

Nesting

Nesting is useful when instances of one component will contain instances of other components. Nesting can be achieved by invoking child components within the update function of the parent component, possibly deriving the value of children props from the props passed into the parent component.

Here's an example of a post component that composes two other components, heading and paragraph.

var heading = d3.component("h1")
      .update(function (selection, props){
        selection.text(props.text);
      }),
    paragraph = d3.component("p")
      .update(function (selection, props){
        selection.text(props.text);
      }),
    post = d3.component("div", "post")
      .update(function (selection, props){
        selection
          .call(heading, { text: props.title })
          .call(paragraph, { text: props.content });
      });

Here's how we would render an instance of the post component.

d3.select("#some-container-div");
  .call(post, {
    title: "Title",
    content: "Content here."
  });

The following DOM structure will be rendered.

<div id="some-container-div">
  <div class="post">
    <h1>Title</h1>
    <p>Content here.</p>
  </div>
</div>

Here's an example of rendering multiple component instances.

d3.select("#some-container-div")
  .call(post, [
    { title: "A Title", content: "a content" },
    { title: "B Title", content: "b content" },
  ]);

The following HTML structure will be rendered.

<div id="some-container-div">
  <div class="post">
    <h1>A Title</h1>
    <p>a content</p>
  </div>
  <div class="post">
    <h1>B Title</h1>
    <p>b content</p>
  </div>
</div>

For a full working example using the components above, see Posts with d3-component.

Containment

Sometimes children components are not known in advance. This is often the case for components that serve as "boxes" that can contain arbitrary content, such as cards or dialogs. There are no special constructs provided for achieving this, but it can be achieved by using a pattern in which the child component and its props are both passed in via the props of the parent component.

Here's an example of a card component that can render arbitrary children.

var card = d3.component("div", "card")
  .enter(function (selection){
    selection
      .append("div").attr("class", "card-block")
      .append("div").attr("class", "card-text");
  })
  .update(function (selection, props){
    selection.select(".card-text")
        .call(props.childComponent, props.childProps);
  });

Here's how we can use this card component to render a card that contains instances of the post component.

d3.select("#some-container-div")
  .call(card, {
    childComponent: post,
    childProps: [
      { title: "A Title", content: "a content" },
      { title: "B Title", content: "b content" },
    ]
  });

The following DOM structure will be rendered.

<div id="some-container-div">
  <div class="card">
    <div class="card-block">
      <div class="card-text">
        <div class="post"><h1>A Title</h1><p>a content</p></div>
        <div class="post"><h1>B Title</h1><p>b content</p></div>
      </div>
    </div>
  </div>
</div>

Conditional Rendering

Sometimes components should render sub-components only under certain conditions. To achieve this, the props passed into the sub-component can either be [] to render zero component instances, or any other value to render one or many component instances. Even if a sub-component is not rendered, it still needs to be invoked with its props as [], in the case that it was rendered previously and its instances need to be removed from the DOM.

Here's an example of a fruit component that conditionally renders either apple or orange components.

var apple = d3.component("span", "apple")
    orange = d3.component("span", "orange")
    fruit = d3.component("div", "fruit")
      .update(function (selection, props){
        selection
          .call(apple, props.type === "apple"? {} : [])
          .call(orange, props.type === "orange"? {} : [])
      });

Here's how we can use this fruit component.

d3.select("#some-container-div")
  .call(fruit, [
    { type: "apple" },
    { type: "orange" },
    { type: "apple" },
    { type: "apple" },
    { type: "orange" }
  ]);

The following DOM structure will be rendered.

<div id="some-container-div">
  <div class="fruit"><span class="apple"></span></div>
  <div class="fruit"><span class="orange"></span></div>
  <div class="fruit"><span class="apple"></span></div>
  <div class="fruit"><span class="apple"></span></div>
  <div class="fruit"><span class="orange"></span></div>
</div>
@curran curran changed the title Find a place for prose on composition patterns Find a place for prose Mar 14, 2017
@curran
Copy link
Owner Author

curran commented Mar 14, 2017

more prose:

I'm working on a lightweight component abstraction for use with D3:

https://github.com/curran/d3-component

The goal is to enable developers to create and use "components" (similar to stateless functional components in React) without leaving the realm of D3. I felt the need to create this when I found myself reaching for React just to add dynamic user interface elements to D3 visualizations, or to embed visualizations inside a complex/dynamic UI. I felt these things should be easy to do within D3, without adding the React+JSX+Babel+Webpack toolchain into the mix, and without implementing the Towards Reusable Charts boilerplate for each and every level of my DOM tree.

So, this is my attempt at an abstraction that may help bridge the feature gap between React and D3. I'd like to share it here and get feedback on the concept / implementation. Any thoughts? Is it potentially useful? Potentially an addition to D3 proper? A silly endeavor given the popularity of React to achieve the same thing? Any feedback is warmly welcome, and feel free to create a new issue for discussions / feature requests / requests for examples.

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

1 participant