Added support for range looping

This commit is contained in:
Patrick Steele-Idem 2014-07-10 07:35:36 -06:00
parent c80b8feda3
commit 04e2f168b1
5 changed files with 192 additions and 5 deletions

View File

@ -35,6 +35,7 @@ Raptor Templates is an extensible, streaming, asynchronous, [high performance](h
- [for](#for)
- [Loop Status Variable](#loop-status-variable)
- [Loop Separator](#loop-separator)
- [Range Looping](#range-looping)
- [Property Looping](#property-looping)
- [Macros](#macros)
- [def](#def)
@ -752,6 +753,37 @@ The `c-for` directive also supports a loop status variable in case you need to k
</div>
```
#### Range Looping
A range can be provided in the following format; `<var-name> from <from> to <to>[ step <step>]`.
The `from`, `to` and `step` values must be numerical expressions. If not specified, step defaults to 1.
```html
<ul>
<li c-for="i from 0 to 10">
$i
</li>
</ul>
```
```html
<ul>
<li c-for="i from 0 to 10 step 2">
$i
</li>
</ul>
```
```html
<ul>
<li c-for="i from 0 to myArray.length-1">
${myArray[i]}
</li>
</ul>
```
#### Property Looping
```html

View File

@ -16,6 +16,25 @@
'use strict';
var forEachRegEx = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+in\s+(.+)$/;
var forEachPropRegEx = /^\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)\s+in\s+(.+)$/;
var forRangeRegEx = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s+from\s+(.+)$/; // i from 0 to 10 or i from 0 to 10 step 5
var forRangeKeywordsRegExp = /"(?:[^"]|\\")*"|'(?:[^']|\\')*'|\s+(to|step)\s+/g;
var integerRegExp = /^-?\d+$/;
var numberRegExp = /^-?(?:\d+|\d+\.\d*|\d*\.\d+|\d+\.\d+)$/;
function convertNumber(str) {
if (!str) {
return str;
}
if (integerRegExp.test(str)) {
return parseInt(str, 10);
} else if (numberRegExp.test(str)) {
return parseFloat(str);
} else {
return str;
}
}
var stringify = require('raptor-json/stringify').stringify;
function parseForEach(value) {
var match = value.match(forEachRegEx);
@ -24,17 +43,79 @@ function parseForEach(value) {
'var': match[1],
'in': match[2]
};
} else {
match = value.match(forEachPropRegEx);
if (!match) {
throw new Error('Invalid each attribute of "' + value + '"');
}
} else if ((match = value.match(forEachPropRegEx))) {
return {
'nameVar': match[1],
'valueVar': match[2],
'in': match[3]
};
} else if ((match = value.match(forRangeRegEx))) {
var nameVar = match[1];
var remainder = match[2];
var rangeMatches;
var fromStart = 0;
var fromEnd = -1;
var toStart = -1;
var toEnd = remainder.length;
var stepStart = -1;
var stepEnd = -1;
while ((rangeMatches = forRangeKeywordsRegExp.exec(remainder))) {
if (rangeMatches[1] === 'to') {
fromEnd = rangeMatches.index;
toStart = forRangeKeywordsRegExp.lastIndex;
} else if (rangeMatches[1] === 'step') {
if (toStart === -1) {
continue;
}
toEnd = rangeMatches.index;
stepStart = forRangeKeywordsRegExp.lastIndex;
stepEnd = remainder.length;
}
}
if (toStart === -1 || fromEnd === -1) {
throw new Error('Invalid each attribute of "' + value + '"');
}
var from = remainder.substring(fromStart, fromEnd).trim();
var to = remainder.substring(toStart, toEnd).trim();
var step;
from = convertNumber(from);
to = convertNumber(to);
if (stepStart !== -1) {
step = remainder.substring(stepStart, stepEnd).trim();
step = convertNumber(step);
} else {
if (typeof from === 'number' && typeof to === 'number') {
if (from < to) {
step = 1;
} else {
step = -1;
}
} else {
step = 1;
}
}
return {
'nameVar': nameVar,
'from': from,
'to': to,
'step': step
};
} else {
throw new Error('Invalid each attribute of "' + value + '"');
}
}
function ForNode(props) {
@ -62,6 +143,47 @@ ForNode.prototype = {
this.generateCodeForChildren(template);
return;
}
if (parts.hasOwnProperty('from')) {
// This is a range loop
var nameVar = parts.nameVar;
var from = parts.from;
var to = parts.to;
var step = parts.step;
var comparison = '<=';
console.log("STEP!!!: ", step, from, to);
if (typeof step === 'number') {
if (step < 0) {
comparison = '>=';
}
if (step === 1) {
step = nameVar + '++';
} else if (step === -1) {
step = nameVar + '--';
} else if (step > 0) {
step = nameVar + '+=' + step;
} else if (step === 0) {
throw new Error('Invalid step of 0');
} else if (step < 0) {
step = 0-step; // Make the step positive and switch to -=
step = nameVar + '-=' + step;
}
} else {
step = nameVar + '+=' + step;
}
template.statement('(function() {').indent(function () {
template.statement('for (var ' + nameVar + '=' + from + '; ' + nameVar + comparison + to + '; ' + step + ') {').indent(function () {
this.generateCodeForChildren(template);
}, this).line('}');
}, this).line('}());');
return;
}
var items = template.makeExpression(parts['in']);
var varName = parts['var'];
var nameVarName = parts.nameVar;
@ -83,6 +205,7 @@ ForNode.prototype = {
}
var funcName;
var forEachParams;
if (customIterator) {
var statusVarFlag = '';
if (statusVar) {

View File

@ -157,6 +157,10 @@ describe('raptor-templates/rhtml' , function() {
it("should allow for looping over properties", function(done) {
testRender("test-project/rhtml-templates/looping-props.rhtml", {}, done);
});
it.only("should allow for looping over ranges", function(done) {
testRender("test-project/rhtml-templates/looping-range.rhtml", {}, done);
});
it("should allow for dynamic attributes", function(done) {
testRender("test-project/rhtml-templates/attrs.rhtml", {"myAttrs": {style: "background-color: #FF0000; <test>", "class": "my-div"}}, done);

View File

@ -0,0 +1,27 @@
<c-for each="i from 0 to 9">
$i
</c-for>
-
<c-for each="i from 9 to 0">
$i
</c-for>
-
<c-for each="i from 9 to 0 step -1">
$i
</c-for>
-
<c-for each="i from 0 to 9 step 2">
$i
</c-for>
-
<c-for each="i from 9 to 0 step -2">
$i
</c-for>
-
<c-for each="i from 0 to 'abc'.length">
$i
</c-for>
-
<c-for each="i from ''.length to 'abc'.length">
$i
</c-for>

View File

@ -0,0 +1 @@
0123456789 - 9876543210 - 9876543210 - 02468 - 97531 - 0123 - 0123