Giter Site home page Giter Site logo

elliotnb / nimbly Goto Github PK

View Code? Open in Web Editor NEW
25.0 2.0 4.0 1005 KB

Nimbly is a JavaScript component framework for single page applications.

JavaScript 97.92% HTML 2.08%
javascript frontend framework framework-javascript state-management mvvm jquery dom front-end javascript-framework

nimbly's Introduction

๐Ÿ„ Nimbly

Build Status Coverage Status

https://github.com/elliotnb/nimbly

Version 0.1.5

Licensed under the MIT license:

http://www.opensource.org/licenses/MIT

Quick Start

Want to learn how to use Nimbly as quickly as possible? Follow this quick start guide!

The README below is best used as a reference manual. Beginners should first consult the quick start guide.

Overview

What is Nimbly?

Nimbly is a JavaScript component framework for single page applications.

Why yet another JS framework?

Modern web application development is run amok with excessive tooling and leaky abstractions. Babel, Webpack, JSX, virtual DOM, source maps, etc -- it's all unnecessarily complicated. As the 2018 npm developer survey concluded:

JavaScript in 2018 is somewhat notorious for requiring a lot of tooling to get going, which is quite a reversal from the situation in 2014... True to that, all of our survey respondents would like to see less tooling, less configuration required to get started, and better documentation of the tools that do exist.

The goal of Nimbly is to keep things simple.

What makes Nimbly different?

  • Fewer abstractions - Nimbly embraces the native DOM. It does not use a virtual DOM abstraction. Nimbly components render plain HTMLElements.
  • No build steps - The code you write runs in the browser. Nimbly does not require transpiling (e.g., Babel) nor a build process (e.g., Webpack).
  • No DSLs - Write code with plain HTML, CSS and JavaScript. You don't need to learn another domain specific language (e.g., React JSX).
  • Easy to debug - Fewer abstractions, no DSLs and ES5 support without transpiling means that Nimbly components are easy to debug. Short stack traces and a minimum of framework-specific error sleuthing.
  • "Plays nice" - Nimbly is self-contained and does not take over the page. Nimbly is perfectly happy existing alongside other non-Nimbly components.

The technical objectives of Nimbly are:

  • Encourage adoption of SPA best practices:
    • Templating.
    • One-way data binding.
    • Intuitive and consistent state management.
    • Elimination of explicit DOM manipulations.
    • Loosely coupled modular components.
  • Encourage expressive and easy-to-follow code:
    • Reduce boilerplate code.
    • Components follow a common structure and organization.
    • Easily identifiable state mutations.
    • Clear linking of state to display.
    • Common public methods and lifecycle hooks for all components.
  • Provide easy-to-use patterns for:
    • Dependency injection.
    • Automated unit testing.
  • Coordinate DOM updates amongst all components to minimize page re-draws, optimize performance and improve the user experience.

Requirements

Nimbly requires the following libraries:

Why jQuery?

Nimbly started out as a project to facilitate easier refactors of jQuery-heavy web apps.

For the time being, Nimbly requires jQuery. But fret not, components built with Nimbly are not required to use jQuery.

Nimbly uses jQuery primarily for:

  • Merging deeply nested objects via $.extend.
  • Updating DOM nodes via $.replaceWith.
  • Creation of new DOM elements $("<div>hello world</div>");

Eventually, this functionality will be bundled directly into Nimbly and jQuery will be retired.

Including jQuery v3.3.1 minified slim adds 69KB to the total footprint of the framework.

Install

<script src="observable-slim.min.js"></script>
<script src="jquery.min.js"></script>
<script src="nimbly.min.js"></script>

Also available via NPM:

$ npm install nimbly --save

Parameters

The Nimbly constructor accepts four parameters:

  1. className - String, required, the name of the component class (e.g., HelloWorld) that extends Nimbly. The class name is used for producing helpful debug messages when errors occur within Nimbly.

  2. defaults - Object, required, a variety of default options and preferences. See below for a definition of each configurable setting.

  3. data - Object, optional, a plain object that sets the initial state values (overwrites the default state defined in defaults).

  4. options - Object, optional, overrides the defaults by merging over the top of it.

