Skip to content

Commit 06fd6a8

Browse files
committed
Merge pull request #19 from joelwkent/develop
Tab array: sortable tabs, hide add and remove
2 parents 0a40f4b + 2308fe5 commit 06fd6a8

8 files changed

+265
-11
lines changed

examples/bootstrap-example.html

+6-3
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ <h3>Schema</h3>
214214
<!-- <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular-sanitize.min.js"></script> -->
215215

216216

217-
<!--
218-
<script type="text/javascript" src="../bower_components/angular-ui-sortable/sortable.js"></script>
219-
-->
217+
<script type="text/javascript" src="../bower_components/angular-ui-sortable/sortable.min.js"></script>
218+
<script type="text/javascript" src="../bower_components/jquery-ui/jquery-ui.min.js"></script>
219+
220220
<script type="text/javascript" src="../bower_components/angular-ui-ace/ui-ace.js"></script>
221221
<script type="text/javascript" src="../bower_components/objectpath/lib/ObjectPath.js"></script>
222222
<script type="text/javascript" src="../bower_components/pickadate/lib/picker.js"></script>
@@ -254,6 +254,9 @@ <h3>Schema</h3>
254254
{ name: "TitleMap Examples", data: 'data/titlemaps.json' },
255255
{ name: "Kitchen Sink", data: 'data/sink.json' },
256256
{ name: "Hack: Conditional required", data: 'data/conditional-required.json' },
257+
{ name: "Tab Array: Add Disabled", data: 'data/tabarray-add-disabled.json' },
258+
{ name: "Tab Array: Remove Disabled", data: 'data/tabarray-remove-disabled.json' },
259+
{ name: "Tab Array: Sortable (Drag and Drop)", data: 'data/tabarray-sortable.json' }
257260
];
258261

259262
$scope.navbarMode = 'default';
+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"schema": {
3+
"type": "object",
4+
"title": "Tab Array: Add Disabled",
5+
"properties": {
6+
"addDisabledTabArray": {
7+
"type": "array",
8+
"items": {
9+
"type": "object",
10+
"properties": {
11+
"name": { "type": "string" },
12+
"nick": { "type": "string" }
13+
}
14+
}
15+
}
16+
}
17+
},
18+
"form": [
19+
{
20+
"type": "section",
21+
"htmlCss": "row",
22+
"items": [
23+
{
24+
"type": "help",
25+
"helpvalue": "<h4>Tab array with add link hidden</h4>"
26+
},
27+
{
28+
"key": "addDisabledTabArray",
29+
"type": "tabarray",
30+
"add": null,
31+
"title": "My name is: {{ value.name }}",
32+
"sortOptions": {
33+
"disabled": true
34+
},
35+
"items" : [
36+
{"key": "addDisabledTabArray[].name", "htmlClass": "nameField"},
37+
{"key": "addDisabledTabArray[].nick", "htmlClass": "nickField"}
38+
]
39+
}
40+
]
41+
}
42+
],
43+
"model": {}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"schema": {
3+
"type": "object",
4+
"title": "Tab Array: Remove Disabled",
5+
"properties": {
6+
"removeDisabledTabArray": {
7+
"type": "array",
8+
"items": {
9+
"type": "object",
10+
"properties": {
11+
"name": { "type": "string" },
12+
"nick": { "type": "string" }
13+
}
14+
}
15+
}
16+
}
17+
},
18+
"form": [
19+
{
20+
"type": "section",
21+
"htmlCss": "row",
22+
"items": [
23+
{
24+
"type": "help",
25+
"helpvalue": "<h4>Tab array with remove button hidden</h4>"
26+
},
27+
{
28+
"key": "removeDisabledTabArray",
29+
"type": "tabarray",
30+
"remove": null,
31+
"title": "My name is: {{ value.name }}",
32+
"sortOptions": {
33+
"disabled": true
34+
},
35+
"items" : [
36+
{"key": "removeDisabledTabArray[].name", "htmlClass": "nameField"},
37+
{"key": "removeDisabledTabArray[].nick", "htmlClass": "nickField"}
38+
]
39+
}
40+
]
41+
}
42+
],
43+
"model": {}
44+
}

