Moving event to use Angular event's system

This commit is contained in:
Chris Cowan 2014-08-01 15:16:20 -07:00
parent 42f444118d
commit 96d3d2926c
19 changed files with 284 additions and 185 deletions

View file

@ -40,7 +40,7 @@ define(function (require) {
}
});
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, AppState, timefilter) {
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, appStateFactory, timefilter) {
return {
controller: function ($scope, $route, $routeParams, $location, configFile) {
var notify = new Notifier({
@ -56,7 +56,7 @@ define(function (require) {
query: ''
};
var $state = $scope.state = new AppState(stateDefaults);
var $state = $scope.state = appStateFactory.create(stateDefaults);
$scope.configTemplate = new ConfigTemplate({
save: require('text!apps/dashboard/partials/save_dashboard.html'),

View file

@ -49,7 +49,7 @@ define(function (require) {
app.controller('discover', function ($scope, config, courier, $route, $window, savedSearches, savedVisualizations,
Notifier, $location, globalState, AppState, timefilter, AdhocVis, Promise, Private) {
Notifier, $location, globalState, appStateFactory, timefilter, AdhocVis, Promise, Private) {
var segmentedFetch = $scope.segmentedFetch = Private(require('apps/discover/_segmented_fetch'));
var HitSortFn = Private(require('apps/discover/_hit_sort_fn'));
@ -93,7 +93,7 @@ define(function (require) {
'year'
];
var $state = $scope.state = new AppState(stateDefaults);
var $state = $scope.state = new appStateFactory.create(stateDefaults);
if (!_.contains(indexPatternList, $state.index)) {
var reason = 'The index specified in the URL is not a configured pattern. ';

View file

@ -12,9 +12,9 @@ define(function (require) {
.directive('kbnSettingsObjects', function (config, Notifier, Private) {
return {
restrict: 'E',
controller: function ($scope, $injector, $q, AppState) {
controller: function ($scope, $injector, $q, appStateFactory) {
var $state = $scope.state = new AppState();
var $state = $scope.state = new appStateFactory.create();
var resetCheckBoxes = function () {
$scope.deleteAll = false;

View file

@ -47,7 +47,7 @@ define(function (require) {
});
app.controller('VisualizeEditor', function ($scope, $route, $timeout, $window, Notifier, $location,
globalState, AppState, timefilter, Private) {
globalState, appStateFactory, timefilter, Private) {
var aggs = Private(require('apps/visualize/saved_visualizations/_aggs'));
var notify = new Notifier({
@ -63,7 +63,7 @@ define(function (require) {
$scope.fields = _.sortBy(indexPattern.fields, 'name');
$scope.fields.byName = indexPattern.fieldsByName;
var $state = $scope.state = new AppState(vis.getState());
var $state = $scope.state = new appStateFactory.create(vis.getState());
if ($state.query) {
vis.searchSource.set('query', $state.query);

View file

@ -1,84 +0,0 @@
define(function (require) {
var _ = require('lodash');
var rison = require('utils/rison');
return function ModelProvider() {
function Model(attributes) {
// Set the attributes or default to an empty object
this._listners = {};
_.assign(this, attributes);
}
/**
* Returns the attirbutes for the model
* @returns {object}
*/
Model.prototype.toObject = function () {
// return just the data.
return _.omit(this, function (value, key) {
return key.charAt(0) === '_' || _.isFunction(value);
});
};
/**
* Serialize the model to RISON
* @returns {string}
*/
Model.prototype.toRISON = function () {
return rison.encode(this.toObject());
};
/**
* Serialize the model to JSON
* @returns {object}
*/
Model.prototype.toJSON = function () {
return this.toObject();
};
/**
* Adds a listner
* @param {string} name The name of the event
* @returns {void}
*/
Model.prototype.on = function (name, listner) {
if (!_.isArray(this._listners[name])) {
this._listners[name] = [];
}
this._listners[name].push(listner);
};
/**
* Removes listener... if listner is empty then it removes all the events
* @param {string} name The name of the event
* @param {function} [listner]
* @returns {void}
*/
Model.prototype.off = function (name, listner) {
if (!listner) return delete this._listners[name];
this._listners = _.filter(this._listners[name], function (fn) {
return fn !== listner;
});
};
/**
* Emits and event
* @param {string} name The name of the event
* @param {mixed} [args...] Arguments pass to the listners
* @returns {void}
*/
Model.prototype.emit = function () {
var args = Array.prototype.slice.call(arguments);
var name = args.shift();
if (this._listners[name]) {
_.each(this._listners[name], function (fn) {
fn.apply(null, args);
});
}
};
return Model;
};
});

View file

@ -0,0 +1,43 @@
define(function (require) {
var _ = require('lodash');
var rison = require('utils/rison');
return function BaseObjectProvider() {
function BaseObject(attributes) {
// Set the attributes or default to an empty object
_.assign(this, attributes);
}
/**
* Returns the attirbutes for the objct
* @returns {object}
*/
BaseObject.prototype.toObject = function () {
// return just the data.
return _.omit(this, function (value, key) {
return key.charAt(0) === '$' || key.charAt(0) === '_' || _.isFunction(value);
});
};
/**
* Serialize the model to RISON
* @returns {string}
*/
BaseObject.prototype.toRISON = function () {
return rison.encode(this.toObject());
};
/**
* Serialize the model to JSON
* @returns {object}
*/
BaseObject.prototype.toJSON = function () {
return this.toObject();
};
return BaseObject;
};
});

View file

@ -0,0 +1,31 @@
define(function (require) {
var _ = require('lodash');
return function IsolatedScopeProvider($rootScope) {
var Scope = $rootScope.constructor;
_.inherits(IsolatedScope, Scope);
function IsolatedScope() {
IsolatedScope.Super.call(this);
// Prepare the new scope and attach it to the rootScope's linked list
this.$root = $rootScope.$root;
// ensure that there is just one async queue per $rootScope and its children
this.$$asyncQueue = $rootScope.$$asyncQueue;
this.$$postDigestQueue = $rootScope.$$postDigestQueue;
this['this'] = this;
this.$parent = $rootScope;
this.$$prevSibling = $rootScope.$$childTail;
if ($rootScope.$$childHead) {
$rootScope.$$childTail.$$nextSibling = this;
$rootScope.$$childTail = this;
} else {
$rootScope.$$childHead = $rootScope.$$childTail = this;
}
}
return IsolatedScope;
};
});

View file

@ -1,16 +1,15 @@
define(function (require) {
var _ = require('lodash');
var module = require('modules').get('kibana/factories');
module.factory('AppState', function (Private) {
return function AppStateProvider(Private) {
var State = Private(require('components/state_management/state'));
_.inherits(AppState, State);
function AppState(defaults) {
AppState.Super.call(this, '_a', defaults);
}
_.inherits(AppState, State);
return AppState;
});
};
});

View file

@ -0,0 +1,41 @@
define(function (require) {
var _ = require('lodash');
var module = require('modules').get('kibana/factories');
module.service('appStateFactory', function (Private) {
var AppState = Private(require('components/state_management/app_state'));
/**
* We need to keep reference to the current so we can destroy it. When
* we create a new one. The needs to happen because we need to reset the
* listner queue and remove the refercne to the RootScope.
*/
var currentAppState;
var appStateFactory = {
/**
* Factory method for creating AppStates
* @param {string} appName The app name
* @param {object} defaults The the defaults for the AppState
* @returns {object} AppState
*/
create: function (defaults) {
// Check to see if the state already exists. If it does then we need
// to call the destory method.
if (!_.isUndefined(currentAppState)) {
currentAppState.$destroy();
}
currentAppState = new AppState(defaults);
return currentAppState;
}
};
return appStateFactory;
});
});

View file

@ -9,10 +9,10 @@ define(function (require) {
module.service('globalState', function (Private, $rootScope) {
var State = Private(require('components/state_management/state'));
_.inherits(GlobalState, State);
function GlobalState(defaults) {
GlobalState.Super.call(this, '_g', defaults);
}
_.inherits(GlobalState, State);
GlobalState.prototype.writeToUrl = function (url) {
return qs.replaceParamInUrl(url, this._urlParam, this.toRISON());

View file

@ -5,8 +5,10 @@ define(function (require) {
var applyDiff = require('utils/diff_object');
return function StateProvider(Private, $rootScope, $location) {
var Model = Private(require('components/state_management/_base_model'));
var BaseObject = Private(require('components/state_management/_base_object'));
var IsolatedScope = Private(require('components/state_management/_isolated_scope'));
_.inherits(State, IsolatedScope);
function State(urlParam, defaults) {
State.Super.call(this);
this._defaults = defaults || {};
@ -18,8 +20,9 @@ define(function (require) {
// Initialize the State with fetch
this.fetch();
}
_.inherits(State, Model);
// // mixin the base object methods
_.mixin(State.prototype, BaseObject.prototype);
/**
* Fetches the state from the url
@ -33,7 +36,7 @@ define(function (require) {
// it will change state in place.
var diffResults = applyDiff(this, stash);
if (diffResults.keys.length) {
this.emit('fetch_with_changes', diffResults.keys);
this.$emit('fetch_with_changes', diffResults.keys);
}
};
@ -50,7 +53,7 @@ define(function (require) {
// it will change stash in place.
var diffResults = applyDiff(stash, state);
if (diffResults.keys.length) {
this.emit('save_with_changes', diffResults.keys);
this.$emit('save_with_changes', diffResults.keys);
}
search[this._urlParam] = this.toRISON();
$location.search(search);
@ -73,12 +76,18 @@ define(function (require) {
* @returns {void}
*/
State.prototype.onUpdate = function (cb) {
this.on('fetch_with_changes', cb);
this.$on('fetch_with_changes', function () {
// onUpdate interface for the listner expects the first argument to
// be the start of the arguments passed to the event emitter. So we need
// to lop off the first argument and pass the rest.
var args = Array.prototype.slice.call(arguments);
args.shift();
cb.apply(null, args);
});
};
return State;
};
});

View file

@ -9,6 +9,7 @@ define(function (require) {
require('components/config/config');
require('components/courier/courier');
require('components/notify/notify');
require('components/state_management/app_state_factory');
require('directives/info');
require('directives/spinner');
require('directives/paginate');

View file

@ -14,6 +14,7 @@ define(function (require) {
var filterPrivateAndMethods = function (obj) {
return function (key) {
if (_.isFunction(obj[key])) return false;
if (key.charAt(0) === '$') return false;
return key.charAt(0) !== '_';
};
};

View file

@ -74,9 +74,10 @@
'specs/utils/versionmath',
'specs/utils/routes/index',
'specs/courier/search_source/_get_normalized_sort',
'specs/state_management/_base_model',
'specs/state_management/_base_object',
'specs/state_management/state',
'specs/utils/diff_object'
'specs/utils/diff_object',
'specs/state_management/_isolated_scope'
], function (kibana, sinon) {
kibana.load(function () {
var xhr = sinon.useFakeXMLHttpRequest();

View file

@ -1,72 +0,0 @@
define(function (require) {
var angular = require('angular');
var mocks = require('angular-mocks');
var _ = require('lodash');
var sinon = require('sinon/sinon');
require('services/private');
// Load kibana
require('index');
describe('State Management', function () {
describe('Model', function () {
var $rootScope;
var Model;
beforeEach(function () {
module('kibana');
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
Model = Private(require('components/state_management/_base_model'));
});
});
it('should take an inital set of values', function () {
var model = new Model({ message: 'test' });
expect(model).to.have.property('message', 'test');
});
it('should trigger $on events', function (done) {
var model = new Model();
var stub = sinon.stub();
model.on('test', stub);
model.emit('test');
sinon.assert.calledOnce(stub);
done();
});
it('should be extendable ($on/$emit)', function (done) {
function MyModel() {
MyModel.Super.call(this);
}
_.inherits(MyModel, Model);
var model = new MyModel();
var stub = sinon.stub();
model.on('test', stub);
model.emit('test');
sinon.assert.calledOnce(stub);
done();
});
it('should serialize _attributes to RISON', function () {
var model = new Model();
model.message = 'Testing... 1234';
var rison = model.toRISON();
expect(rison).to.equal('(message:\'Testing... 1234\')');
});
it('should serialize _attributes for JSON', function () {
var model = new Model();
model.message = 'Testing... 1234';
var json = JSON.stringify(model);
expect(json).to.equal('{"message":"Testing... 1234"}');
});
});
});
});

View file

@ -0,0 +1,49 @@
define(function (require) {
var angular = require('angular');
var mocks = require('angular-mocks');
var _ = require('lodash');
var sinon = require('sinon/sinon');
require('services/private');
// Load kibana
require('index');
describe('State Management', function () {
describe('BaseObject', function () {
var $rootScope;
var BaseObject;
beforeEach(function () {
module('kibana');
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
BaseObject = Private(require('components/state_management/_base_object'));
});
});
it('should take an inital set of values', function () {
var baseObject = new BaseObject({ message: 'test' });
expect(baseObject).to.have.property('message', 'test');
});
it('should serialize _attributes to RISON', function () {
var baseObject = new BaseObject();
baseObject.message = 'Testing... 1234';
var rison = baseObject.toRISON();
expect(rison).to.equal('(message:\'Testing... 1234\')');
});
it('should serialize _attributes for JSON', function () {
var baseObject = new BaseObject();
baseObject.message = 'Testing... 1234';
baseObject._private = 'foo';
baseObject.$private = 'stuff';
var json = JSON.stringify(baseObject);
expect(json).to.equal('{"message":"Testing... 1234"}');
});
});
});
});

View file

@ -0,0 +1,62 @@
define(function (require) {
var angular = require('angular');
var mocks = require('angular-mocks');
var _ = require('lodash');
var sinon = require('sinon/sinon');
require('services/private');
// Load kibana
require('index');
describe('State Management', function () {
describe('IsolatedScope', function () {
var $rootScope;
var IsolatedScope;
beforeEach(function () {
module('kibana');
inject(function (_$rootScope_, Private) {
$rootScope = _$rootScope_;
IsolatedScope = Private(require('components/state_management/_isolated_scope'));
});
});
it('should handle $on events', function () {
var scope = new IsolatedScope();
var stub = sinon.stub();
scope.$on('test', stub);
scope.$emit('test', 'Hello World');
sinon.assert.calledOnce(stub);
});
it('should handle $watch events', function () {
var scope = new IsolatedScope();
var stub = sinon.stub();
scope.$watch('test', stub);
scope.test = 'Hello World';
$rootScope.$apply();
sinon.assert.calledOnce(stub);
});
it('should work with inherited objects', function () {
_.inherits(MyScope, IsolatedScope);
function MyScope() {
MyScope.Super.call(this);
}
var scope = new MyScope();
var eventStub = sinon.stub();
var watchStub = sinon.stub();
scope.$on('test', eventStub);
scope.$emit('test', 'Hello World');
scope.$watch('test', watchStub);
scope.test = 'Hello World';
$rootScope.$apply();
sinon.assert.calledOnce(eventStub);
sinon.assert.calledOnce(watchStub);
});
});
});
});

View file

@ -11,7 +11,7 @@ define(function (require) {
describe('State Management', function () {
describe('State', function () {
var $rootScope, $location, State, Model;
var $rootScope, $location, State, IsolatedScope;
beforeEach(function () {
module('kibana');
@ -20,13 +20,13 @@ define(function (require) {
$location = _$location_;
$rootScope = _$rootScope_;
State = Private(require('components/state_management/state'));
Model = Private(require('components/state_management/_base_model'));
IsolatedScope = Private(require('components/state_management/_isolated_scope'));
});
});
it('should inherit from Model', function () {
it('should inherit from IsolatedScope', function () {
var state = new State();
expect(state).to.be.an(Model);
expect(state).to.be.an(IsolatedScope);
});
it('should save to $location.search()', function () {
@ -39,13 +39,14 @@ define(function (require) {
it('should emit an event if changes are saved', function (done) {
var state = new State();
state.on('save_with_changes', function (keys) {
state.$on('save_with_changes', function (event, keys) {
expect(keys).to.eql(['test']);
done();
});
state.test = 'foo';
state.save();
var search = $location.search();
$rootScope.$apply();
});
it('should fetch the state from $location.search()', function () {
@ -57,7 +58,7 @@ define(function (require) {
it('should emit an event if changes are fetched', function (done) {
var state = new State();
state.on('fetch_with_changes', function (keys) {
state.$on('fetch_with_changes', function (events, keys) {
expect(keys).to.eql(['foo']);
done();
});
@ -66,6 +67,16 @@ define(function (require) {
expect(state).to.have.property('foo', 'bar');
});
it('should have events that attach to scope', function (done) {
var state = new State();
state.$on('test', function (event, message) {
expect(message).to.equal('foo');
done();
});
state.$emit('test', 'foo');
$rootScope.$apply();
});
it('should fire listeners for #onUpdate() on #fetch()', function (done) {
var state = new State();
state.onUpdate(function (keys) {
@ -102,9 +113,9 @@ define(function (require) {
it('should call fetch when $routeUpdate is fired on $rootScope', function () {
var state = new State();
var stub = sinon.spy(state, 'fetch');
var spy = sinon.spy(state, 'fetch');
$rootScope.$emit('$routeUpdate', 'test');
sinon.assert.calledOnce(stub);
sinon.assert.calledOnce(spy);
});
});

View file

@ -50,5 +50,12 @@ define(function (require) {
expect(target).to.not.have.property('_private');
});
it('should ignore dollar signs', function () {
var target = { foo: 'bar', test: 'foo' };
var source = { foo: 'test', $private: 'foo' };
diff(target, source);
expect(target).to.not.have.property('$private');
});
});
});