search
HomeWeb Front-endJS TutorialThinking in Components

Thinking in Components

Key Takeaways

  • The component approach to interface development offers benefits over traditional MVC and MVVM frameworks, focusing on the smallest abstract sections of interaction rather than modeling business domains.
  • Components should be designed for re-use, distanced from the business domain, and adaptable to different contexts. This approach can be exemplified in the creation of a suggestive text input component that can be used universally, rather than a specific Type-Ahead Help Search component.
  • Breaking down interfaces into composable components not only simplifies the development process but also facilitates isolated testing. Components are independent from each other, allowing for modifications and additions without interfering with the overall system.

Web Components, React, Polymer, Flight — all are intended for building interface components. This is a different toolset from the big MVC and MVVM frameworks, and requires a different mindset when planning how you’ll implement your interface. While I still use models like MVC for server applications, I’m a dedicated convert to the benefits of a component approach for interface development. In this article I’ll outline how thinking in components differs from thinking in MVC and implement that approach in a real-world example.

In my mind, the headspace for MVC design is “How do I model my business domain? How do I model the processes of interacting with that domain? How do I model the interface to facilitate those processes?”. It is my opinion that this headspace does not facilitate good component design. In fact it’s the polar opposite for how you should be thinking when you set out to break down an interface into composable components. At best you’ll end up with micro apps. At worst you’ll build God components. The last thing you want to do is model your business domain as components. What you should aim to model is the smallest abstract sections of interaction you can describe.

Designing for Re-Use

Instead of “How do I make this dismissible alert panel?”, ask yourself “If I was adding new HTML elements to facilitate this interaction, what would they be?”. I find this leads to components which are safely distanced from the business domain and inherently the most re-usable in different contexts.

As another example, don’t make a Type-Ahead Help Search component that be used everywhere you want to allow searching the Help system, make a suggestive text input component that knows about the interactions involved in providing input suggestions. Then make a Help Search API data component that knows how to receive requests for data, interact with the Help Search API and broadcast results. Now your suggestive text input’s tests don’t need any mocking of APIs, and when you’re asked to add suggestions to a “tag” field, you can drop in your existing suggestive text input component, wire up a simple data component that talks to the tag API, and done!

Practical Example – “Project List”

For a concrete example, lets take a look at implementing a simple interface as isolated components. The following mockup is an extraction from 99designs 1-to-1 Projects system. While the UI has been drastically simplified, the JavaScript we’ll build up to is production code from our site at the time of writing. Here is the wireframe:

Thinking in Components

What we have is navigation between three lists of projects — Active, Drafts, and Archived. Each project has an action that can be performed on it — archiving an active project, deleting a draft, or re-activating an archived project. In app design thinking we’d start modeling a project and giving it methods like “archive” and “delete”, and a “status” property to track which of the three lists it belongs in. Bringing that line of reasoning to component design is exactly what we want to avoid, so we’re going to concern ourselves only with the interactions and what is needed to facilitate them.

At the core of it we have an action per row. When that action is performed we want to remove the row from the list. Already we’ve shed any Project-specific domain knowledge! Further, we have a count with how many items are in each list. To restrain the scope of this article, we assume each page to be generated server-side, with the tab navigation causing a full page refresh. As we don’t need to force dependance on JavaScript, our action buttons will be form elements with submit event handlers that will asynchronously perform the form’s action and broadcast an event when it’s complete.

Here’s some HTML for a single project row:

<span><span><span><li>></li></span>
</span>  <span><span><span><a> href<span>="/projects/99"</span> title<span>="View project"</span>></a></span>Need sticker designs for XYZ Co.<span><span></span>></span>
</span>  <span><span><span><div> class<span>="project__actions"</span>>
    <span><span><span><a> href<span>="/projects/99"</span> class<span>="button"</span>></a></span>View<span><span></span>></span>