examples/data/tabarray-sortable.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"schema": {
3+
"type": "object",
4+
"title": "Tab Array: Sortable (Drag and Drop)",
5+
"properties": {
6+
"sortableTabArray": {
7+
"type": "array",
8+
"items": {
9+
"type": "object",
10+
"properties": {
11+
"name": { "type": "string" },
12+
"nick": { "type": "string" }
13+
}
14+
}
15+
}
16+
}
17+
},
18+
"form": [
19+
{
20+
"type": "section",
21+
"htmlCss": "row",
22+
"items": [
23+
{
24+
"type": "help",
25+
"helpvalue": "<h4>Drag and drop sortable tab array</h4>"
26+
},
27+
{
28+
"key": "sortableTabArray",
29+
"type": "tabarray",
30+
"title": "My name is: {{ value.name }}",
31+
"items" : [
32+
{"key": "sortableTabArray[].name", "htmlClass": "nameField"},
33+
{"key": "sortableTabArray[].nick", "htmlClass": "nickField"}
34+
]
35+
}
36+
]
37+
}
38+
],
39+
"model": {}
40+
}

gulp/tasks/protractor.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ gulp.task('protractor', ['webdriver-update'], function(cb) {
2727
}).on('end', cb);
2828
});
2929

30-
['validation-messages', 'custom-validation'].forEach(function(name) {
30+
['validation-messages', 'custom-validation', 'tabarray'].forEach(function(name) {
3131
gulp.task('protractor:' + name, ['webdriver-update'], function(cb) {
3232
gulp.src(['test/protractor/specs/' + name + '.js']).pipe(protractor.protractor({
3333
configFile: 'test/protractor/conf.js',

src/bootstrap-decorator.js

+12-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ function(decoratorsProvider, sfBuilderProvider, sfPathProvider) {
2727
});
2828
}
2929
};
30+
31+
// Set tabArray sortOptions.items default.
32+
var tabArray = function(args) {
33+
if(args.form.hasOwnProperty('sortOptions')) {
34+
if(!args.form.sortOptions.hasOwnProperty('items')) {
35+
args.form.sortOptions['items'] = 'li:not(:last-child)';
36+
}
37+
} else {
38+
args.form['sortOptions'] = {items: 'li:not(:last-child)'};
39+
}
40+
}
3041

3142
var selectPlaceholder = function(args) {
3243
if (args.form.placeholder) {
@@ -60,7 +71,7 @@ function(decoratorsProvider, sfBuilderProvider, sfPathProvider) {
6071
textarea: {template: base + 'textarea.html', builder: defaults},
6172
fieldset: {template: base + 'fieldset.html', builder: [sfField, simpleTransclusion, condition]},
6273
array: {template: base + 'array.html', builder: [sfField, ngModelOptions, ngModel, array, condition]},
63-
tabarray: {template: base + 'tabarray.html', builder: [sfField, ngModelOptions, ngModel, array, condition]},
74+
tabarray: {template: base + 'tabarray.html', builder: [sfField, ngModelOptions, ngModel, array, condition, tabArray]},
6475
tabs: {template: base + 'tabs.html', builder: [sfField, ngModelOptions, tabs, condition]},
6576
section: {template: base + 'section.html', builder: [sfField, simpleTransclusion, condition]},
6677
conditional: {template: base + 'section.html', builder: [sfField, simpleTransclusion, condition]},

src/tabarray.html

+7-6
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@
66
class="clearfix schema-form-tabarray schema-form-tabarray-{{form.tabType || 'left'}} {{form.htmlClass}}">
77
<div ng-if="!form.tabType || form.tabType !== 'right'"
88
ng-class="{'col-xs-3': !form.tabType || form.tabType === 'left'}">
9-
<ul class="nav nav-tabs"
10-
ng-class="{ 'tabs-left': !form.tabType || form.tabType === 'left'}">
9+
<ol class="nav nav-tabs"
10+
ng-class="{ 'tabs-left': !form.tabType || form.tabType === 'left'}"
11+
sf-field-model ui-sortable="form.sortOptions">
1112
<li sf-field-model="ng-repeat"
1213
ng-repeat="item in $$value$$ track by $index"
1314
ng-click="$event.preventDefault() || (selected.tab = $index)"
1415
ng-class="{active: selected.tab === $index}">
1516
<a href="#">{{interp(form.title,{'$index':$index, value: item}) || $index}}</a>
1617
</li>
17-
<li ng-hide="form.readonly"
18+
<li ng-hide="form.readonly || form.add === null"
1819
ng-disabled="form.schema.maxItems <= modelArray.length"
1920
ng-click="$event.preventDefault() || (selected.tab = appendToArray().length - 1)">
2021
<a href="#">
2122
<i class="glyphicon glyphicon-plus"></i>
2223
{{ form.add || 'Add'}}
2324
</a>
2425
</li>
25-
</ul>
26+
</ol>
2627
</div>
2728

2829
<div ng-class="{'col-xs-9': !form.tabType || form.tabType === 'left' || form.tabType === 'right'}">
@@ -35,7 +36,7 @@
3536

3637
<div schema-form-array-items></div>
3738

38-
<button ng-hide="form.readonly"
39+
<button ng-hide="form.readonly || form.remove === null"
3940
ng-click="selected.tab = deleteFromArray($index).length - 1"
4041
ng-disabled="form.schema.minItems >= modelArray.length"
4142
type="button"
@@ -59,7 +60,7 @@
5960
ng-class="{active: selected.tab === $index}">
6061
<a href="#">{{interp(form.title,{'$index':$index, value: item}) || $index}}</a>
6162
</li>
62-
<li ng-hide="form.readonly"
63+
<li ng-hide="form.readonly || form.add === null"
6364
ng-disabled="form.schema.maxItems <= modelArray.length"
6465
ng-click="$event.preventDefault() || (selected.tab = appendToArray().length - 1)">
6566
<a href="#">

test/protractor/specs/tabarray.js

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
describe('tab array', function () {
2+
it('form should exist', function () {
3+
browser.get('http://localhost:8080/examples/bootstrap-example.html');
4+
5+
element(by.css('#selectTest')).all(by.cssContainingText('option', 'Tab Array')).first().click().then(function() {
6+
expect(element(by.css('form.ng-valid-schema-form')).getInnerHtml()).not.toEqual('');
7+
});
8+
});
9+
10+
it('add link should be hidden', function () {
11+
browser.get('http://localhost:8080/examples/bootstrap-example.html');
12+
13+
/* select the add disabled example */
14+
element(by.css('#selectTest')).element(by.cssContainingText('option', 'Tab Array: Add Disabled')).click().then(function() {
15+
16+
/* Add link should not be displayed */
17+
var tabs = element.all(by.css('.nav-tabs li'));
18+
expect(tabs.get(0).isDisplayed()).toBeTruthy();
19+
expect(tabs.get(1).isDisplayed()).toBeFalsy();
20+
21+
var addLink = element.all(by.partialLinkText('Add'));
22+
expect(addLink.count()).toBe(0);
23+
24+
/*** control tests ***/
25+
/* Remove button should be displayed */
26+
var removeButton = element.all(by.partialButtonText('Remove')).get(0);
27+
expect(removeButton.isDisplayed()).toBeTruthy();
28+
});
29+
});
30+
31+
it('remove button should be hidden', function () {
32+
browser.get('http://localhost:8080/examples/bootstrap-example.html');
33+
34+
/* select the remove disabled example */
35+
element(by.css('#selectTest')).element(by.cssContainingText('option', 'Tab Array: Remove Disabled')).click().then(function() {
36+
37+
/* Remove button should not be displayed */
38+
var removeButton = element.all(by.partialButtonText('Remove')).get(0);
39+
expect(removeButton.isDisplayed()).toBeFalsy();
40+
41+
/*** control tests ***/
42+
/* Add link should not be displayed */
43+
var tabs = element.all(by.css('.nav-tabs li'));
44+
expect(tabs.get(0).isDisplayed()).toBeTruthy();
45+
expect(tabs.get(1).isDisplayed()).toBeTruthy();
46+
47+
var addLink = element.all(by.partialLinkText('Add'));
48+
expect(addLink.count()).toBe(1);
49+
});
50+
});
51+
52+
it('should be able order elements in array by dragging the tabs', function () {
53+
browser.get('http://localhost:8080/examples/bootstrap-example.html');
54+
55+
function checkDragDrop(i) {
56+
browser.driver.wait(protractor.until.elementLocated(by.xpath("//ol/li[1]/a[text()='My name is: Name " + (i + 1) +"']")), 10000);
57+
expect(element.all(by.css('.nav-tabs li a')).get(0).getText()).toBe('My name is: Name ' + (i + 1));
58+
}
59+
60+
function populateTab(i) {
61+
browser.driver.wait(protractor.until.elementLocated(by.css('.tab-pane.active.index' + i)), 5000);
62+
63+
browser.driver.wait(protractor.until.elementLocated(by.css('.tab-pane.index' + i + ' div.nickField > input')), 5000);
64+
input = element.all(by.css('.tab-pane.index' + i + ' div.nickField > input')).first();
65+
input.sendKeys('Nickname ' + i);
66+
67+
browser.driver.wait(protractor.until.elementLocated(by.css('.tab-pane.index' + i + ' div.nameField > input')), 5000);
68+
input = element.all(by.css('.tab-pane.index' + i + ' div.nameField > input')).first();
69+
input.sendKeys('Name ' + i);
70+
71+
browser.driver.wait(protractor.until.elementLocated(by.linkText('My name is: Name ' + i)), 10000);
72+
}
73+
74+
/* select the sortable example */
75+
element(by.css('#selectTest')).element(by.cssContainingText('option', 'Tab Array: Sortable')).click().then(function() {
76+
77+
var i;
78+
var elementsToAdd = 9;
79+
80+
/* the array starts with 1 element, populate the first element */
81+
populateTab(0);
82+
83+
/* add elements and populate */
84+
for (i = 1; i <= elementsToAdd; i++) {
85+
var tabLink = element.all(by.css('.glyphicon-plus'));
86+
tabLink.click().then(populateTab(i));
87+
}
88+
89+
/* continue when all tabs have been populated*/
90+
browser.driver.wait(protractor.until.elementLocated(by.linkText('My name is: Name ' + elementsToAdd)), 10000);
91+
92+
/* check the number of tabs */
93+
var tabs = element.all(by.css('.nav-tabs li'));
94+
expect(tabs.count()).toBe(elementsToAdd + 2); //Extra 1 for the "+ Add" link
95+
96+
/* drag the tabs into reverse order (descending) */
97+
for (i = 0; i < elementsToAdd; i++) {
98+
var draggable_element = element.all(by.css('.nav-tabs li')).get(0);
99+
var target_element = element.all(by.css('.nav-tabs li')).get(elementsToAdd - i);
100+
expect(draggable_element.isPresent()).toEqual(true);
101+
expect(target_element.isPresent()).toEqual(true);
102+
browser.actions().dragAndDrop(draggable_element, target_element).perform().then(checkDragDrop(i));
103+
}
104+
105+
/* final check of the reverse ordered tabs */
106+
for (i = 0; i <= elementsToAdd; i++) {
107+
expect(element.all(by.css('.nav-tabs li a')).get(i).getText()).toBe('My name is: Name ' + (elementsToAdd - i));
108+
}
109+
});
110+
});
111+
});

0 commit comments

Comments
 (0)