diff --git a/CHANGELOG b/CHANGELOG index e0422341a..78c97c2d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +v0.4.0 +------ + * Create tabs with the 'tab' type, just for show! + * Add arbitrary HTML with the 'help' type, just because you can. + v0.3.0 ------ * A shiny new datepicker using pickadate.js diff --git a/README.md b/README.md index 52a9c2c33..2ee846c14 100644 --- a/README.md +++ b/README.md @@ -73,10 +73,12 @@ manually) It depends on AngularJS (duh!), [tv4](https://github.com/geraintluff/tv4), and if you like to use the date picker you also need jQuery and -[pickadate.js](http://amsul.ca/pickadate.js/) +[pickadate.js](http://amsul.ca/pickadate.js/). Also if you use the ```help``` +type to inject HTML you'll want to use ngSanitize as well. The minified files also includes all templates so they are all you need. + Addons ------ Currently there is only one addon, a date picker using @@ -85,6 +87,41 @@ the excellent [pickadate.js](http://amsul.ca/pickadate.js/). See the [docs](docs/datepicker.md) for usage. +Building +-------- +The files in the ```dist``` plus dependencies are all you need to use Schema +Form, but if you like to build it yourself we use [gulp](http://gulpjs.com/). + +First off you need to have nodejs installed. Then install all dev dependencies +of the project with npm, install gulp and run the default task. + +```bash +$ npm install +$ sudo npm install -g gulp +$ gulp +``` + +The default task uses [gulp-angular-templatecache](https://github.com/miickel/gulp-angular-templatecache) +to compile all html templates to js and then concatenates and minifies them with +the rest of the sources. + +You can also run ```gulp watch``` to have it rebuild on change. + +Tests +----- +Unit tests are run with [karma](http://karma-runner.github.io) and written using +[mocha](http://visionmedia.github.io/mocha/), [chai](http://chaijs.com/) +and [sinon](http://sinonjs.org/) + +To run the tests first install all dependencies with npm (if you haven't done it +already) and install the karma cli to run the test. + +```bash +$ npm install +$ sudo npm install -g karma-cli +$ karma start karma.conf.js +``` + Contributing ------------ diff --git a/bower.json b/bower.json index 69a5907e2..932770ab4 100644 --- a/bower.json +++ b/bower.json @@ -5,7 +5,7 @@ "dist/bootstrap-decorator.min.js", "dist/bootstrap-datepicker.min.js" ], - "version": "0.3.0", + "version": "0.4.0", "authors": [ "Textalk", "David Jensen " @@ -24,7 +24,8 @@ "dependencies": { "angular": "~1.2.18", "tv4": "~1.0.15", - "pickadate": "~3.5.2" + "pickadate": "~3.5.2", + "angular-sanitize": "~1.2.18" }, "devDependencies": { "angular-ui-ace": "bower" diff --git a/dist/bootstrap-decorator.min.js b/dist/bootstrap-decorator.min.js index 9bdf2d41d..6fdb10abf 100644 --- a/dist/bootstrap-decorator.min.js +++ b/dist/bootstrap-decorator.min.js @@ -1 +1 @@ -angular.module("schemaForm").run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions-trcl.html",'
'),e.put("directives/decorators/bootstrap/actions.html",'
'),e.put("directives/decorators/bootstrap/checkbox.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/checkboxes.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/default.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
'),e.put("directives/decorators/bootstrap/fieldset-trcl.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/radio-buttons.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/radios.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/readonly.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/section.html",'
'),e.put("directives/decorators/bootstrap/select.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
'),e.put("directives/decorators/bootstrap/submit.html",'
'),e.put("directives/decorators/bootstrap/textarea.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
')}]),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){var t="directives/decorators/bootstrap/";e.createDecorator("bootstrapDecorator",{textarea:t+"textarea.html",fieldset:t+"fieldset.html",section:t+"section.html",conditional:t+"section.html",actions:t+"actions.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",password:t+"default.html",submit:t+"submit.html",button:t+"submit.html",radios:t+"radios.html",radiobuttons:t+"radio-buttons.html","default":t+"default.html"},[function(e){return e.readonly&&e.key&&"fieldset"!==e.type?t+"readonly.html":void 0}]),e.createDirectives({textarea:t+"textarea.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",submit:t+"submit.html",button:t+"submit.html",text:t+"default.html",date:t+"default.html",password:t+"default.html",datepicker:t+"datepicker.html",input:t+"default.html",radios:t+"radios.html",radiobuttons:t+"radio-buttons.html"})}]).directive("sfFieldset",function(){return{transclude:!0,scope:!0,templateUrl:"directives/decorators/bootstrap/fieldset-trcl.html",link:function(e,t,s){e.title=e.$eval(s.title)}}}); \ No newline at end of file +angular.module("schemaForm").run(["$templateCache",function(e){e.put("directives/decorators/bootstrap/actions-trcl.html",'
'),e.put("directives/decorators/bootstrap/actions.html",'
'),e.put("directives/decorators/bootstrap/checkbox.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/checkboxes.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/default.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
'),e.put("directives/decorators/bootstrap/fieldset-trcl.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/fieldset.html",'
{{ form.title }}
'),e.put("directives/decorators/bootstrap/help.html",'
'),e.put("directives/decorators/bootstrap/radio-buttons.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/radios.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/readonly.html",'
{{form.description}}
'),e.put("directives/decorators/bootstrap/section.html",'
'),e.put("directives/decorators/bootstrap/select.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
'),e.put("directives/decorators/bootstrap/submit.html",'
'),e.put("directives/decorators/bootstrap/tabs.html",'
'),e.put("directives/decorators/bootstrap/textarea.html",'
{{ (hasError() && errorMessage(schemaError())) || form.description}}
')}]),angular.module("schemaForm").config(["schemaFormDecoratorsProvider",function(e){var t="directives/decorators/bootstrap/";e.createDecorator("bootstrapDecorator",{textarea:t+"textarea.html",fieldset:t+"fieldset.html",tabs:t+"tabs.html",section:t+"section.html",conditional:t+"section.html",actions:t+"actions.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",password:t+"default.html",submit:t+"submit.html",button:t+"submit.html",radios:t+"radios.html",radiobuttons:t+"radio-buttons.html",help:t+"help.html","default":t+"default.html"},[function(e){return e.readonly&&e.key&&"fieldset"!==e.type?t+"readonly.html":void 0}]),e.createDirectives({textarea:t+"textarea.html",select:t+"select.html",checkbox:t+"checkbox.html",checkboxes:t+"checkboxes.html",number:t+"default.html",submit:t+"submit.html",button:t+"submit.html",text:t+"default.html",date:t+"default.html",password:t+"default.html",datepicker:t+"datepicker.html",input:t+"default.html",radios:t+"radios.html",radiobuttons:t+"radio-buttons.html"})}]).directive("sfFieldset",function(){return{transclude:!0,scope:!0,templateUrl:"directives/decorators/bootstrap/fieldset-trcl.html",link:function(e,t,s){e.title=e.$eval(s.title)}}}); \ No newline at end of file diff --git a/dist/schema-form.min.js b/dist/schema-form.min.js index b3c3c34f4..7f38a0c61 100644 --- a/dist/schema-form.min.js +++ b/dist/schema-form.min.js @@ -1 +1 @@ -angular.module("schemaForm",[]),angular.module("schemaForm").provider("schemaFormDecorators",["$compileProvider",function(e){var r="",t={},n=function(e,n){"sfDecorator"===e&&(e=r);for(var a=t[e],i=a.rules,o=0;o',link:function(e,t,n){var a={items:"c",titleMap:"c",schema:"c"},i={type:r},o=!0;angular.forEach(n,function(r,t){if("$"!==t[0]&&0!==t.indexOf("ng")&&"sfField"!==t){var u=function(r){angular.isDefined(r)&&r!==i[t]&&(i[t]=r,o&&i.type&&(i.key||angular.isUndefined(n.key))&&(e.form=i,o=!1))};"model"===t?e.$watch(r,function(r){r&&e.model!==r&&(e.model=r)}):"c"===a[t]?e.$watchCollection(r,u):n.$observe(t,u)}})}}})};this.createDecorator=function(e,n,i){t[e]={mappings:n||{},rules:i||[]},t[r]||(r=e),a(e)},this.createDirective=i,this.createDirectives=function(e){angular.forEach(e,function(e,r){i(r,e)})},this.directive=function(e){return e=e||r,t[e]},this.addMapping=function(e,r,n){t[e]&&(t[e].mappings[r]=n)},this.$get=function(){return{directive:function(e){return t[e]},defaultDecorator:r}},a("sfDecorator")}]),angular.module("schemaForm").provider("schemaForm",[function(){var e=function(e,r,t){var n=l[r.type];if(n)for(var a,i=0;i0){f.schema=c,f.form=l;var m=r.merge(c,l,s),p=document.createDocumentFragment();i.schemaForm={form:m,schema:c},angular.forEach(m,function(e,r){var a=document.createElement(u.sfDecorator||n(t.defaultDecorator,"-"));a.setAttribute("form","schemaForm.form["+r+"]"),p.appendChild(a)}),o.children(":not(.schema-form-ignore)").remove(),o[0].appendChild(p),e(o.children())(i),a(c,function(e,r){angular.isDefined(e["default"])&&i.$eval("model."+r+" = model."+r+" || defaltValue",{defaltValue:e["default"]})})}})}}}]),angular.module("schemaForm").directive("schemaValidate",function(){return{restrict:"A",scope:!1,require:"ngModel",link:function(e,r,t,n){e.ngModel=n;var a=null,i=e.$eval(t.schemaValidate);n.$parsers.unshift(function(r){if(i||(i=e.$eval(t.schemaValidate)),!i)return r;if(angular.isUndefined(r))return void 0;var o=r;"integer"===i.type?o=parseInt(o,10):"number"===i.type?o=parseFloat(o,10):"boolean"===i.type&&"string"==typeof r&&("true"===r?o=!0:"false"===r&&(o=!1));var u=tv4.validateResult(o,i);return u.valid?(n.$setValidity("schema",!0),a=null,r):(n.$setValidity("schema",!1),void(a=u.error))}),e.hasSuccess=function(){return n.$valid&&(!n.$pristine||!n.$isEmpty(n.$modelValue))},e.hasError=function(){return n.$invalid&&!n.$pristine},e.schemaError=function(){return a}}}}); \ No newline at end of file +try{angular.module("ngSanitize"),angular.module("schemaForm",["ngSanitize"])}catch(e){angular.module("schemaForm",[])}angular.module("schemaForm").provider("schemaFormDecorators",["$compileProvider",function(e){var r="",t={},n=function(e,n){"sfDecorator"===e&&(e=r);for(var a=t[e],i=a.rules,o=0;o',link:function(e,t,n){var a={items:"c",titleMap:"c",schema:"c"},i={type:r},o=!0;angular.forEach(n,function(r,t){if("$"!==t[0]&&0!==t.indexOf("ng")&&"sfField"!==t){var u=function(r){angular.isDefined(r)&&r!==i[t]&&(i[t]=r,o&&i.type&&(i.key||angular.isUndefined(n.key))&&(e.form=i,o=!1))};"model"===t?e.$watch(r,function(r){r&&e.model!==r&&(e.model=r)}):"c"===a[t]?e.$watchCollection(r,u):n.$observe(t,u)}})}}})};this.createDecorator=function(e,n,i){t[e]={mappings:n||{},rules:i||[]},t[r]||(r=e),a(e)},this.createDirective=i,this.createDirectives=function(e){angular.forEach(e,function(e,r){i(r,e)})},this.directive=function(e){return e=e||r,t[e]},this.addMapping=function(e,r,n){t[e]&&(t[e].mappings[r]=n)},this.$get=function(){return{directive:function(e){return t[e]},defaultDecorator:r}},a("sfDecorator")}]),angular.module("schemaForm").provider("schemaForm",[function(){var e=function(e,r,t){var n=l[r.type];if(n)for(var a,i=0;i0){f.schema=c,f.form=l;var m=r.merge(c,l,s),p=document.createDocumentFragment();i.schemaForm={form:m,schema:c},angular.forEach(m,function(e,r){var a=document.createElement(u.sfDecorator||n(t.defaultDecorator,"-"));a.setAttribute("form","schemaForm.form["+r+"]"),p.appendChild(a)}),o.children(":not(.schema-form-ignore)").remove(),o[0].appendChild(p),e(o.children())(i),a(c,function(e,r){angular.isDefined(e["default"])&&i.$eval("model."+r+" = model."+r+" || defaltValue",{defaltValue:e["default"]})})}})}}}]),angular.module("schemaForm").directive("schemaValidate",function(){return{restrict:"A",scope:!1,require:"ngModel",link:function(e,r,t,n){e.ngModel=n;var a=null,i=e.$eval(t.schemaValidate);n.$parsers.unshift(function(r){if(i||(i=e.$eval(t.schemaValidate)),!i)return r;if(angular.isUndefined(r))return void 0;var o=r;"integer"===i.type?o=parseInt(o,10):"number"===i.type?o=parseFloat(o,10):"boolean"===i.type&&"string"==typeof r&&("true"===r?o=!0:"false"===r&&(o=!1));var u=tv4.validateResult(o,i);return u.valid?(n.$setValidity("schema",!0),a=null,r):(n.$setValidity("schema",!1),void(a=u.error))}),e.hasSuccess=function(){return n.$valid&&(!n.$pristine||!n.$isEmpty(n.$modelValue))},e.hasError=function(){return n.$invalid&&!n.$pristine},e.schemaError=function(){return a}}}}); \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 91f5f1123..028b1f057 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,13 +9,15 @@ Documentation 1. [onChange](#onchange) 1. [Validation Messages](#validation-messages) 1. [Inline feedback icons](#inline-feedback-icons) -1. [Specific options per type](#specific-options-per-type) +1. [Specific options and types](#specific-options-and-types) 1. [fieldset and section](#fieldset-and-section) 1. [conditional](#conditional) 1. [select and checkboxes](#select-and-checkboxes) 1. [actions](#actions) 1. [button](#button) 1. [radios and radiobuttons](#radios-and-radiobuttons) + 1. [help](#help) + 1. [tabs](#tabs) 1. [Post process function](#post-process-function) Form types @@ -38,6 +40,8 @@ Schema Form currently supports the following form field types out of the box: | button | a button | | radios | radio buttons | | radiobuttons | radio buttons with bootstrap buttons | +| help | insert arbitrary html | +| tab | tabs with content | More field types can be added, for instance a "datepicker" type can be added by including the [datepicker addon](datepicker.md) @@ -224,8 +228,8 @@ Useful things in the decorators scope are -Specific options per type -------------------------- +Specific options and types +-------------------------- ### fieldset and section @@ -364,6 +368,100 @@ function FormCtrl($scope) { } ``` +### help +Help fields is not really a field, but instead let's you inser arbitrary HTML +into a form, suitable for help texts with links etc. + +It uses ```ng-bind-html``` so you need to include the +[ngSanitize](https://docs.angularjs.org/api/ngSanitize) module for it to work, +or expicitly turning of strict contextual escaping with +```$sceProvider.enabled(false)``` (not recomended). It's enough to include +```angular-sanitize.min.js``` before ```angular-schema.min.js``` and Schema Form +will pick up that it's there and include it as a module dependency. + +The get a help field you need to specify the type ```help``` and have a html +snippet as a string in the option ```helpvalue``` + +Ex. +```javascript +function FormCtrl($scope) { + $scope.schema = { + type: "object", + properties: { + name: { + title: "Name", + type: "string" + } + } + }; + + $scope.form = [ + { + type: "help", + helpvalue: "

Yo Ninja!

" + }, + "name" + ]; +} +``` + +### tabs +The ```tabs``` form type lets you split your form into tabs. It is similar to +```fieldset``` in that it just changes the presentation of the form. ```tabs``` +takes a option, also called ```tabs```, that is a list of tab objects. Each tab +object consist of a *title* and a *items* list of form objects. + +Ex. +```javascript +function FormCtrl($scope) { + $scope.schema = { + type: "object", + properties: { + name: { + title: "Name", + type: "string" + }, + nick: { + title: "Nick", + type: "string" + } + alias: { + title: "Alias", + type: "string" + } + tag: { + title: "Tag", + type: "string" + } + } + }; + + $scope.form = [ + "name", + { + type: "tabs", + tabs: [ + { + title: "Tab 1", + items: [ + "nick", + "alias" + ] + }, + { + title: "Tab 2", + items: [ + "tag" + ] + } + ] + } + ]; +} +``` + + + Post process function --------------------- diff --git a/examples/bootstrap-example.html b/examples/bootstrap-example.html index 85241393c..55320a68e 100644 --- a/examples/bootstrap-example.html +++ b/examples/bootstrap-example.html @@ -49,6 +49,7 @@

Schema

+ @@ -143,21 +144,45 @@

Schema

$scope.form = [ { - key: "name", - placeholder: "Check the console", - onChange: "log(modelValue)", - feedback: "{'glyphicon': true, 'glyphicon-ok': hasSuccess(), 'glyphicon-star': !hasSuccess() }" - }, - { - key: "favorite", - feedback: false + type: "fieldset", + title: "Stuff", + items: [ + { + type: "tabs", + tabs: [ + { + title: "Simple stuff", + items: [ + { + key: "name", + placeholder: "Check the console", + onChange: "log(modelValue)", + feedback: "{'glyphicon': true, 'glyphicon-ok': hasSuccess(), 'glyphicon-star': !hasSuccess() }" + }, + { + key: "favorite", + feedback: false + } + ] + }, + { + title: "More stuff", + items: [ + "attributes", + { + key: "shoesize", + feedback: false + }, + "things" + ] + }] + }, + ] }, - "attributes", { - key: "shoesize", - feedback: false + type: "help", + helpvalue: "
" }, - "things", "soul", { type: "conditional", diff --git a/package.json b/package.json index 1e3aa25b1..336b0f63f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-schema-form", - "version": "0.3.0", + "version": "0.4.0", "description": "Create forms from a JSON schema", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/src/directives/decorators/bootstrap/bootstrap-decorator.js b/src/directives/decorators/bootstrap/bootstrap-decorator.js index 154216870..5233d94f7 100644 --- a/src/directives/decorators/bootstrap/bootstrap-decorator.js +++ b/src/directives/decorators/bootstrap/bootstrap-decorator.js @@ -4,6 +4,7 @@ angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(dec decoratorsProvider.createDecorator('bootstrapDecorator',{ textarea: base+'textarea.html', fieldset: base+'fieldset.html', + tabs: base+'tabs.html', section: base+'section.html', conditional: base+'section.html', actions: base+'actions.html', @@ -16,6 +17,7 @@ angular.module('schemaForm').config(['schemaFormDecoratorsProvider',function(dec button: base+'submit.html', radios: base+'radios.html', radiobuttons: base+'radio-buttons.html', + help: base+'help.html', 'default': base+'default.html' },[ function(form){ diff --git a/src/directives/decorators/bootstrap/help.html b/src/directives/decorators/bootstrap/help.html new file mode 100644 index 000000000..863a260ef --- /dev/null +++ b/src/directives/decorators/bootstrap/help.html @@ -0,0 +1 @@ +
diff --git a/src/directives/decorators/bootstrap/tabs.html b/src/directives/decorators/bootstrap/tabs.html new file mode 100644 index 000000000..59e954b7f --- /dev/null +++ b/src/directives/decorators/bootstrap/tabs.html @@ -0,0 +1,19 @@ +
+ + +
+
+ + +
+
+
diff --git a/src/module.js b/src/module.js index 3feb69ef3..8dc5ad7db 100644 --- a/src/module.js +++ b/src/module.js @@ -1 +1,9 @@ -angular.module('schemaForm',[]); \ No newline at end of file +//Its up to the user to use form type help or not. +try { + //This throws an expection if module does not exist. + angular.module('ngSanitize'); + + angular.module('schemaForm',['ngSanitize']); +} catch (e) { + angular.module('schemaForm',[]); +} diff --git a/src/services/schema-form.js b/src/services/schema-form.js index 28626e040..e457b67df 100644 --- a/src/services/schema-form.js +++ b/src/services/schema-form.js @@ -248,6 +248,12 @@ angular.module('schemaForm').provider('schemaForm',[function(){ obj.items = service.merge(schema,obj.items,ignore); } + //if its has tabs, merge them also! + if (obj.tabs) { + angular.forEach(obj.tabs,function(tab){ + tab.items = service.merge(schema,tab.items,ignore); + }); + } //extend with std form from schema. if (obj.key && lookup[obj.key]) { diff --git a/test/schema-form-test.js b/test/schema-form-test.js index dc5298f99..59268e335 100644 --- a/test/schema-form-test.js +++ b/test/schema-form-test.js @@ -851,6 +851,115 @@ describe('Schema form',function(){ }); }); + it('should render custom html when type "help" is specified',function(){ + + //We don't need no sanitation. We don't need no though control. + module(function($sceProvider){ + $sceProvider.enabled(false); + }); + + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.person = { }; + + scope.schema = { + type: "object", + properties: { + name: { + type: "string", + } + } + }; + + + scope.form = [ + { + type: "help", + helpvalue: "

Yo Ninja!

" + }, + "name" + ]; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + tmpl.children().length.should.eq(2); + tmpl.children().eq(0).is('div').should.be.true; + tmpl.children().eq(0).children().length.should.eq(1); + tmpl.children().eq(0).children().html().should.be.eq("Yo Ninja!"); + + }); + }); + + it.only('should render tabs with items in them when specified',function(){ + + inject(function($compile,$rootScope){ + var scope = $rootScope.$new(); + scope.person = { }; + + scope.schema = { + type: "object", + properties: { + name: { type: "string", title: "Name" }, + alias: { type: "string", title: "Alias" }, + nick: { type: "string", title: "Nickname" }, + tag: { type: "string", title: "Tag" }, + } + }; + + scope.form = [ + { + type: "tabs", + tabs: [ + { + title: "Tab 1", + items: [ + "name", + "tag" + ] + }, + { + title: "Tab 2", + items: [ + "alias", + "nick" + ] + } + ] + }, + ]; + + var tmpl = angular.element('
'); + + $compile(tmpl)(scope); + $rootScope.$apply(); + + tmpl.children().length.should.eq(1); + var tabs = tmpl.children().children().eq(0); + var panes = tmpl.children().children().eq(1); + + tabs.is('ul').should.be.true; + tabs.children().length.should.be.eq(2); + tabs.children().eq(0).children().html().should.equal('Tab 1'); + tabs.children().eq(1).children().html().should.equal('Tab 2'); + + panes.is('div').should.be.true; + + panes.children().length.should.be.eq(2); + panes.children().eq(0).children().length.should.be.eq(2); + panes.children().eq(0).children().eq(0).find('label').html().should.eq('Name'); + panes.children().eq(0).children().eq(1).find('label').html().should.eq('Tag'); + + panes.children().eq(1).children().length.should.be.eq(2); + panes.children().eq(1).children().eq(0).find('label').html().should.eq('Alias'); + panes.children().eq(1).children().eq(1).find('label').html().should.eq('Nickname'); + + }); + }); + + });