</span>    <span><span><span><form> class<span>="action"</span> action<span>="/projects/99/archive"</span> method<span>="post"</span>></form></span>
</span>        <span><span><span><button>></button></span>Archive<span><span></span>></span>
</span>    <span><span><span></span>></span>
</span>  <span><span><span></span></span></span></span></span></span>
</div></span>></span>
</span><span><span><span></span>></span></span></span></span>

I’ll be using Flight to build our components. Flight is currently our default JS component library at 99designs for the reasons I outlined in my previous SitePoint JavaScript article.

Here’s our AsyncForm component for handling the form submission and broadcasting an event:

<span>define(function(require) {
</span>  <span>'use strict';
</span>
  <span>var defineComponent = require('flight/lib/component');
</span>
  <span>function <span>AsyncForm</span>() {
</span>    <span>this.defaultAttrs({
</span>      <span>broadcastEvent: 'uiFormProcessed'
</span>    <span>});
</span>
    <span>this.after('initialize', function() {
</span>      <span>this.on(this.node, 'submit', this.asyncSubmit.bind(this));
</span>    <span>});
</span>
    <span>this.asyncSubmit = function(event) {
</span>      event<span>.preventDefault();
</span>      $<span>.ajax({
</span>        <span>'url': this.$node.attr('action'),
</span>        <span>'dataType': 'json',
</span>        <span>'data': this.$node.serializeArray(),
</span>        <span>'type': this.$node.attr('method')
</span>      <span>}).done(function(response<span>, data</span>) {
</span>        <span>this.$node.trigger(this.attr.broadcastEvent, data);
</span>      <span>}.bind(this)).fail(function() {
</span>        <span>// error handling excluded for brevity
</span>      <span>});
</span>    <span>};
</span>  <span>}
</span>
  <span>return defineComponent(AsyncForm);
</span><span>});</span>

We maintain a strict policy of never using class attributes for JavaScript, so we’ll add a data-async-form attribute to our action forms, and attach our components to all matching forms like so:

<span><span><span><li>></li></span>
</span>  <span><span><span><a> href<span>="/projects/99"</span> title<span>="View project"</span>></a></span>Need sticker designs for XYZ Co.<span><span></span>></span>
</span>  <span><span><span><div> class<span>="project__actions"</span>>
    <span><span><span><a> href<span>="/projects/99"</span> class<span>="button"</span>></a></span>View<span><span></span>></span>
</span>    <span><span><span><form> class<span>="action"</span> action<span>="/projects/99/archive"</span> method<span>="post"</span>></form></span>
</span>        <span><span><span><button>></button></span>Archive<span><span></span>></span>
</span>    <span><span><span></span>></span>
</span>  <span><span><span></span></span></span></span></span></span>
</div></span>></span>
</span><span><span><span></span>></span></span></span></span>

Now we have the ability to perform the action, and broadcast an event which will propagate up the DOM tree on success. The next step is listening for that event and removing the row that it bubbles up to. For that we have Removable:

<span>define(function(require) {
</span>  <span>'use strict';
</span>
  <span>var defineComponent = require('flight/lib/component');
</span>
  <span>function <span>AsyncForm</span>() {
</span>    <span>this.defaultAttrs({
</span>      <span>broadcastEvent: 'uiFormProcessed'
</span>    <span>});
</span>
    <span>this.after('initialize', function() {
</span>      <span>this.on(this.node, 'submit', this.asyncSubmit.bind(this));
</span>    <span>});
</span>
    <span>this.asyncSubmit = function(event) {
</span>      event<span>.preventDefault();
</span>      $<span>.ajax({
</span>        <span>'url': this.$node.attr('action'),
</span>        <span>'dataType': 'json',
</span>        <span>'data': this.$node.serializeArray(),
</span>        <span>'type': this.$node.attr('method')
</span>      <span>}).done(function(response<span>, data</span>) {
</span>        <span>this.$node.trigger(this.attr.broadcastEvent, data);
</span>      <span>}.bind(this)).fail(function() {
</span>        <span>// error handling excluded for brevity
</span>      <span>});
</span>    <span>};
</span>  <span>}
</span>
  <span>return defineComponent(AsyncForm);
</span><span>});</span>

