Skip to content

Commit

Permalink
Loading states api (#93)
Browse files Browse the repository at this point in the history
* Pass loading prop down to components instead of rendering div

* First pass at providing 'loading' prop to dash components

* Add componentName and propName props to loading object, cleanup

* Cleanup

* Updated core components tarball

* Change prop names for loading state to be more Pythonic

* Use latest dcc that fixes Input reliant tests

* Left-over from rebasing

* Update everything to it's latest version expect html-components

* Add tarball back in

* Change props.status to props.loading_state to be more specific

* Pass loading prop down to components instead of rendering div

* First pass at providing 'loading' prop to dash components

* Add componentName and propName props to loading object, cleanup

* Cleanup

* Updated core components tarball

* Change prop names for loading state to be more Pythonic

* Rebuild bundles

* Fixed simple.py regression

* Replace dcc tarball with rc1 version

* Try with new componentWillRecieveProps changes in dcc tarball

* Revert everything in dev-requirements except dcc tarball

* Revert back to old dev-requirements.txt completely

* Refactored requestQueue map into forEach

* Add tarball back in dev-requirements

* Revert back to old dev-requirements

* Fix tests by checking if controllerId is null

* Remove newlines from NotifyObservers parameters

* Version bump to 0.16.0

* Change const to let

* Update version to 0.16.0rc1

* Revert dcc upgrade in deps

* Calculate loading_state in TreeContainer so children have access to it too

* Add confirm dialog test from DCC

* Remove dcc test again

* Cleanup

* Only rerender TreeContainer if props are new

* Release 0.18.0rc3 with more dcc test fixes

* Remove loading prop from APIcontroller causing unneccesary re-renders

* Bump rc version

* Update package.json as well

* Reset simple.py

* Refactored recursivelyRender call in TreeContainer

* Fix formatting
  • Loading branch information
valentijnnieman committed Feb 28, 2019
1 parent 7065325 commit b3362dc
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 54 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [UNRELEASED]
### Added
- Loading states API [#267](https://github.com/plotly/dash/issues/267)
## [0.19.0] - 2019-02-25
## Added
- Added which properties fired to update prop request. [#124](https://github.com/plotly/dash-renderer/pull/124)
Expand Down
116 changes: 90 additions & 26 deletions dash_renderer/dash_renderer.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -33092,11 +33092,9 @@ Object.defineProperty(exports, "__esModule", {
value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _ramda = __webpack_require__(/*! ramda */ "./node_modules/ramda/index.js");
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

var _ramda2 = _interopRequireDefault(_ramda);
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _react = __webpack_require__(/*! react */ "react");

Expand All @@ -33114,6 +33112,12 @@ var _NotifyObservers = __webpack_require__(/*! ./components/core/NotifyObservers

var _NotifyObservers2 = _interopRequireDefault(_NotifyObservers);

var _reactRedux = __webpack_require__(/*! react-redux */ "./node_modules/react-redux/lib/index.js");

var _ramda = __webpack_require__(/*! ramda */ "./node_modules/ramda/index.js");

var _constants = __webpack_require__(/*! ./constants/constants */ "./src/constants/constants.js");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
Expand Down Expand Up @@ -33141,68 +33145,120 @@ var TreeContainer = function (_Component) {
}, {
key: 'render',
value: function render() {
return _render(this.props.layout);
return recursivelyRender(this.props.layout, this.props.requestQueue);
}
}]);

return TreeContainer;
}(_react.Component);

exports.default = TreeContainer;


TreeContainer.propTypes = {
layout: _propTypes2.default.object
layout: _propTypes2.default.object,
requestQueue: _propTypes2.default.object
};

function _render(component) {
if (_ramda2.default.contains(_ramda2.default.type(component), ['String', 'Number', 'Null', 'Boolean'])) {
function recursivelyRender(component, requestQueue) {
if ((0, _ramda.contains)((0, _ramda.type)(component), ['String', 'Number', 'Null', 'Boolean'])) {
return component;
}

if ((0, _ramda.isEmpty)(component)) {
return null;
}

// Create list of child elements
var children = void 0;

var componentProps = _ramda2.default.propOr({}, 'props', component);
var componentProps = (0, _ramda.propOr)({}, 'props', component);

if (!_ramda2.default.has('props', component) || !_ramda2.default.has('children', component.props) || typeof component.props.children === 'undefined') {
if (!(0, _ramda.has)('props', component) || !(0, _ramda.has)('children', component.props) || typeof component.props.children === 'undefined') {
// No children
children = [];
} else if (_ramda2.default.contains(_ramda2.default.type(component.props.children), ['String', 'Number', 'Null', 'Boolean'])) {
} else if ((0, _ramda.contains)((0, _ramda.type)(component.props.children), ['String', 'Number', 'Null', 'Boolean'])) {
children = [component.props.children];
} else {
// One or multiple objects
// Recursively render the tree
// TODO - I think we should pass in `key` here.
children = (Array.isArray(componentProps.children) ? componentProps.children : [componentProps.children]).map(_render);
children = (Array.isArray(componentProps.children) ? componentProps.children : [componentProps.children]).map(function (child) {
return recursivelyRender(child, requestQueue);
});
}

if (!component.type) {
/* eslint-disable no-console */
console.error(_ramda2.default.type(component), component);
console.error((0, _ramda.type)(component), component);
/* eslint-enable no-console */
throw new Error('component.type is undefined');
}
if (!component.namespace) {
/* eslint-disable no-console */
console.error(_ramda2.default.type(component), component);
console.error((0, _ramda.type)(component), component);
/* eslint-enable no-console */
throw new Error('component.namespace is undefined');
}
var element = _registry2.default.resolve(component.type, component.namespace);

var parent = _react2.default.createElement.apply(_react2.default, [element, _ramda2.default.omit(['children'], component.props)].concat(_toConsumableArray(children)));
var parent = _react2.default.createElement.apply(_react2.default, [element, (0, _ramda.omit)(['children'], component.props)].concat(_toConsumableArray(children)));

// loading prop coming from TreeContainer
var isLoading = false;
var loadingProp = void 0;
var loadingComponent = void 0;

var id = componentProps.id;

if (requestQueue && requestQueue.filter) {
(0, _ramda.forEach)(function (r) {
var controllerId = (0, _ramda.isNil)(r.controllerId) ? '' : r.controllerId;
if (r.status === 'loading' && (0, _ramda.contains)(id, controllerId)) {
isLoading = true;

var _r$controllerId$split = r.controllerId.split('.');

var _r$controllerId$split2 = _slicedToArray(_r$controllerId$split, 2);

loadingComponent = _r$controllerId$split2[0];
loadingProp = _r$controllerId$split2[1];
}
}, requestQueue);

var thisRequest = requestQueue.filter(function (r) {
var controllerId = (0, _ramda.isNil)(r.controllerId) ? '' : r.controllerId;
return (0, _ramda.contains)(id, controllerId);
});
if (thisRequest.status === _constants.STATUS.OK) {
isLoading = false;
}
}

// Set loading state
var loading_state = {
is_loading: isLoading,
prop_name: loadingProp,
component_name: loadingComponent
};

return _react2.default.createElement(
_NotifyObservers2.default,
{ key: componentProps.id, id: componentProps.id },
{
key: componentProps.id,
id: componentProps.id,
loading_state: loading_state
},
parent
);
}

_render.propTypes = {
children: _propTypes2.default.object
};
function mapStateToProps(state, ownProps) {
return {
layout: ownProps.layout,
loading: ownProps.loading,
requestQueue: state.requestQueue
};
}

exports.default = (0, _reactRedux.connect)(mapStateToProps)(TreeContainer);

/***/ }),

Expand Down Expand Up @@ -34182,8 +34238,6 @@ Object.defineProperty(exports, "__esModule", {

var _reactRedux = __webpack_require__(/*! react-redux */ "./node_modules/react-redux/lib/index.js");

var _ramda = __webpack_require__(/*! ramda */ "./node_modules/ramda/index.js");

var _actions = __webpack_require__(/*! ../../actions */ "./src/actions/index.js");

var _react = __webpack_require__(/*! react */ "react");
Expand All @@ -34194,6 +34248,8 @@ var _propTypes = __webpack_require__(/*! prop-types */ "./node_modules/prop-type

var _propTypes2 = _interopRequireDefault(_propTypes);

var _ramda = __webpack_require__(/*! ramda */ "./node_modules/ramda/index.js");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/*
Expand All @@ -34220,6 +34276,8 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
children: ownProps.children,
dependencies: stateProps.dependencies,
paths: stateProps.paths,
loading_state: ownProps.loading_state,
requestQueue: stateProps.requestQueue,

setProps: function setProps(newProps) {
var payload = {
Expand All @@ -34242,7 +34300,8 @@ function NotifyObserversComponent(_ref) {
id = _ref.id,
paths = _ref.paths,
dependencies = _ref.dependencies,
setProps = _ref.setProps;
setProps = _ref.setProps,
loading_state = _ref.loading_state;

var thisComponentSharesState = dependencies && dependencies.find(function (dependency) {
return dependency.inputs.find(function (input) {
Expand Down Expand Up @@ -34275,6 +34334,10 @@ function NotifyObserversComponent(_ref) {
extraProps.setProps = setProps;
}

if (children.props && !children.props.loading_state) {
extraProps.loading_state = loading_state;
}

if (!(0, _ramda.isEmpty)(extraProps)) {
return _react2.default.cloneElement(children, extraProps);
}
Expand All @@ -34284,7 +34347,8 @@ function NotifyObserversComponent(_ref) {
NotifyObserversComponent.propTypes = {
id: _propTypes2.default.string.isRequired,
children: _propTypes2.default.node.isRequired,
path: _propTypes2.default.array.isRequired
path: _propTypes2.default.array.isRequired,
loading_state: _propTypes2.default.object
};

exports.default = (0, _reactRedux.connect)(mapStateToProps, mapDispatchToProps, mergeProps)(NotifyObserversComponent);
Expand Down
2 changes: 1 addition & 1 deletion dash_renderer/dash_renderer.dev.js.map

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions dash_renderer/dash_renderer.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dash_renderer/dash_renderer.min.js.map

Large diffs are not rendered by default.

Binary file added dash_renderer/favicon.ico
Binary file not shown.
98 changes: 79 additions & 19 deletions src/TreeContainer.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,60 @@
'use strict';

import R from 'ramda';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import Registry from './registry';
import NotifyObservers from './components/core/NotifyObservers.react';
import {connect} from 'react-redux';
import {
isNil,
omit,
contains,
isEmpty,
forEach,
propOr,
type,
has,
} from 'ramda';
import {STATUS} from './constants/constants';

export default class TreeContainer extends Component {
class TreeContainer extends Component {
shouldComponentUpdate(nextProps) {
return nextProps.layout !== this.props.layout;
}

render() {
return render(this.props.layout);
return recursivelyRender(this.props.layout, this.props.requestQueue);
}
}

TreeContainer.propTypes = {
layout: PropTypes.object,
requestQueue: PropTypes.object,
};

function render(component) {
if (
R.contains(R.type(component), ['String', 'Number', 'Null', 'Boolean'])
) {
function recursivelyRender(component, requestQueue) {
if (contains(type(component), ['String', 'Number', 'Null', 'Boolean'])) {
return component;
}

if (isEmpty(component)) {
return null;
}

// Create list of child elements
let children;

const componentProps = R.propOr({}, 'props', component);
const componentProps = propOr({}, 'props', component);

if (
!R.has('props', component) ||
!R.has('children', component.props) ||
!has('props', component) ||
!has('children', component.props) ||
typeof component.props.children === 'undefined'
) {
// No children
children = [];
} else if (
R.contains(R.type(component.props.children), [
contains(type(component.props.children), [
'String',
'Number',
'Null',
Expand All @@ -55,32 +69,78 @@ function render(component) {
children = (Array.isArray(componentProps.children)
? componentProps.children
: [componentProps.children]
).map(render);
).map(child => recursivelyRender(child, requestQueue));
}

if (!component.type) {
/* eslint-disable no-console */
console.error(R.type(component), component);
console.error(type(component), component);
/* eslint-enable no-console */
throw new Error('component.type is undefined');
}
if (!component.namespace) {
/* eslint-disable no-console */
console.error(R.type(component), component);
console.error(type(component), component);
/* eslint-enable no-console */
throw new Error('component.namespace is undefined');
}
const element = Registry.resolve(component.type, component.namespace);

const parent = React.createElement(
element,
R.omit(['children'], component.props),
omit(['children'], component.props),
...children
);

return <NotifyObservers key={componentProps.id} id={componentProps.id}>{parent}</NotifyObservers>;
// loading prop coming from TreeContainer
let isLoading = false;
let loadingProp;
let loadingComponent;

const id = componentProps.id;

if (requestQueue && requestQueue.filter) {
forEach(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
if (r.status === 'loading' && contains(id, controllerId)) {
isLoading = true;
[loadingComponent, loadingProp] = r.controllerId.split('.');
}
}, requestQueue);

const thisRequest = requestQueue.filter(r => {
const controllerId = isNil(r.controllerId) ? '' : r.controllerId;
return contains(id, controllerId);
});
if (thisRequest.status === STATUS.OK) {
isLoading = false;
}
}

// Set loading state
const loading_state = {
is_loading: isLoading,
prop_name: loadingProp,
component_name: loadingComponent,
};

return (
<NotifyObservers
key={componentProps.id}
id={componentProps.id}
loading_state={loading_state}
>
{parent}
</NotifyObservers>
);
}

function mapStateToProps(state, ownProps) {
return {
layout: ownProps.layout,
loading: ownProps.loading,
requestQueue: state.requestQueue,
};
}

render.propTypes = {
children: PropTypes.object,
};
export default connect(mapStateToProps)(TreeContainer);
Loading

0 comments on commit b3362dc

Please sign in to comment.