Browse files

Added nowrap to allow specifying functions that should not have the r…

…eturn value wrapped.
  • Loading branch information...
1 parent 9bede09 commit ea06abbe23fa41669f87bee03b19d369286d73aa @doug-martin committed Dec 11, 2012
Showing with 188 additions and 14 deletions.
  1. +42 −3 README.md
  2. +32 −3 docs/index.html
  3. +73 −8 extender.js
  4. +41 −0 test/extender.test.js
View
45 README.md
@@ -6,6 +6,12 @@
`extender` is a library that helps in making chainable APIs, by creating a function that accepts different values and returns an object decorated with functions based on the type.
+##Why Is Extender Different?
+
+Extender is different than normal chaining because is does more than return `this`. It decorates your values in a type safe manner.
+
+For example if you return an array from a string based method then the returned value will be decorated with array methods and not the string methods. This allow you as the developer to focus on your API and not worrying about how to properly build and connect your API.
+
##Installation
@@ -43,8 +49,7 @@ function isString(obj) {
}
-var myExtender =
- .define(isString, {
+var myExtender = extender.define(isString, {
multiply: function (str, times) {
var ret = str;
for (var i = 1; i < times; i++) {
@@ -62,7 +67,7 @@ myExtender("hello").multiply(2).value(); //hellohello
```
-If do not specify a tester function and just pass in an object of `functions` then all values passed in will be decorated with methods.
+If you do not specify a tester function and just pass in an object of `functions` then all values passed in will be decorated with methods.
```javascript
@@ -159,6 +164,31 @@ myExtender.define(isString, {
});
```
+**`noWrap`**
+
+`extender` also allows you to specify methods that should not have the value wrapped providing a cleaner exit function other than `value()`.
+
+For example suppose you have an API that allows you to build a validator, rather than forcing the user to invoke the `value` method you could add a method called `validator` which makes more syntactic sense.
+
+```
+
+var myValidator = extender.define({
+ //chainable validation methods
+ //...
+ //end chainable validation methods
+
+ noWrap : {
+ validator : function(){
+ //return your validator
+ }
+ }
+});
+
+myValidator().isNotNull().isEmailAddress().validator(); //now you dont need to call .value()
+
+
+```
+
**Using `instanceof`**
When using extenders you can test if a value is an `instanceof` of an extender by using the instanceof operator.
@@ -172,3 +202,12 @@ str instanceof myExtender; //true
##Examples
To see more examples click [here](https://github.com/doug-martin/extender/tree/master/examples)
+
+
+
+
+
+
+
+
+
View
35 docs/index.html
@@ -169,6 +169,13 @@
<h1>Extender</h1>
<p><code>extender</code> is a library that helps in making chainable APIs, by creating a function that accepts different values and returns an object decorated with functions based on the type.
+</p>
+<h2>Why Is Extender Different?</h2>
+<p>Extender is different than normal chaining because is does more than return <code>this</code>. It decorates your values in a type safe manner.
+
+</p>
+<p>For example if you return an array from a string based method then the returned value will be decorated with array methods and not the string methods. This allow you as the developer to focus on your API and not worrying about how to properly build and connect your API.
+
</p>
<h2>Installation</h2>
@@ -201,8 +208,7 @@
}
-var myExtender =
- .define(isString, {
+var myExtender = extender.define(isString, {
multiply: function (str, times) {
var ret = str;
for (var i = 1; i &lt; times; i++) {
@@ -217,7 +223,7 @@
});
myExtender(&quot;hello&quot;).multiply(2).value(); //hellohello</code></pre>
-<p>If do not specify a tester function and just pass in an object of <code>functions</code> then all values passed in will be decorated with methods.
+<p>If you do not specify a tester function and just pass in an object of <code>functions</code> then all values passed in will be decorated with methods.
</p>
<pre class='prettyprint linenums lang-js'><code class="lang-javascript">
@@ -311,6 +317,29 @@
this._value = val.trimRight().trimLeft();
}
});</code></pre>
+<p><strong><code>noWrap</code></strong>
+
+</p>
+<p><code>extender</code> also allows you to specify methods that should not have the value wrapped providing a cleaner exit function other than <code>value()</code>.
+
+</p>
+<p>For example suppose you have an API that allows you to build a validator, rather than forcing the user to invoke the <code>value</code> method you could add a method called <code>validator</code> which makes more syntactic sense.
+
+</p>
+<pre class='prettyprint linenums lang-js'><code>
+var myValidator = extender.define({
+ //chainable validation methods
+ //...
+ //end chainable validation methods
+
+ noWrap : {
+ validator : function(){
+ //return your validator
+ }
+ }
+});
+
+myValidator().isNotNull().isEmailAddress().validator(); //now you dont need to call .value()</code></pre>
<p><strong>Using <code>instanceof</code></strong>
</p>
View
81 extender.js
@@ -11,6 +11,12 @@
*
* `extender` is a library that helps in making chainable APIs, by creating a function that accepts different values and returns an object decorated with functions based on the type.
*
+ * ##Why Is Extender Different?
+ *
+ * Extender is different than normal chaining because is does more than return `this`. It decorates your values in a type safe manner.
+ *
+ * For example if you return an array from a string based method then the returned value will be decorated with array methods and not the string methods. This allow you as the developer to focus on your API and not worrying about how to properly build and connect your API.
+ *
*
* ##Installation
*
@@ -48,8 +54,7 @@
* }
*
*
- * var myExtender =
- * .define(isString, {
+ * var myExtender = extender.define(isString, {
* multiply: function (str, times) {
* var ret = str;
* for (var i = 1; i < times; i++) {
@@ -67,7 +72,7 @@
*
* ```
*
- * If do not specify a tester function and just pass in an object of `functions` then all values passed in will be decorated with methods.
+ * If you do not specify a tester function and just pass in an object of `functions` then all values passed in will be decorated with methods.
*
* ```javascript
*
@@ -164,6 +169,31 @@
* });
* ```
*
+ * **`noWrap`**
+ *
+ * `extender` also allows you to specify methods that should not have the value wrapped providing a cleaner exit function other than `value()`.
+ *
+ * For example suppose you have an API that allows you to build a validator, rather than forcing the user to invoke the `value` method you could add a method called `validator` which makes more syntactic sense.
+ *
+ * ```
+ *
+ * var myValidator = extender.define({
+ * //chainable validation methods
+ * //...
+ * //end chainable validation methods
+ *
+ * noWrap : {
+ * validator : function(){
+ * //return your validator
+ * }
+ * }
+ * });
+ *
+ * myValidator().isNotNull().isEmailAddress().validator(); //now you dont need to call .value()
+ *
+ *
+ * ```
+ *
* **Using `instanceof`**
*
* When using extenders you can test if a value is an `instanceof` of an extender by using the instanceof operator.
@@ -254,6 +284,45 @@
proto[name] = extendedMethod
}
+ function addNoWrapMethod(proto, name, func) {
+ if ("function" !== typeof func) {
+ throw new TypeError("when extending type you must provide a function");
+ }
+ var extendedMethod;
+ if (name === "constructor") {
+ extendedMethod = function () {
+ var args = slice.call(arguments);
+ this._super(arguments);
+ func.apply(this, arguments);
+ }
+ } else {
+ extendedMethod = function extendedMethod() {
+ var args = slice.call(arguments);
+ args.unshift(this._value)
+ return func.apply(this, args);
+ }
+ }
+ proto[name] = extendedMethod
+ }
+
+ function decorateProto(proto, decoration, nowrap) {
+ for (var i in decoration) {
+ if (decoration.hasOwnProperty(i)) {
+ if (i !== "getters" && i !== "setters") {
+ if (i === "noWrap") {
+ decorateProto(proto, decoration[i], true);
+ } else if (nowrap) {
+ addNoWrapMethod(proto, i, decoration[i])
+ } else {
+ addMethod(proto, i, decoration[i]);
+ }
+ } else {
+ proto[i] = decoration[i];
+ }
+ }
+ }
+ }
+
function _extender(obj) {
var ret = obj;
if (!(obj instanceof Base)) {
@@ -279,11 +348,7 @@
tester = always;
}
var proto = {};
- for (var i in decorate) {
- if (decorate.hasOwnProperty(i)) {
- addMethod(proto, i, decorate[i]);
- }
- }
+ decorateProto(proto, decorate);
defined.push([tester, proto]);
return _extender;
}
View
41 test/extender.test.js
@@ -35,6 +35,21 @@ it.describe("extender",function (it) {
toArray: function (str, delim) {
delim = delim || "";
return str.split(delim);
+ },
+
+ noWrap: {
+ multiplyPlain: function (str, times) {
+ var ret = str;
+ for (var i = 1; i < times; i++) {
+ ret += str;
+ }
+ return ret;
+ },
+
+ toArrayPlain: function (str, delim) {
+ delim = delim || "";
+ return str.split(delim);
+ }
}
})
.define(is.array, {
@@ -44,12 +59,28 @@ it.describe("extender",function (it) {
ret.push(arr[i][m]);
}
return ret;
+ },
+
+ noWrap: {
+ pluckPlain: function (arr, m) {
+ var ret = [];
+ for (var i = 0, l = arr.length; i < l; i++) {
+ ret.push(arr[i][m]);
+ }
+ return ret;
+ }
}
})
.define(is.boolean, {
invert: function (val) {
return !val;
+ },
+
+ noWrap: {
+ invertPlain: function (val) {
+ return !val;
+ }
}
});
@@ -84,6 +115,16 @@ it.describe("extender",function (it) {
assert.deepEqual(extended.pluck("a").value(), ["a", "b", "c"]);
});
+ it.should("not wrap methods in noWrap", function () {
+ assert.equal(myExtender("hello").multiplyPlain(5), "hellohellohellohellohello");
+ assert.isFalse(myExtender(true).invertPlain());
+ assert.deepEqual(myExtender([
+ {a: "a"},
+ {a: "b"},
+ {a: "c"}
+ ]).pluckPlain("a"), ["a", "b", "c"]);
+ });
+
it.should("keep extenders in their own scope", function () {
var myExtender = extender
.define({

0 comments on commit ea06abb

Please sign in to comment.