Again we add a data-removable attribute to our project rows, and attach the component to the row elements:

<span>AsyncForm.attachTo('[data-async-form]');</span>

Done! Two small components with one event each, and we’ve handled the three types of actions in our three forms in a way that gracefully degrades. Only one thing left, and that’s our count on each tab. Should be easy enough, all we need is to decrement the active tab’s count by one every time a row is removed. But wait! When an active project is archived, the archived count needs to increase, and when an archived project is re-activated, the activated count needs to increase. First lets make a Count component that can receive instructions to alter its number:

<span>define(function(require) {
</span>  <span>'use strict';
</span>
  <span>var defineComponent = require('flight/lib/component');
</span>
  <span>function <span>Removable</span>() {
</span>    <span>this.defaultAttrs({
</span>      <span>'removeOn': 'uiFormProcessed'
</span>    <span>});
</span>
    <span>this.after('initialize', function() {
</span>      <span>this.on(this.attr.removeOn, this.remove.bind(this));
</span>    <span>});
</span>
    <span>this.remove = function(event) {
</span>      <span>// Animate row removal, remove DOM node, teardown component
</span>      $<span>.when(this.$node
</span>        <span>.animate({'opacity': 0}, 'fast')
</span>        <span>.slideUp('fast')
</span>      <span>).done(function() {
</span>        <span>this.$node.remove();
</span>      <span>}.bind(this));
</span>    <span>};
</span>  <span>}
</span>
  <span>return defineComponent(Removable);
</span><span>});</span>

Our Count would be represented in HTML as something like 4. Because the Count listens to events at the document level, we’ll make its event property null. This will force any use of it to define an event that this instance should listen to, and prevent accidentally having multiple Count instances listening for instructions on the same event.

<span>Removable.attachTo('[data-removable]');</span>

The final piece of the puzzle is getting our Removable instances to fire an event with a modifier to their respective counter(s) when they’re removed. We certainly don’t want any coupling between the components, so we’ll give Removable an attribute that is an array of events to fire when it is removed:

<span>define(function(require) {
</span>  <span>'use strict';
</span>
  <span>var defineComponent = require('flight/lib/component');
</span>
  <span>function <span>Count</span>() {
</span>    <span>this.defaultAttrs({
</span>      <span>'event': null
</span>    <span>});
</span>
    <span>this.after('initialize', function() {
</span>      <span>this.on(document, this.attr.event, this.update.bind(this));
</span>    <span>});
</span>
    <span>this.update = function(event<span>, data</span>) {
</span>      <span>this.$node.text(
</span>        <span>parseInt(this.$node.text(), 10) + data.modifier
</span>      <span>);
</span>    <span>}
</span>  <span>}
</span>
  <span>return defineComponent(Count);
</span><span>});</span>

Now the coupling between Count and Removable happens in the use case specific page script where we attach our components to the DOM:

<span>Count.attachTo(
</span>  <span>'[data-counter="active"]',
</span>  <span>{'event': 'uiActiveCountChanged'}
</span><span>);
</span>
<span>Count.attachTo(
</span>  <span>'[data-counter="draft"]',
</span>  <span>{'event': 'uiDraftCountChanged'}
</span><span>);
</span>
<span>Count.attachTo(
</span>  <span>'[data-counter="archived"]',
</span>  <span>{'event': 'uiArchivedCountChanged'}
</span><span>);</span>

Mission accomplished. Our counters know nothing of our project list rows, which know nothing of the forms inside them. And none of the components are in the slightest way designed around the concept of a list of projects.

Last Minute Addition

