diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 6498081..131c27b 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -2,6 +2,34 @@ X-editable changelog
 =============================
 
 
+Version 1.2.0 Dec 6, 2012
+----------------------------  
+[enh #36] 'submit' method: added 'ajaxOptions' property to modify ajax request (vitalets)  
+[enh] inputs now internally use 'value2submit' method instead of previous 'value2str' (vitalets)  
+[enh] editableContainer removed from docs (vitalets)  
+[enh] editableContainer: removed 'autohide' option and 'cancel' event. Use 'hidden' event instead (vitalets)  
+[enh] 'hidden' event: added param 'reason' that points to reason caused hiding (vitalets)  
+[enh] 'select' submit by enter (vitalets)  
+[bug #37] fix incorrectly shown datepicker in jquery 1.7.1 + webkit (vitalets)  
+[enh] added url param 'jquery' to run tests in different versions of jquery, e.g. '&jquery=1.7.2' (vitalets)  
+[enh] 'enablefocus' option removed. More efficient to use 'save/hide' events to set focus to any element (vitalets)  
+[enh] 'init' event was added due to removal of render event (vitalets)  
+[enh] 'render' event was removed, use 'display' callback instead (vitalets)  
+[enh] 'checklist' submit value as array, not comma separated string (vitalets)  
+[enh] 'checklist' was refactored: options 'viewseparator', 'limit', 'limitText' are supressed by 'display' callback (vitalets)  
+[enh] new option: 'display' callback. Makes far more flexible rendering value into element's text. (vitalets)  
+[bug] fix typos (atrophic) 
+[enh] all callbacks scope changed to element (vitalets) 
+[enh] new option: 'savenochange' to save or cancel value when it was not changed in form (vitalets) 
+[enh] composite pk can be defined as JSON in data-pk attribute (vitalets) 
+[enh #30] new option 'sourceCache' true|false to disable cache for select (vitalets) 
+[bug #34] inputclass span* broken with fluid bootstrap layout. Classes changed to 'input-*'. (vitalets) 
+[enh] utils now added to $.fn.editableutils instead of $.fn.editableform.utils (vitalets)
+[enh] input types now added to $.fn.editabletypes instead of $.fn.editableform.types (vitalets)
+[enh] playground and tests now use requirejs (vitalets)  
+[bug #27] 'today' button toggle bug in bootstrap-datepicker (vitalets)  
+
+
 Version 1.1.1 Nov 30, 2012
 ----------------------------   
 [enh] 'showbuttons' option to hide buttons in form (vitalets)  
@@ -29,7 +57,7 @@ Version 1.0.1 Nov 22, 2012
 [enh] contribution guide in README.md (vitalets)   
 [enh #7] 'shown', 'hidden' events added (vitalets)         
 [enh #1] params can be a function to calculate it dynamically (vitalets)         
-[enh #6] do not preventDetault() in click when toggle='manual'. This allows to have clickable links (vitalets)         
+[enh #6] do not preventDefault() in click when toggle='manual'. This allows to have clickable links (vitalets)
 [bug #3] should not mark element with unsave css if url is user's function (vitalets)         
           
                  
@@ -48,13 +76,13 @@ Here list of differences to help you to upgrade your application:
 [change] 'toggle' option value can be only click|manual (not toggling element id). In case of 'manual' you should write handler calling 'show' method.
 [change] 'validate' option cannot be defined as object anymore.
 [change] events 'init', 'update', 'shown', 'hidden' removed. Events 'save', 'cancel' added. Event 'render' remains. 
-[change] input's optiom 'template' renamed to 'tpl' (to exclude conflict with container's template).
-[change] value can be stored internaly as object (previously was always string). Usefull for date inupt.
+[change] input's option 'template' renamed to 'tpl' (to exclude conflict with container's template).
+[change] value can be stored internally as object (previously was always string). Useful for date input.
 [change] 'error' callback option is removed. 'success' callback remained.
-[enh] 'source' option in select can be array of structure [{value: 1, text: 'abc'}, {...}]. This allows to keep ordering of items in dropdown list. Previous format is supported for compability.
+[enh] 'source' option in select can be array of structure [{value: 1, text: 'abc'}, {...}]. This allows to keep ordering of items in dropdown list. Previous format is supported for compatibility.
 [enh] api method 'setValue' to set manually value of editable.
 [change] locales directory is excluded from bootstrap-datepicker input. If you need localization you should jus download corresponding file from github.
 [change] date and dateui specific options can be set only via 'datepicker' option in first level of config (previously it was possible to set some options directly in config, e.g. weekStart).
-[change] if 'url' option defined as function - it is used as submit method instead of ajax (previously it was dynamically return url string and ajax occured anyway)
+[change] if 'url' option defined as function - it is used as submit method instead of ajax (previously it was dynamically return url string and ajax occurred anyway)
 
-Also all known bugs of bootstrap-editable were closed.
\ No newline at end of file
+Also all known bugs of bootstrap-editable were closed.
diff --git a/README.md b/README.md
index aa774c1..1f3320b 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ It is a new life of [bootstrap-editable plugin](http://github.com/vitalets/boots
 See **http://vitalets.github.com/x-editable**
 
 ## Reporting issues
-When creating issues please provide jsFiddle example. You can just fork [this fiddle](http://jsfiddle.net/xBB5x/5/) as starting point.  
+When creating issues please provide jsFiddle example. You can easily fork one of templates: [bootstrap](http://jsfiddle.net/xBB5x/25/), [jqueryui](http://jsfiddle.net/xBB5x/24/), [plain](http://jsfiddle.net/xBB5x/23/).  
 Your feedback is very appreciated!
 
 ## Contribution
@@ -19,7 +19,7 @@ A few steps how to start contributing:
 **x-editable**  
  | -- **lib** (repo related to <code>dev</code> and <code>master</code> branches)  
  | -- **gh-pages** (repo related to <code>gh-pages</code> branch for docs & demo)  
- | -- **playground** (simple node-server and html page for testing, [playground.zip](https://github.com/downloads/vitalets/x-editable/playground.zip))      
+ | -- **playground** (simple node-server and html page for testing, [playground_1.2.zip](https://github.com/downloads/vitalets/x-editable/playground_1.2.zip), **updated in 1.2.0!**)      
 
 To make it easy follow this script ( _assuming you have [nodejs](http://nodejs.org) installed_ ).
 Please replace <code>&lt;your-github-name&gt;</code> with your name:
@@ -43,7 +43,7 @@ npm install
 cd ..
 
 #playground 
-#download playground.zip from https://github.com/downloads/vitalets/x-editable/playground.zip
+#download playground.zip from https://github.com/downloads/vitalets/x-editable/playground_1.2.zip
 unzip playground.zip
 cd playground
 npm install 
diff --git a/grunt.js b/grunt.js
index 4282da2..e7427c9 100644
--- a/grunt.js
+++ b/grunt.js
@@ -100,10 +100,19 @@ module.exports = function(grunt) {
 
  //module for testing
  var module = ''; 
-//module = '&module=textarea';
+// module = '&module=textarea';
 //module = '&module=select';
 //module = '&module=text';
 
+ var qunit_testover = [];
+ ['bootstrap', 'jqueryui', 'plain'].forEach(function(f){
+     ['popup', 'inline'].forEach(function(c){
+         ['1.6.4', '1.7.1', '1.7.2', '1.8.2', '1.8.3'].forEach(function(jqver) {
+             qunit_testover.push('http://localhost:8000/test/index.html?f='+f+'&c='+c+'&jquery='+jqver+module); 
+         });
+     });
+ });    
+
  //get js and css for different builds
  var files = getFiles();
  
@@ -133,8 +142,9 @@ module.exports = function(grunt) {
       plain: [
                   'http://localhost:8000/test/index.html?f=plain&c=popup'+module,
                   'http://localhost:8000/test/index.html?f=plain&c=inline'+module
-                 ]                                    
-//      files: ['test/index.html']
+                 ],            
+      //test all builds under several versions of jquery                                   
+      testover: qunit_testover
     },
     server: {
         port: 8000,
@@ -208,38 +218,14 @@ module.exports = function(grunt) {
          }
        } 
     },
-    
-    //compress does not work properly for MAC OS (see https://github.com/vitalets/bootstrap-editable/issues/19)
-    //zip will be created manually
-    /*
-    compress: {
-        zip: {
-            options: {
-                mode: "zip",
-                //TODO: unfortunatly here <%= dist_source %> and <config:dist_source> does not work
-                basePath: "dist"
-               },
-            files: {
-                "<%= dist %>/bootstrap-editable-v<%= pkg.version %>.zip": ["<%= dist_source %>/ **", "<%= dist %>/libs/ **"]
-            }
-        },
-        tgz: {
-            options: {
-                mode: "tgz",
-                basePath: "dist"
-               },
-            files: {
-                "<%= dist %>/bootstrap-editable-v<%= pkg.version %>.tar.gz": ["<%= dist_source %>/ **", "<%= dist %>/libs/ **"]
-            }
-        }
-    },
-    */    
+ 
     uglify: {}
   });
 
   //test task
   grunt.registerTask('test', 'lint server qunit:bootstrap');
-  grunt.registerTask('testall', 'lint server qunit');  
+  grunt.registerTask('testall', 'lint server qunit:bootstrap qunit:jqueryui qunit:plain');  
+  grunt.registerTask('testover', 'lint server qunit:testover');  
   
   // Default task.
 //  grunt.registerTask('default', 'lint qunit');
diff --git a/package.json b/package.json
index e0700a2..5f92913 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
   "name": "X-editable",
   "title": "X-editable",
   "description": "In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery",
-  "version": "1.1.1",
+  "version": "1.2.0",
   "homepage": "http://github.com/vitalets/x-editable",
   "author": {
     "name": "Vitaliy Potapov",
diff --git a/src/containers/editable-container.css b/src/containers/editable-container.css
index 1a91e98..bebf690 100644
--- a/src/containers/editable-container.css
+++ b/src/containers/editable-container.css
@@ -1,5 +1,5 @@
 .editable-container {
-    max-width: none; /* without this rule poshytip does not stretch */
+    max-width: none !important; /* without this rule poshytip/tooltip does not stretch */
 }  
 
 .editable-container.popover {
@@ -10,4 +10,8 @@
 .editable-container.editable-inline {
     display: inline; 
     vertical-align: middle;
+}
+
+.editable-container.ui-widget {
+   font-size: inherit;  /* jqueryui widget font 1.1em too big, overwrite it */
 }
\ No newline at end of file
diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js
index 0029eba..1094c46 100644
--- a/src/containers/editable-container.js
+++ b/src/containers/editable-container.js
@@ -20,7 +20,7 @@ Applied as jQuery method.
         init: function(element, options) {
             this.$element = $(element);
             //todo: what is in priority: data or js?
-            this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableform.utils.getConfigData(this.$element), options);         
+            this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);         
             this.splitOptions();
             this.initContainer();
 
@@ -75,12 +75,15 @@ Applied as jQuery method.
         },
 
         initForm: function() {
+            this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
             this.$form = $('<div>')
             .editableform(this.formOptions)
             .on({
                 save: $.proxy(this.save, this),
-                cancel: $.proxy(this.cancel, this),
-                show: $.proxy(this.setPosition, this), //re-position container every time form is shown (after loading state)
+                cancel: $.proxy(function(){
+                   this.hide('cancel'); 
+                }, this),
+                show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
                 rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
                 rendered: $.proxy(function(){
                     /**        
@@ -119,7 +122,7 @@ Applied as jQuery method.
         /**
         Shows container with form
         @method show()
-        @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
         **/          
         show: function (closeAll) {
             this.$element.addClass('editable-open');
@@ -143,8 +146,9 @@ Applied as jQuery method.
         /**
         Hides container with form
         @method hide()
+        @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|undefined (=manual)</code>
         **/         
-        hide: function() {  
+        hide: function(reason) {  
             if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
                 return;
             }
@@ -154,9 +158,17 @@ Applied as jQuery method.
             Fired when container was hidden. It occurs on both save or cancel.
 
             @event hidden 
-            @param {Object} event event object
+            @param {object} event event object
+            @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|undefined (=manual)</code>
+            @example
+            $('#username').on('hidden', function(e, reason) {
+                if(reason === 'save' || reason === 'cancel') {
+                    //auto-open next editable
+                    $(this).closest('tr').next().find('.editable').editable('show');
+                } 
+            });            
             **/             
-            this.$element.triggerHandler('hidden');   
+            this.$element.triggerHandler('hidden', reason);   
         },
         
         /* internal hide method. To be overwritten in child classes */
@@ -167,7 +179,7 @@ Applied as jQuery method.
         /**
         Toggles container visibility (show / hide)
         @method toggle()
-        @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
         **/          
         toggle: function(closeAll) {
             if(this.tip && this.tip().is(':visible')) {
@@ -185,23 +197,8 @@ Applied as jQuery method.
             //tbd in child class
         },
 
-        cancel: function() {     
-            if(this.options.autohide) {
-                this.hide();
-            }
-            /**        
-            Fired when form was cancelled by user
-            
-            @event cancel 
-            @param {Object} event event object
-            **/             
-            this.$element.triggerHandler('cancel');
-        },
-
         save: function(e, params) {
-            if(this.options.autohide) {
-                this.hide();
-            }
+            this.hide('save');
             /**        
             Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
             
@@ -262,8 +259,8 @@ Applied as jQuery method.
         */
         closeOthers: function(element) {
             $('.editable-open').each(function(i, el){
-                //do nothing with passed element
-                if(el === element) {
+                //do nothing with passed element and it's children
+                if(el === element || $(el).find(element).length) {
                     return;
                 }
 
@@ -276,7 +273,7 @@ Applied as jQuery method.
                 }
                 
                 if(ec.options.onblur === 'cancel') {
-                    $el.data('editableContainer').hide();
+                    $el.data('editableContainer').hide('onblur');
                 } else if(ec.options.onblur === 'submit') {
                     $el.data('editableContainer').tip().find('form').submit();
                 }
@@ -350,7 +347,7 @@ Applied as jQuery method.
         **/        
         placement: 'top',
         /**
-        Wether to hide container on save/cancel.
+        Whether to hide container on save/cancel.
 
         @property autohide 
         @type boolean
@@ -365,6 +362,7 @@ Applied as jQuery method.
         @property onblur 
         @type string
         @default 'cancel'
+        @since 1.1.1
         **/        
         onblur: 'cancel'
     };
diff --git a/src/containers/editable-inline.js b/src/containers/editable-inline.js
index df585a6..05800dc 100644
--- a/src/containers/editable-inline.js
+++ b/src/containers/editable-inline.js
@@ -43,10 +43,6 @@
         innerHide: function () {
             this.$form.hide(this.options.anim, $.proxy(function() {
                 this.$element.show();
-                //return focus on element
-                if (this.options.enablefocus) {
-                    this.$element.focus();
-                }  
             }, this)); 
         },
         
@@ -57,8 +53,7 @@
 
     //defaults
     $.fn.editableContainer.defaults = $.extend({}, $.fn.editableContainer.defaults, {
-        anim: 'fast',
-        enablefocus: false
+        anim: 'fast'
     });    
 
 
diff --git a/src/containers/editable-popover.js b/src/containers/editable-popover.js
index e369b6f..863148b 100644
--- a/src/containers/editable-popover.js
+++ b/src/containers/editable-popover.js
@@ -14,7 +14,7 @@
             $.extend(this.containerOptions, {
                 trigger: 'manual',
                 selector: false,
-                content: 'dfgh'
+                content: ' '
             });
             this.call(this.containerOptions);
         },        
diff --git a/src/editable-form/editable-form-utils.js b/src/editable-form/editable-form-utils.js
index 4d8169d..aa22f64 100644
--- a/src/editable-form/editable-form-utils.js
+++ b/src/editable-form/editable-form-utils.js
@@ -2,10 +2,11 @@
 * EditableForm utilites
 */
 (function ($) {
-    $.fn.editableform.utils = {
+    //utils
+    $.fn.editableutils = {
         /**
         * classic JS inheritance function
-        */
+        */  
         inherit: function (Child, Parent) {
             var F = function() { };
             F.prototype = Parent.prototype;
@@ -116,6 +117,13 @@
                 return k;
             }
 
-        }   
+        },
+        
+       /**
+        method to escape html.
+       **/
+       escape: function(str) {
+           return $('<div>').text(str).html();
+       }           
     };      
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/editable-form/editable-form.css b/src/editable-form/editable-form.css
index 874705a..c7bc226 100644
--- a/src/editable-form/editable-form.css
+++ b/src/editable-form/editable-form.css
@@ -16,6 +16,8 @@
    *display: inline;
 }
 
+
+
 .editable-input {
     vertical-align: top; 
     display: inline-block; /* should be inline to take effect of parent's white-space: nowrap */
@@ -31,8 +33,9 @@
 }
 
 /*for jquery-ui buttons need set height to look more pretty*/
-.editable-buttons button.ui-button {
+.editable-buttons button.ui-button-icon-only {
    height: 24px; 
+   width: 30px;
 }
 
 .editableform-loading {
@@ -79,6 +82,10 @@
     margin: 0;
 }
 
+.editable-checklist label {
+    white-space: nowrap; 
+}
+
 .editable-clear {
    clear: both;
    font-size: 0.9em;
diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js
index a2db423..f85e170 100644
--- a/src/editable-form/editable-form.js
+++ b/src/editable-form/editable-form.js
@@ -1,7 +1,7 @@
 /**
 Form with single input element, two buttons and two states: normal/loading.
-Applied as jQuery method to DIV tag (not to form tag!)
-Editableform is linked with one of input types, e.g. 'text' or 'select'.
+Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
+Editableform is linked with one of input types, e.g. 'text', 'select' etc.
 
 @class editableform
 @uses text
@@ -9,9 +9,12 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
 **/
 (function ($) {
 
-    var EditableForm = function (element, options) {
+    var EditableForm = function (div, options) {
         this.options = $.extend({}, $.fn.editableform.defaults, options);
-        this.$element = $(element); //div, containing form. Not form tag! Not editable-element.
+        this.$div = $(div); //div, containing form. Not form tag! Not editable-element.
+        if(!this.options.scope) {
+            this.options.scope = this;
+        }
         this.initInput();
     };
 
@@ -21,9 +24,9 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
             var TypeConstructor, typeOptions;
 
             //create input of specified type
-            if(typeof $.fn.editableform.types[this.options.type] === 'function') {
-                TypeConstructor = $.fn.editableform.types[this.options.type];
-                typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
+            if(typeof $.fn.editabletypes[this.options.type] === 'function') {
+                TypeConstructor = $.fn.editabletypes[this.options.type];
+                typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
                 this.input = new TypeConstructor(typeOptions);
             } else {
                 $.error('Unknown type: '+ this.options.type);
@@ -45,7 +48,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
         **/        
         render: function() {
             this.$loading = $($.fn.editableform.loading);        
-            this.$element.empty().append(this.$loading);
+            this.$div.empty().append(this.$loading);
             this.showLoading();
             
             //init form template and buttons
@@ -61,7 +64,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
             @event rendering 
             @param {Object} event event object
             **/            
-            this.$element.triggerHandler('rendering');
+            this.$div.triggerHandler('rendering');
 
             //render input
             $.when(this.input.render())
@@ -80,21 +83,23 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
                 }                
 
                 //append form to container
-                this.$element.append(this.$form);
-
+                this.$div.append(this.$form);
+                 
                 //attach 'cancel' handler
                 this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
-                //                this.$form.find('.editable-buttons button').eq(1).click($.proxy(this.cancel, this));
 
                 if(this.input.error) {
                     this.error(this.input.error);
                     this.$form.find('.editable-submit').attr('disabled', true);
                     this.input.$input.attr('disabled', true);
+                    //prevent form from submitting
+                    this.$form.submit(function(e){ e.preventDefault(); });
                 } else {
                     this.error(false);
                     this.input.$input.removeAttr('disabled');
                     this.$form.find('.editable-submit').removeAttr('disabled');
                     this.input.value2input(this.value);
+                    //attach submit handler
                     this.$form.submit($.proxy(this.submit, this));
                 }
 
@@ -103,7 +108,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
                 @event rendered
                 @param {Object} event event object
                 **/            
-                this.$element.triggerHandler('rendered');                
+                this.$div.triggerHandler('rendered');                
 
                 this.showForm();
             }, this));
@@ -114,7 +119,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
             @event cancel 
             @param {Object} event event object
             **/              
-            this.$element.triggerHandler('cancel');
+            this.$div.triggerHandler('cancel');
         },
         showLoading: function() {
             var w;
@@ -133,16 +138,18 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
             this.$loading.show(); 
         },
 
-        showForm: function() {
+        showForm: function(activate) {
             this.$loading.hide();
             this.$form.show();
-            this.input.activate(); 
+            if(activate !== false) {
+                this.input.activate(); 
+            }
             /**        
             Fired when form is shown
             @event show 
             @param {Object} event event object
             **/                    
-            this.$element.triggerHandler('show');
+            this.$div.triggerHandler('show');
         },
 
         error: function(msg) {
@@ -163,8 +170,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
             e.preventDefault();
             
             var error,
-                newValue = this.input.input2value(), //get new value from input
-                newValueStr;
+                newValue = this.input.input2value(); //get new value from input
 
             //validation
             if (error = this.validate(newValue)) {
@@ -173,25 +179,29 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
                 return;
             } 
             
-            //value as string
-            newValueStr = this.input.value2str(newValue);
-
             //if value not changed --> cancel
             /*jslint eqeq: true*/
-            if (newValueStr == this.input.value2str(this.value)) {
+            if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
             /*jslint eqeq: false*/                
                 this.cancel();
                 return;
             } 
 
             //sending data to server
-            $.when(this.save(newValueStr))
+            $.when(this.save(newValue))
             .done($.proxy(function(response) {
                 //run success callback
-                var res = typeof this.options.success === 'function' ? this.options.success.call(this, response, newValue) : null;
+                var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
                 
-                //if success callback returns string --> show error
-                if(res && typeof res === 'string') {
+                //if success callback returns false --> keep form open and do not activate input
+                if(res === false) {
+                    this.error(false);
+                    this.showForm(false);
+                    return;
+                }     
+                
+                //if success callback returns string -->  keep form open, show error and activate input               
+                if(typeof res === 'string') {
                     this.error(res);
                     this.showForm();
                     return;
@@ -218,7 +228,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
                     if(params.newValue === 'username') {...}
                 });                    
                 **/                
-                this.$element.triggerHandler('save', {newValue: newValue, response: response});
+                this.$div.triggerHandler('save', {newValue: newValue, response: response});
             }, this))
             .fail($.proxy(function(xhr) {
                 this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'); 
@@ -226,10 +236,16 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
             }, this));
         },
 
-        save: function(value) {
-            var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this) : this.options.pk,
+        save: function(newValue) {
+            //convert value for submitting to server
+            var submitValue = this.input.value2submit(newValue);
+            
+            //try parse composite pk defined as json string in data-pk 
+            this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 
+            
+            var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
             send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
-            params, ajaxOptions;
+            params;
 
             if (send) { //send to server
                 this.showLoading();
@@ -237,30 +253,29 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
                 //standard params
                 params = {
                     name: this.options.name || '',
-                    value: value,
+                    value: submitValue,
                     pk: pk 
                 };
 
                 //additional params
                 if(typeof this.options.params === 'function') {
-                    $.extend(params, this.options.params.call(this, params));  
+                    $.extend(params, this.options.params.call(this.options.scope, params));  
                 } else {
                     //try parse json in single quotes (from data-params attribute)
-                    this.options.params = $.fn.editableform.utils.tryParseJson(this.options.params, true);   
+                    this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);   
                     $.extend(params, this.options.params);
                 }
 
                 if(typeof this.options.url === 'function') { //user's function
-                    return this.options.url.call(this, params);
-                } else {  //send ajax to server and return deferred object
-                    ajaxOptions = $.extend({
+                    return this.options.url.call(this.options.scope, params);
+                } else {  
+                    //send ajax to server and return deferred object
+                    return $.ajax($.extend({
                         url     : this.options.url,
                         data    : params,
                         type    : 'post',
                         dataType: 'json'
-                    }, this.options.ajaxOptions);
-
-                    return $.ajax(ajaxOptions);
+                    }, this.options.ajaxOptions));
                 }
             }
         }, 
@@ -270,7 +285,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
                 value = this.value;
             }
             if (typeof this.options.validate === 'function') {
-                return this.options.validate.call(this, value);
+                return this.options.validate.call(this.options.scope, value);
             }
         },
 
@@ -377,7 +392,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
         name: null,
         /**
         Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
-        Can be calculated dinamically via function.
+        Can be calculated dynamically via function.
 
         @property pk 
         @type string|object|function
@@ -418,7 +433,7 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
         validate: null,
         /**
         Success callback. Called when value successfully sent on server and **response status = 200**.  
-        Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
+        Useful to work with json response. For example, if your backend response can be <code>{success: true}</code>
         or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.  
         If it returns **string** - means error occured and string is shown as error message.  
         If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user.  
@@ -440,28 +455,44 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
         @property ajaxOptions 
         @type object
         @default null
+        @since 1.1.1        
         **/        
         ajaxOptions: null,
         /**
-        Wether to show buttons or not.  
+        Whether to show buttons or not.  
         Form without buttons can be auto-submitted by input or by onblur = 'submit'.
+        @example 
+        ajaxOptions: {
+            method: 'PUT',
+            dataType: 'xml'
+        }
 
         @property showbuttons 
         @type boolean
         @default true
+        @since 1.1.1
         **/         
-        showbuttons: true
-        
-        /*todo: 
-        Submit strategy. Can be <code>normal|never</code>
-        <code>submitmode='never'</code> usefull for turning into classic form several inputs and submitting them together manually.
-        Works pretty with <code>showbuttons=false</code>
+        showbuttons: true,
+        /**
+        Scope for callback methods (success, validate).  
+        If <code>null</code> means editableform instance itself. 
 
-        @property submitmode 
-        @type string
-        @default normal
-        */         
-//        submitmode: 'normal' 
+        @property scope 
+        @type DOMElement|object
+        @default null
+        @since 1.2.0
+        @private
+        **/            
+        scope: null,
+        /**
+        Whether to save or cancel value when it was not changed but form was submitted
+
+        @property savenochange 
+        @type boolean
+        @default false
+        @since 1.2.0
+        **/
+        savenochange: false         
     };   
 
     /*
@@ -482,15 +513,9 @@ Editableform is linked with one of input types, e.g. 'text' or 'select'.
     $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
     '<button type="button" class="editable-cancel">cancel</button>';      
 
-    //error class attahced to control-group
+    //error class attached to control-group
     $.fn.editableform.errorGroupClass = null;  
 
-    //error class attahced to editable-error-block
+    //error class attached to editable-error-block
     $.fn.editableform.errorBlockClass = 'editable-error';
-
-    //input types
-    $.fn.editableform.types = {};
-    //utils
-    $.fn.editableform.utils = {};
-
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/element/editable-element.js b/src/element/editable-element.js
index bd23548..1a20c7b 100644
--- a/src/element/editable-element.js
+++ b/src/element/editable-element.js
@@ -8,7 +8,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
 
     var Editable = function (element, options) {
         this.$element = $(element);
-        this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableform.utils.getConfigData(this.$element), options);  
+        this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableutils.getConfigData(this.$element), options);  
         this.init();
     };
 
@@ -20,9 +20,6 @@ Makes editable any HTML element on the page. Applied as jQuery method.
                 doAutotext, 
                 finalize;
 
-            //initialization flag    
-            this.isInit = true;    
-                
             //editableContainer must be defined
             if(!$.fn.editableContainer) {
                 $.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)');
@@ -33,9 +30,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             this.options.name = this.options.name || this.$element.attr('id');
              
             //create input of specified type. Input will be used for converting value, not in form
-            if(typeof $.fn.editableform.types[this.options.type] === 'function') {
-                TypeConstructor = $.fn.editableform.types[this.options.type];
-                this.typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
+            if(typeof $.fn.editabletypes[this.options.type] === 'function') {
+                TypeConstructor = $.fn.editabletypes[this.options.type];
+                this.typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
                 this.input = new TypeConstructor(this.typeOptions);
             } else {
                 $.error('Unknown type: '+ this.options.type);
@@ -47,13 +44,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
                 this.value = this.input.html2value($.trim(this.$element.html()));
                 isValueByText = true;
             } else {
+                /*
+                  value can be string when received from 'data-value' attribute
+                  for complext objects value can be set as json string in data-value attribute, 
+                  e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
+                */
+                this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true); 
                 if(typeof this.options.value === 'string') {
-                   this.options.value = $.trim(this.options.value);
+                    this.value = this.input.str2value(this.options.value);
+                } else {
+                    this.value = this.options.value;
                 }
-                this.value = this.input.str2value(this.options.value);
             }
             
-            //add 'editable' class
+            //add 'editable' class to every editable element
             this.$element.addClass('editable');
             
             //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
@@ -81,29 +85,46 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             //if value was generated by text or value is empty, no sense to run autotext
             doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
             doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
-            $.when(doAutotext ? this.input.value2html(this.value, this.$element) : true).then($.proxy(function() {
+            $.when(doAutotext ? this.render() : true).then($.proxy(function() {
                 if(this.options.disabled) {
                     this.disable();
                 } else {
                     this.enable(); 
                 }
                /**        
-               Fired each time when element's text is rendered. Occurs on initialization and on each update of value.
-               Can be used for display customization.
+               Fired when element was initialized by editable method.
                               
-               @event render 
+               @event init 
                @param {Object} event event object
                @param {Object} editable editable instance
-               @example
-               $('#action').on('render', function(e, editable) {
-                    var colors = {0: "gray", 1: "green", 2: "blue", 3: "red"};
-                    $(this).css("color", colors[editable.value]);  
-               });                  
+               @since 1.2.0
                **/                  
-                this.$element.triggerHandler('render', this);
-                this.isInit = false;    
+                this.$element.triggerHandler('init', this);
             }, this));
         },
+
+        /*
+        Renders value into element's text.
+        Can call custom display method from options.
+        Can return deferred object.
+        @method render()
+        */          
+        render: function() {
+            //do not display anything
+            if(this.options.display === false) {
+                return;
+            }
+            //if it is input with source, we pass callback in third param to be called when source is loaded
+            if(this.input.options.hasOwnProperty('source')) {
+                return this.input.value2html(this.value, this.$element[0], this.options.display); 
+            //if display method defined --> use it    
+            } else if(typeof this.options.display === 'function') {
+                return this.options.display.call(this.$element[0], this.value);
+            //else use input's original value2html() method    
+            } else {
+                return this.input.value2html(this.value, this.$element[0]); 
+            }
+        },
         
         /**
         Enables editable
@@ -191,6 +212,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         * set emptytext if element is empty (reverse: remove emptytext if needed)
         */
         handleEmpty: function () {
+            //do not handle empty if we do not display anything
+            if(this.options.display === false) {
+                return;
+            }
+            
             var emptyClass = 'editable-empty';
             //emptytext shown only for enabled
             if(!this.options.disabled) {
@@ -211,7 +237,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         /**
         Shows container with form
         @method show()
-        @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
         **/  
         show: function (closeAll) {
             if(this.options.disabled) {
@@ -221,13 +247,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             //init editableContainer: popover, tooltip, inline, etc..
             if(!this.container) {
                 var containerOptions = $.extend({}, this.options, {
-                    value: this.value,
-                    autohide: false //element will take care to show/hide container
+                    value: this.value
                 });
                 this.$element.editableContainer(containerOptions);
                 this.$element.on({
-                    save: $.proxy(this.save, this),
-                    cancel: $.proxy(this.hide, this)
+                    save: $.proxy(this.save, this)
                 });
                 this.container = this.$element.data('editableContainer'); 
             } else if(this.container.tip().is(':visible')) {
@@ -246,17 +270,12 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             if(this.container) {  
                 this.container.hide();
             }
-                
-            //return focus on element
-            if (this.options.enablefocus && this.options.toggle === 'click') {
-                this.$element.focus();
-            }   
         },
         
         /**
         Toggles container visibility (show / hide)
         @method toggle()
-        @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
         **/  
         toggle: function(closeAll) {
             if(this.container && this.container.tip().is(':visible')) {
@@ -271,13 +290,13 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         */          
         save: function(e, params) {
             //if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css. 
-            if(typeof this.options.url !== 'function' && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) { 
+            if(typeof this.options.url !== 'function' && this.options.display !== false && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) { 
                 this.$element.addClass('editable-unsaved');
             } else {
                 this.$element.removeClass('editable-unsaved');
             }
             
-            this.hide();
+           // this.hide();
             this.setValue(params.newValue);
             
             /**        
@@ -312,7 +331,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         Sets new value of editable
         @method setValue(value, convertStr)
         @param {mixed} value new value 
-        @param {boolean} convertStr wether to convert value from string to internal format        
+        @param {boolean} convertStr whether to convert value from string to internal format
         **/         
         setValue: function(value, convertStr) {
             if(convertStr) {
@@ -323,10 +342,9 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             if(this.container) {
                 this.container.option('value', this.value);
             }
-            $.when(this.input.value2html(this.value, this.$element))
+            $.when(this.render())
             .then($.proxy(function() {
                 this.handleEmpty();
-                this.$element.triggerHandler('render', this);                        
             }, this));
         },
         
@@ -338,7 +356,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             if(this.container) {
                this.container.activate(); 
             }
-        }                 
+        }
     };
 
     /* EDITABLE PLUGIN DEFINITION
@@ -369,7 +387,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             $('#username, #fullname').editable('validate');
             // possible result:
             {
-              username: "username is requied",
+              username: "username is required",
               fullname: "fullname should be minimum 3 letters length"
             }
             **/             
@@ -398,7 +416,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
                 this.each(function () {
                     var $this = $(this), data = $this.data(datakey);
                     if (data && data.value !== undefined && data.value !== null) {
-                        result[data.options.name] = data.input.value2str(data.value);
+                        result[data.options.name] = data.input.value2submit(data.value);
                     }
                 });
             return result;
@@ -411,6 +429,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
             @param {object} options 
             @param {object} options.url url to submit data 
             @param {object} options.data additional data to submit
+            @param {object} options.ajaxOptions additional ajax options            
             @param {function} options.error(obj) error handler (called on both client-side and server-side validation errors)
             @param {function} options.success(obj) success handler 
             @returns {Object} jQuery object
@@ -421,21 +440,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
                 errors = this.editable('validate'),
                 values;
 
-                if(typeof config.error !== 'function') {
-                    config.error = function() {};
-                } 
-
                 if($.isEmptyObject(errors)) {
                     values = this.editable('getValue'); 
                     if(config.data) {
                         $.extend(values, config.data);
-                    }
-                    $.ajax({
-                        type: 'POST',
+                    }                    
+                    
+                    $.ajax($.extend({
                         url: config.url, 
                         data: values, 
+                        type: 'POST',                        
                         dataType: 'json'
-                    }).success(function(response) {
+                    }, config.ajaxOptions))
+                    .success(function(response) {
+                        //successful response 
                         if(typeof response === 'object' && response.id) {
                             $elems.editable('option', 'pk', response.id); 
                             $elems.removeClass('editable-unsaved');
@@ -443,13 +461,20 @@ Makes editable any HTML element on the page. Applied as jQuery method.
                                 config.success.apply($elems, arguments);
                             } 
                         } else { //server-side validation error
+                           if(typeof config.error === 'function') {
+                                config.error.apply($elems, arguments);
+                           }
+                        }
+                    })
+                    .error(function(){  //ajax error
+                        if(typeof config.error === 'function') {
                             config.error.apply($elems, arguments);
                         }
-                    }).error(function(){  //ajax error
-                        config.error.apply($elems, arguments);
                     });
                 } else { //client-side validation error
-                    config.error.call($elems, {errors: errors});
+                    if(typeof config.error === 'function') {
+                        config.error.call($elems, {errors: errors});
+                    }
                 }
             return this;
         }
@@ -505,7 +530,6 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         @default 'click'
         **/          
         toggle: 'click',
-
         /**
         Text shown when element is empty.
 
@@ -515,7 +539,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         **/         
         emptytext: 'Empty',
         /**
-        Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Usefull for select and date.
+        Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
         For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.  
         <code>auto</code> - text will be automatically set only if element is empty.  
         <code>always|never</code> - always(never) try to set element's text.
@@ -526,22 +550,31 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         **/          
         autotext: 'auto', 
         /**
-        Wether to return focus on element after form is closed. 
-        This allows fully keyboard input.
-
-        @property enablefocus 
-        @type boolean
-        @default false
-        **/          
-        enablefocus: false,
-        /**
         Initial value of input. Taken from <code>data-value</code> or element's text.
 
         @property value 
         @type mixed
         @default element's text
         **/
-        value: null
+        value: null,
+        /**
+        Callback to perform custom displaying of value in element's text.  
+        If <code>null</code>, default input's value2html() will be called.  
+        If <code>false</code>, no displaying methods will be called, element's text will no change.  
+        Runs under element's scope.  
+        Second parameter __sourceData__ is passed for inputs with source (select, checklist).
+        
+        @property display 
+        @type function|boolean
+        @default null
+        @since 1.2.0
+        @example
+        display: function(value, sourceData) {
+            var escapedValue = $('<div>').text(value).html();
+            $(this).html('<b>'+escapedValue+'</b>');
+        }
+        **/          
+        display: null
     };
     
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/inputs-ext/address/address.js b/src/inputs-ext/address/address.js
index ec144b8..fa62147 100644
--- a/src/inputs-ext/address/address.js
+++ b/src/inputs-ext/address/address.js
@@ -26,19 +26,21 @@ $(function(){
         this.init('address', options, Address.defaults);
     };
 
-    $.fn.editableform.utils.inherit(Address, $.fn.editableform.types.abstract);
+    $.fn.editableutils.inherit(Address, $.fn.editabletypes.abstract);
 
     $.extend(Address.prototype, {
          render: function() {
              Address.superclass.render.call(this);
-             
-            // this.$input.
          },
         
-        
+        //standard way to show value in element. Used only if display option not defined.
         value2html: function(value, element) {
-            var html = value.city + ', ' + value.street + ' st., bld. ' + value.building;
-            $(element).text(html); 
+            if(!value) {
+                $(element).empty();
+                return; 
+            }
+            var html = $('<div>').text(value.city).html() + ', ' + $('<div>').text(value.street).html() + ' st., bld. ' + $('<div>').text(value.building).html();
+            $(element).html(html); 
         },
         
         html2value: function(html) {        
@@ -59,11 +61,17 @@ $(function(){
         },
       
        /*
-        method for converting data before sent on server.
-        As jQuery correctly sends objects via ajax, you can just return value
+        converts value to string. 
+        It is used in internal comparing (not for sending to server).
        */
        value2str: function(value) {
-           return value;
+           var str = '';
+           if(value) {
+               for(var k in value) {
+                   str = str + k + ':' + value[k] + ';';  
+               }
+           }
+           return str;
        }, 
        
        /*
@@ -94,14 +102,14 @@ $(function(){
        }  
     });
 
-    Address.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
-        tpl: '<div><label><span>City: </span><input type="text" name="city" class="span2"></label></div>'+
-             '<div><label><span>Street: </span><input type="text" name="street" class="span2"></label></div>'+
-             '<div><label><span>Building: </span><input type="text" name="building" class="span1"></label></div>',
+    Address.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
+        tpl: '<div><label><span>City: </span><input type="text" name="city" class="input-small"></label></div>'+
+             '<div><label><span>Street: </span><input type="text" name="street" class="input-small"></label></div>'+
+             '<div><label><span>Building: </span><input type="text" name="building" class="input-mini"></label></div>',
              
         inputclass: 'editable-address'
     });
 
-    $.fn.editableform.types.address = Address;
+    $.fn.editabletypes.address = Address;
 
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/inputs/abstract.js b/src/inputs/abstract.js
index 0da5ddb..40fcb0c 100644
--- a/src/inputs/abstract.js
+++ b/src/inputs/abstract.js
@@ -6,11 +6,14 @@ To create your own input you should inherit from this class.
 **/
 (function ($) {
 
+    //types
+    $.fn.editabletypes = {};
+    
     var Abstract = function () { };
 
     Abstract.prototype = {
        /**
-        Iinitializes input
+        Initializes input
         
         @method init() 
         **/
@@ -46,8 +49,7 @@ To create your own input you should inherit from this class.
         @param {DOMElement} element
        **/       
        value2html: function(value, element) {
-           var html = this.escape(value);
-           $(element).html(html);
+           $(element).text(value);
        },
         
        /**
@@ -62,7 +64,7 @@ To create your own input you should inherit from this class.
        },
         
        /**
-        Converts value to string (for submiting to server)
+        Converts value to string (for comparering)
         
         @method value2str(value) 
         @param {mixed} value
@@ -83,6 +85,17 @@ To create your own input you should inherit from this class.
            return str;
        }, 
        
+       /**
+        Converts value for submitting to server
+        
+        @method value2submit(value) 
+        @param {mixed} value
+        @returns {mixed}
+       **/       
+       value2submit: function(value) {
+           return value;
+       },         
+       
        /**
         Sets value of input.
         
@@ -114,11 +127,11 @@ To create your own input you should inherit from this class.
        },
        
        /**
-        Creares input. 
+        Creates input.
         
         @method clear() 
        **/        
-       clear:  function() {
+       clear: function() {
            this.$input.val(null);
        },
        
@@ -130,7 +143,7 @@ To create your own input you should inherit from this class.
        },
        
        /**
-        attach handler to automatically submit form when value changed (usefull when buttons not shown)
+        attach handler to automatically submit form when value changed (useful when buttons not shown)
        **/       
        autosubmit: function() {
         
@@ -148,12 +161,12 @@ To create your own input you should inherit from this class.
         tpl: '',
         /**
         CSS class automatically applied to input
-
+        
         @property inputclass 
         @type string
-        @default span2
+        @default input-medium
         **/         
-        inputclass: 'span2',
+        inputclass: 'input-medium',
         /**
         Name attribute of input
 
@@ -164,6 +177,6 @@ To create your own input you should inherit from this class.
         name: null
     };
     
-    $.extend($.fn.editableform.types, {abstract: Abstract});
+    $.extend($.fn.editabletypes, {abstract: Abstract});
         
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/inputs/checklist.js b/src/inputs/checklist.js
index 60af4ce..ea61ac3 100644
--- a/src/inputs/checklist.js
+++ b/src/inputs/checklist.js
@@ -27,7 +27,7 @@ $(function(){
         this.init('checklist', options, Checklist.defaults);
     };
 
-    $.fn.editableform.utils.inherit(Checklist, $.fn.editableform.types.list);
+    $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
 
     $.extend(Checklist.prototype, {
         renderList: function() {
@@ -49,10 +49,8 @@ $(function(){
         },
        
        value2str: function(value) {
-           return $.isArray(value) ? value.join($.trim(this.options.separator)) : '';
-           //it is also possible to sent as array
-           //return value;
-       },        
+           return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
+       },  
        
        //parse separated string
         str2value: function(str) {
@@ -95,19 +93,18 @@ $(function(){
           
        //collect text of checked boxes
         value2htmlFinal: function(value, element) {
-           var selected = [], item, i, html = '';
-           if($.isArray(value) && value.length <= this.options.limit) {    
-               for(i=0; i<value.length; i++){
-                   item = this.itemByVal(value[i]);
-                   if(item) {
-                       selected.push($('<div>').text(item.text).html());
-                   }
-               }
-               html = selected.join(this.options.viewseparator);
-           } else {  
-               html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length); 
+           var html = [],
+               /*jslint eqeq: true*/
+               checked = $.grep(this.sourceData, function(o){
+                   return $.grep(value, function(v){ return v == o.value; }).length;
+               });
+               /*jslint eqeq: false*/
+           if(checked.length) {
+               $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
+               $(element).html(html.join('<br>'));
+           } else {
+               $(element).empty(); 
            }
-           $(element).html(html);
         },
         
        activate: function() {
@@ -123,7 +120,7 @@ $(function(){
        }
     });      
 
-    Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
+    Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
         /**
         @property tpl 
         @default <div></div>
@@ -133,46 +130,20 @@ $(function(){
         /**
         @property inputclass 
         @type string
-        @default span2 editable-checklist
+        @default editable-checklist
         **/         
-        inputclass: 'span2 editable-checklist',        
+        inputclass: 'editable-checklist',        
         
         /**
-        Separator of values in string when sending to server
+        Separator of values when reading from 'data-value' string
 
         @property separator 
         @type string
         @default ', '
         **/         
-        separator: ',',
-        /**
-        Separator of text when display as element content.
-
-        @property viewseparator 
-        @type string
-        @default '<br>'
-        **/         
-        viewseparator: '<br>',
-        /**
-        Maximum number of items shown as element content. 
-        If checked more items - <code>limitText</code> will be shown.
-
-        @property limit 
-        @type integer
-        @default 4
-        **/         
-        limit: 4,
-        /**
-        Text shown when count of checked items is greater than <code>limit</code> parameter.
-        You can use <code>{checked}</code> and <code>{count}</code> placeholders.
-
-        @property limitText 
-        @type string
-        @default 'Selected {checked} of {count}'
-        **/         
-        limitText: 'Selected {checked} of {count}'        
+        separator: ','
     });
 
-    $.fn.editableform.types.checklist = Checklist;      
+    $.fn.editabletypes.checklist = Checklist;      
 
 }(window.jQuery));
diff --git a/src/inputs/date/bootstrap-datepicker/js/bootstrap-datepicker.js b/src/inputs/date/bootstrap-datepicker/js/bootstrap-datepicker.js
index f236209..fae1165 100644
--- a/src/inputs/date/bootstrap-datepicker/js/bootstrap-datepicker.js
+++ b/src/inputs/date/bootstrap-datepicker/js/bootstrap-datepicker.js
@@ -292,7 +292,7 @@
 						.text(dates[this.language].months[month]+' '+year);
 			this.picker.find('tfoot th.today')
 						.text(dates[this.language].today)
-						.toggle(this.todayBtn);
+						.toggle(this.todayBtn !== false);
 			this.updateNavArrows();
 			this.fillMonths();
 			var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
@@ -429,10 +429,7 @@
 								break;
 							case 'today':
 								var date = new Date();
-								date.setUTCHours(0);
-								date.setUTCMinutes(0);
-								date.setUTCSeconds(0);
-								date.setUTCMilliseconds(0);
+								date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
 
 								this.showMode(-2);
 								var which = this.todayBtn == 'linked' ? null : 'view';
@@ -655,7 +652,17 @@
 			if (dir) {
 				this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
 			}
-			this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
+            /*
+              vitalets: fixing bug of very special conditions:
+              jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
+              Method show() does not set display css correctly and datepicker is not shown.
+              Changed to .css('display', 'block') solve the problem.
+              See https://github.com/vitalets/x-editable/issues/37
+              
+              In jquery 1.7.2+ everything works fine.
+            */
+            //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
+			this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
 			this.updateNavArrows();
 		}
 	};
@@ -773,7 +780,7 @@
 				val, filtered, part;
 			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
 			setters_map['dd'] = setters_map['d'];
-			date = UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
+			date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
 			if (parts.length == format.parts.length) {
 				for (var i=0, cnt = format.parts.length; i < cnt; i++) {
 					val = parseInt(parts[i], 10);
diff --git a/src/inputs/date/date.js b/src/inputs/date/date.js
index 2fb8eab..1de781f 100644
--- a/src/inputs/date/date.js
+++ b/src/inputs/date/date.js
@@ -27,7 +27,7 @@ $(function(){
         this.init('date', options, Date.defaults);
         
         //set popular options directly from settings or data-* attributes
-        var directOptions =  $.fn.editableform.utils.sliceObj(this.options, ['format']);
+        var directOptions =  $.fn.editableutils.sliceObj(this.options, ['format']);
 
         //overriding datepicker config (as by default jQuery extend() is not recursive)
         this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
@@ -48,7 +48,7 @@ $(function(){
         this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
     };
 
-    $.fn.editableform.utils.inherit(Date, $.fn.editableform.types.abstract);    
+    $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstract);    
     
     $.extend(Date.prototype, {
         render: function () {
@@ -79,7 +79,11 @@ $(function(){
        
        str2value: function(str) {
            return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
-       },             
+       }, 
+       
+       value2submit: function(value) {
+           return this.value2str(value);
+       },                    
 
        value2input: function(value) {
            this.$input.datepicker('update', value);
@@ -108,7 +112,7 @@ $(function(){
 
     });
     
-    Date.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+    Date.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
         /**
         @property tpl 
         @default <div></div>
@@ -165,6 +169,6 @@ $(function(){
         clear: '&times; clear'
     });   
 
-    $.fn.editableform.types.date = Date;
+    $.fn.editabletypes.date = Date;
 
 }(window.jQuery));
diff --git a/src/inputs/dateui/dateui.js b/src/inputs/dateui/dateui.js
index a8978dc..baa8afc 100644
--- a/src/inputs/dateui/dateui.js
+++ b/src/inputs/dateui/dateui.js
@@ -27,7 +27,7 @@ $(function(){
         this.init('dateui', options, DateUI.defaults);
         
         //set popular options directly from settings or data-* attributes
-        var directOptions =  $.fn.editableform.utils.sliceObj(this.options, ['format']);
+        var directOptions =  $.fn.editableutils.sliceObj(this.options, ['format']);
 
         //overriding datepicker config (as by default jQuery extend() is not recursive)
         this.options.datepicker = $.extend({}, DateUI.defaults.datepicker, directOptions, options.datepicker);
@@ -46,7 +46,7 @@ $(function(){
         this.options.datepicker.dateFormat = this.options.datepicker.format;        
     };
 
-    $.fn.editableform.utils.inherit(DateUI, $.fn.editableform.types.abstract);    
+    $.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstract);    
     
     $.extend(DateUI.prototype, {
         render: function () {
@@ -97,7 +97,11 @@ $(function(){
            } catch(e) {}
            
            return d;
-       },             
+       }, 
+       
+       value2submit: function(value) {
+           return this.value2str(value);
+       },                     
 
        value2input: function(value) {
            this.$input.datepicker('setDate', value);
@@ -125,7 +129,7 @@ $(function(){
 
     });
     
-    DateUI.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+    DateUI.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
         /**
         @property tpl 
         @default <div></div>
@@ -183,7 +187,7 @@ $(function(){
         clear: '&times; clear'        
     });   
 
-    $.fn.editableform.types.dateui = DateUI;
-    $.fn.editableform.types.date = DateUI;
+    $.fn.editabletypes.dateui = DateUI;
+    $.fn.editabletypes.date = DateUI;
 
 }(window.jQuery));
diff --git a/src/inputs/list.js b/src/inputs/list.js
index ad53e68..7af00b0 100644
--- a/src/inputs/list.js
+++ b/src/inputs/list.js
@@ -10,7 +10,7 @@ List - abstract class for inputs that have source option loaded from js array or
        
     };
 
-    $.fn.editableform.utils.inherit(List, $.fn.editableform.types.abstract);
+    $.fn.editableutils.inherit(List, $.fn.editabletypes.abstract);
 
     $.extend(List.prototype, {
         render: function () {
@@ -34,10 +34,15 @@ List - abstract class for inputs that have source option loaded from js array or
             return null; //can't set value by text
         },
         
-        value2html: function (value, element) {
+        value2html: function (value, element, display) {
             var deferred = $.Deferred();
             this.onSourceReady(function () {
-                this.value2htmlFinal(value, element);
+                if(typeof display === 'function') {
+                    //custom display method
+                    display.call(element, value, this.sourceData); 
+                } else {
+                    this.value2htmlFinal(value, element);
+                }
                 deferred.resolve();
             }, function () {
                 List.superclass.value2html(this.options.sourceError, element);
@@ -58,7 +63,7 @@ List - abstract class for inputs that have source option loaded from js array or
 
             // try parse json in single quotes (for double quotes jquery does automatically)
             try {
-                this.options.source = $.fn.editableform.utils.tryParseJson(this.options.source, false);
+                this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
             } catch (e) {
                 error.call(this);
                 return;
@@ -66,32 +71,35 @@ List - abstract class for inputs that have source option loaded from js array or
 
             //loading from url
             if (typeof this.options.source === 'string') {
-                var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
-                cache;
+                //try to get from cache
+                if(this.options.sourceCache) {
+                    var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
+                    cache;
 
-                if (!$(document).data(cacheID)) {
-                    $(document).data(cacheID, {});
-                }
-                cache = $(document).data(cacheID);
+                    if (!$(document).data(cacheID)) {
+                        $(document).data(cacheID, {});
+                    }
+                    cache = $(document).data(cacheID);
 
-                //check for cached data
-                if (cache.loading === false && cache.sourceData) { //take source from cache
-                    this.sourceData = cache.sourceData;
-                    success.call(this);
-                    return;
-                } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
-                    cache.callbacks.push($.proxy(function () {
+                    //check for cached data
+                    if (cache.loading === false && cache.sourceData) { //take source from cache
                         this.sourceData = cache.sourceData;
                         success.call(this);
-                    }, this));
+                        return;
+                    } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
+                        cache.callbacks.push($.proxy(function () {
+                            this.sourceData = cache.sourceData;
+                            success.call(this);
+                        }, this));
 
-                    //also collecting error callbacks
-                    cache.err_callbacks.push($.proxy(error, this));
-                    return;
-                } else { //no cache yet, activate it
-                    cache.loading = true;
-                    cache.callbacks = [];
-                    cache.err_callbacks = [];
+                        //also collecting error callbacks
+                        cache.err_callbacks.push($.proxy(error, this));
+                        return;
+                    } else { //no cache yet, activate it
+                        cache.loading = true;
+                        cache.callbacks = [];
+                        cache.err_callbacks = [];
+                    }
                 }
                 
                 //loading sourceData from server
@@ -102,23 +110,32 @@ List - abstract class for inputs that have source option loaded from js array or
                     data: this.options.name ? {name: this.options.name} : {},
                     dataType: 'json',
                     success: $.proxy(function (data) {
-                        cache.loading = false;
+                        if(cache) {
+                            cache.loading = false;
+                        }
                         this.sourceData = this.makeArray(data);
                         if($.isArray(this.sourceData)) {
                             this.doPrepend();
-                            //store result in cache
-                            cache.sourceData = this.sourceData;
                             success.call(this);
-                            $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
+                            if(cache) {
+                                //store result in cache
+                                cache.sourceData = this.sourceData;
+                                $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
+                            }
                         } else {
                             error.call(this);
-                            $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
+                            if(cache) {
+                                $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
+                            }
                         }
                     }, this),
                     error: $.proxy(function () {
-                        cache.loading = false;
                         error.call(this);
-                        $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
+                        if(cache) {
+                             cache.loading = false;
+                             //run error callbacks for other fields
+                             $.each(cache.err_callbacks, function () { this.call(); }); 
+                        }
                     }, this)
                 });
             } else { //options as json/array
@@ -139,7 +156,7 @@ List - abstract class for inputs that have source option loaded from js array or
             
             if(!$.isArray(this.prependData)) {
                 //try parse json in single quotes
-                this.options.prepend = $.fn.editableform.utils.tryParseJson(this.options.prepend, true);
+                this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
                 if (typeof this.options.prepend === 'string') {
                     this.options.prepend = {'': this.options.prepend};
                 }              
@@ -220,19 +237,20 @@ List - abstract class for inputs that have source option loaded from js array or
 
     });      
 
-    List.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+    List.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
         /**
         Source data for list. If string - considered ajax url to load items. Otherwise should be an array.
         Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br>
         For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order.      
-
+        If source is **string**, results will be cached for fields with the same source and name. See also <code>sourceCache</code> option.
+        
         @property source 
         @type string|array|object
         @default null
         **/         
         source:null, 
         /**
-        Data automatically prepended to the begining of dropdown list.
+        Data automatically prepended to the beginning of dropdown list.
         
         @property prepend 
         @type string|array|object
@@ -246,9 +264,19 @@ List - abstract class for inputs that have source option loaded from js array or
         @type string
         @default Error when loading list
         **/          
-        sourceError: 'Error when loading list'
+        sourceError: 'Error when loading list',
+        /**
+        if <code>true</code> and source is **string url** - results will be cached for fields with the same source and name.  
+        Usefull for editable grids.
+        
+        @property sourceCache 
+        @type boolean
+        @default true
+        @since 1.2.0
+        **/        
+        sourceCache: true
     });
 
-    $.fn.editableform.types.list = List;      
+    $.fn.editabletypes.list = List;      
 
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/inputs/select.js b/src/inputs/select.js
index 42ffc77..60afb6b 100644
--- a/src/inputs/select.js
+++ b/src/inputs/select.js
@@ -26,7 +26,7 @@ $(function(){
         this.init('select', options, Select.defaults);
     };
 
-    $.fn.editableform.utils.inherit(Select, $.fn.editableform.types.list);
+    $.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
 
     $.extend(Select.prototype, {
         renderList: function() {
@@ -37,6 +37,13 @@ $(function(){
             for(var i=0; i<this.sourceData.length; i++) {
                 this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text)); 
             }
+            
+            //enter submit
+            this.$input.on('keydown.editable', function (e) {
+                if (e.which === 13) {
+                    $(this).closest('form').submit();
+                }
+            });            
         },
        
         value2htmlFinal: function(value, element) {
@@ -48,13 +55,13 @@ $(function(){
         },
         
         autosubmit: function() {
-            this.$input.on('change', function(){
+            this.$input.off('keydown.editable').on('change.editable', function(){
                 $(this).closest('form').submit();
             });
         }
     });      
 
-    Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
+    Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
         /**
         @property tpl 
         @default <select></select>
@@ -62,6 +69,6 @@ $(function(){
         tpl:'<select></select>'
     });
 
-    $.fn.editableform.types.select = Select;      
+    $.fn.editabletypes.select = Select;      
 
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/inputs/text.js b/src/inputs/text.js
index 8557c2a..ce5872e 100644
--- a/src/inputs/text.js
+++ b/src/inputs/text.js
@@ -20,18 +20,18 @@ $(function(){
         this.init('text', options, Text.defaults);
     };
 
-    $.fn.editableform.utils.inherit(Text, $.fn.editableform.types.abstract);
+    $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstract);
 
     $.extend(Text.prototype, {
         activate: function() {
             if(this.$input.is(':visible')) {
                 this.$input.focus();
-                $.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length);
+                $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
             }
         }  
     });
 
-    Text.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+    Text.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
         /**
         @property tpl 
         @default <input type="text">
@@ -47,6 +47,6 @@ $(function(){
         placeholder: null
     });
 
-    $.fn.editableform.types.text = Text;
+    $.fn.editabletypes.text = Text;
 
 }(window.jQuery));
diff --git a/src/inputs/textarea.js b/src/inputs/textarea.js
index eb4cd3c..0a8918e 100644
--- a/src/inputs/textarea.js
+++ b/src/inputs/textarea.js
@@ -21,7 +21,7 @@ $(function(){
         this.init('textarea', options, Textarea.defaults);
     };
 
-    $.fn.editableform.utils.inherit(Textarea, $.fn.editableform.types.abstract);
+    $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstract);
 
     $.extend(Textarea.prototype, {
         render: function () {
@@ -60,13 +60,13 @@ $(function(){
 
         activate: function() {
             if(this.$input.is(':visible')) {
-                $.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length);
+                $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
                 this.$input.focus();
             }
         }         
     });
 
-    Textarea.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+    Textarea.defaults = $.extend({}, $.fn.editabletypes.abstract.defaults, {
         /**
         @property tpl 
         @default <textarea></textarea>
@@ -74,9 +74,9 @@ $(function(){
         tpl:'<textarea></textarea>',
         /**
         @property inputclass 
-        @default span3
+        @default input-large
         **/          
-        inputclass:'span3',
+        inputclass: 'input-large',
         /**
         Placeholder attribute of input. Shown when input is empty.
 
@@ -87,6 +87,6 @@ $(function(){
         placeholder: null        
     });
 
-    $.fn.editableform.types.textarea = Textarea;    
+    $.fn.editabletypes.textarea = Textarea;    
 
-}(window.jQuery));
\ No newline at end of file
+}(window.jQuery));
diff --git a/test/index.html b/test/index.html
index fe1e4df..233b5e7 100644
--- a/test/index.html
+++ b/test/index.html
@@ -3,55 +3,25 @@
 <head>
   <meta charset="utf-8">
   <title>Test X-editable</title>
-
-  <!-- jquery -->
-  <script src="libs/jquery/jquery-1.8.2.js"></script>
-
-  <!-- qunit -->
-  <link rel="stylesheet" href="libs/qunit/qunit-1.10.0.css" type="text/css" media="screen" />
-  <script src="libs/qunit/qunit-1.10.0.js"></script>
-  
-  <!-- mockjax -->
-  <script src="libs/mockjax/jquery.mockjax.js"></script>  
-  <script src="mocks.js"></script>
-  
-  <script src="loader.js"></script>
   <script>
-      var fc = getFC(),
-      assets = getAssets(fc.f, fc.c, '../src/', 'libs/');
-      loadAssets(assets.css, assets.js);
-      
-      var fx, sfx;  
-      $(function () {
-          $.fx.off = true;
-          $.support.transition = false;
-      });       
-  </script>  
+    window.onload = function() {
+       QUnit.config.autostart = false;
+    };
+  </script>
+  
+<!--  <script data-main="main.js" src="require-jquery.js"></script>-->
+  <script data-main="main.js" src="require.js"></script>
+  
+   <!-- qunit (should be included here, otherwise phantomjs hangs-up on several tests )-->
+   <link rel="stylesheet" href="libs/qunit/qunit-1.10.0.css" type="text/css" media="screen" />
+   <script src="libs/qunit/qunit-1.10.0.js"></script>
 
 </head>
 <body>
-  <div>
+  <div> 
     <div id="qunit"></div>
     <div id="qunit-fixture"></div>
     <div id="async-fixture"></div>
   </div>
-  
-  <!-- unit tests -->
-  <!--  You can also add "?module=[module]" URL to run only tests within that module-->
-  <script src="unit/common.js"></script>
-  <script src="unit/text.js"></script>
-  <script src="unit/select.js"></script>  
-  <script src="unit/textarea.js"></script>  
-  <script src="unit/api.js"></script>
-  <script src="unit/checklist.js"></script>
-  <script>  
-    if(fc.f === 'bootstrap') {
-        loadJs('unit/date.js'); 
-    } else {
-        loadJs('unit/dateui.js'); 
-    }
-  </script>  
-   
-  
 </body>
 </html>
diff --git a/test/loader.js b/test/loader.js
index b1b4a6b..3ccb962 100644
--- a/test/loader.js
+++ b/test/loader.js
@@ -1,128 +1,170 @@
-/**
-* load requred js and css according to f (form) and c (container) parameters
-*/
+define(function () {
+         
+    function loadCss(url) {
+        var link = document.createElement("link");
+        link.type = "text/css";
+        link.rel = "stylesheet";
+        link.href = url;
+        document.getElementsByTagName("head")[0].appendChild(link);
+    };     
+                
+    return {
+        loadCss: loadCss,
+        getConfig: function (baseUrl) {
+            var params = this.getParams(),
+                f = params.f, 
+                c = params.c,
+                shim = {
+                'containers/editable-container': {
+                    deps: ['require', 'editable-form/editable-form-utils', 'editable-form/editable-form'],
+                    init: function(require) {
+                        loadCss(require.toUrl("./editable-container.css")); 
+                    }                  
+                },   
+                'element/editable-element': {
+                    deps: ['require'], //here should be dynamically added container
+                    init: function(require) {
+                        loadCss(require.toUrl("./editable-element.css")); 
+                    }                         
+                },
+                'editable-form/editable-form': {
+                    deps: ['require',
+                    'inputs/text',
+                    'inputs/textarea',
+                    'inputs/select',
+                    'inputs/checklist',
+                    'inputs-ext/address/address'],
+                    init: function(require) {
+                        loadCss(require.toUrl("./editable-form.css")); 
+                    }      
+                },
+                'inputs/select': ['inputs/list'],
+                'inputs/checklist': ['inputs/list'],
+                'inputs/list': ['inputs/abstract'],
+                'inputs/text': ['inputs/abstract'],
+                'inputs/textarea': ['inputs/abstract'],
+                'inputs/abstract': ['editable-form/editable-form-utils'],   
 
-function getFC() {
-    var url = window.location.href, f, c;
-    if(url.match(/f=jqueryui/i)) { 
-        f = 'jqueryui';
-    } else if(url.match(/f=plain/i)) {
-        f = 'plain';
-    } else {      //bootstrap
-        f = 'bootstrap';
+                //bootstrap
+                'bootstrap/js/bootstrap': {
+                    deps: ['require'],
+                    init: function(require) {
+                        loadCss(require.toUrl("../css/bootstrap.css")); 
+                    }                
+                },
+                'editable-form/editable-form-bootstrap': [
+                'editable-form/editable-form', 
+                'bootstrap/js/bootstrap'
+                ],
+                'containers/editable-popover': ['containers/editable-container', 
+                'bootstrap/js/bootstrap'
+                ],
+                'inputs/date/date': {
+                    deps: ['require', 
+                    'bootstrap/js/bootstrap',
+                    'inputs/abstract', 
+                    'inputs/date/bootstrap-datepicker/js/bootstrap-datepicker'],
+                    init: function(require) {
+                        loadCss(require.toUrl("./bootstrap-datepicker/css/datepicker.css")); 
+                    }
+                },
+
+                //jqueryui
+                'jqueryui/js/jquery-ui-1.9.1.custom': {
+                    deps: ['require'],
+                    init: function(require) {
+                        loadCss(require.toUrl("../css/redmond/jquery-ui-1.9.1.custom.css")); 
+                    }                
+                },  
+                'editable-form/editable-form-jqueryui': [
+                'editable-form/editable-form', 
+                'jqueryui/js/jquery-ui-1.9.1.custom'
+                ],            
+                'containers/editable-tooltip': ['containers/editable-container', 
+                'jqueryui/js/jquery-ui-1.9.1.custom'
+                ],                      
+                'inputs/dateui/dateui': ['inputs/abstract'],
+
+                //plain
+                //'inputs/dateui/dateui': ['inputs/abstract', 'inputs/date/bootstrap-datepicker/js/bootstrap-datepicker'],
+                'containers/editable-poshytip': [ 
+                'containers/editable-container', 
+                'poshytip/jquery.poshytip'
+                ],
+                'poshytip/jquery.poshytip': {
+                    deps: ['require'],
+                    init: function(require) {
+                        loadCss(require.toUrl("./tip-yellowsimple/tip-yellowsimple.css")); 
+                    }                
+                },
+                'inputs/dateui/jquery-ui-datepicker/js/jquery-ui-1.9.1.custom': {
+                    deps: ['require'],
+                    init: function(require) {
+                        loadCss(require.toUrl("../css/redmond/jquery-ui-1.9.1.custom.css")); 
+                    } 
+                },
+
+                //inline container
+                'containers/editable-inline': ['containers/editable-container'],
+
+                //inputs-ext
+                'inputs-ext/address/address': {
+                    deps: ['require', 'inputs/abstract'],
+                    init: function(require) {
+                        loadCss(require.toUrl("./address.css")); 
+                    }
+                }
+            };
+
+            /*
+             modify shim for bootstrap, jqueryui or plain
+            */
+            
+            if(f === 'bootstrap') { 
+                //bootstrap
+                shim['editable-form/editable-form'].deps.push('inputs/date/date');
+                shim['element/editable-element'].deps.push('editable-form/editable-form-bootstrap');
+                shim['element/editable-element'].deps.push(c === 'popup' ? 'containers/editable-popover' : 'containers/editable-inline');
+            } else if(f === 'jqueryui') {
+                //jqueryui
+                shim['editable-form/editable-form'].deps.push('inputs/dateui/dateui');
+                shim['element/editable-element'].deps.push('editable-form/editable-form-jqueryui');
+                shim['element/editable-element'].deps.push(c === 'popup' ? 'containers/editable-tooltip' : 'containers/editable-inline');
+            } else {    
+                //plain
+                shim['editable-form/editable-form'].deps.push('inputs/dateui/dateui');
+                shim['inputs/dateui/dateui'].push('inputs/dateui/jquery-ui-datepicker/js/jquery-ui-1.9.1.custom');
+                shim['element/editable-element'].deps.push(c === 'popup' ? 'containers/editable-poshytip' : 'containers/editable-inline');        
+            }            
+            
+            
+            /*
+             return requirejs config
+            */            
+            
+            return {
+                baseUrl: baseUrl,
+                paths: {
+                    "bootstrap": "../test/libs/bootstrap221", 
+                    "jqueryui": "../test/libs/jquery-ui-1.9.1.custom", 
+                    "poshytip": "../test/libs/poshytip",
+                    "test": "../test" 
+                },
+                shim: shim
+            };  
+        },
+
+        getParams: function() {
+            var url = window.location.href, f, c;
+            if(url.match(/f=jqueryui/i)) { 
+                f = 'jqueryui';
+            } else if(url.match(/f=plain/i)) {
+                f = 'plain';
+            } else {      
+                f = 'bootstrap';
+            }
+            c = url.match(/c=inline/i) ? 'inline' : 'popup';
+            return {f: f, c: c};
+        }
     }
-    c = url.match(/c=inline/i) ? 'inline' : 'popup';
-    return {f: f, c: c};
-}
-
-function getAssets(f, c, src, libs) {
-    var
-    forms = src+'editable-form/',
-    inputs = src+'inputs/',
-    containers = src+'containers/',
-    element = src+'element/',
-
-    bootstrap = libs+'bootstrap221/',
-    jqueryui = libs+'jquery-ui-1.9.1.custom/',
-
-    js = [
-    forms+'editable-form.js',
-    forms+'editable-form-utils.js',
-    containers+'editable-container.js',
-    element+'editable-element.js',
-    inputs+'abstract.js',
-    inputs+'list.js',
-    inputs+'text.js',
-    inputs+'textarea.js',
-    inputs+'select.js',  
-    inputs+'checklist.js'  
-    ],
-
-    css = [
-    forms+'editable-form.css',
-    containers+'editable-container.css',
-    element+'editable-element.css'
-    ];
-
-
-    //tune js and css
-    if(f==='jqueryui') { 
-        //core
-        js.unshift(jqueryui+'js/jquery-ui-1.9.1.custom.js')
-        css.unshift(jqueryui+'css/redmond/jquery-ui-1.9.1.custom.css');
-
-        //editable
-        js.push(forms+'editable-form-jqueryui.js');
-        js.push(getContainer('editable-tooltip.js'));
-
-        //date
-        js.push(inputs+'dateui/dateui.js');
-
-        //style
-        css.push('style.css');
-    } else if(f==='plain') { 
-        //core
-        js.unshift(libs+'poshytip/jquery.poshytip.js');
-        css.unshift(libs+'poshytip/tip-yellowsimple/tip-yellowsimple.css');
-
-        //editable
-        js.push(getContainer('editable-poshytip.js'));
-
-        //date
-        js.push(inputs+'dateui/dateui.js');
-        js.push(inputs+'dateui/jquery-ui-datepicker/js/jquery-ui-1.9.1.custom.js');    
-        css.unshift(inputs+'dateui/jquery-ui-datepicker/css/redmond/jquery-ui-1.9.1.custom.css');
-
-        //style
-        css.push('style.css');    
-    /* bootstrap */        
-    } else {      
-        //core
-        js.unshift(bootstrap+'js/bootstrap.js')
-        css.unshift(bootstrap+'css/bootstrap.css');
-        css.unshift(bootstrap+'css/bootstrap-responsive.css');
-
-        //editable
-        js.push(forms+'editable-form-bootstrap.js');
-        js.push(getContainer('editable-popover.js'));
-
-        //date
-        js.push(inputs+'date/bootstrap-datepicker/js/bootstrap-datepicker.js');
-        js.push(inputs+'date/date.js');
-        css.push(inputs+'date/bootstrap-datepicker/css/datepicker.css');    
-    }
-
-    function getContainer(container) {
-        return (c === 'inline') ? containers+'/editable-inline.js' : containers + container;
-    }  
-
-    //js.push('main.js');
-    
-    return {css: css, js: js};
-}
-
-
-function loadAssets(css, js) {
-    for(var i = 0; i < css.length; i++) {
-        loadCss(css[i]);
-    }    
-    
-    for(i = 0; i < js.length; i++) {
-        loadJs(js[i]);
-    }
-}   
-    
-function loadCss(url) {
-    var link = document.createElement("link");
-    link.type = "text/css";
-    link.rel = "stylesheet";
-    link.href = url;
-    document.getElementsByTagName("head")[0].appendChild(link);
-}
-
-function loadJs(url) {
-    if(!url) return;
-    var script = document.createElement("script");
-    script.src = url;
-    document.getElementsByTagName("head")[0].appendChild(script);
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/test/main.js b/test/main.js
new file mode 100644
index 0000000..6264575
--- /dev/null
+++ b/test/main.js
@@ -0,0 +1,33 @@
+//detect version of jquery from url param, e.g. 'jquery=1.7.2' 
+var jqver = decodeURIComponent((new RegExp('[?|&]' + 'jquery' + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null;
+    jqurl = jqver ? "http://ajax.googleapis.com/ajax/libs/jquery/"+jqver+"/jquery.min.js" : "libs/jquery/jquery-1.8.2.min.js";
+    
+require(["loader", jqurl], function(loader) {
+
+    requirejs.config(loader.getConfig("../src"));
+   
+    require(['element/editable-element', 
+             'test/libs/mockjax/jquery.mockjax'
+             ], 
+    function() {
+        //disable effects
+        $.fx.off = true;
+        $.support.transition = false;           
+        
+        var params = loader.getParams();
+        
+        require([
+            'test/mocks',
+            'test/unit/common',
+            'test/unit/text',
+            'test/unit/textarea',
+            'test/unit/select',
+            'test/unit/checklist',
+            'test/unit/api',
+            (params.f === 'bootstrap') ?  'test/unit/date' :  'test/unit/dateui'
+        ], function() {
+            QUnit.load();
+            QUnit.start();
+        });
+    });
+});
\ No newline at end of file
diff --git a/test/mocks.js b/test/mocks.js
index 46d7a4f..446c901 100644
--- a/test/mocks.js
+++ b/test/mocks.js
@@ -57,7 +57,7 @@ $(function () {
     
 });
 
-// usefull functions
+// useful functions
 
 function tip(e) {
     return e.data('editableContainer').tip();   
diff --git a/test/require.js b/test/require.js
new file mode 100644
index 0000000..8de013d
--- /dev/null
+++ b/test/require.js
@@ -0,0 +1,35 @@
+/*
+ RequireJS 2.1.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ Available via the MIT or new BSD license.
+ see: http://github.com/jrburke/requirejs for details
+*/
+var requirejs,require,define;
+(function(Y){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function x(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function r(b,c){return da.call(b,c)}function i(b,c){return r(b,c)&&b[c]}function E(b,c){for(var d in b)if(r(b,d)&&c(b[d],d))break}function Q(b,c,d,i){c&&E(c,function(c,h){if(d||!r(b,h))i&&"string"!==typeof c?(b[h]||(b[h]={}),Q(b[h],
+c,d,i)):b[h]=c});return b}function t(b,c){return function(){return c.apply(b,arguments)}}function Z(b){if(!b)return b;var c=Y;x(b.split("."),function(b){c=c[b]});return c}function J(b,c,d,i){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=i;d&&(c.originalError=d);return c}function ea(b){function c(a,g,v){var e,n,b,c,d,j,f,h=g&&g.split("/");e=h;var l=m.map,k=l&&l["*"];if(a&&"."===a.charAt(0))if(g){e=i(m.pkgs,g)?h=[g]:h.slice(0,h.length-1);g=a=e.concat(a.split("/"));
+for(e=0;g[e];e+=1)if(n=g[e],"."===n)g.splice(e,1),e-=1;else if(".."===n)if(1===e&&(".."===g[2]||".."===g[0]))break;else 0<e&&(g.splice(e-1,2),e-=2);e=i(m.pkgs,g=a[0]);a=a.join("/");e&&a===g+"/"+e.main&&(a=g)}else 0===a.indexOf("./")&&(a=a.substring(2));if(v&&(h||k)&&l){g=a.split("/");for(e=g.length;0<e;e-=1){b=g.slice(0,e).join("/");if(h)for(n=h.length;0<n;n-=1)if(v=i(l,h.slice(0,n).join("/")))if(v=i(v,b)){c=v;d=e;break}if(c)break;!j&&(k&&i(k,b))&&(j=i(k,b),f=e)}!c&&j&&(c=j,d=f);c&&(g.splice(0,d,
+c),a=g.join("/"))}return a}function d(a){z&&x(document.getElementsByTagName("script"),function(g){if(g.getAttribute("data-requiremodule")===a&&g.getAttribute("data-requirecontext")===j.contextName)return g.parentNode.removeChild(g),!0})}function y(a){var g=i(m.paths,a);if(g&&I(g)&&1<g.length)return d(a),g.shift(),j.require.undef(a),j.require([a]),!0}function f(a){var g,b=a?a.indexOf("!"):-1;-1<b&&(g=a.substring(0,b),a=a.substring(b+1,a.length));return[g,a]}function h(a,g,b,e){var n,u,d=null,h=g?g.name:
+null,l=a,m=!0,k="";a||(m=!1,a="_@r"+(L+=1));a=f(a);d=a[0];a=a[1];d&&(d=c(d,h,e),u=i(p,d));a&&(d?k=u&&u.normalize?u.normalize(a,function(a){return c(a,h,e)}):c(a,h,e):(k=c(a,h,e),a=f(k),d=a[0],k=a[1],b=!0,n=j.nameToUrl(k)));b=d&&!u&&!b?"_unnormalized"+(M+=1):"";return{prefix:d,name:k,parentMap:g,unnormalized:!!b,url:n,originalName:l,isDefine:m,id:(d?d+"!"+k:k)+b}}function q(a){var g=a.id,b=i(k,g);b||(b=k[g]=new j.Module(a));return b}function s(a,g,b){var e=a.id,n=i(k,e);if(r(p,e)&&(!n||n.defineEmitComplete))"defined"===
+g&&b(p[e]);else q(a).on(g,b)}function C(a,g){var b=a.requireModules,e=!1;if(g)g(a);else if(x(b,function(g){if(g=i(k,g))g.error=a,g.events.error&&(e=!0,g.emit("error",a))}),!e)l.onError(a)}function w(){R.length&&(fa.apply(F,[F.length-1,0].concat(R)),R=[])}function A(a,g,b){var e=a.map.id;a.error?a.emit("error",a.error):(g[e]=!0,x(a.depMaps,function(e,c){var d=e.id,h=i(k,d);h&&(!a.depMatched[c]&&!b[d])&&(i(g,d)?(a.defineDep(c,p[d]),a.check()):A(h,g,b))}),b[e]=!0)}function B(){var a,g,b,e,n=(b=1E3*m.waitSeconds)&&
+j.startTime+b<(new Date).getTime(),c=[],h=[],f=!1,l=!0;if(!T){T=!0;E(k,function(b){a=b.map;g=a.id;if(b.enabled&&(a.isDefine||h.push(b),!b.error))if(!b.inited&&n)y(g)?f=e=!0:(c.push(g),d(g));else if(!b.inited&&(b.fetched&&a.isDefine)&&(f=!0,!a.prefix))return l=!1});if(n&&c.length)return b=J("timeout","Load timeout for modules: "+c,null,c),b.contextName=j.contextName,C(b);l&&x(h,function(a){A(a,{},{})});if((!n||e)&&f)if((z||$)&&!U)U=setTimeout(function(){U=0;B()},50);T=!1}}function D(a){r(p,a[0])||
+q(h(a[0],null,!0)).init(a[1],a[2])}function G(a){var a=a.currentTarget||a.srcElement,b=j.onScriptLoad;a.detachEvent&&!V?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=j.onScriptError;(!a.detachEvent||V)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function K(){var a;for(w();F.length;){a=F.shift();if(null===a[0])return C(J("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));D(a)}}var T,W,j,N,U,m={waitSeconds:7,
+baseUrl:"./",paths:{},pkgs:{},shim:{},map:{},config:{}},k={},X={},F=[],p={},S={},L=1,M=1;N={require:function(a){return a.require?a.require:a.require=j.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?a.exports:a.exports=p[a.map.id]={}},module:function(a){return a.module?a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return m.config&&i(m.config,a.map.id)||{}},exports:p[a.map.id]}}};W=function(a){this.events=i(X,a.id)||{};this.map=a;this.shim=
+i(m.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};W.prototype={init:function(a,b,c,e){e=e||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=t(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=c;this.inited=!0;this.ignore=e.ignore;e.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=
+b)},fetch:function(){if(!this.fetched){this.fetched=!0;j.startTime=(new Date).getTime();var a=this.map;if(this.shim)j.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],t(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=this.map.url;S[a]||(S[a]=!0,j.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var e=this.exports,n=this.factory;
+if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(H(n)){if(this.events.error)try{e=j.execCb(c,n,b,e)}catch(d){a=d}else e=j.execCb(c,n,b,e);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==this.exports?e=b.exports:void 0===e&&this.usingExports&&(e=this.exports));if(a)return a.requireMap=this.map,a.requireModules=[this.map.id],a.requireType="define",C(this.error=a)}else e=n;this.exports=e;if(this.map.isDefine&&
+!this.ignore&&(p[c]=e,l.onResourceLoad))l.onResourceLoad(j,this.map,this.depMaps);delete k[c];this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=h(a.prefix);this.depMaps.push(d);s(d,"defined",t(this,function(e){var n,d;d=this.map.name;var v=this.map.parentMap?this.map.parentMap.name:null,f=j.makeRequire(a.parentMap,{enableBuildCallback:!0,
+skipMap:!0});if(this.map.unnormalized){if(e.normalize&&(d=e.normalize(d,function(a){return c(a,v,!0)})||""),e=h(a.prefix+"!"+d,this.map.parentMap),s(e,"defined",t(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=i(k,e.id)){this.depMaps.push(e);if(this.events.error)d.on("error",t(this,function(a){this.emit("error",a)}));d.enable()}}else n=t(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),n.error=t(this,function(a){this.inited=!0;this.error=
+a;a.requireModules=[b];E(k,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&delete k[a.map.id]});C(a)}),n.fromText=t(this,function(e,c){var d=a.name,u=h(d),v=O;c&&(e=c);v&&(O=!1);q(u);r(m.config,b)&&(m.config[d]=m.config[b]);try{l.exec(e)}catch(k){throw Error("fromText eval for "+d+" failed: "+k);}v&&(O=!0);this.depMaps.push(u);j.completeLoad(d);f([d],n)}),e.load(a.name,f,n,m)}));j.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){this.enabling=this.enabled=!0;x(this.depMaps,t(this,function(a,
+b){var c,e;if("string"===typeof a){a=h(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=i(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",t(this,function(a){this.defineDep(b,a);this.check()}));this.errback&&s(a,"error",this.errback)}c=a.id;e=k[c];!r(N,c)&&(e&&!e.enabled)&&j.enable(a,this)}));E(this.pluginMaps,t(this,function(a){var b=i(k,a.id);b&&!b.enabled&&j.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=
+this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){x(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};j={config:m,contextName:b,registry:k,defined:p,urlFetched:S,defQueue:F,Module:W,makeModuleMap:h,nextTick:l.nextTick,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=m.pkgs,c=m.shim,e={paths:!0,config:!0,map:!0};E(a,function(a,b){e[b]?"map"===b?Q(m[b],a,!0,!0):Q(m[b],a,!0):m[b]=a});a.shim&&(E(a.shim,function(a,
+b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=j.makeShimExports(a);c[b]=a}),m.shim=c);a.packages&&(x(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,location:a.location||a.name,main:(a.main||"main").replace(ga,"").replace(aa,"")}}),m.pkgs=b);E(k,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=h(b))});if(a.deps||a.callback)j.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Y,arguments));
+return b||a.exports&&Z(a.exports)}},makeRequire:function(a,d){function f(e,c,u){var i,m;d.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof e){if(H(c))return C(J("requireargs","Invalid require call"),u);if(a&&r(N,e))return N[e](k[a.id]);if(l.get)return l.get(j,e,a);i=h(e,a,!1,!0);i=i.id;return!r(p,i)?C(J("notloaded",'Module name "'+i+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):p[i]}K();j.nextTick(function(){K();m=q(h(null,a));m.skipMap=d.skipMap;
+m.init(e,c,u,{enabled:!0});B()});return f}d=d||{};Q(f,{isBrowser:z,toUrl:function(b){var d=b.lastIndexOf("."),g=null;-1!==d&&(g=b.substring(d,b.length),b=b.substring(0,d));return j.nameToUrl(c(b,a&&a.id,!0),g)},defined:function(b){return r(p,h(b,a,!1,!0).id)},specified:function(b){b=h(b,a,!1,!0).id;return r(p,b)||r(k,b)}});a||(f.undef=function(b){w();var c=h(b,a,!0),d=i(k,b);delete p[b];delete S[c.url];delete X[b];d&&(d.events.defined&&(X[b]=d.events),delete k[b])});return f},enable:function(a){i(k,
+a.id)&&q(a).enable()},completeLoad:function(a){var b,c,d=i(m.shim,a)||{},h=d.exports;for(w();F.length;){c=F.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);D(c)}c=i(k,a);if(!b&&!r(p,a)&&c&&!c.inited){if(m.enforceDefine&&(!h||!Z(h)))return y(a)?void 0:C(J("nodefine","No define call for "+a,null,[a]));D([a,d.deps||[],d.exportsFn])}B()},nameToUrl:function(a,b){var c,d,h,f,j,k;if(l.jsExtRegExp.test(a))f=a+(b||"");else{c=m.paths;d=m.pkgs;f=a.split("/");for(j=f.length;0<j;j-=1)if(k=
+f.slice(0,j).join("/"),h=i(d,k),k=i(c,k)){I(k)&&(k=k[0]);f.splice(0,j,k);break}else if(h){c=a===h.name?h.location+"/"+h.main:h.location;f.splice(0,j,c);break}f=f.join("/");f+=b||(/\?/.test(f)?"":".js");f=("/"===f.charAt(0)||f.match(/^[\w\+\.\-]+:/)?"":m.baseUrl)+f}return m.urlArgs?f+((-1===f.indexOf("?")?"?":"&")+m.urlArgs):f},load:function(a,b){l.load(j,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ha.test((a.currentTarget||a.srcElement).readyState))P=
+null,a=G(a),j.completeLoad(a.id)},onScriptError:function(a){var b=G(a);if(!y(b.id))return C(J("scripterror","Script error",a,[b.id]))}};j.require=j.makeRequire();return j}var l,w,A,D,s,G,P,K,ba,ca,ia=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,ja=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,aa=/\.js$/,ga=/^\.\//;w=Object.prototype;var L=w.toString,da=w.hasOwnProperty,fa=Array.prototype.splice,z=!!("undefined"!==typeof window&&navigator&&document),$=!z&&"undefined"!==typeof importScripts,ha=z&&
+"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,V="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),B={},q={},R=[],O=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(H(requirejs))return;q=requirejs;requirejs=void 0}"undefined"!==typeof require&&!H(require)&&(q=require,require=void 0);l=requirejs=function(b,c,d,y){var f,h="_";!I(b)&&"string"!==typeof b&&(f=b,I(c)?(b=c,c=d,d=y):b=[]);f&&f.context&&(h=f.context);(y=i(B,h))||(y=B[h]=l.s.newContext(h));
+f&&y.configure(f);return y.require(b,c,d)};l.config=function(b){return l(b)};l.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=l);l.version="2.1.2";l.jsExtRegExp=/^\/|:|\?|\.js$/;l.isBrowser=z;w=l.s={contexts:B,newContext:ea};l({});x(["toUrl","undef","defined","specified"],function(b){l[b]=function(){var c=B._;return c.require[b].apply(c,arguments)}});if(z&&(A=w.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))A=
+w.head=D.parentNode;l.onError=function(b){throw b;};l.load=function(b,c,d){var i=b&&b.config||{},f;if(z)return f=i.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script"),f.type=i.scriptType||"text/javascript",f.charset="utf-8",f.async=!0,f.setAttribute("data-requirecontext",b.contextName),f.setAttribute("data-requiremodule",c),f.attachEvent&&!(f.attachEvent.toString&&0>f.attachEvent.toString().indexOf("[native code"))&&!V?(O=!0,f.attachEvent("onreadystatechange",
+b.onScriptLoad)):(f.addEventListener("load",b.onScriptLoad,!1),f.addEventListener("error",b.onScriptError,!1)),f.src=d,K=f,D?A.insertBefore(f,D):A.appendChild(f),K=null,f;$&&(importScripts(d),b.completeLoad(c))};z&&M(document.getElementsByTagName("script"),function(b){A||(A=b.parentNode);if(s=b.getAttribute("data-main"))return q.baseUrl||(G=s.split("/"),ba=G.pop(),ca=G.length?G.join("/")+"/":"./",q.baseUrl=ca,s=ba),s=s.replace(aa,""),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var i,
+f;"string"!==typeof b&&(d=c,c=b,b=null);I(c)||(d=c,c=[]);!c.length&&H(d)&&d.length&&(d.toString().replace(ia,"").replace(ja,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c));if(O){if(!(i=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),i=P;i&&(b||(b=i.getAttribute("data-requiremodule")),f=B[i.getAttribute("data-requirecontext")])}(f?f.defQueue:R).push([b,c,d])};define.amd=
+{jQuery:!0};l.exec=function(b){return eval(b)};l(q)}})(this);
diff --git a/test/unit/api.js b/test/unit/api.js
index 47f6ca2..ca7a1d9 100644
--- a/test/unit/api.js
+++ b/test/unit/api.js
@@ -68,43 +68,7 @@ $(function () {
         ok(!('dob' in values), 'date value not present') ;
      });    
      
-    /* 
-    //deprecated in 2.0
-      asyncTest("'update' event", function () {
-        expect(2);
-        var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable(),
-            e_nopk = $('<a href="#" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable(),
-            newVal = 'xyt';
-        
-        e.on('update', function() {
-             equal($(this).data('editable').value, newVal, 'triggered update after submit');
-        });
-
-        e_nopk.on('update', function() {
-             equal($(this).data('editable').value, newVal, 'triggered update after no-submit');
-        });
-
-        e_nopk.click();
-        var p = e_nopk.data('popover').$tip;
-        p.find('input').val(newVal);
-        p.find('form').submit();        
-                              
-        e.click();
-        p = tip(e);
-        p.find('input').val(newVal);
-        p.find('form').submit();
-                
-        setTimeout(function() {
-           e.remove();    
-           e_nopk.remove();    
-           start();  
-        }, timeout);                     
-      });     
-     */
-     
-     /* 
-     //deprecated in 2.0
-     test("'init' event", function () {
+    test("'init' event", function () {
         expect(1);
         var e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo('#qunit-fixture');
         
@@ -113,74 +77,11 @@ $(function () {
         });
 
         e.editable();
-      });      
-     */
-      
-     asyncTest("'render' event for text", function () {
-        expect(4);
-        var val = 'afas',
-            e = $('<a href="#" data-pk="1" data-type="text" data-url="post.php" data-name="text1">'+val+'</a>').appendTo(fx),
-            isInit = true;
-        
-        e.on('render', function(e, editable) {
-             equal(editable.isInit, isInit, 'isInit flag correct');
-             equal(editable.value, val, 'value correct');
-        });
-
-        e.editable();   
-        
-        isInit = false;
-        val = '123';
-        
-        e.click();
-        var p = tip(e);
-        p.find('input[type=text]').val(val);
-        p.find('form').submit(); 
-        
-        setTimeout(function() {
-           e.remove();    
-           start();  
-        }, timeout);                     
-        
-     });    
-     
-    asyncTest("'render' event for select", function () {
-        expect(4);
-        var val = '1',
-            e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text1" data-value="'+val+'"></a>').appendTo(fx),
-            isInit = true;
-        
-        e.on('render', function(e, editable) {
-             equal(editable.isInit, isInit, 'isInit flag correct');
-             equal(editable.value, val, 'init triggered, value correct');
-        });
-
-        e.editable({
-            source: 'groups.php',
-            autotext: 'always'
-        });
-        
-        setTimeout(function() {
-            isInit = false;
-            val = '3';
-            
-            e.click();
-            var p = tip(e);
-            p.find('select').val(val);
-            p.find('form').submit(); 
-            
-            setTimeout(function() {
-               e.remove();    
-               start();  
-            }, timeout);  
-        }, timeout);                                        
-        
-     });  
-              
-     
-     asyncTest("events: shown / cancel / hidden", function () {
-        expect(3);
-        var val = '1',
+    });      
+  
+     asyncTest("events: shown / hidden (reason: cancel, onblur, manual)", function () {
+        expect(11);
+        var val = '1', test_reason, 
             e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text" data-value="'+val+'"></a>').appendTo(fx);
         
         e.on('shown', function(event) {
@@ -188,14 +89,8 @@ $(function () {
              equal(editable.value, val, 'shown triggered, value correct');
         });
         
-        e.on('cancel', function(event) {
-             var editable = $(this).data('editable');
-             ok(true, 'cancel triggered'); 
-        });     
-        
-        e.on('hidden', function(event) {
-             var editable = $(this).data('editable');
-             ok(true, 'hidden triggered'); 
+        e.on('hidden', function(event, reason) {
+             ok((reason === test_reason) || (test_reason === 'manual' && reason === undefined), 'hidden triggered, reason ok'); 
         });            
         
         e.editable({
@@ -206,41 +101,54 @@ $(function () {
         
         setTimeout(function() {
              var p = tip(e);
-             p.find('button[type=button]').click(); 
-             setTimeout(function() {
-                 e.remove();    
-                 start();  
-             }, timeout);
+             
+             test_reason = 'cancel'
+             p.find('button[type=button]').click();  //cancel
+             ok(!p.is(':visible'), 'popover closed');
+
+             test_reason = 'onblur'            
+             e.click();
+             p = tip(e);
+             ok(p.is(':visible'), 'popover shown');
+             e.parent().click();
+             ok(!p.is(':visible'), 'popover closed');
+             
+             test_reason = 'manual'            
+             e.click();
+             p = tip(e);
+             ok(p.is(':visible'), 'popover shown');
+             e.editable('hide');
+             ok(!p.is(':visible'), 'popover closed');             
+             
+             e.remove();    
+             start();  
         }, timeout);                                        
         
      });    
      
-     asyncTest("event: save / hidden", function () {
+     asyncTest("event: save / hidden (reason: save)", function () {
         expect(2);
         var val = '1',
             e = $('<a href="#" data-pk="1" data-type="select" data-url="post.php" data-name="text" data-value="'+val+'"></a>').appendTo(fx);
         
         e.on('save', function(event, params) {
-            var editable = $(this).data('editable');
             equal(params.newValue, 2, 'save triggered, value correct');
         });
         
-        e.on('hidden', function(event) {
-             var editable = $(this).data('editable');
-             ok(true, 'hidden triggered'); 
+        e.on('hidden', function(event, reason) {
+            equal(reason, 'save', 'hidden triggered, reason ok'); 
         });         
         
         e.editable({
-            source: 'groups.php',
+            source: groups,
         });
         
         e.click();
         var p = tip(e);
         p.find('select').val(2);
         p.find('form').submit(); 
-        
+
         setTimeout(function() {
-             p.find('button[type=button]').click(); 
              e.remove();    
              start();  
         }, timeout);                                        
@@ -305,7 +213,6 @@ $(function () {
      });    
      
       asyncTest("'submit' method: client and server validation", function () {
-        expect(6);  
         var ev1 = 'ev1',
             ev2 = 'ev2',
             e1v = 'e1v',
@@ -322,6 +229,7 @@ $(function () {
                 equal(settings.data.text, ev2, 'first value ok');
                 equal(settings.data.text1, e1v, 'second value ok');
                 equal(settings.data.a, 123, 'custom data ok');
+                equal(settings.type, 'PUT', 'ajaxOptions ok');
                 this.responseText = {errors: {
                     text1: 'server-invalid'
                   }
@@ -348,11 +256,13 @@ $(function () {
             data: {a: 123},
             error: function(data) {
                 equal(data.errors.text1, 'server-invalid', 'server validation error ok');
-                
                 e.remove();
                 e1.remove();
                 start(); 
-            }            
+            },
+            ajaxOptions: {
+                type: 'PUT'
+            }
         });       
        
      });                  
@@ -431,4 +341,4 @@ $(function () {
         equal(e.text(), groups[2], 'new text shown correctly');
      });                                
   
-});            
\ No newline at end of file
+});            
diff --git a/test/unit/checklist.js b/test/unit/checklist.js
index 294d2a5..f94604a 100644
--- a/test/unit/checklist.js
+++ b/test/unit/checklist.js
@@ -4,21 +4,28 @@ $(function () {
         setup: function(){
             sfx = $('#qunit-fixture'),
             fx = $('#async-fixture');               
-            $.support.transition = false;
         }
     });  
    
     asyncTest("should load options, set correct value and save new value", function () {
-         var sep = '-',
+         var sep = '<br>',
              newValue,
-             e = $('<a href="#" data-type="checklist" data-url="post.php"></a>').appendTo(fx).editable({
+             e = $('<a href="#" data-type="checklist" data-url="post-checklist.php"></a>').appendTo(fx).editable({
              pk: 1,
              source: groupsArr,
-             value: [2, 3],
-             viewseparator: sep
+             value: [2, 3]
         });
            
-        equal(e.text(), groups[2]+sep+groups[3], 'autotext ok');
+        equal(e.html(), groups[2]+sep+groups[3], 'autotext ok');
+        
+          $.mockjax({
+              url: 'post-checklist.php',
+              response: function(settings) {
+                 ok($.isArray(settings.data.value), 'value submitted as array');
+                 equal(settings.data.value.sort().join(''), [newValue, 3].join(''), 'submitted array correct');
+              }
+          });         
+        
         
         e.click();
         var p = tip(e);
@@ -28,8 +35,8 @@ $(function () {
         equal(p.find('input[type="checkbox"]:checked').eq(1).val(), 3, '2nd checked');
 
         //set new value
-        p.find('input[type="checkbox"]:checked').eq(0).click(); 
-        p.find('input[type="checkbox"]').first().click();
+        p.find('input[type="checkbox"]:checked').eq(0).click();  //uncheck 2
+        p.find('input[type="checkbox"]').first().click(); //check first 
         newValue = p.find('input[type="checkbox"]').first().val();
         
         //submit
@@ -39,7 +46,7 @@ $(function () {
                ok(!p.is(':visible'), 'popup closed');
                
                equal(e.data('editable').value.join(''), [newValue, 3].join(''), 'new value ok')
-               equal(e.text(), groups[newValue]+sep+groups[3], 'new text ok');
+               equal(e.html(), groups[newValue]+'<br>'+groups[3], 'new text ok');
               
                // open container again to see what checked
                e.click()
@@ -54,40 +61,5 @@ $(function () {
                start();  
          }, timeout);                              
     });                  
-   
-     asyncTest("limit option", function () {
-         var e = $('<a href="#" data-type="checklist" data-value="2,3" data-url="post.php"></a>').appendTo(fx).editable({
-             pk: 1,
-             source: groupsArr,
-             limit: 1,
-             limitText: '{checked} of {count}'
-        });
-           
-        equal(e.text(), '2 of '+groupsArr.length, 'autotext ok');
-        
-        e.click();
-        var p = tip(e);
-
-        equal(p.find('input[type="checkbox"]:checked').length, 2, 'checked count ok');
-        equal(p.find('input[type="checkbox"]:checked').eq(0).val(), 2, '1st checked');
-        equal(p.find('input[type="checkbox"]:checked').eq(1).val(), 3, '2nd checked');
-
-        //set new value
-        p.find('input[type="checkbox"]').first().click();
-        newValue = p.find('input[type="checkbox"]').first().val();
-        
-        //submit
-        p.find('form').submit(); 
-         
-         setTimeout(function() {
-               ok(!p.is(':visible'), 'popup closed');
-               
-               equal(e.text(), '3 of '+groupsArr.length, 'autotext ok');
-               
-               e.remove();    
-               start();  
-         }, timeout);                              
-    });   
-   
      
 });
\ No newline at end of file
diff --git a/test/unit/common.js b/test/unit/common.js
index a5e6eb6..d0a5bee 100644
--- a/test/unit/common.js
+++ b/test/unit/common.js
@@ -62,9 +62,9 @@
 //        equal(e3.data('editable').lastSavedValue, v, 'lastSavedValue taken from text correctly (escaped)');             
       }); 
       
-      test("should take container's title from json options", function () {
+      test("container's title and placement from json options", function () {
         //do not test inline  
-        if(fc.c === 'inline') {
+        if($.fn.editableContainer.Constructor.prototype.containerName === 'editableform') {
             expect(0);
             return;
         }
@@ -81,7 +81,7 @@
         ok(p.is(':visible'), 'popover shown');   
 
         //todo: for jqueryui phantomjs calcs wrong position. Need investigation
-        if(!$.browser.webkit && fc.f !== 'jqueryui') {
+        if(!$.browser.webkit && $.fn.editableContainer.Constructor.prototype.containerName !== 'tooltip') {
             ok(p.offset().top > e.offset().top, 'placement ok');
         }
         
@@ -362,36 +362,88 @@
         ok(!p.find('.editable-buttons').length, '.editable-buttons block not rendered'); 
      });            
       
-      //unfortunatly, testing this feature does not always work in browsers. Tested manually.
-      /*
-       test("enablefocus option", function () {
-            // focusing not passed in phantomjs
-            if($.browser.webkit) {
-                ok(true, 'skipped in PhantomJS');
-                return;
-            }
-            
-            var e = $('<a href="#">abc</a>').appendTo('#qunit-fixture').editable({
-              enablefocus: true
-            }),
-             e1 = $('<a href="#">abcd</a>').appendTo('#qunit-fixture').editable({
-              enablefocus: false
-            });            
-            
-            e.click()
-            var p = tip(e);
-            ok(p.is(':visible'), 'popover 1 visible');
-            p.find('button[type=button]').click();
-            ok(!p.is(':visible'), 'popover closed');            
-            ok(e.is(':focus'), 'element 1 is focused');            
-            
-            e1.click()
-            p = tip(e1);
-            ok(p.is(':visible'), 'popover 2 visible');
-            p.find('button[type=button]').click();
-            ok(!p.is(':visible'), 'popover closed');            
-            ok(!e1.is(':focus'), 'element 2 is not focused');            
-      });
-     */
+      asyncTest("composite pk defined as json in data-pk attribute", function () {
+        var e = $('<a href="#" data-pk="{a: 1, b: 2}" data-url="post-pk.php">abc</a>').appendTo(fx).editable({
+             name: 'username'
+          }),  
+          newText = 'cd<e>;"'
+
+          $.mockjax({
+              url: 'post-pk.php',
+              response: function(settings) {
+                 equal(settings.data.pk.a, 1, 'first part ok');
+                 equal(settings.data.pk.b, 2, 'second part ok');
+              }
+          });          
+          
+        e.click()
+        var p = tip(e);
+
+        ok(p.find('input[type=text]').length, 'input exists')
+        p.find('input').val(newText);
+        p.find('form').submit(); 
+        
+        setTimeout(function() {
+           e.remove();    
+           start();  
+        }, timeout);             
+        
+    });        
+     
+      asyncTest("savenochange: false", function () {
+        var v = 'abc',
+            e = $('<a href="#" data-type="text" data-pk="1" data-url="post-no.php" data-name="text1">'+v+'</a>').appendTo(fx).editable({
+            savenochange: false
+        }),
+            req = 0;
+
+         $.mockjax({
+                url: 'post-no.php',
+                response: function() {
+                    req++;
+                }
+         });          
+        
+        e.click();
+        var p = tip(e);
+        ok(p.is(':visible'), 'popover visible');
+        p.find('input[type="text"]').val(v); 
+        p.find('form').submit(); 
+                
+        setTimeout(function() {
+           ok(!p.is(':visible'), 'popover closed');
+           equal(req, 0, 'request was not performed');
+           e.remove();    
+           start();  
+        }, timeout);                     
+      });    
+      
+      asyncTest("savenochange: true", function () {
+        var v = 'abc',
+            e = $('<a href="#" data-type="text" data-pk="1" data-url="post-yes.php" data-name="text1">'+v+'</a>').appendTo(fx).editable({
+            savenochange: true
+        }),
+            req = 0;
+
+         $.mockjax({
+                url: 'post-yes.php',
+                response: function() {
+                    req++;
+                }
+         });          
+        
+        e.click();
+        var p = tip(e);
+        ok(p.is(':visible'), 'popover visible');
+        p.find('input[type="text"]').val(v); 
+        p.find('form').submit(); 
+                
+        setTimeout(function() {
+           ok(!p.is(':visible'), 'popover closed');
+           equal(req, 1, 'request was performed');
+           e.remove();    
+           start();  
+        }, timeout);                     
+      });        
           
 }(jQuery));  
\ No newline at end of file
diff --git a/test/unit/date.js b/test/unit/date.js
index 16ca25a..ac25878 100644
--- a/test/unit/date.js
+++ b/test/unit/date.js
@@ -14,9 +14,7 @@ $(function () {
     }
      
     asyncTest("container should contain datepicker with value and save new entered date", function () {
-        expect(9);
-        
-        $.fn.editableform.types.date.defaults.datepicker.weekStart = 1;
+        $.fn.editabletypes.date.defaults.datepicker.weekStart = 1;
         
         var d = '15.05.1984',
             e = $('<a href="#" data-type="date" data-pk="1" data-url="post-date.php">'+d+'</a>').appendTo(fx).editable({
@@ -39,8 +37,10 @@ $(function () {
         e.click();
         var p = tip(e);
         ok(p.find('.datepicker').is(':visible'), 'datepicker exists');
+        ok(p.find('.datepicker').find('.datepicker-days').is(':visible'), 'datepicker days visible');        
         
         equal(frmt(e.data('editable').value, f), d, 'day set correct');
+        ok(p.find('td.day.active').is(':visible'), 'active day is visible');
         equal(p.find('td.day.active').text(), 15, 'day shown correct');
         equal(p.find('th.dow').eq(0).text(), 'Mo', 'weekStart correct');
 
@@ -60,7 +60,7 @@ $(function () {
      
      asyncTest("viewformat, init by text", function () {
          
-        $.fn.editableform.types.date.defaults.datepicker.weekStart = 1;
+        $.fn.editabletypes.date.defaults.datepicker.weekStart = 1;
          
         var dview = '15/05/1984',
             d = '1984-05-15',
diff --git a/test/unit/select.js b/test/unit/select.js
index 05d37b3..a0e7e06 100644
--- a/test/unit/select.js
+++ b/test/unit/select.js
@@ -345,7 +345,7 @@ $(function () {
          var e = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="1" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">35</a>').appendTo(fx).editable(),
              e1 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="2" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">35</a>').appendTo(fx).editable(),
              e2 = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="3" data-autotext="always" data-url="post.php" data-source="groups-cache-sim-err.php">6456</a>').appendTo(fx).editable(),
-             errText = $.fn.editableform.types.select.defaults.sourceError;
+             errText = $.fn.editabletypes.select.defaults.sourceError;
            
           setTimeout(function() {
 
@@ -363,6 +363,55 @@ $(function () {
      });     
      
      
+    asyncTest("sourceCache: false", function () {
+         var e = $('<a href="#" data-type="select" data-pk="1" data-name="name1" data-value="2" data-url="post.php" data-source="groups-cache-false.php">customer</a>').appendTo(fx).editable({
+              sourceCache: false
+         }),
+            e1 = $('<a href="#" data-type="select" data-pk="1" id="name1" data-value="2" data-url="post.php" data-source="groups-cache-false.php">customer</a>').appendTo(fx).editable({
+              sourceCache: false                 
+          }),
+          req = 0;
+
+        $.mockjax({
+                url: 'groups-cache-false.php',
+                response: function() {
+                    req++;
+                    this.responseText = groups;
+                }
+         });           
+           
+        //click first
+        e.click();
+        var p = tip(e);
+        
+        setTimeout(function() {
+            ok(p.is(':visible'), 'popover visible');
+            equal(p.find('select').find('option').length, size, 'options loaded');
+            equal(req, 1, 'one request performed');
+            
+            p.find('button[type=button]').click(); 
+            ok(!p.is(':visible'), 'popover was removed');  
+            
+            //click second
+            e1.click();
+            p = tip(e1);
+            
+            setTimeout(function() {
+                ok(p.is(':visible'), 'popover2 visible');
+                equal(p.find('select').find('option').length, size, 'options loaded');
+                equal(req, 2, 'second request performed');
+                
+                p.find('button[type=button]').click(); 
+                ok(!p.is(':visible'), 'popover was removed');                  
+                
+                e.remove();    
+                e1.remove();    
+                start();  
+            }, timeout);
+        }, timeout);  
+        
+     });       
+     
      
      asyncTest("autotext: auto", function () {
          expect(3);
@@ -474,6 +523,60 @@ $(function () {
                e.remove();    
                start();  
          }, timeout);   
-     });    
+     });   
      
-});
\ No newline at end of file
+     asyncTest("'display' callback", function () {
+         var e = $('<a href="#" data-type="select" data-value="2" data-url="post.php"></a>').appendTo(fx).editable({
+             pk: 1,
+             source: groups,
+             display: function(value, sourceData) {
+                var els = $.grep(sourceData, function(o) {return o.value == value;});  
+                $(this).text('qq' + els[0].text);
+             }
+        }),
+        selected = 3;
+
+        equal(e.text(), 'qq'+groups[2], 'autotext display ok'); 
+        
+        e.click();
+        var p = tip(e);
+
+        p.find('select').val(selected);
+        p.find('form').submit();
+         
+         setTimeout(function() {
+               ok(!p.is(':visible'), 'popover closed');
+               equal(e.data('editable').value, selected, 'new value saved')
+               equal(e.text(), 'qq'+groups[selected], 'text shown correctly') 
+               e.remove();    
+               start();  
+         }, timeout);   
+     });   
+     
+     asyncTest("submit by enter", function () {
+         var e = $('<a href="#" data-type="select" data-value="2" data-url="post.php"></a>').appendTo(fx).editable({
+             pk: 1,
+             source: groups
+        }),
+        selected = 3;
+             
+        e.click();
+        var p = tip(e);
+        p.find('select').val(selected);
+        
+        var event = jQuery.Event("keydown");
+        event.which = 13;
+       
+        p.find('select').trigger(event);
+        
+        setTimeout(function() {
+           ok(!p.is(':visible'), 'popover closed');
+           equal(e.data('editable').value, selected, 'new value saved')
+           equal(e.text(), groups[selected], 'text shown correctly') 
+           e.remove();    
+           start(); 
+        }, timeout);           
+    })       
+          
+     
+});
diff --git a/test/unit/text.js b/test/unit/text.js
index f7fc3e9..fa236ec 100644
--- a/test/unit/text.js
+++ b/test/unit/text.js
@@ -43,16 +43,16 @@ $(function () {
      asyncTest("should load correct value and save new entered text (and value)", function () {
         var  v = 'ab<b>"',
              esc_v = $('<div>').text(v).html(),
-             e = $('<a href="#" data-pk="1" data-name="text1" data-url="post-text.php" data-params="{\'q\': \'w\'}">'+esc_v+'</a>').appendTo(fx).editable({
-             success: function(data) {
-                 return false;
+             e = $('<a href="#" data-pk="1" data-name="text1" data-url="post-text-main.php" data-params="{\'q\': \'w\'}">'+esc_v+'</a>').appendTo(fx).editable({
+             success: function(response, newValue) {
+                  equal(newValue, newText, 'new value in success correct');
              } 
           }),  
           data,
-          newText = 'cd<e>;"';
+          newText = 'cd&gt;e>;"';
         
           $.mockjax({
-              url: 'post-text.php',
+              url: 'post-text-main.php',
               response: function(settings) {
                   data = settings.data;
               }
@@ -61,13 +61,13 @@ $(function () {
 
         e.click()
         var p = tip(e);
-        ok(p.is(':visible'), 'popover visible')
-        ok(p.find('.editableform-loading').length, 'loading class exists')
-        ok(!p.find('.editableform-loading').is(':visible'), 'loading class is hidden')
-        ok(p.find('input[type=text]').length, 'input exists')
-        equal(p.find('input[type=text]').val(), v, 'input contain correct value')
+        ok(p.is(':visible'), 'popover visible');
+        ok(p.find('.editableform-loading').length, 'loading class exists');
+        ok(!p.find('.editableform-loading').is(':visible'), 'loading class is hidden');
+        ok(p.find('input[type=text]').length, 'input exists');
+        equal(p.find('input[type=text]').val(), v, 'input contain correct value');
         p.find('input').val(newText);
-        p.find('button[type=submit]').click(); 
+        p.find('form').submit(); 
         ok(p.find('.editableform-loading').is(':visible'), 'loading class is visible');
         
         setTimeout(function() {
@@ -88,7 +88,10 @@ $(function () {
      asyncTest("should show error on server validation", function () {
         var msg = 'required',
            e = $('<a href="#" data-name="text1">abc</a>').appendTo(fx).editable({
-              validate: function(value) { if(value == '') return msg; }
+              validate: function(value) { 
+                  ok(this === e[0], 'scope is ok');
+                  if(value == '') return msg; 
+              }
           }),
           newText = '';
 
@@ -149,34 +152,11 @@ $(function () {
      });        
       */
       
-      asyncTest("should not perform request if value not changed", function () {
-        var e = $('<a href="#" data-pk="1" data-url="post-no.php" data-name="text1">abc</a>').appendTo(fx).editable(),
-            req = 0;
-
-         $.mockjax({
-                url: 'post-no.php',
-                response: function() {
-                    req++;
-                }
-         });          
-        
-        e.click();
-        var p = tip(e);
-        ok(p.is(':visible'), 'popover visible');
-        p.find('button[type=submit]').click(); 
-                
-        setTimeout(function() {
-           ok(!p.is(':visible'), 'popover closed');
-           equal(req, 0, 'request was not performed');
-           e.remove();    
-           start();  
-        }, timeout);                     
-      });       
-      
      asyncTest("should show error if success callback returns string", function () {
         var newText = 'cd<e>;"',
             e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable({
              success: function(response, newValue) {
+                 ok(this === e[0], 'scope is ok');
                  equal(newValue, newText, 'value in success passed correctly');
                  return 'error';
              } 
@@ -266,6 +246,7 @@ $(function () {
         var e = $('<a href="#" data-pk="1" data-url="post-resp.php">abc</a>').appendTo(fx).editable({
              name: 'username',
              params: function(params) {
+                 ok(this === e[0], 'scope is ok');
                  equal(params.pk, 1, 'params in func already have values (pk)');
                  return { q: 2, pk: 3 };
              },
@@ -328,11 +309,12 @@ $(function () {
                    
       
      asyncTest("submit to url defined as function", function () {
-        expect(3);
+        expect(4);
         var newText = 'qwe',
             //should be called even without pk!
             e = $('<a href="#" data-pk1="1" id="a"></a>').appendTo(fx).editable({
             url: function(params) {
+               ok(this === e[0], 'scope is ok');
                ok(params.value, newText, 'new text passed in users function');
                var d = new $.Deferred;
                return d.reject('my error');
@@ -472,6 +454,55 @@ $(function () {
             delete $.fn.editable.defaults.name;
             var e = $('<a href="#" id="cde">abc</a>').appendTo('#qunit-fixture').editable();
             equal(e.data('editable').options.name, 'cde', 'name is taken from id');
-      });      
+      });   
+      
+     asyncTest("'display' callback", function () {
+        var newText = 'cd<e>;"',
+            e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1">abc</a>').appendTo(fx).editable({
+             display: function(value) {
+                 ok(this === e[0], 'scope is ok');
+                 $(this).text('qq'+value);
+             } 
+          });  
+
+        e.click()
+        var p = tip(e);
+
+        ok(p.find('input[type=text]').length, 'input exists')
+        p.find('input').val(newText);
+        p.find('form').submit(); 
+        
+        setTimeout(function() {
+           ok(!p.is(':visible'), 'popover was removed');
+           equal(e.text(), 'qq'+newText, 'custom display ok');
+           e.remove();    
+           start();  
+        }, timeout);             
+        
+      });
+      
+     asyncTest("display: false", function () {
+        var newText = 'cd<e>;"',
+            e = $('<a href="#" data-pk="1" data-url="post.php" data-name="text1" data-value="abc"></a>').appendTo(fx).editable({
+              display: false
+          });  
+
+        ok(!e.text().length, 'element still empty, autotext did not display value');          
+          
+        e.click()
+        var p = tip(e);
+
+        p.find('input').val(newText);
+        p.find('form').submit(); 
+        
+        setTimeout(function() {
+           ok(!p.is(':visible'), 'popover was removed');
+           ok(!e.text().length, 'element still empty, new value was not displayed');  
+           equal(e.data('editable').value, newText, 'new text saved to value');
+           e.remove();    
+           start();  
+        }, timeout);             
+        
+      });                   
          
 });    
\ No newline at end of file
diff --git a/test/unit/textarea.js b/test/unit/textarea.js
index 6a76813..5784189 100644
--- a/test/unit/textarea.js
+++ b/test/unit/textarea.js
@@ -34,8 +34,8 @@ $(function () {
         var e = $('<a href="#" data-pk="1" data-url="post.php">'+v1+'</a>').appendTo(fx).editable({
              type: 'textarea',
              send: 'ifpk',
-             success: function(data) {
-                 return false;
+             success: function(response, newvalue) {
+                 equal(newvalue, v2, 'value in success ok');         
              } 
           });
 
@@ -93,7 +93,7 @@ $(function () {
         var p = tip(e);
         p.find('textarea').val(vnew);
         
-        event = jQuery.Event("keydown");
+        var event = jQuery.Event("keydown");
         event.ctrlKey = true;
         event.which = 13;