React 0.13.1

- all dialogs must be rendered using show() method instead of utils.showDialog
- React.addon.classSet is deprecated, use utils.classNames instead
- component._owner is unavailable now

Change-Id: I290e0053976b27eb43eeb3b4ecfeebaf1088b457
This commit is contained in:
Vitaly Kramskikh 2015-03-11 21:31:37 +07:00
parent 722881fcca
commit 345f5f5422
21 changed files with 715 additions and 671 deletions

View File

@ -3,7 +3,8 @@
"dependencies": {
"jquery": "1.9.1",
"jquery-cookie": "1.4.1",
"react": "0.12.1",
"classnames": "1.1.4",
"react": "0.13.1",
"requirejs": "2.1.15",
"requirejs-plugins": "1.0.3",
"requirejs-text": "2.0.12",

1184
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@
"grunt-jison": "~1.2.1",
"grunt-jscs": "~1.2.0",
"grunt-lintspaces": "~0.6.0",
"grunt-react": "~0.10.0",
"grunt-react": "~0.12.0",
"grunt-text-replace": "~0.3.12",
"jison": "~0.4.13",
"jshint-stylish": "~0.4.0",

View File

@ -32,6 +32,7 @@ define(function() {
underscore: 'js/libs/bower/lodash/js/lodash.compat',
backbone: 'js/libs/bower/backbone/backbone',
'backbone-lodash-monkeypatch': 'js/libs/custom/backbone-lodash-monkeypatch',
classnames: 'js/libs/bower/classnames/index',
react: 'js/libs/bower/react/js/react-with-addons',
JSXTransformer: 'js/libs/bower/react/js/JSXTransformer',
jsx: 'js/libs/bower/jsx-requirejs-plugin/jsx',
@ -72,6 +73,9 @@ define(function() {
deps: ['backbone', 'underscore'],
exports: 'Coccyx'
},
classnames: {
exports: 'classNames'
},
bootstrap: {
deps: ['jquery']
},

View File

@ -20,10 +20,11 @@ define([
'underscore',
'i18n',
'backbone',
'classnames',
'expression',
'expression/objects',
'react'
], function(require, $, _, i18n, Backbone, Expression, expressionObjects, React) {
], function(require, $, _, i18n, Backbone, classNames, Expression, expressionObjects, React) {
'use strict';
var utils = {
@ -55,6 +56,7 @@ define([
composeList: function(value) {
return _.isUndefined(value) ? [] : _.isArray(value) ? value : [value];
},
classNames: classNames,
parseModelPath: function(path, models) {
var modelPath = new expressionObjects.ModelPath(path);
modelPath.setModel(models);
@ -116,14 +118,11 @@ define([
React.unmountComponentAtNode(view._mountNode || view.getDOMNode().parentNode);
}
},
showDialog: function(Dialog, options) {
return utils.universalMount(Dialog, options, $('#modal-container'));
},
showErrorDialog: function(options) {
var dialogs = require('jsx!views/dialogs'); // avoid circular dependencies
options.response = options.response ? utils.getResponseText(options.response) :
options.message || i18n('dialog.error_dialog.warning');
this.showDialog(dialogs.ErrorDialog, options);
dialogs.ErrorDialog.show(options);
},
showBandwidth: function(bandwidth) {
bandwidth = parseInt(bandwidth, 10);

View File

@ -36,8 +36,7 @@ define(
function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins, dialogs, NodesTab, NetworkTab, SettingsTab, LogsTab, ActionsTab, HealthCheckTab, vmWare) {
'use strict';
var ClusterPage, ClusterInfo, DeploymentResult, DeploymentControl,
cs = React.addons.classSet;
var ClusterPage, ClusterInfo, DeploymentResult, DeploymentControl;
ClusterPage = React.createClass({
mixins: [
@ -138,7 +137,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins
var href = $(e.currentTarget).attr('href');
if (Backbone.history.getHash() != href.substr(1) && this.hasChanges()) {
e.preventDefault();
utils.showDialog(dialogs.DiscardSettingsChangesDialog, {
dialogs.DiscardSettingsChangesDialog.show({
verification: this.props.cluster.tasks({group: 'network', status: 'running'}).length,
cb: _.bind(function() {
this.revertChanges();
@ -247,7 +246,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins
<div className='whitebox'>
<ul className='nav nav-tabs cluster-tabs'>
{tabs.map(function(url) {
return <li key={url} className={cs({active: this.props.activeTab == url})}>
return <li key={url} className={utils.classNames({active: this.props.activeTab == url})}>
<a href={'#cluster/' + cluster.id + '/' + url}>
<b className={'tab-' + url + '-normal'} />
<div className='tab-title'>{i18n('cluster_page.tabs.' + url)}</div>
@ -263,7 +262,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins
</ul>
<div className='tab-content'>
{tabs.map(function(url) {
return <div key={url} className={cs({'tab-pane': true, active: this.props.activeTab == url})} id={'tab-' + url}>
return <div key={url} className={utils.classNames({'tab-pane': true, active: this.props.activeTab == url})} id={'tab-' + url}>
{this.props.activeTab == url && tab}
</div>;
}, this)}
@ -339,7 +338,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins
return (
<div className='deployment-result'>
{task &&
<div className={cs(classes)}>
<div className={utils.classNames(classes)}>
<button className='close' onClick={this.dismissTaskResult}>&times;</button>
<h4>{i18n('common.' + (error ? 'error' : 'success'))}</h4>
<span className='enable-selection' dangerouslySetInnerHTML={{__html: utils.urlify(summary)}} />
@ -368,11 +367,11 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, componentMixins
})
],
showDialog: function(Dialog) {
utils.showDialog(Dialog, {cluster: this.props.cluster});
Dialog.show({cluster: this.props.cluster});
},
onDeployRequest: function() {
if (this.props.hasChanges()) {
utils.showDialog(dialogs.DiscardSettingsChangesDialog, {cb: _.bind(function() {
dialogs.DiscardSettingsChangesDialog.show({cb: _.bind(function() {
this.props.revertChanges();
if (this.props.activeTab == 'nodes') app.navigate('cluster/' + this.props.cluster.id + '/nodes', {trigger: true, replace: true});
this.showDialog(dialogs.DeployChangesDialog);

View File

@ -171,7 +171,7 @@ function(_, i18n, React, utils, models, dispatcher, dialogs, componentMixins) {
},
applyAction: function(e) {
e.preventDefault();
utils.showDialog(dialogs.ResetEnvironmentDialog, {cluster: this.props.cluster});
dialogs.ResetEnvironmentDialog.show({cluster: this.props.cluster});
},
render: function() {
var isLocked = this.isLocked();
@ -197,7 +197,7 @@ function(_, i18n, React, utils, models, dispatcher, dialogs, componentMixins) {
var DeleteEnvironmentAction = React.createClass({
applyAction: function(e) {
e.preventDefault();
utils.showDialog(dialogs.RemoveClusterDialog, {cluster: this.props.cluster});
dialogs.RemoveClusterDialog.show({cluster: this.props.cluster});
},
render: function() {
return (
@ -247,7 +247,7 @@ function(_, i18n, React, utils, models, dispatcher, dialogs, componentMixins) {
updateEnvironmentAction: function() {
var cluster = this.props.cluster,
isDowngrade = _.contains(cluster.get('release').get('can_update_from_versions'), this.props.releases.findWhere({id: parseInt(this.state.pendingReleaseId) || cluster.get('release_id')}).get('version'));
utils.showDialog(dialogs.UpdateEnvironmentDialog, {
dialogs.UpdateEnvironmentDialog.show({
cluster: cluster,
action: this.getAction(),
isDowngrade: isDowngrade,
@ -256,10 +256,10 @@ function(_, i18n, React, utils, models, dispatcher, dialogs, componentMixins) {
},
retryUpdateEnvironmentAction: function() {
var cluster = this.props.cluster;
utils.showDialog(dialogs.UpdateEnvironmentDialog, {cluster: cluster, pendingReleaseId: cluster.get('pending_release_id'), action: 'retry'});
dialogs.UpdateEnvironmentDialog.show({cluster: cluster, pendingReleaseId: cluster.get('pending_release_id'), action: 'retry'});
},
rollbackEnvironmentAction: function() {
utils.showDialog(dialogs.UpdateEnvironmentDialog, {cluster: this.props.cluster, action: 'rollback'});
dialogs.UpdateEnvironmentDialog.show({cluster: this.props.cluster, action: 'rollback'});
},
getPendingReleaseId: function() {
var release = _.find(releases.models, this.isAvailableForUpdate, this);

View File

@ -308,7 +308,7 @@ function($, _, i18n, Backbone, React, models, utils, componentMixins, controls)
toggleable={name == 'password'}
description={i18n('cluster_page.healthcheck_tab.' + name + '_description')}
labelClassName='openstack-sub-title'
descriptionClassName={React.addons.classSet({'healthcheck-password': name == 'password'})}
descriptionClassName={utils.classNames({'healthcheck-password': name == 'password'})}
disabled={this.props.disabled}
/>);
}, this)}

View File

@ -29,8 +29,6 @@ define(
function($, _, i18n, Backbone, React, models, dispatcher, utils, componentMixins, controls) {
'use strict';
var cx = React.addons.classSet;
var NetworkModelManipulationMixin = {
setValue: function(attribute, value, options) {
function convertToStringIfNaN(value) {
@ -215,7 +213,7 @@ function($, _, i18n, Backbone, React, models, dispatcher, utils, componentMixins
wrapperClasses[this.props.wrapperClassName] = this.props.wrapperClassName;
return (
<div className={cx(wrapperClasses)}>
<div className={utils.classNames(wrapperClasses)}>
{!this.props.hiddenHeader &&
<div className='range-row-header'>
<div>{i18n(ns + 'range_start')}</div>
@ -420,7 +418,7 @@ function($, _, i18n, Backbone, React, models, dispatcher, utils, componentMixins
},
ns = 'cluster_page.network_tab.';
return (
<div className={cx(classes)}>
<div className={utils.classNames(classes)}>
<h3>{i18n(ns + 'title')}</h3>
{this.state.loading ?
<controls.ProgressBar />

View File

@ -31,8 +31,7 @@ define(
function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, controls, ComponentMixins) {
'use strict';
var cx = React.addons.classSet,
ScreenMixin, EditNodeInterfacesScreen, NodeInterface;
var ScreenMixin, EditNodeInterfacesScreen, NodeInterface;
ScreenMixin = {
goToNodeList: function() {
@ -47,7 +46,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
},
returnToNodeList: function() {
if (this.hasChanges()) {
utils.showDialog(dialogs.DiscardSettingsChangesDialog, {cb: _.bind(this.goToNodeList, this)});
dialogs.DiscardSettingsChangesDialog.show({cb: _.bind(this.goToNodeList, this)});
} else {
this.goToNodeList();
}
@ -348,7 +347,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
return (
<div className='edit-node-networks-screen' style={{display: 'block'}} ref='nodeNetworksScreen'>
<div className={cx({'edit-node-interfaces': true, 'changes-locked': locked})}>
<div className={utils.classNames({'edit-node-interfaces': true, 'changes-locked': locked})}>
<h3>
{i18n(configureInterfacesTransNS + 'title', {count: nodes.length, name: nodeNames.join(', ')})}
</h3>
@ -579,7 +578,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
}
return (
<div className={cx({'physical-network-box': true, nodrag: this.props.errors})}>
<div className={utils.classNames({'physical-network-box': true, nodrag: this.props.errors})}>
<div className='network-box-item'>
{ifc.isBond() &&
<div className='network-box-name'>
@ -638,7 +637,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
<div className='network-connections-block'>
{_.map(slaveInterfaces, function(slaveInterface) {
return <div key={'network-connections-slave-' + slaveInterface.get('name')} className='network-interfaces-status'>
<div className={cx(slaveOnlineClass(slaveInterface))}></div>
<div className={utils.classNames(slaveOnlineClass(slaveInterface))}></div>
<div className='network-interfaces-name'>{slaveInterface.get('name')}</div>
</div>;
})
@ -678,7 +677,7 @@ function($, _, Backbone, React, i18n, utils, models, dispatcher, dialogs, contro
},
vlanRange = network.getVlanRange(networkingParameters);
return <div key={'network-box-' + network.get('id')} className={cx(classes)}>
return <div key={'network-box-' + network.get('id')} className={utils.classNames(classes)}>
{_.map(networkGroup, function(interfaceNetwork) {
return (
<div key={'interface-network-' + interfaceNetwork.get('name')}

View File

@ -33,7 +33,7 @@ function(_, utils, models, dialogs, Screen) {
},
returnToNodeList: function() {
if (this.hasChanges()) {
utils.showDialog(dialogs.DiscardSettingsChangesDialog, {cb: _.bind(this.goToNodeList, this)});
dialogs.DiscardSettingsChangesDialog.show({cb: _.bind(this.goToNodeList, this)});
} else {
this.goToNodeList();
}

View File

@ -29,8 +29,7 @@ define(
],
function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialogs, componentMixins) {
'use strict';
var cx = React.addons.classSet,
NodeListScreen, ManagementPanel, RolePanel, SelectAllMixin, NodeList, NodeGroup, Node;
var NodeListScreen, ManagementPanel, RolePanel, SelectAllMixin, NodeList, NodeGroup, Node;
NodeListScreen = React.createClass({
mixins: [
@ -184,7 +183,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
this.changeScreen(action, true);
},
showDeleteNodesDialog: function() {
utils.showDialog(dialogs.DeleteNodesDialog, {nodes: this.props.nodes, cluster: this.props.cluster});
dialogs.DeleteNodesDialog.show({nodes: this.props.nodes, cluster: this.props.cluster});
},
applyChanges: function() {
this.setState({actionInProgress: true});
@ -278,7 +277,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
] : [
<button
key='disks'
className={cx({'btn btn-configure-disks': true, conflict: disksConflict})}
className={utils.classNames({'btn btn-configure-disks': true, conflict: disksConflict})}
disabled={this.props.locked || !this.props.nodes.length}
onClick={_.bind(this.goToConfigurationScreen, this, 'disks', disksConflict)}
>
@ -288,7 +287,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
!this.props.nodes.any(function(node) {return node.get('status') == 'error';}) &&
<button
key='interfaces'
className={cx({'btn btn-configure-interfaces': true, conflict: interfaceConflict})}
className={utils.classNames({'btn btn-configure-interfaces': true, conflict: interfaceConflict})}
disabled={this.props.locked || !this.props.nodes.length}
onClick={_.bind(this.goToConfigurationScreen, this, 'interfaces', interfaceConflict)}
>
@ -647,7 +646,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
return '#cluster/' + this.props.cluster.id + '/logs/' + utils.serializeTabOptions(options);
},
removeNode: function() {
utils.showDialog(dialogs.RemoveNodeConfirmDialog, {
dialogs.RemoveNodeConfirmDialog.show({
cb: this.removeNodeConfirmed
});
},
@ -672,7 +671,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
},
showNodeDetails: function(e) {
e.preventDefault();
utils.showDialog(dialogs.ShowNodeInfoDialog, {node: this.props.node});
dialogs.ShowNodeInfoDialog.show({node: this.props.node});
},
calculateNodeViewStatus: function() {
var node = this.props.node;
@ -735,8 +734,8 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
var nodeBoxClasses = {'node-box': true, disabled: disabled};
nodeBoxClasses[status] = status;
return (
<div className={cx({node: true, checked: this.props.checked})}>
<label className={cx(nodeBoxClasses)}>
<div className={utils.classNames({node: true, checked: this.props.checked})}>
<label className={utils.classNames(nodeBoxClasses)}>
<controls.Input
type='checkbox'
name={node.id}
@ -745,7 +744,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
onChange={this.props.onNodeSelection}
/>
<div className='node-content'>
<div className={cx(logoClasses)} />
<div className={utils.classNames(logoClasses)} />
<div className='node-name-roles'>
<div className='name enable-selection'>
{this.state.renaming ?
@ -784,10 +783,10 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, dialo
</button>
)}
</div>
<div className={cx(statusClasses)}>
<div className={utils.classNames(statusClasses)}>
<div className='node-status-container'>
{_.contains(['provisioning', 'deploying'], status) &&
<div className={cx({progress: true, 'progress-success': status == 'deploying'})}>
<div className={utils.classNames({progress: true, 'progress-success': status == 'deploying'})}>
<div className='bar' style={{width: _.max([node.get('progress'), 3]) + '%'}} />
</div>
}

View File

@ -151,7 +151,7 @@ function($, _, i18n, React, utils, models, Expression, componentMixins, controls
hasChanges = this.hasChanges(),
allocatedRoles = _.uniq(_.flatten(_.union(cluster.get('nodes').pluck('roles'), cluster.get('nodes').pluck('pending_roles'))));
return (
<div key={this.state.key} className={React.addons.classSet({'openstack-settings wrapper': true, 'changes-locked': locked})}>
<div key={this.state.key} className={utils.classNames({'openstack-settings wrapper': true, 'changes-locked': locked})}>
<h3>{i18n('cluster_page.settings_tab.title')}</h3>
{this.state.loading ?
<controls.ProgressBar />

View File

@ -20,11 +20,10 @@
* Based on https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Input.jsx
**/
define(['jquery', 'underscore', 'react'], function($, _, React) {
define(['jquery', 'underscore', 'react', 'utils'], function($, _, React, utils) {
'use strict';
var controls = {},
cx = React.addons.classSet;
var controls = {};
var tooltipMixin = controls.tooltipMixin = {
componentDidMount: function() {
@ -93,7 +92,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
ref: 'input',
key: 'input',
type: (this.props.toggleable && this.state.visible) ? 'text' : this.props.type,
className: cx(classes),
className: utils.classNames(classes),
// debounced onChange callback is supported for uncontrolled inputs
onChange: this.props.value ? this.onChange : this.debouncedChange
},
@ -102,7 +101,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
isCheckboxOrRadio = this.isCheckboxOrRadio(),
inputWrapperClasses = {'input-wrapper': this.props.type != 'hidden', 'custom-tumbler': isCheckboxOrRadio};
return (
<div key='input-wrapper' className={cx(inputWrapperClasses)}>
<div key='input-wrapper' className={utils.classNames(inputWrapperClasses)}>
{input}
{isCheckboxOrRadio && <span>&nbsp;</span>}
{this.props.extraContent}
@ -128,7 +127,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
labelClasses[this.props.labelClassName] = this.props.labelClassName;
labelWrapperClasses[this.props.labelWrapperClassName] = this.props.labelWrapperClassName;
var labelElement = (
<div className={cx(labelWrapperClasses)}>
<div className={utils.classNames(labelWrapperClasses)}>
<span>{this.props.label}</span>
{this.renderTooltipIcon()}
</div>
@ -136,7 +135,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
labelBefore = (!this.isCheckboxOrRadio() || this.props.labelBeforeControl) ? labelElement : null,
labelAfter = (this.isCheckboxOrRadio() && !this.props.labelBeforeControl) ? labelElement : null;
return this.props.label ? (
<label key='label' className={cx(labelClasses)} htmlFor={this.props.id}>
<label key='label' className={utils.classNames(labelClasses)} htmlFor={this.props.id}>
{labelBefore}
{children}
{labelAfter}
@ -149,7 +148,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
classes = {'parameter-description': true};
classes[this.props.descriptionClassName] = this.props.descriptionClassName;
return error || this.props.description ? (
<div key='description' className={cx(classes)}>
<div key='description' className={utils.classNames(classes)}>
{error ?
this.props.error :
this.props.description.split('\n').map(function(line, index) {
@ -170,7 +169,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
disabled: this.props.disabled
};
classes[this.props.wrapperClassName] = this.props.wrapperClassName;
return (<div className={cx(classes)}>{children}</div>);
return (<div className={utils.classNames(classes)}>{children}</div>);
},
render: function() {
return this.renderWrapper([
@ -198,7 +197,7 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
return (
<div className='radio-group'>
{this.props.label &&
<label className={cx(labelClasses)}>
<label className={utils.classNames(labelClasses)}>
{this.props.label}
{this.renderTooltipIcon()}
<hr />
@ -244,13 +243,13 @@ define(['jquery', 'underscore', 'react'], function($, _, React) {
var tableClasses = {'table table-bordered': true, 'table-striped': !this.props.noStripes};
tableClasses[this.props.tableClassName] = this.props.tableClassName;
return (
<table className={cx(tableClasses)}>
<table className={utils.classNames(tableClasses)}>
<thead>
<tr>
{_.map(this.props.head, function(column, index) {
var classes = {};
classes[column.className] = column.className;
return <th key={index} className={cx(classes)}>{column.label}</th>;
return <th key={index} className={utils.classNames(classes)}>{column.label}</th>;
})}
</tr>
</thead>

View File

@ -19,8 +19,9 @@ define([
'underscore',
'i18n',
'react',
'utils',
'jsx!views/controls'
], function($, _, i18n, React, controls) {
], function($, _, i18n, React, utils, controls) {
'use strict';
var customControls = {};
@ -119,7 +120,7 @@ define([
};
return (
<div className={React.addons.classSet(classes)} key={this.state.key}>
<div className={utils.classNames(classes)} key={this.state.key}>
{this.props.description &&
<div className='custom-description parameter-description'>
{this.props.description}

View File

@ -29,8 +29,7 @@ define(
function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, componentMixins) {
'use strict';
var dialogs = {},
cx = React.addons.classSet;
var dialogs = {};
var dialogMixin = {
propTypes: {
@ -39,18 +38,23 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
modalClass: React.PropTypes.node,
error: React.PropTypes.bool
},
statics: {
show: function(options) {
return utils.universalMount(this, options, $('#modal-container'));
}
},
getInitialState: function() {
return {actionInProgress: false};
},
componentDidMount: function() {
app.on('route', this.close, this);
Backbone.history.on('route', this.close, this);
var $el = $(this.getDOMNode());
$el.on('hidden', this.handleHidden);
$el.on('shown', function() {$el.find('[autofocus]:first').focus();});
$el.modal({background: true, keyboard: true});
},
componentWillUnmount: function() {
app.off(null, null, this);
Backbone.history.off(null, null, this);
$(this.getDOMNode()).off('shown hidden');
},
handleHidden: function() {
@ -71,7 +75,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
var classes = {'modal fade': true};
classes[this.props.modalClass] = this.props.modalClass;
return (
<div className={cx(classes)} tabIndex="-1">
<div className={utils.classNames(classes)} tabIndex="-1">
<div className='modal-header'>
<button type='button' className='close' onClick={this.close}>&times;</button>
<h3>{this.props.title || this.state.title || (this.props.error ? i18n('dialog.error_dialog.title') : '')}</h3>
@ -254,7 +258,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
<div className='display-changes-dialog'>
{(settingsLocked || needsRedeployment || this.state.isInvalid) &&
<div>
<div className={cx(warningClasses)}>
<div className={utils.classNames(warningClasses)}>
<i className='icon-attention' />
<span>{i18n(this.state.ns + (this.state.isInvalid ? 'warnings.no_deployment' :
settingsLocked ? 'locked_settings_alert' : 'redeployment_needed'))}</span>
@ -320,7 +324,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
'alert-warning': !!verificationWarning
};
return (
<div className={cx(classes)}>
<div className={utils.classNames(classes)}>
{verificationWarning || verificationError}
{' ' + i18n(this.state.ns + 'get_more_info') + ' '}
<a href={'#cluster/' + this.props.cluster.id + '/network'}>
@ -338,7 +342,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
return ([
<button key='cancel' className='btn' disabled={this.state.actionInProgress} onClick={this.close}>{i18n('common.cancel_button')}</button>,
<button key='deploy'
className={cx(classes)}
className={utils.classNames(classes)}
disabled={this.state.actionInProgress || this.state.isInvalid}
onClick={this.deployCluster}
>{i18n(this.state.ns + 'deploy')}</button>
@ -464,7 +468,7 @@ function($, _, i18n, Backbone, React, utils, models, dispatcher, controls, compo
},
renderFooter: function() {
var action = this.props.action,
classes = React.addons.classSet({
classes = utils.classNames({
'btn update-environment-btn': true,
'btn-success': action == 'update',
'btn-danger': action != 'update'

View File

@ -31,8 +31,6 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
var components = {};
var cx = React.addons.classSet;
components.Navbar = React.createClass({
mixins: [
componentMixins.dispatcherMixin('updateNodeStats', 'updateNodeStats'),
@ -43,7 +41,7 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
],
showChangePasswordDialog: function(e) {
e.preventDefault();
utils.showDialog(dialogs.ChangePasswordDialog);
dialogs.ChangePasswordDialog.show();
},
togglePopover: function(visible) {
this.setState({popoverVisible: _.isBoolean(visible) ? visible : !this.state.popoverVisible});
@ -74,6 +72,13 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
}
}, this);
},
handleBodyClick: function(e) {
if (_.all([this.refs.popover, this.refs.notifications], function(component) {
return !$(e.target).closest(component.getDOMNode()).length;
})) {
_.defer(_.partial(this.togglePopover, false));
}
},
getDefaultProps: function() {
return {
notificationsDisplayCount: 5,
@ -110,7 +115,7 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
</li>
{_.map(this.props.elements, function(element) {
return <li key={element.label}>
<a className={cx({active: this.props.activeElement == element.url.slice(1)})} href={element.url}>{i18n('navbar.' + element.label, {defaultValue: element.label})}</a>
<a className={utils.classNames({active: this.props.activeElement == element.url.slice(1)})} href={element.url}>{i18n('navbar.' + element.label, {defaultValue: element.label})}</a>
</li>;
}, this)}
<li className='space'></li>
@ -128,6 +133,7 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
notifications={this.props.notifications}
displayCount={this.props.notificationsDisplayCount}
togglePopover={this.togglePopover}
handleBodyClick={this.handleBodyClick}
/>
}
</div>
@ -170,20 +176,15 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
var NotificationsPopover = React.createClass({
mixins: [componentMixins.backboneMixin('notifications')],
getDefaultProps: function() {
return {
eventNamespace: 'click.click-notifications'
};
},
showNodeInfo: function(id) {
var node = new models.Node({id: id});
node.fetch();
utils.showDialog(dialogs.ShowNodeInfoDialog, {node: node});
},
toggle: function(visible) {
this.props.togglePopover(visible);
},
handleBodyClick: function(e) {
if (_.all([this.getDOMNode(), this._owner.refs.notifications.getDOMNode()], function(el) {
return !$(e.target).closest(el).length;
})) {
_.defer(_.bind(this.toggle, this, false));
}
dialogs.ShowNodeInfoDialog.show({node: node});
},
markAsRead: function() {
var notificationsToMark = new models.Notifications(this.props.notifications.where({status: 'unread'}));
@ -200,13 +201,12 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
},
componentDidMount: function() {
this.markAsRead();
this.eventNamespace = 'click.click-notifications';
$('html').on(this.eventNamespace, _.bind(this.handleBodyClick, this));
Backbone.history.on('route', this.toggle, this);
$('html').on(this.props.eventNamespace, this.props.handleBodyClick);
Backbone.history.on('route', _.partial(this.props.togglePopover, false), this);
},
componentWillUnmount: function() {
$('html').off(this.eventNamespace);
Backbone.history.off('route', this.toggle, this);
$('html').off(this.props.eventNamespace);
Backbone.history.off('route', this.props.togglePopover, this);
},
getInitialState: function() {
return {unreadNotificationsIds: []};
@ -224,7 +224,7 @@ function($, _, i18n, i18next, Backbone, React, utils, models, componentMixins, d
return [
<li
key={'notification' + notification.id}
className={cx({'enable-selection': true, new: unread, clickable: nodeId}) + ' ' + notification.get('topic')}
className={utils.classNames({'enable-selection': true, new: unread, clickable: nodeId}) + ' ' + notification.get('topic')}
onClick={nodeId && _.bind(this.showNodeInfo, this, nodeId)}
>
<i className={{error: 'icon-attention', warning: 'icon-attention', discover: 'icon-bell'}[notification.get('topic')] || 'icon-info-circled'}></i>

View File

@ -59,7 +59,7 @@ function(i18n, React, utils, models, dialogs, componentMixins) {
showNodeInfo: function(id) {
var node = new models.Node({id: id});
node.fetch();
utils.showDialog(dialogs.ShowNodeInfoDialog, {node: node});
dialogs.ShowNodeInfoDialog.show({node: node});
},
markAsRead: function() {
var notification = this.props.notification;

View File

@ -199,7 +199,7 @@ define([
return (
<div>
<div className='statistics-text-box'>
<div className={React.addons.classSet({notice: isMirantisIso})}>{this.getText(ns + 'help_to_improve')}</div>
<div className={utils.classNames({notice: isMirantisIso})}>{this.getText(ns + 'help_to_improve')}</div>
<button className="btn-link" onClick={this.toggleItemsList}>{i18n(ns + 'learn_whats_collected')}</button>
{this.state.showItems &&
<div className='statistics-disclaimer-box'>

View File

@ -141,14 +141,14 @@ function($, _, i18n, React, utils, models, dialogs, componentMixins, statisticsM
return _.contains(app.version.get('feature_groups'), 'mirantis') && !_.contains(app.version.get('feature_groups'), 'techpreview');
},
showRegistrationDialog: function() {
utils.showDialog(dialogs.RegistrationDialog, {
dialogs.RegistrationDialog.show({
registrationForm: new models.MirantisRegistrationForm(),
setConnected: this.props.setConnected,
settings: this.props.settings
});
},
showRetrievePasswordDialog: function() {
utils.showDialog(dialogs.RetrievePasswordDialog);
dialogs.RetrievePasswordDialog.show();
},
render: function() {
var settings = this.props.settings,

View File

@ -21,14 +21,13 @@ define(
'i18n',
'underscore',
'dispatcher',
'utils',
'jsx!views/controls',
'jsx!component_mixins',
'plugins/vmware/vmware_models'
], function(React, $, i18n, _, dispatcher, controls, componentMixins, vmwareModels) {
], function(React, $, i18n, _, dispatcher, utils, controls, componentMixins, vmwareModels) {
'use strict';
var cx = React.addons.classSet;
var Field = React.createClass({
mixins: [controls.tooltipMixin],
onChange: function(name, value) {
@ -41,7 +40,7 @@ define(
options = this.props.options || [],
errors = this.props.model.validationError,
errorText = errors ? errors[metadata.name] : null;
var classes = cx({
var classes = utils.classNames({
'settings-group table-wrapper parameter-box': true,
password: metadata.type == 'password',
'has-error': !!errorText
@ -54,7 +53,7 @@ define(
value={this.props.model.get(metadata.name)}
checked={this.props.model.get(metadata.name)}
description={errorText || metadata.description}
descriptionClassName={cx({'validation-error': errorText})}
descriptionClassName={utils.classNames({'validation-error': errorText})}
toggleable={metadata.type == 'password'}
wrapperClassName='tablerow-wrapper'
onChange={this.onChange}
@ -116,7 +115,7 @@ define(
if (!model) {
return null;
}
var removeButtonClasses = cx({'btn btn-link': true, hide: this.props.isRemovable});
var removeButtonClasses = utils.classNames({'btn btn-link': true, hide: this.props.isRemovable});
return (
<div className='nova-compute'>
<h4>