Our UX designer has pointed out that it would be better if we asked for confirmation when someone tries to delete a draft, as this action cannot be undone. No problem, we can whip up a component that does just that:

<span>define(function(require) {
</span>  <span>'use strict';
</span>
  <span>var defineComponent = require('flight/lib/component');
</span>
  <span>function <span>Removable</span>() {
</span>    <span>this.defaultAttrs({
</span>      <span>'removeOn': 'uiFormProcessed',
</span>      <span>'broadcastEvents': [
</span>        <span>{'event': 'uiRemoved', 'data': {}}
</span>      <span>]
</span>    <span>});
</span>
    <span>this.after('initialize', function() {
</span>      <span>this.on(this.attr.removeOn, this.remove.bind(this));
</span>    <span>});
</span>
    <span>this.remove = function(event) {
</span>      <span>// Broadcast events to notify the rest of the UI that this component has been removed
</span>      <span>this.attr.broadcastEvents.forEach(function(eventObj) {
</span>        <span>this.trigger(eventObj.event, eventObj.data);
</span>      <span>}.bind(this));
</span>
      <span>// Animate row removal, remove DOM node, teardown component
</span>      $<span>.when(this.$node
</span>        <span>.animate({'opacity': 0}, 'fast')
</span>        <span>.slideUp('fast')
</span>      <span>).done(function() {
</span>        <span>this.$node.remove();
</span>      <span>}.bind(this));
</span>    <span>};
</span>  <span>}
</span>
  <span>return defineComponent(Removable);
</span><span>});</span>

Attach that to the delete buttons, and we’ve got what we were asked for. The confirm dialog will intercept the button, and allow the form submission if the user selects “OK”. We haven’t had to alter our AsyncForm component, as we can compose these components without interfering with each other. In our production code we also use a SingleSubmit component on the action button which gives visual feedback that the form has been submitted and prevents multiple submissions.

Final Components, Tests, and Fixtures

Hopefully this article has demonstrated how your projects could benefit from breaking down interfaces into composable components. An important benefit of component design that I haven’t covered is their ease of isolated testing, so here are the final components along with their jasmine tests and HTML test fixtures:

  • AsyncForm
  • Removable
  • Count
  • Confirm

If you have any questions regarding what I’ve covered, please ask for details in the comments and I’ll do my best to help.

Frequently Asked Questions (FAQs) about Thinking Components

What are the key components of effective thinking?

Effective thinking is a multifaceted process that involves several key components. These include clarity, precision, accuracy, relevance, depth, breadth, logic, significance, and fairness. Each of these components plays a crucial role in ensuring that our thinking process is effective and leads to accurate conclusions.

How can I improve my critical thinking skills?

Improving critical thinking skills involves practicing certain habits such as questioning assumptions, seeking diverse perspectives, and being open to new ideas. It also involves developing skills such as analysis, interpretation, inference, evaluation, explanation, and self-regulation.

What is the role of logic in critical thinking?

Logic is a fundamental component of critical thinking. It involves the ability to reason correctly, to derive conclusions from premises, to evaluate claims, and to avoid fallacies or errors in reasoning.

How does relevance contribute to effective thinking?

Relevance ensures that the information or ideas we are considering are directly related to the issue or problem at hand. It helps us to stay focused and avoid distractions or irrelevant information.

What is the significance of depth in critical thinking?

Depth in critical thinking refers to the ability to delve beneath the surface of an issue or problem, to understand its underlying causes or implications, and to explore it from multiple perspectives.

How can I develop breadth in my thinking?

Developing breadth in thinking involves considering a wide range of perspectives, ideas, and sources of information. It requires being open-minded, curious, and willing to explore new ideas or viewpoints.

What is the role of fairness in critical thinking?

Fairness in critical thinking involves being unbiased, impartial, and objective. It requires us to consider all relevant viewpoints and evidence, and to avoid favoritism, bias, or prejudice.

How does precision contribute to effective thinking?

