mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Merge pull request #8148 from BigFunger/appstatus_port
Port of appStatus from 4.x branch
This commit is contained in:
commit
0b472a384e
8 changed files with 428 additions and 20 deletions
|
@ -1,4 +1,4 @@
|
|||
<div dashboard-app class="app-container dashboard-container">
|
||||
<dashboard-app class="app-container dashboard-container">
|
||||
<kbn-top-nav name="dashboard" config="topNavMenu">
|
||||
<div class="kibana-nav-info">
|
||||
<span ng-show="dash.id" class="kibana-nav-info-title">
|
||||
|
@ -46,4 +46,4 @@
|
|||
</div>
|
||||
|
||||
<dashboard-grid></dashboard-grid>
|
||||
</div>
|
||||
</dashboard-app>
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'plugins/kibana/dashboard/services/saved_dashboards';
|
|||
import 'plugins/kibana/dashboard/styles/main.less';
|
||||
import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter';
|
||||
import DocTitleProvider from 'ui/doc_title';
|
||||
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import uiModules from 'ui/modules';
|
||||
import indexTemplate from 'plugins/kibana/dashboard/index.html';
|
||||
|
@ -54,6 +55,8 @@ uiRoutes
|
|||
|
||||
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'dashboardApp',
|
||||
controller: function ($scope, $rootScope, $route, $routeParams, $location, Private, getAppState) {
|
||||
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
|
@ -92,8 +95,10 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
|||
filters: _.reject(dash.searchSource.getOwn('filter'), matchQueryFilter),
|
||||
};
|
||||
|
||||
let stateMonitor;
|
||||
const $state = $scope.state = new AppState(stateDefaults);
|
||||
const $uiState = $scope.uiState = $state.makeStateful('uiState');
|
||||
const $appStatus = $scope.appStatus = this.appStatus = {};
|
||||
|
||||
$scope.$watchCollection('state.options', function (newVal, oldVal) {
|
||||
if (!angular.equals(newVal, oldVal)) $state.save();
|
||||
|
@ -143,6 +148,14 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
|||
}
|
||||
|
||||
initPanelIndices();
|
||||
|
||||
// watch for state changes and update the appStatus.dirty value
|
||||
stateMonitor = stateMonitorFactory.create($state, stateDefaults);
|
||||
stateMonitor.onChange((status) => {
|
||||
$appStatus.dirty = status.dirty;
|
||||
});
|
||||
$scope.$on('$destroy', () => stateMonitor.destroy());
|
||||
|
||||
$scope.$emit('application.load');
|
||||
}
|
||||
|
||||
|
@ -216,6 +229,7 @@ app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter,
|
|||
|
||||
dash.save()
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
if (id) {
|
||||
notify.info('Saved Dashboard as "' + dash.title + '"');
|
||||
|
|
|
@ -22,6 +22,7 @@ import PluginsKibanaDiscoverHitSortFnProvider from 'plugins/kibana/discover/_hit
|
|||
import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter';
|
||||
import FilterManagerProvider from 'ui/filter_manager';
|
||||
import AggTypesBucketsIntervalOptionsProvider from 'ui/agg_types/buckets/_interval_options';
|
||||
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import uiModules from 'ui/modules';
|
||||
import indexTemplate from 'plugins/kibana/discover/index.html';
|
||||
|
@ -79,7 +80,15 @@ uiRoutes
|
|||
}
|
||||
});
|
||||
|
||||
app.controller('discover', function ($scope, config, courier, $route, $window, Notifier,
|
||||
app.directive('discoverApp', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'discoverApp',
|
||||
controller: discoverController
|
||||
};
|
||||
});
|
||||
|
||||
function discoverController($scope, config, courier, $route, $window, Notifier,
|
||||
AppState, timefilter, Promise, Private, kbnUrl, highlightTags) {
|
||||
|
||||
const Vis = Private(VisProvider);
|
||||
|
@ -136,6 +145,8 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
|||
docTitle.change(savedSearch.title);
|
||||
}
|
||||
|
||||
let stateMonitor;
|
||||
const $appStatus = $scope.appStatus = this.appStatus = {};
|
||||
const $state = $scope.state = new AppState(getStateDefaults());
|
||||
$scope.uiState = $state.makeStateful('uiState');
|
||||
|
||||
|
@ -178,6 +189,12 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
|||
$scope.failuresShown = showTotal;
|
||||
};
|
||||
|
||||
stateMonitor = stateMonitorFactory.create($state, getStateDefaults());
|
||||
stateMonitor.onChange((status) => {
|
||||
$appStatus.dirty = status.dirty;
|
||||
});
|
||||
$scope.$on('$destroy', () => stateMonitor.destroy());
|
||||
|
||||
$scope.updateDataSource()
|
||||
.then(function () {
|
||||
$scope.$listen(timefilter, 'fetch', function () {
|
||||
|
@ -303,6 +320,7 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
|||
|
||||
return savedSearch.save()
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
|
@ -571,4 +589,4 @@ app.controller('discover', function ($scope, config, courier, $route, $window, N
|
|||
}
|
||||
|
||||
init();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div ng-controller="discover" class="app-container">
|
||||
<discover-app class="app-container">
|
||||
<kbn-top-nav name="discover" config="topNavMenu">
|
||||
<div class="kibana-nav-info">
|
||||
<span ng-show="opts.savedSearch.id" class="kibana-nav-info-title">
|
||||
|
@ -126,4 +126,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</discover-app>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div ng-controller="VisEditor" class="app-container vis-editor vis-type-{{ vis.type.name }}">
|
||||
<visualize-app class="app-container vis-editor vis-type-{{ vis.type.name }}">
|
||||
|
||||
<kbn-top-nav name="visualize" config="topNavMenu">
|
||||
<div class="vis-editor-info">
|
||||
|
@ -91,5 +91,4 @@
|
|||
</visualize>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</visualize-app>
|
||||
|
|
|
@ -12,6 +12,7 @@ import DocTitleProvider from 'ui/doc_title';
|
|||
import UtilsBrushEventProvider from 'ui/utils/brush_event';
|
||||
import FilterBarQueryFilterProvider from 'ui/filter_bar/query_filter';
|
||||
import FilterBarFilterBarClickHandlerProvider from 'ui/filter_bar/filter_bar_click_handler';
|
||||
import stateMonitorFactory from 'ui/state_management/state_monitor_factory';
|
||||
import uiRoutes from 'ui/routes';
|
||||
import uiModules from 'ui/modules';
|
||||
import editorTemplate from 'plugins/kibana/visualize/editor/editor.html';
|
||||
|
@ -55,8 +56,15 @@ uiModules
|
|||
'kibana/notify',
|
||||
'kibana/courier'
|
||||
])
|
||||
.controller('VisEditor', function ($scope, $route, timefilter, AppState, $location, kbnUrl, $timeout, courier, Private, Promise) {
|
||||
.directive('visualizeApp', function () {
|
||||
return {
|
||||
restrict: 'E',
|
||||
controllerAs: 'visualizeApp',
|
||||
controller: VisEditor,
|
||||
};
|
||||
});
|
||||
|
||||
function VisEditor($scope, $route, timefilter, AppState, $location, kbnUrl, $timeout, courier, Private, Promise) {
|
||||
const docTitle = Private(DocTitleProvider);
|
||||
const brushEvent = Private(UtilsBrushEventProvider);
|
||||
const queryFilter = Private(FilterBarQueryFilterProvider);
|
||||
|
@ -66,6 +74,9 @@ uiModules
|
|||
location: 'Visualization Editor'
|
||||
});
|
||||
|
||||
let stateMonitor;
|
||||
const $appStatus = this.appStatus = {};
|
||||
|
||||
const savedVis = $route.current.locals.savedVis;
|
||||
|
||||
const vis = savedVis.vis;
|
||||
|
@ -104,16 +115,16 @@ uiModules
|
|||
docTitle.change(savedVis.title);
|
||||
}
|
||||
|
||||
let $state = $scope.$state = (function initState() {
|
||||
const savedVisState = vis.getState();
|
||||
const stateDefaults = {
|
||||
uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
|
||||
linked: !!savedVis.savedSearchId,
|
||||
query: searchSource.getOwn('query') || {query_string: {query: '*'}},
|
||||
filters: searchSource.getOwn('filter') || [],
|
||||
vis: savedVisState
|
||||
};
|
||||
const savedVisState = vis.getState();
|
||||
const stateDefaults = {
|
||||
uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {},
|
||||
linked: !!savedVis.savedSearchId,
|
||||
query: searchSource.getOwn('query') || {query_string: {query: '*'}},
|
||||
filters: searchSource.getOwn('filter') || [],
|
||||
vis: savedVisState
|
||||
};
|
||||
|
||||
let $state = $scope.$state = (function initState() {
|
||||
$state = new AppState(stateDefaults);
|
||||
|
||||
if (!angular.equals($state.vis, savedVisState)) {
|
||||
|
@ -138,10 +149,18 @@ uiModules
|
|||
$scope.editableVis = editableVis;
|
||||
$scope.state = $state;
|
||||
$scope.uiState = $state.makeStateful('uiState');
|
||||
$scope.appStatus = $appStatus;
|
||||
|
||||
vis.setUiState($scope.uiState);
|
||||
$scope.timefilter = timefilter;
|
||||
$scope.opts = _.pick($scope, 'doSave', 'savedVis', 'shareData', 'timefilter');
|
||||
|
||||
stateMonitor = stateMonitorFactory.create($state, stateDefaults);
|
||||
stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => {
|
||||
$appStatus.dirty = status.dirty;
|
||||
});
|
||||
$scope.$on('$destroy', () => stateMonitor.destroy());
|
||||
|
||||
editableVis.listeners.click = vis.listeners.click = filterBarClickHandler($state);
|
||||
editableVis.listeners.brush = vis.listeners.brush = brushEvent;
|
||||
|
||||
|
@ -248,6 +267,7 @@ uiModules
|
|||
|
||||
savedVis.save()
|
||||
.then(function (id) {
|
||||
stateMonitor.setInitialState($state.toJSON());
|
||||
$scope.kbnTopNav.close('save');
|
||||
|
||||
if (id) {
|
||||
|
@ -316,4 +336,4 @@ uiModules
|
|||
}
|
||||
|
||||
init();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import stateMonitor from 'ui/state_management/state_monitor_factory';
|
||||
import SimpleEmitter from 'ui/utils/simple_emitter';
|
||||
|
||||
describe('stateMonitorFactory', function () {
|
||||
const noop = () => {};
|
||||
const eventTypes = [
|
||||
'save_with_changes',
|
||||
'reset_with_changes',
|
||||
'fetch_with_changes',
|
||||
];
|
||||
|
||||
let mockState;
|
||||
let stateJSON;
|
||||
|
||||
function setState(mockState, obj, emit = true) {
|
||||
mockState.toJSON = () => cloneDeep(obj);
|
||||
stateJSON = cloneDeep(obj);
|
||||
if (emit) mockState.emit(eventTypes[0]);
|
||||
}
|
||||
|
||||
function createMockState(state = {}) {
|
||||
const mockState = new SimpleEmitter();
|
||||
setState(mockState, state, false);
|
||||
return mockState;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockState = createMockState({});
|
||||
});
|
||||
|
||||
it('should have a create method', function () {
|
||||
expect(stateMonitor).to.have.property('create');
|
||||
expect(stateMonitor.create).to.be.a('function');
|
||||
});
|
||||
|
||||
describe('factory creation', function () {
|
||||
it('should not call onChange with only the state', function () {
|
||||
const monitor = stateMonitor.create(mockState);
|
||||
const changeStub = sinon.stub();
|
||||
monitor.onChange(changeStub);
|
||||
sinon.assert.notCalled(changeStub);
|
||||
});
|
||||
|
||||
it('should not call onChange with matching defaultState', function () {
|
||||
const monitor = stateMonitor.create(mockState, {});
|
||||
const changeStub = sinon.stub();
|
||||
monitor.onChange(changeStub);
|
||||
sinon.assert.notCalled(changeStub);
|
||||
});
|
||||
|
||||
it('should call onChange with differing defaultState', function () {
|
||||
const monitor = stateMonitor.create(mockState, { test: true });
|
||||
const changeStub = sinon.stub();
|
||||
monitor.onChange(changeStub);
|
||||
sinon.assert.calledOnce(changeStub);
|
||||
});
|
||||
});
|
||||
|
||||
describe('instance', function () {
|
||||
let monitor;
|
||||
|
||||
beforeEach(() => {
|
||||
monitor = stateMonitor.create(mockState);
|
||||
});
|
||||
|
||||
describe('onChange', function () {
|
||||
it('should throw if not given a handler function', function () {
|
||||
const fn = () => monitor.onChange('not a function');
|
||||
expect(fn).to.throwException(/must be a function/);
|
||||
});
|
||||
|
||||
eventTypes.forEach((eventType) => {
|
||||
describe(`when ${eventType} is emitted`, function () {
|
||||
let handlerFn;
|
||||
|
||||
beforeEach(() => {
|
||||
handlerFn = sinon.stub();
|
||||
monitor.onChange(handlerFn);
|
||||
sinon.assert.notCalled(handlerFn);
|
||||
});
|
||||
|
||||
it('should get called', function () {
|
||||
mockState.emit(eventType);
|
||||
sinon.assert.calledOnce(handlerFn);
|
||||
});
|
||||
|
||||
it('should be given the state status', function () {
|
||||
mockState.emit(eventType);
|
||||
const args = handlerFn.firstCall.args;
|
||||
expect(args[0]).to.be.an('object');
|
||||
});
|
||||
|
||||
it('should be given the event type', function () {
|
||||
mockState.emit(eventType);
|
||||
const args = handlerFn.firstCall.args;
|
||||
expect(args[1]).to.equal(eventType);
|
||||
});
|
||||
|
||||
it('should be given the changed keys', function () {
|
||||
const keys = ['one', 'two', 'three'];
|
||||
mockState.emit(eventType, keys);
|
||||
const args = handlerFn.firstCall.args;
|
||||
expect(args[2]).to.equal(keys);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ignoreProps', function () {
|
||||
it('should not set status to dirty when ignored properties change', function () {
|
||||
let status;
|
||||
const mockState = createMockState({ messages: { world: 'hello', foo: 'bar' } });
|
||||
const monitor = stateMonitor.create(mockState);
|
||||
const changeStub = sinon.stub();
|
||||
monitor.ignoreProps('messages.world');
|
||||
monitor.onChange(changeStub);
|
||||
sinon.assert.notCalled(changeStub);
|
||||
|
||||
// update the ignored state prop
|
||||
setState(mockState, { messages: { world: 'howdy', foo: 'bar' } });
|
||||
sinon.assert.calledOnce(changeStub);
|
||||
status = changeStub.firstCall.args[0];
|
||||
expect(status).to.have.property('clean', true);
|
||||
expect(status).to.have.property('dirty', false);
|
||||
|
||||
// update a prop that is not ignored
|
||||
setState(mockState, { messages: { world: 'howdy', foo: 'baz' } });
|
||||
sinon.assert.calledTwice(changeStub);
|
||||
status = changeStub.secondCall.args[0];
|
||||
expect(status).to.have.property('clean', false);
|
||||
expect(status).to.have.property('dirty', true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setInitialState', function () {
|
||||
let changeStub;
|
||||
|
||||
beforeEach(() => {
|
||||
changeStub = sinon.stub();
|
||||
monitor.onChange(changeStub);
|
||||
sinon.assert.notCalled(changeStub);
|
||||
});
|
||||
|
||||
it('should throw if no state is provided', function () {
|
||||
const fn = () => monitor.setInitialState();
|
||||
expect(fn).to.throwException(/must be an object/);
|
||||
});
|
||||
|
||||
it('should throw if given the wrong type', function () {
|
||||
const fn = () => monitor.setInitialState([]);
|
||||
expect(fn).to.throwException(/must be an object/);
|
||||
});
|
||||
|
||||
it('should trigger the onChange handler', function () {
|
||||
monitor.setInitialState({ new: 'state' });
|
||||
sinon.assert.calledOnce(changeStub);
|
||||
});
|
||||
|
||||
it('should change the status with differing state', function () {
|
||||
monitor.setInitialState({ new: 'state' });
|
||||
sinon.assert.calledOnce(changeStub);
|
||||
|
||||
const status = changeStub.firstCall.args[0];
|
||||
expect(status).to.have.property('clean', false);
|
||||
expect(status).to.have.property('dirty', true);
|
||||
});
|
||||
|
||||
it('should not trigger the onChange handler without state change', function () {
|
||||
monitor.setInitialState(cloneDeep(mockState.toJSON()));
|
||||
sinon.assert.notCalled(changeStub);
|
||||
});
|
||||
});
|
||||
|
||||
describe('status object', function () {
|
||||
let handlerFn;
|
||||
|
||||
beforeEach(() => {
|
||||
handlerFn = sinon.stub();
|
||||
monitor.onChange(handlerFn);
|
||||
});
|
||||
|
||||
it('should be clean by default', function () {
|
||||
mockState.emit(eventTypes[0]);
|
||||
const status = handlerFn.firstCall.args[0];
|
||||
expect(status).to.have.property('clean', true);
|
||||
expect(status).to.have.property('dirty', false);
|
||||
});
|
||||
|
||||
it('should be dirty when state changes', function () {
|
||||
setState(mockState, { message: 'i am dirty now' });
|
||||
const status = handlerFn.firstCall.args[0];
|
||||
expect(status).to.have.property('clean', false);
|
||||
expect(status).to.have.property('dirty', true);
|
||||
});
|
||||
|
||||
it('should be clean when state is reset', function () {
|
||||
const defaultState = { message: 'i am the original state' };
|
||||
const handlerFn = sinon.stub();
|
||||
|
||||
let status;
|
||||
|
||||
// initial state and monitor setup
|
||||
const mockState = createMockState(defaultState);
|
||||
const monitor = stateMonitor.create(mockState);
|
||||
monitor.onChange(handlerFn);
|
||||
sinon.assert.notCalled(handlerFn);
|
||||
|
||||
// change the state and emit an event
|
||||
setState(mockState, { message: 'i am dirty now' });
|
||||
sinon.assert.calledOnce(handlerFn);
|
||||
status = handlerFn.firstCall.args[0];
|
||||
expect(status).to.have.property('clean', false);
|
||||
expect(status).to.have.property('dirty', true);
|
||||
|
||||
// reset the state and emit an event
|
||||
setState(mockState, defaultState);
|
||||
sinon.assert.calledTwice(handlerFn);
|
||||
status = handlerFn.secondCall.args[0];
|
||||
expect(status).to.have.property('clean', true);
|
||||
expect(status).to.have.property('dirty', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('destroy', function () {
|
||||
let stateSpy;
|
||||
let cleanMethod;
|
||||
|
||||
beforeEach(() => {
|
||||
stateSpy = sinon.spy(mockState, 'off');
|
||||
sinon.assert.notCalled(stateSpy);
|
||||
});
|
||||
|
||||
it('should remove the listeners', function () {
|
||||
monitor.onChange(noop);
|
||||
monitor.destroy();
|
||||
sinon.assert.callCount(stateSpy, eventTypes.length);
|
||||
eventTypes.forEach((eventType) => {
|
||||
sinon.assert.calledWith(stateSpy, eventType);
|
||||
});
|
||||
});
|
||||
|
||||
it('should stop the instance from being used any more', function () {
|
||||
monitor.onChange(noop);
|
||||
monitor.destroy();
|
||||
const fn = () => monitor.onChange(noop);
|
||||
expect(fn).to.throwException(/has been destroyed/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
104
src/ui/public/state_management/state_monitor_factory.js
Normal file
104
src/ui/public/state_management/state_monitor_factory.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
import { cloneDeep, isEqual, set, isPlainObject } from 'lodash';
|
||||
|
||||
export default {
|
||||
create: (state, customInitialState) => stateMonitor(state, customInitialState)
|
||||
};
|
||||
|
||||
function stateMonitor(state, customInitialState) {
|
||||
let destroyed = false;
|
||||
let ignoredProps = [];
|
||||
let changeHandlers = [];
|
||||
let initialState;
|
||||
|
||||
setInitialState(customInitialState);
|
||||
|
||||
function setInitialState(customInitialState) {
|
||||
// state.toJSON returns a reference, clone so we can mutate it safely
|
||||
initialState = cloneDeep(customInitialState) || cloneDeep(state.toJSON());
|
||||
}
|
||||
|
||||
function removeIgnoredProps(state) {
|
||||
ignoredProps.forEach(path => {
|
||||
set(state, path, true);
|
||||
});
|
||||
return state;
|
||||
}
|
||||
|
||||
function getStatus() {
|
||||
// state.toJSON returns a reference, clone so we can mutate it safely
|
||||
const currentState = removeIgnoredProps(cloneDeep(state.toJSON()));
|
||||
const isClean = isEqual(currentState, initialState);
|
||||
|
||||
return {
|
||||
clean: isClean,
|
||||
dirty: !isClean,
|
||||
};
|
||||
}
|
||||
|
||||
function dispatchChange(type = null, keys = []) {
|
||||
const status = getStatus();
|
||||
changeHandlers.forEach(changeHandler => {
|
||||
changeHandler(status, type, keys);
|
||||
});
|
||||
}
|
||||
|
||||
function dispatchFetch(keys) {
|
||||
dispatchChange('fetch_with_changes', keys);
|
||||
};
|
||||
|
||||
function dispatchSave(keys) {
|
||||
dispatchChange('save_with_changes', keys);
|
||||
};
|
||||
|
||||
function dispatchReset(keys) {
|
||||
dispatchChange('reset_with_changes', keys);
|
||||
};
|
||||
|
||||
return {
|
||||
setInitialState(customInitialState) {
|
||||
if (!isPlainObject(customInitialState)) throw new TypeError('The default state must be an object');
|
||||
|
||||
// check the current status
|
||||
const previousStatus = getStatus();
|
||||
|
||||
// update the initialState and apply ignoredProps
|
||||
setInitialState(customInitialState);
|
||||
removeIgnoredProps(initialState);
|
||||
|
||||
// fire the change handler if the status has changed
|
||||
if (!isEqual(previousStatus, getStatus())) dispatchChange();
|
||||
},
|
||||
|
||||
ignoreProps(props) {
|
||||
ignoredProps = ignoredProps.concat(props);
|
||||
removeIgnoredProps(initialState);
|
||||
return this;
|
||||
},
|
||||
|
||||
onChange(callback) {
|
||||
if (destroyed) throw new Error('Monitor has been destroyed');
|
||||
if (typeof callback !== 'function') throw new Error('onChange handler must be a function');
|
||||
|
||||
changeHandlers.push(callback);
|
||||
|
||||
// Listen for state events.
|
||||
state.on('fetch_with_changes', dispatchFetch);
|
||||
state.on('save_with_changes', dispatchSave);
|
||||
state.on('reset_with_changes', dispatchReset);
|
||||
|
||||
// if the state is already dirty, fire the change handler immediately
|
||||
const status = getStatus();
|
||||
if (status.dirty) dispatchChange();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
destroy() {
|
||||
destroyed = true;
|
||||
changeHandlers = undefined;
|
||||
state.off('fetch_with_changes', dispatchFetch);
|
||||
state.off('save_with_changes', dispatchSave);
|
||||
state.off('reset_with_changes', dispatchReset);
|
||||
}
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue