Skip to content

Tab array: sortable tabs, hide add and remove #19

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

Merged
merged 6 commits into from
May 29, 2016
Merged
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
9 changes: 6 additions & 3 deletions examples/bootstrap-example.html
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ <h3>Schema</h3>
<!-- <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular-sanitize.min.js"></script> -->


<!--
<script type="text/javascript" src="../bower_components/angular-ui-sortable/sortable.js"></script>
-->
<script type="text/javascript" src="../bower_components/angular-ui-sortable/sortable.min.js"></script>
<script type="text/javascript" src="../bower_components/jquery-ui/jquery-ui.min.js"></script>

<script type="text/javascript" src="../bower_components/angular-ui-ace/ui-ace.js"></script>
<script type="text/javascript" src="../bower_components/objectpath/lib/ObjectPath.js"></script>
<script type="text/javascript" src="../bower_components/pickadate/lib/picker.js"></script>
Expand Down Expand Up @@ -254,6 +254,9 @@ <h3>Schema</h3>
{ name: "TitleMap Examples", data: 'data/titlemaps.json' },
{ name: "Kitchen Sink", data: 'data/sink.json' },
{ name: "Hack: Conditional required", data: 'data/conditional-required.json' },
{ name: "Tab Array: Add Disabled", data: 'data/tabarray-add-disabled.json' },
{ name: "Tab Array: Remove Disabled", data: 'data/tabarray-remove-disabled.json' },
{ name: "Tab Array: Sortable (Drag and Drop)", data: 'data/tabarray-sortable.json' }
];

$scope.navbarMode = 'default';
Expand Down
44 changes: 44 additions & 0 deletions examples/data/tabarray-add-disabled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"schema": {
"type": "object",
"title": "Tab Array: Add Disabled",
"properties": {
"addDisabledTabArray": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"nick": { "type": "string" }
}
}
}
}
},
"form": [
{
"type": "section",
"htmlCss": "row",
"items": [
{
"type": "help",
"helpvalue": "<h4>Tab array with add link hidden</h4>"
},
{
"key": "addDisabledTabArray",
"type": "tabarray",
"add": null,
"title": "My name is: {{ value.name }}",
"sortOptions": {
"disabled": true
},
"items" : [
{"key": "addDisabledTabArray[].name", "htmlClass": "nameField"},
{"key": "addDisabledTabArray[].nick", "htmlClass": "nickField"}
]
}
]
}
],
"model": {}
}
44 changes: 44 additions & 0 deletions examples/data/tabarray-remove-disabled.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"schema": {
"type": "object",
"title": "Tab Array: Remove Disabled",
"properties": {
"removeDisabledTabArray": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"nick": { "type": "string" }
}
}
}
}
},
"form": [
{
"type": "section",
"htmlCss": "row",
"items": [
{
"type": "help",
"helpvalue": "<h4>Tab array with remove button hidden</h4>"
},
{
"key": "removeDisabledTabArray",
"type": "tabarray",
"remove": null,
"title": "My name is: {{ value.name }}",
"sortOptions": {
"disabled": true
},
"items" : [
{"key": "removeDisabledTabArray[].name", "htmlClass": "nameField"},
{"key": "removeDisabledTabArray[].nick", "htmlClass": "nickField"}
]
}
]
}
],
"model": {}
}
40 changes: 40 additions & 0 deletions examples/data/tabarray-sortable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"schema": {
"type": "object",
"title": "Tab Array: Sortable (Drag and Drop)",
"properties": {
"sortableTabArray": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"nick": { "type": "string" }
}
}
}
}
},
"form": [
{
"type": "section",
"htmlCss": "row",
"items": [
{
"type": "help",
"helpvalue": "<h4>Drag and drop sortable tab array</h4>"
},
{
"key": "sortableTabArray",
"type": "tabarray",
"title": "My name is: {{ value.name }}",
"items" : [
{"key": "sortableTabArray[].name", "htmlClass": "nameField"},
{"key": "sortableTabArray[].nick", "htmlClass": "nickField"}
]
}
]
}
],
"model": {}
}
2 changes: 1 addition & 1 deletion gulp/tasks/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ gulp.task('protractor', ['webdriver-update'], function(cb) {
}).on('end', cb);
});

['validation-messages', 'custom-validation'].forEach(function(name) {
['validation-messages', 'custom-validation', 'tabarray'].forEach(function(name) {
gulp.task('protractor:' + name, ['webdriver-update'], function(cb) {
gulp.src(['test/protractor/specs/' + name + '.js']).pipe(protractor.protractor({
configFile: 'test/protractor/conf.js',
Expand Down
13 changes: 12 additions & 1 deletion src/bootstrap-decorator.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,24 @@ function(decoratorsProvider, sfBuilderProvider, sfPathProvider) {
});
}
};

// Set tabArray sortOptions.items default.
var tabArray = function(args) {
if(args.form.hasOwnProperty('sortOptions')) {
if(!args.form.sortOptions.hasOwnProperty('items')) {
args.form.sortOptions['items'] = 'li:not(:last-child)';
}
} else {
args.form['sortOptions'] = {items: 'li:not(:last-child)'};
}
}

var defaults = [sfField, ngModel, ngModelOptions, condition];
decoratorsProvider.defineDecorator('bootstrapDecorator', {
textarea: {template: base + 'textarea.html', builder: defaults},
fieldset: {template: base + 'fieldset.html', builder: [sfField, simpleTransclusion, condition]},
array: {template: base + 'array.html', builder: [sfField, ngModelOptions, ngModel, array, condition]},
tabarray: {template: base + 'tabarray.html', builder: [sfField, ngModelOptions, ngModel, array, condition]},
tabarray: {template: base + 'tabarray.html', builder: [sfField, ngModelOptions, ngModel, array, condition, tabArray]},
tabs: {template: base + 'tabs.html', builder: [sfField, ngModelOptions, tabs, condition]},
section: {template: base + 'section.html', builder: [sfField, simpleTransclusion, condition]},
conditional: {template: base + 'section.html', builder: [sfField, simpleTransclusion, condition]},
Expand Down
13 changes: 7 additions & 6 deletions src/tabarray.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,24 @@
class="clearfix schema-form-tabarray schema-form-tabarray-{{form.tabType || 'left'}} {{form.htmlClass}}">
<div ng-if="!form.tabType || form.tabType !== 'right'"
ng-class="{'col-xs-3': !form.tabType || form.tabType === 'left'}">
<ul class="nav nav-tabs"
ng-class="{ 'tabs-left': !form.tabType || form.tabType === 'left'}">
<ol class="nav nav-tabs"
ng-class="{ 'tabs-left': !form.tabType || form.tabType === 'left'}"
sf-field-model ui-sortable="form.sortOptions">
<li sf-field-model="ng-repeat"
ng-repeat="item in $$value$$ track by $index"
ng-click="$event.preventDefault() || (selected.tab = $index)"
ng-class="{active: selected.tab === $index}">
<a href="#">{{interp(form.title,{'$index':$index, value: item}) || $index}}</a>
</li>
<li ng-hide="form.readonly"
<li ng-hide="form.readonly || form.add === null"
ng-disabled="form.schema.maxItems <= modelArray.length"
ng-click="$event.preventDefault() || (selected.tab = appendToArray().length - 1)">
<a href="#">
<i class="glyphicon glyphicon-plus"></i>
{{ form.add || 'Add'}}
</a>
</li>
</ul>
</ol>
</div>

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

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

<button ng-hide="form.readonly"
<button ng-hide="form.readonly || form.remove === null"
ng-click="selected.tab = deleteFromArray($index).length - 1"
ng-disabled="form.schema.minItems >= modelArray.length"
type="button"
Expand All @@ -59,7 +60,7 @@
ng-class="{active: selected.tab === $index}">
<a href="#">{{interp(form.title,{'$index':$index, value: item}) || $index}}</a>
</li>
<li ng-hide="form.readonly"
<li ng-hide="form.readonly || form.add === null"
ng-disabled="form.schema.maxItems <= modelArray.length"
ng-click="$event.preventDefault() || (selected.tab = appendToArray().length - 1)">
<a href="#">
Expand Down
111 changes: 111 additions & 0 deletions test/protractor/specs/tabarray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
describe('tab array', function () {
it('form should exist', function () {
browser.get('http://localhost:8080/examples/bootstrap-example.html');

element(by.css('#selectTest')).all(by.cssContainingText('option', 'Tab Array')).first().click().then(function() {
expect(element(by.css('form.ng-valid-schema-form')).getInnerHtml()).not.toEqual('');
});
});

it('add link should be hidden', function () {
browser.get('http://localhost:8080/examples/bootstrap-example.html');

/* select the add disabled example */
element(by.css('#selectTest')).element(by.cssContainingText('option', 'Tab Array: Add Disabled')).click().then(function() {

/* Add link should not be displayed */
var tabs = element.all(by.css('.nav-tabs li'));
expect(tabs.get(0).isDisplayed()).toBeTruthy();
expect(tabs.get(1).isDisplayed()).toBeFalsy();

var addLink = element.all(by.partialLinkText('Add'));
expect(addLink.count()).toBe(0);

/*** control tests ***/
/* Remove button should be displayed */
var removeButton = element.all(by.partialButtonText('Remove')).get(0);
expect(removeButton.isDisplayed()).toBeTruthy();
});
});

it('remove button should be hidden', function () {
browser.get('http://localhost:8080/examples/bootstrap-example.html');

/* select the remove disabled example */
element(by.css('#selectTest')).element(by.cssContainingText('option', 'Tab Array: Remove Disabled')).click().then(function() {

/* Remove button should not be displayed */
var removeButton = element.all(by.partialButtonText('Remove')).get(0);
expect(removeButton.isDisplayed()).toBeFalsy();

/*** control tests ***/
/* Add link should not be displayed */
var tabs = element.all(by.css('.nav-tabs li'));
expect(tabs.get(0).isDisplayed()).toBeTruthy();
expect(tabs.get(1).isDisplayed()).toBeTruthy();

var addLink = element.all(by.partialLinkText('Add'));
expect(addLink.count()).toBe(1);
});
});

it('should be able order elements in array by dragging the tabs', function () {
browser.get('http://localhost:8080/examples/bootstrap-example.html');

function checkDragDrop(i) {
browser.driver.wait(protractor.until.elementLocated(by.xpath("//ol/li[1]/a[text()='My name is: Name " + (i + 1) +"']")), 10000);
expect(element.all(by.css('.nav-tabs li a')).get(0).getText()).toBe('My name is: Name ' + (i + 1));
}

function populateTab(i) {
browser.driver.wait(protractor.until.elementLocated(by.css('.tab-pane.active.index' + i)), 5000);

browser.driver.wait(protractor.until.elementLocated(by.css('.tab-pane.index' + i + ' div.nickField > input')), 5000);
input = element.all(by.css('.tab-pane.index' + i + ' div.nickField > input')).first();
input.sendKeys('Nickname ' + i);

browser.driver.wait(protractor.until.elementLocated(by.css('.tab-pane.index' + i + ' div.nameField > input')), 5000);
input = element.all(by.css('.tab-pane.index' + i + ' div.nameField > input')).first();
input.sendKeys('Name ' + i);

browser.driver.wait(protractor.until.elementLocated(by.linkText('My name is: Name ' + i)), 10000);
}

/* select the sortable example */
element(by.css('#selectTest')).element(by.cssContainingText('option', 'Tab Array: Sortable')).click().then(function() {

var i;
var elementsToAdd = 9;

/* the array starts with 1 element, populate the first element */
populateTab(0);

/* add elements and populate */
for (i = 1; i <= elementsToAdd; i++) {
var tabLink = element.all(by.css('.glyphicon-plus'));
tabLink.click().then(populateTab(i));
}

/* continue when all tabs have been populated*/
browser.driver.wait(protractor.until.elementLocated(by.linkText('My name is: Name ' + elementsToAdd)), 10000);

/* check the number of tabs */
var tabs = element.all(by.css('.nav-tabs li'));
expect(tabs.count()).toBe(elementsToAdd + 2); //Extra 1 for the "+ Add" link

/* drag the tabs into reverse order (descending) */
for (i = 0; i < elementsToAdd; i++) {
var draggable_element = element.all(by.css('.nav-tabs li')).get(0);
var target_element = element.all(by.css('.nav-tabs li')).get(elementsToAdd - i);
expect(draggable_element.isPresent()).toEqual(true);
expect(target_element.isPresent()).toEqual(true);
browser.actions().dragAndDrop(draggable_element, target_element).perform().then(checkDragDrop(i));
}

/* final check of the reverse ordered tabs */
for (i = 0; i <= elementsToAdd; i++) {
expect(element.all(by.css('.nav-tabs li a')).get(i).getText()).toBe('My name is: Name ' + (elementsToAdd - i));
}
});
});
});