Precision in thinking involves being exact, detailed, and clear in our thoughts and expressions. It helps us to avoid vagueness, ambiguity, or confusion, and to communicate our ideas effectively.

What is the significance of accuracy in critical thinking?

Accuracy in critical thinking involves ensuring that our information, ideas, and conclusions are correct, reliable, and free from errors or distortions. It is crucial for making sound decisions and judgments.

How can I improve the clarity of my thinking?

Improving clarity in thinking involves practicing clear communication, avoiding jargon or complex language, and striving for simplicity and straightforwardness in our thoughts and expressions.

The above is the detailed content of Thinking in Components. For more information, please follow other related articles on the PHP Chinese website!

Statement
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
C   and JavaScript: The Connection ExplainedC and JavaScript: The Connection ExplainedApr 23, 2025 am 12:07 AM

C and JavaScript achieve interoperability through WebAssembly. 1) C code is compiled into WebAssembly module and introduced into JavaScript environment to enhance computing power. 2) In game development, C handles physics engines and graphics rendering, and JavaScript is responsible for game logic and user interface.

From Websites to Apps: The Diverse Applications of JavaScriptFrom Websites to Apps: The Diverse Applications of JavaScriptApr 22, 2025 am 12:02 AM

JavaScript is widely used in websites, mobile applications, desktop applications and server-side programming. 1) In website development, JavaScript operates DOM together with HTML and CSS to achieve dynamic effects and supports frameworks such as jQuery and React. 2) Through ReactNative and Ionic, JavaScript is used to develop cross-platform mobile applications. 3) The Electron framework enables JavaScript to build desktop applications. 4) Node.js allows JavaScript to run on the server side and supports high concurrent requests.

Python vs. JavaScript: Use Cases and Applications ComparedPython vs. JavaScript: Use Cases and Applications ComparedApr 21, 2025 am 12:01 AM

Python is more suitable for data science and automation, while JavaScript is more suitable for front-end and full-stack development. 1. Python performs well in data science and machine learning, using libraries such as NumPy and Pandas for data processing and modeling. 2. Python is concise and efficient in automation and scripting. 3. JavaScript is indispensable in front-end development and is used to build dynamic web pages and single-page applications. 4. JavaScript plays a role in back-end development through Node.js and supports full-stack development.

The Role of C/C   in JavaScript Interpreters and CompilersThe Role of C/C in JavaScript Interpreters and CompilersApr 20, 2025 am 12:01 AM

C and C play a vital role in the JavaScript engine, mainly used to implement interpreters and JIT compilers. 1) C is used to parse JavaScript source code and generate an abstract syntax tree. 2) C is responsible for generating and executing bytecode. 3) C implements the JIT compiler, optimizes and compiles hot-spot code at runtime, and significantly improves the execution efficiency of JavaScript.

JavaScript in Action: Real-World Examples and ProjectsJavaScript in Action: Real-World Examples and ProjectsApr 19, 2025 am 12:13 AM

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

JavaScript and the Web: Core Functionality and Use CasesJavaScript and the Web: Core Functionality and Use CasesApr 18, 2025 am 12:19 AM

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

Understanding the JavaScript Engine: Implementation DetailsUnderstanding the JavaScript Engine: Implementation DetailsApr 17, 2025 am 12:05 AM

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python vs. JavaScript: The Learning Curve and Ease of UsePython vs. JavaScript: The Learning Curve and Ease of UseApr 16, 2025 am 12:12 AM

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

See all articles

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Atom editor mac version download

Atom editor mac version download

The most popular open source editor

Dreamweaver Mac version

Dreamweaver Mac version

Visual web development tools

PhpStorm Mac version

PhpStorm Mac version

The latest (2018.2.1) professional PHP integrated development tool

mPDF

mPDF

mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

EditPlus Chinese cracked version

EditPlus Chinese cracked version

Small size, syntax highlighting, does not support code prompt function