Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add defineClass to addons #443

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.DS_STORE
node_modules
*~
*.swp
*.pyc
static
.grunt
Expand Down
4 changes: 3 additions & 1 deletion src/ReactWithAddons.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@
var LinkedStateMixin = require('LinkedStateMixin');
var React = require('React');
var ReactTransitionGroup = require('ReactTransitionGroup');
var defineClass = require('defineClass');

var cx = require('cx');

React.addons = {
classSet: cx,
LinkedStateMixin: LinkedStateMixin,
TransitionGroup: ReactTransitionGroup
TransitionGroup: ReactTransitionGroup,
defineClass: defineClass
};

module.exports = React;
Expand Down
94 changes: 94 additions & 0 deletions src/addons/defineClass/Advice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule Advice
*/

'use strict';

var Advice = {
/**
* Creates a function which invokes the callback before the base function.
* The callback is invoked with the same context and arguments as the wrapper
* function. The return value is the same as it would have been otherwise.
*
* @param {function} base Function to be wrapped
* @param {function} callback Function to be invoked before base
* @return {function} Wrapped function
*/
before: function wrappedBefore(base, callback) {
return function() {
callback.apply(this, arguments);
return base.apply(this, arguments);
};
},

/**
* Creates a function which invokes the callback after the base function.
* The callback is invoked with the same context and arguments as the wrapper
* function. The return value is the same as it would have been otherwise.
*
* @param {function} base Function to be wrapped
* @param {function} callback Function to be invoked after base
* @return {function} Wrapped function
*/
after: function(base, callback) {
return function wrappedAfter() {
var result = base.apply(this, arguments);
callback.apply(this, arguments);
return result;
};
},

/**
* Creates a function which invokes the callback with the base function
* unshifted onto its arguments. The remaining arguments and context are
* callback are preserved, but the return value is replaced with the return
* value of the callback.
*
* @param {function} base Function to be wrapped
* @param {function} callback Function to be invoked with base as first arg
* @return {function} Wrapped function
*/
around: function(base, callback) {
return function wrappedAround() {
var args = [];
args.push(base.bind(this));
for (var i = 0, l = arguments.length; i < l; i++) {
args.push(arguments[i]);
}
return callback.apply(this, args);
};
},

/**
* Creates a function which takes a predicate, evaluates the predicate with
* the same arguments and context as the base, and turns the function into a
* no-op if the predicate evaluates to false.
*
* @param {function} base Function to be wrapped
* @param {function(): boolean} predicate A predicate function
* @return {function} Wrapped function
*/
filter: function(base, predicate) {
return function wrappedFiltered() {
if (predicate.apply(this, arguments)) {
return base.apply(this, arguments);
}
};
}
};

module.exports = Advice;
155 changes: 155 additions & 0 deletions src/addons/defineClass/AdviceDefinition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule AdviceDefinition
*/

'use strict';

// For more information on some of the terminology/variable names used in this
// file, see the Wikipedia page on aspect-oriented programming.
// http://en.wikipedia.org/wiki/Aspect-oriented_programming

var Advice = require('Advice');
var ReactCompositeComponent = require('ReactCompositeComponent');
var SpecPolicy = ReactCompositeComponent.SpecPolicy;
var ReactCompositeComponentInterface = ReactCompositeComponent.Interface;

var invariant = require('invariant');
var mergeMethod = require('mergeMethod');

/**
* These are shims to simulate pointcuts on ReactCompositeComponent which
* correspond to its lifecycle. @see ReactCompositeComponent#LifeCycle.
* Refactoring ReactCompositeComponent to actually use these methods names is
* left as an exercise for the reader.
* I apologize for the repetition. I apologize.
*/
var RESERVED_ADVICE_KEYS = {
mount: function(joinPoint, callback) {
invariant(
joinPoint === 'before' ||
joinPoint === 'after',
'AdviceDefinition: The %s method is not supported for "mount"',
joinPoint
);
switch (joinPoint) {
case 'before':
mergeMethod(this, 'componentWillMount', callback);
break;
case 'after':
mergeMethod(this, 'componentDidMount', callback);
break;
default:
break;
}
},

receiveProps: function(joinPoint, callback) {
invariant(
joinPoint === 'before',
'AdviceDefinition: The %s method is not supported for "receiveProps"',
joinPoint
);
switch (joinPoint) {
case 'before':
mergeMethod(this, 'componentWillReceiveProps', callback);
break;
default:
break;
}
},

update: function(joinPoint, callback) {
invariant(
joinPoint === 'before' ||
joinPoint === 'after' ||
joinPoint === 'filter',
'AdviceDefinition: The %s method is not supported for "update"',
joinPoint
);
switch (joinPoint) {
case 'before':
mergeMethod(this, 'componentWillUpdate', callback);
break;
case 'after':
mergeMethod(this, 'componentDidUpdate', callback);
break;
case 'filter':
mergeMethod(this, 'shouldComponentUpdate', callback);
break;
default:
break;
}
},

unmount: function(joinPoint, callback) {
invariant(
joinPoint === 'before',
'AdviceDefinition: The % method is not supported for "unmount"',
joinPoint
);
switch (joinPoint) {
case 'before':
mergeMethod(this, 'componentWillUnmount', callback);
default:
break;
}
}
};

function validateAdvice(spec, joinPoint, methodName) {
invariant(
typeof spec[methodName] === 'function',
'AdviceDefinition: You are attempting to use advice methods on %s, when ' +
'its type is %s. Advice methods can only work on spec properties which' +
'are functions.',
methodName, typeof spec[methodName]
);

// We should not use around and filter with DEFINE_ONCE methods because they
// might modify the method in unacceptable ways (e.g. modify the return value,
// turn the method into a no-op).
var specPolicy = ReactCompositeComponentInterface[methodName];
if (specPolicy === SpecPolicy.DEFINE_ONCE) {
invariant(
joinPoint === 'before' ||
joinPoint === 'after',
'AdviceDefinition: You may not use the %s method to override %s. ' +
'Use the before or after methods instead.',
joinPoint,
methodName
);
}
}

function wrapMethod(joinPoint, methodName, callback) {
if (RESERVED_ADVICE_KEYS.hasOwnProperty(methodName)) {
RESERVED_ADVICE_KEYS[methodName].call(this, joinPoint, callback);
} else {
validateAdvice(this, joinPoint, methodName);
this[methodName] = Advice[joinPoint](this[methodName], callback);
}
}

function AdviceDefinition() {
'before after around filter'.split(' ').forEach(function(joinPoint) {
this[joinPoint] = function(methodName, callback) {
wrapMethod.call(this, joinPoint, methodName, callback);
};
}.bind(this));
}

module.exports = AdviceDefinition;
37 changes: 37 additions & 0 deletions src/addons/defineClass/BaseDefinition.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @providesModule BaseDefinition
*/

'use strict';

var mergeMethod = require('mergeMethod');

function BaseDefinition() {
this.initialState = function(stateObj) {
mergeMethod(this, 'getInitialState', function() {
return stateObj;
});
};

this.defaultProps = function(propsObj) {
mergeMethod(this, 'getDefaultProps', function() {
return propsObj;
});
};
}

module.exports = BaseDefinition;
Loading