Properties

All Nimbly components have the following public properties:

  1. options - Object, the component default settings with any user-defined options merged over the top.

  2. className - String, the name of the class that initialized the base class. We store this value for debugging and logging purposes.

  3. domNode - the DOM Node rendered by this component.

  4. jqDom - jQuery reference to this.domNode.

  5. initialized - Boolean, set to true when the this.init() method has completed and the component is ready to render.

  6. childComponents - Hash, if a component registers (via .registerChild) other components, then those child components will be added to the hash. Child components are indexed on the hash according to what list or repeatable section they belong to. If a component is not part of a list, then that component is added to this.childComponents.default.

  7. templates - Hash, where the key is the name of the template and the value is a string containing the template. The hash contains each template used by the component. The template element identifiers are passed in via options.templates and below we will populate this.templates with the content of the template.

  8. loadingTemplate - String, template content OR an element identifier of the template used to display a loading spinner or loading message. The loadingTemplate is utilized only if the .render() method is invoked before the component has been initialized. It allows the component to return a rendered DomNode that will be subsequently updated as soon as initialization completes.

  9. data - ES6 Proxy, serves as a proxy to the component state data (this._data) and enables the component to observe any changes that occur to the data. All modifications to component data should be made through this property.

Base Class Methods

These methods are implemented by the Nimbly base class and are available on all components that extend Nimbly:

Public

All Nimbly components have the following public methods:

  1. observe(fnChanges) - Allows external entities to observe changes that occur on the this.data property.

    • Parameters:
      • fnChanges - a function that is invoked with with a single argument whenever 'var data' is modified. The single argument will have the following format: [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah"}]
    • Returns: ES6 Proxy.
  2. init() - Runs any initialization procedures (stored on this.options.initList or defined in the defaults) required before the component renders for the first time. Typically this means retrieving data from external APIs.

    • Parameters: None.
    • Returns: Nothing.
  3. render() - Renders and returns the component. The render method will first verify that the component has been initialized, if it has not been initialized, then it will invoke this.init(). If the initialization is already in progress, then it will return a temporary 'loading' display. If the component has already been initialized, then the standard render method this._render() is invoked.

    • Parameters: None.
    • Returns: jQuery-referenced DocumentFragment, the component ready to be inserted into the main DOM.
  4. refresh() - This method is invoked when we want to re-render the component.

    • Parameters: None.
    • Returns: Nothing.
  5. destroy() - This method will remove this.jqDom from the DOM and delete the observable that was created during initialization. If the component initialized any child components, then those will be destroyed as well. This helps ensure that memory usage does not balloon after repeated refreshes and UI updates.

    • Parameters: None.
    • Returns: Nothing.
  6. eachChildComponent - This method will iterate over each component that has been registered as a child of the current component. This method accepts one parameter, a callback function. That callback function invoked upon each interation of eachChildComponent. The callback function is passed three parameters: 1. a reference to the child component instance, 2. the template section the component belongs to, and 3. a method that when invoked will unregister and destroy the child component.

    • Parameters:
      • handler - function, required, accepts three parameters: 1. the current section name (string), 2. the child component (object) and 3. a callback method to remove the child component (function)

Protected

JavaScript does not support protected methods, but the following methods are intended to be used as protected methods. They should not be invoked externally, but they may be invoked within component class that extends the Nimbly base class.

  1. registerChild(childComponent, sectionName) - When a component nests other components within it, we refer to the original component as the "parent component" and the nested component(s) as "child component(s)". In order for refreshes of the parent component to work properly, we must register the child components on the parent component. This will allow our ._refresh() method to intelligently determine if it is necessary to re-render the child component(s) when an update occurs to the parent component.

    This method is overloaded. It has two modes of operation depending on what arguments are passed in.

    First mode - register single stand-alone component:

    • Parameters: childComponents - required, a single Nimbly child component instance. targetName - required, a CSS selector that uniquely identifies the component in the parent component's template. For example, the tag <foobar></foobar> could be uniquely identified with the CSS selector foobar. Additionally, <foobar field="test"></foobar> could be uniquely identified with the selector foobar[field=test].

    Second mode - register multiple components to a repeatable section:

    • Parameters: childComponents - required, an array of objects with the following structure:

       	[
       		{
       			"comp":child1,
       			"tagName":"child-comp-one"
       		}, 
       		{
       			"comp":child2,
       			"tagName":"child-comp-two[attr=testing]"
       		}    
       	]
      

      targetName - required, a CSS selector that uniquely identifies the repeatable section tag that the child components will be inserted into.

When using the second mode, registerChild should be invoked for each iteration of the repeatable section.

  1. parseHTML(strHTML) - Utility method intended to assist components with the process of converting HTML into an HTMLElement that the components can then bind event handlers to.

    • Parameters:

      • strHTML - A string of HTML that should be converted into an HTMLElement. Should only have one top-level element (e.g., <div>one</div><div>two</div> is not allowed).
    • Returns: HTMLElement.

  2. addTemplate(templateName, templateHtml) - Templates are typically defined statically in the component config. This method allows us to add more templates during runtime.

    • Parameters:

      • templateName - string, required, a descriptive alphanumeric for the template, dashes and underscores allowed (e.g., my_template_name).
      • templateHtml - string, required, the HTML content of the template.
    • Returns: HTMLElement.

  3. getTemplateComponents(templateName) - Utility method that will fetch a breakdown of all the child components defined in a given template.

    • Parameters:

      • templateName - string, required, the name of the template that should be evaluated. The template should already be defined (or registered at runtime via this.addTemplate) on the component (i.e., present on this.templates).
    • Returns: Array of objects -- array contains a breakdown of all child components defined in the template as well as any attribute values defined in the template.

Component Methods

The following methods are implemented by Nimbly components.

  1. _render - Required, every component must have a _render method. The _render method is prefixed with an underscore denoting that it is private and should not be invoked externally. The _render method is the only place where the UI for the component is generated. No other portion of the component is allowed to modify the UI or make any manual DOM manipulations. The single _render method provides non-author devs a single place to inspect when they want to understand the display logic of a component or figure out why a component looks the way it does

    • Parameters: None.
    • Returns: jQuery-referenced DocumentFragment with bound event handlers ready for insertion into the main DOM.
  2. _renderLoading() Sometimes loading displays require more logic than a simple HTML blurb can provide. In cases where the loadingTemplate alone is insufficient, you may define _renderLoading on your component that will be invoked when it comes time to render and display the loading screen.

    • Returns: jQuery-referenced DocumentFragment, the component ready to be inserted into the main DOM.
  3. _fetch*(resolve, reject) Optional, the dataBindings and initList in the settings both contain references to _fetch* methods. Fetch methods retrieve data from external sources via ajax and update values stored on this.data. They are treated as Promises in that they accept resolve and reject functions as parameters.

    • Parameters:
      • resolve - Function, required, invoked if the _fetch* method is successful in retrieving data and updating this.data.
      • reject - Function, required, invoked if the _fetch* method is unsuccessful in retrieving data and updating this.data.
      • changes - Array of objects, optional, if the _fetch* method is invoked as a result of a dataBinding a copy of the changes that triggered the dataBinding is passed along via this parameter.
    • Returns: Nothing.
  4. showLoadMask() - Executed when we need to display a loading mask over the component. Loading masks are displayed when we fetch data that must be retrieved before the UI can refresh.

    • Parameters: None.
    • Returns: Nothing.
  5. hideLoadMask() - Executed when we need to hide the loading mask over the component.

    • Parameters: None.
    • Returns: Nothing.

Lifecycle Hooks

Components have a lifecycle that is managed by Nimbly. Nimbly offers lifecycle hooks that provide visibility into key life moments and the ability to act when they occur.

The implementation of lifecycle hooks is optional.

  1. _init() - this method is invoked immediately after the component has initialized (i.e., after the methods defined in this.initList have completed).

  2. _afterRender() - invoked immediately after the child components are inserted into this component. Passes in a reference to the just rendered component.

    • Parameters:
      • jqDom - a jQuery-referenced DocumentFragment of the just rendered component.
  3. _afterRefresh() - invoked immediately after the display of a component is refreshed.

    • Parameters:
      • fullRefresh - boolean, set to true if the entire component was refreshed, set to false is the refresh only affected part of the component.
      • jqOldDom - jQuery-referenced DocumentFragment of the component's previous display state.
  4. _afterInDocument() - invoked immediately after the component is inserted into the document for the first time. Re-invoked after every full refresh of the component, but not a partial refresh. Useful for performing manipulations that must be done in the visible document, such as drawing <canvas> elements or conditionally sizing elements with JavaScript.

  5. _destroy() - invoked immediately before a component is destroyed. A place to perform any component-specific clean-up procedures and avoid memory leaks.

Settings

The defaults and options parameters are the two most significant parameters passed into the Nimbly constructor. Together they dictate how the component initializes itself, how the UI should update in response to data changes, what actions to take after data changes occur, what templates the component should use, etc.

defaults is a plain JavaScript object that is the same for each component class that extends Nimbly -- it does not change per instance of the component. options however can change per instance of the component. options is intended to allow users the ability to customize the behavior or display of a component for a particular use case. When options is supplied to the Nimbly constructor function, options simply merges on top of (overwrites) the settings stored in defaults.

The full list of component settings is as follows:

  1. tagName - String, required, if this component is registered as a child of another component, then the tagName defines what custom tag in the template will include this component (e.g., tagName:"hello-world" == <hello-world></hello-world>).

  2. templates - Object, required, a set of name value pairs -- the name being the template name and the value being a string (or template litereal) containing the template (typically a Mustache template).

    For ES5 support (e.g., IE11, no support for template literals) , templates may also be an Array of element IDs pointing to <script> tags on the document whose innerHTML contains the template.

    Example:

    templates:{
    		t4m_tpl_cm_temp_page:`
    			<div data-role="page" data-theme="b" class="t4m-cm-generic-loading-page" location_hash="{{location_hash}}">
    				<div class="t4m-cm-spinner-container">
    						<i class="fa fa-asterisk fa-spin fa-5x fa-fw"></i>
    				</div>
    			</div>
    		`
    		,t4m_tpl_cm_handoff_page_new:`
    			<div>
    				Hello {{first_name}}, this is the home page.
    			</div>
    		`
    }

    ES5 example:

    templates:["t4m_tpl_cm_temp_page","t4m_tpl_cm_handoff_page_new"]
  3. loadingTemplate - String, optional, if this component needs to display a loading message or a loading spinner, specify that template here.

  4. initList - Array, optional, list of "fetch" methods (defined on the component, see below for details) that should be invoked in order to initialize the components.

    Each item of initList is an object containing two properties: method and preventRender. method is a string that refers to which method on the component should be invoked and preventRender is a boolean which when set to true will prevent the component from rendering until the initialization method has resolved (the loading template will be displayed until then).

    Example:

    initList:[
    	{"method":"_fetchUserOrgDetails","preventRender":true},
    	{"method":"_fetchProviderInfo","preventRender":true}
    ]

    The methods defined in the initList (as well as for the dataBindings below) should accept two parameters resolve and reject which should be invoked as appropriate when the method is complete.

  5. uiBindings - Object, optional, key value pairs that define what changes to this.data should trigger what portions of the component to update. The key is a string or regular expression that defines which part of this.data should be observed for changes. The value is either an array or boolean. If set to true, then the whole component will refresh. If set to an array, then the portions of the component that match the CSS selectors in the array will be refreshed. In the example below, a change to this.data.new_issue.show would trigger refresh of <div class="t4m_issuetracker_issue_list_header"></div> and <div class="t4m_issuetracker_issue_list_footer"></div>, but the rest of the component would remain unchanged.

    Example:

    uiBindings:{
    	"/issuelist.*selected/": [".t4m_issuetracker_issue_list_expanded_row"]
    	,"issuelist":true
    	,"new_issue.show": [".t4m_issuetracker_issue_list_header",".t4m_issuetracker_issue_list_footer"]
    }

    Note: uiBindings do not take effect until the component has initialized (i.e., when this.initialized === true). This prevents this.data modifications in the constructor from triggering UI refreshes.

  6. dataBindings - Object, optional, key value pairs that define what data fetch methods should be invoked when specified data changes occur. Follows the same logic as uiBindings above, except that instead of specifying portions of the component to refresh, the value specifies which methods should be invoked and whether or not we should delay refreshing the component until the fetch method returns.

    Example:

    dataBindings:{
    	"categoryId":{"delayRefresh": true,"methods": ["_fetchIssueList", "_fetchStatSummary"]}
    	,"/^filters/":{"delayRefresh": false,"methods": ["_applyFilters"]}
    	,"filters.issue_open_closed":{"delayRefresh": false,"methods": ["_fetchIssueList", "_fetchStatSummary"]}
    	,"/quick_filters/":{"delayRefresh": true,"methods": ["_fetchIssueList", "_fetchStatSummary"]}
    	,"new_issue.save_issue":{"delayRefresh": false,"methods": ["_fetchIssueList", "_fetchStatSummary"]}
    }

    As above with the methods used in the initList, each method that is paired with a dataBinding should accept two parameters resolve and reject which are callback methods that should be invoked as appropriate when the method completes.

    Note: dataBindings do not take effect until the component has initialized (i.e., when this.initialized === true). This prevents this.data modifications in the constructor from triggering data retrievals.

  7. data - Object, required, this is the default data passed into the component. often times this data is just null because it must first be populated by the _fetch* methods defined in the initList above.

    Example:

    data:{
    	"user_name":""
    	,"person_id":3453456
    	,"patient_name":null
    	,"birth_date":null
    	,"admit_date":null
    }
  8. delayInit - Boolean, optional, defaults to true. If true, then we do not fire off the _fetch* methods defined in the initList automatically when the component is initialized -- we would have do it manually at a later time using the this.init() method.

  9. renderjQuery - Boolean, optional, defaults to false. Only set this to true if your component's ._render method and ._renderLoading methods return a jQuery-referenced HTMLElement (i.e., return $("<div>Hello world</div>");).

Usage

Sample Component

Please see the sample folder for examples of components built with Nimbly.

Registering Child Components

If your component renders and inserts other components within itself, then those child components must be explicitly registered. Typically the registration of child components is done directly in the _render method or _init method.

Example:

// instantiate an instance of the issue list component
var issueList = new ITIssueList(this.data);

// register the issue list as a child of this component
this.registerChild(this.issueList);

Registering a child component will enable Nimbly to handle component refreshes without ballooning memory usage and avoid unnecessary child component re-renders.

IE11 Notes

Proxy polyfill restrictions

Nimbly relies on the Observable Slim library to observe changes to this.data and trigger uiBindings and dataBindings. The Observable Slim library in turn relies on ES6 Proxy to perform those observations. ES6 Proxy is supported by Chrome 49+, Edge 12+, Firefox 18+, Opera 36+ and Safari 10+, but it is not supported by IE11.

For IE11, the Proxy polyfill must be used. However, the polyfill does have limitations, notably:

  1. Object properties must be known at creation time. New properties cannot be added later.
  2. Modifications to .length cannot be observed.
  3. Array re-sizing via a .length modification cannot be observed.
  4. Property deletions (e.g., delete proxy.property;) cannot be observed.

This means that if you intend to support IE11, you must:

  1. Declare all properties on this.data at the time of initialization. New properties on this.data cannot be observed.
  2. Do not use uiBindings or dataBindings that rely a property being deleted (e.g., delete this.data.some_property).

Bypassing the Proxy for reads

IE11 performs considerably worse than other modern browsers when iterating through very large nested objects. The performance degradation is only exacerbated with the Proxy polyfill, because Proxy intercepts both modifying object properties and reading object properties.

In order to keep IE11 performing well with very large nested objects, it can be beneficial to bypass the this.data proxy and access the original data object this._data directly. However, take care to do so only when reading values -- all data modifications still must be done through this.data else they will not be observed and will not trigger the appropriate uiBindings or dataBindings.

nimbly's People

Contributors

conneraiken avatar elliotnb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

nimbly's Issues

dataBinding delayRefresh can have unintended side-effect of preventing an update

If a dataBinding has delayRefresh set to true and qualifies on the same change as a uiBinding, then if that dataBinding doesn't trigger its own refresh later, then the initial overlapping uiBinding is just ignored and the UI is not updated.

Maybe force delayRefresh items to always result in a full refresh when they complete.

Improve child component registration, reduce coupling

Today, to register child components, we must do something like this:

this.registerChild(childComponent);

The template of the parent component might then look something like this:

<div>
    <h1>Hello world!</h1>
    <child-component-name></child-component-name>
</div>

The trouble is that child-component-name is defined inside the JS file for the child component -- the tagName. It creates a hard dependency between the two classes. It is possible for the parent component to overwrite that component name by passing in the tagName to the child component constructor -- but that's cumbersome and not very convenient. In practice, the parent component just ends up using whatever the child component defined as their tagName.

If the child component gets refactored at a later date and gets a new tagName, then it would break any other component that uses it.

As a possible solution I think we should remove tagName from the Nimbly component configs and allow components to specify the name of a child component when it's being registered.

So the registerChild method would become something like this:

this.registerChild(childComponent, "component-name");

This will get a bit more complicated for registering child components for repeatable sections, will need to discuss with the team.

Add a warning if a fetch method never resolves or rejects

If a fetch method is improperly coded and does not eventually resolve or reject, then the Promise.all in TXMBase._fetch is never invoked and it can lead to issues with refreshing and this.pendingFetchCount will never equal 0 which means that this.isReady will always return false.

Add support for es6 imports

It would be nice to support CommonJS/ES6 imports for the ObservableSlim and Nimbly libraries for transpiling/minification/bundling purposes.

Boilerplate repo: https://github.com/ConnerAiken/nimbly-boilerplate

The master branch is a working boilerplate.
The es6-import branch is an example of how I would expect the library to be imported using es6 modules.
The es5-import branch is an attempt at using CommonJS (require) for the libraries, but I get the error: MutationObserver is not a constructor

Consider a boolean flag in the config that allows dataBindings to trigger while `preventRender:true` (required) initList methods are running

Since the chained dataBinding events are not part of the initList we would also need to think of a way that the subsequently fired dataBindings would keep the component as uninitialized and prevent the initial render.

We could use the isReady method to determine if the component is doing any processing at all....

Not a clear solution as of yet...

The issue we want to resolve came up with the CMAddTask component where we want _fetchEncounterInfo to trigger other dataBindings that fetch further about the service, handoff page config, etc. All the while, we want to keep the loading page displayed. However, we were running into issues because if we set preventRender to true in the initList fetchEncounterInfo method, then uiBindings and dataBindings are disabled while required initialization steps are running. Thus the subsequent queries to retrieve service info and handoff page info never get triggered.

Enhance dataBindings so that they support any sort of handler function

It would be useful if the dataBindings could invoke more than just _fetch methods. For example, we might want to setup a rule, that if this.data.person_id is modified then this.data.team_id should be set to null. Right now we would accomplish that using somewhat complicated ObservableSlim.create handlers, but it could be integrated with the dataBindings functionality fairly simply.

Throw an error if the loadingTemplate isn't valid

This issue came up with the DWH front-end when the loadingTemplate was set to an empty string. Because the template was an empty string, there was no content for the framework to select and replace with the result of the _render method.

It's difficult (if not impossible) to understand the cause of this issue without in-depth knowledge of the inner workings of the framework.

Therefore, the framework should be modified to throw an error if the loadingTemplate supplied by the component is not valid HTML, an empty string or contains two root-level elements.

We need some way to prevent components from being unregistered if they're not contained in the parent component DOMNode

TXMBase is designed to unregister and delete components if they are found to not be contained within the DOMNode of a parent component after a refresh. TXMBase was built this way in order to prevent memory leaks after successive component refreshes and updates. However, it is possible that we want to display a registered component for the first display, hide it for another display mode and then display it again on a successive refresh into another display mode. We need to figure out a way to only unregister the components that have no possibility of being displayed again.

Add Gulp, Mocha and Travis CI

  • Add Gulp for minification and transpile to ES5.
  • Add Mocha for unit tests.
  • Integrate Travis CI to run unit tests after every commit.

Add whenReady to complement isReady by returning a Promise

Today, we have the isReady method, which returns true when the component (and all child components) are no longer processing any UI updates, data requests, etc. We should add the method whenReady which returns a Promise that resolves whenever the component becomes ready. If the component is already ready at the time whenReady is invoked, then the Promise should immediately resolve.

We've encountered a few different scenarios where it's beneficial to have a listener for a component is completely finished loading, rendering, etc.

@atorres-t4m might be interested in tackling this one.

Document base class usage rules

The framework has some usage rules that aren't likely to be anticipated by new users. These rules should be prominently documented in the README.

  • The _render method should not access nor manipulate this.jqDom -- only the base class should.

  • The _render method should not interact with the DOM at all. It should render and return a fully independent jQuery-referenced DocumentFragment.

  • The _render method must always return the fully rendered component -- it shouldn't only return a partial component under certain circumstances.

  • A component should only have one public render method (i.e., ._render accessed via .render from the base class). One-way data binding will not work for any other public render methods. A component can break up the rendering processing into multiple smaller private render methods, but everything must be invoked through .render.

  • If you instantiate a child component using some or all of this.data from the parent component, then it should be passed along using this._data to avoid creating a proxy of a proxy.

  • You should not register something as a child of a given parent if that child will not be contained within the DOM of the parent.

uiBinding and dataBinding targets should be able to append a wildcard to qualify on changes to any nested children

For example, if we have a filters object like this:

"filters": {
    "issue_product" :"",
    "issue_starred" : "",
    "issue_open_closed" : "OPEN",
    "my_assigned_ind": "0",
    "my_techincal_ind": "0",
    "my_upcoming_ind": "0",
    "overdue_ind": "0",
    "on_fire_ind": "0"
}

It is likely that we would want to re-apply filters anytime one of the filters change. Presently, to do that, we would have to specify a data binding for each individual filter. There is no way to listen for changes on all of them at once.

Produce a more helpful error message and stack trace when a uiBinding or dataBinding method doesn't exist.

We recently had an issue where a bad merge caused a dataBinding to look like this:

person_id:{delay_refresh:true,methods:["_setPatient", ,"_fetchConfig"]} (note the extra commas).

This caused TXMBase to try to invoke an undefined method. It then threw an error correctly, but it wasn't quite clear what the root cause was and the stack trace didn't go back to the original config definition.

We should perform input validation immediately once the constructor is initialized and throw an error that is easy to understand.

Add the ObservableSlim `change` that triggered a `dataBinding` to the arguments of the handler method.`

In addition to the existing resolve and reject arguments for dataBinding handler methods, we should also include the ObservableSlim changes that triggered the dataBinding.

This would help us deal with child components (particularly lists of child components driven off of data stored on the parent component) in a cleaner manner instead of having to manually use ObservableSlim within the component to listen to child component data changes

Improve ease of unregistering components that are registered during the _render method.

This would be very helpful for components that register child components (e.g., list items) during the _render method.

If you don't unregister components that were previously registered during a ._render then a new set of components will be appended and the list will double in length.

To address this, we should:

  • Add a method for unregistering ALL components at once.
  • Add a method for unregistering all components for a particular section.
  • Modify the registerChild method to indicate that the component was registered during the ._render method and that it should be unregistered before the next ._render.
  • OR automatically detect if rendering is in progress, and automatically set any register child components to be automatically removed during the next re-render.

But we have to be mindful of partial re-renders. If a component refreshes and we're only updating part of the UI, then we don't want to de-register a component that is not part of that UI update.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.