From 5c5a24b86bad0a9f17539409478fdf369fed528a Mon Sep 17 00:00:00 2001
From: vitalets <noginsk@rambler.ru>
Date: Sat, 31 Aug 2013 09:53:34 +0400
Subject: [PATCH] bs3 first working version

---
 src/containers/editable-container.js          |   5 +-
 src/containers/editable-popover.js            |   5 +-
 src/containers/editable-popover3.js           | 199 ++++++++++++++++++
 src/containers/editable-poshytip.js           |   1 +
 src/containers/editable-tooltip.js            |   5 +-
 src/editable-form/editable-form-bootstrap.js  |  24 ++-
 src/editable-form/editable-form-bootstrap3.js |  61 ++++++
 src/editable-form/editable-form-jqueryui.js   |   2 +
 src/editable-form/editable-form.js            |   9 +-
 src/element/editable-element.js               |   2 +-
 src/inputs/abstract.js                        |   6 +-
 src/inputs/combodate/combodate.js             |   7 +
 src/inputs/select2/select2.js                 |   2 +-
 test/loader.js                                |  35 ++-
 14 files changed, 339 insertions(+), 24 deletions(-)
 create mode 100644 src/containers/editable-popover3.js
 create mode 100644 src/editable-form/editable-form-bootstrap3.js

diff --git a/src/containers/editable-container.js b/src/containers/editable-container.js
index 0bfbe94..a106784 100644
--- a/src/containers/editable-container.js
+++ b/src/containers/editable-container.js
@@ -24,6 +24,8 @@ Applied as jQuery method.
         containerDataName: null, //object name in element's .data()
         innerCss: null, //tbd in child class
         containerClass: 'editable-container editable-popup', //css class applied to container element
+        defaults: {}, //container itself defaults
+        
         init: function(element, options) {
             this.$element = $(element);
             //since 1.4.1 container do not use data-* directly as they already merged into options.
@@ -101,10 +103,9 @@ Applied as jQuery method.
                 throw new Error(this.containerName + ' not found. Have you included corresponding js file?');   
             }
             
-            var cDef = $.fn[this.containerName].defaults;
             //keys defined in container defaults go to container, others go to form
             for(var k in this.options) {
-              if(k in cDef) {
+              if(k in this.defaults) {
                  this.containerOptions[k] = this.options[k];
               } else {
                  this.formOptions[k] = this.options[k];
diff --git a/src/containers/editable-popover.js b/src/containers/editable-popover.js
index 867f5bf..4740ac0 100644
--- a/src/containers/editable-popover.js
+++ b/src/containers/editable-popover.js
@@ -11,13 +11,14 @@
         containerName: 'popover',
         //for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content) 
         innerCss: $.fn.popover && $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
-
+        defaults: $.fn.popover.defaults,
+        
         initContainer: function(){
             $.extend(this.containerOptions, {
                 trigger: 'manual',
                 selector: false,
                 content: ' ',
-                template: $.fn.popover.defaults.template
+                template: this.defaults.template
             });
             
             //as template property is used in inputs, hide it from popover
diff --git a/src/containers/editable-popover3.js b/src/containers/editable-popover3.js
new file mode 100644
index 0000000..0ae0575
--- /dev/null
+++ b/src/containers/editable-popover3.js
@@ -0,0 +1,199 @@
+/**
+* Editable Popover3 (for Bootstrap 3) 
+* ---------------------
+* requires bootstrap-popover.js
+*/
+(function ($) {
+    "use strict";
+
+    //extend methods
+    $.extend($.fn.editableContainer.Popup.prototype, {
+        containerName: 'popover',
+        containerDataName: 'bs.popover',
+        innerCss: '.popover-content',
+        defaults: $.fn.popover.Constructor.DEFAULTS,
+
+        initContainer: function(){
+            $.extend(this.containerOptions, {
+                trigger: 'manual',
+                selector: false,
+                content: ' ',
+                template: this.defaults.template
+            });
+            
+            //as template property is used in inputs, hide it from popover
+            var t;
+            if(this.$element.data('template')) {
+               t = this.$element.data('template');
+               this.$element.removeData('template');  
+            } 
+            
+            this.call(this.containerOptions);
+            
+            if(t) {
+               //restore data('template')
+               this.$element.data('template', t); 
+            }
+        }, 
+        
+        /* show */
+        innerShow: function () {
+            this.call('show');                
+        },  
+        
+        /* hide */
+        innerHide: function () {
+            this.call('hide');       
+        }, 
+        
+        /* destroy */
+        innerDestroy: function() {
+            this.call('destroy');
+        },                               
+        
+        setContainerOption: function(key, value) {
+            this.container().options[key] = value; 
+        },               
+
+        /**
+        * move popover to new position. This function mainly copied from bootstrap-popover.
+        */
+        /*jshint laxcomma: true*/
+        setPosition: function () { 
+
+            (function() {
+            /*    
+                var $tip = this.tip()
+                , inside
+                , pos
+                , actualWidth
+                , actualHeight
+                , placement
+                , tp
+                , tpt
+                , tpb
+                , tpl
+                , tpr;
+
+                placement = typeof this.options.placement === 'function' ?
+                this.options.placement.call(this, $tip[0], this.$element[0]) :
+                this.options.placement;
+
+                inside = /in/.test(placement);
+               
+                $tip
+              //  .detach()
+              //vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
+                .removeClass('top right bottom left')
+                .css({ top: 0, left: 0, display: 'block' });
+              //  .insertAfter(this.$element);
+               
+                pos = this.getPosition(inside);
+
+                actualWidth = $tip[0].offsetWidth;
+                actualHeight = $tip[0].offsetHeight;
+
+                placement = inside ? placement.split(' ')[1] : placement;
+
+                tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
+                tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
+                tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
+                tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
+
+                switch (placement) {
+                    case 'bottom':
+                        if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) {
+                            if (tpt.top > $(window).scrollTop()) {
+                                placement = 'top';
+                            } else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
+                                placement = 'right';
+                            } else if (tpl.left > $(window).scrollLeft()) {
+                                placement = 'left';
+                            } else {
+                                placement = 'right';
+                            }
+                        }
+                        break;
+                    case 'top':
+                        if (tpt.top < $(window).scrollTop()) {
+                            if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) {
+                                placement = 'bottom';
+                            } else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
+                                placement = 'right';
+                            } else if (tpl.left > $(window).scrollLeft()) {
+                                placement = 'left';
+                            } else {
+                                placement = 'right';
+                            }
+                        }
+                        break;
+                    case 'left':
+                        if (tpl.left < $(window).scrollLeft()) {
+                            if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
+                                placement = 'right';
+                            } else if (tpt.top > $(window).scrollTop()) {
+                                placement = 'top';
+                            } else if (tpt.top > $(window).scrollTop()) {
+                                placement = 'bottom';
+                            } else {
+                                placement = 'right';
+                            }
+                        }
+                        break;
+                    case 'right':
+                        if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) {
+                            if (tpl.left > $(window).scrollLeft()) {
+                                placement = 'left';
+                            } else if (tpt.top > $(window).scrollTop()) {
+                                placement = 'top';
+                            } else if (tpt.top > $(window).scrollTop()) {
+                                placement = 'bottom';
+                            }
+                        }
+                        break;
+                }
+
+                switch (placement) {
+                    case 'bottom':
+                        tp = tpb;
+                        break;
+                    case 'top':
+                        tp = tpt;
+                        break;
+                    case 'left':
+                        tp = tpl;
+                        break;
+                    case 'right':
+                        tp = tpr;
+                        break;
+                }
+
+                $tip
+                .offset(tp)
+                .addClass(placement)
+                .addClass('in');
+           */
+                     
+           
+            var $tip = this.tip();
+            
+            var placement = typeof this.options.placement == 'function' ?
+                this.options.placement.call(this, $tip[0], this.$element[0]) :
+                this.options.placement;            
+            
+            
+            var pos = this.getPosition();
+            var actualWidth = $tip[0].offsetWidth;
+            var actualHeight = $tip[0].offsetHeight;
+            var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);
+
+            this.applyPlacement(calculatedOffset, placement);            
+           
+           
+                
+            }).call(this.container());
+          /*jshint laxcomma: false*/  
+        }            
+    });
+
+}(window.jQuery));
diff --git a/src/containers/editable-poshytip.js b/src/containers/editable-poshytip.js
index b797080..32a1d6f 100644
--- a/src/containers/editable-poshytip.js
+++ b/src/containers/editable-poshytip.js
@@ -10,6 +10,7 @@
     $.extend($.fn.editableContainer.Popup.prototype, {
         containerName: 'poshytip',
         innerCss: 'div.tip-inner',
+        defaults: $.fn.poshytip.defaults,
         
         initContainer: function(){
             this.handlePlacement();
diff --git a/src/containers/editable-tooltip.js b/src/containers/editable-tooltip.js
index 0392727..8dd1cb9 100644
--- a/src/containers/editable-tooltip.js
+++ b/src/containers/editable-tooltip.js
@@ -12,6 +12,7 @@
         //object name in element's .data() 
         containerDataName: 'ui-tooltip', 
         innerCss: '.ui-tooltip-content', 
+        defaults: $.ui.tooltip.prototype.options,
         
         //split options on containerOptions and formOptions
         splitOptions: function() {
@@ -23,10 +24,10 @@
                 $.error('Please use jQueryUI with "tooltip" widget! http://jqueryui.com/download');
                 return;
             }
+            
             //defaults for tooltip
-            var cDef = $.ui[this.containerName].prototype.options;
             for(var k in this.options) {
-              if(k in cDef) {
+              if(k in this.defaults) {
                  this.containerOptions[k] = this.options[k];
               } else {
                  this.formOptions[k] = this.options[k];
diff --git a/src/editable-form/editable-form-bootstrap.js b/src/editable-form/editable-form-bootstrap.js
index 8254279..cbcc044 100644
--- a/src/editable-form/editable-form-bootstrap.js
+++ b/src/editable-form/editable-form-bootstrap.js
@@ -1,13 +1,31 @@
 /*
-Editableform based on Twitter Bootstrap
+Editableform based on Twitter Bootstrap 2
 */
 (function ($) {
     "use strict";
     
+    //store parent methods
+    var pInitInput = $.fn.editableform.Constructor.prototype.initInput;    
+    
     $.extend($.fn.editableform.Constructor.prototype, {
          initTemplate: function() {
             this.$form = $($.fn.editableform.template); 
             this.$form.find('.editable-error-block').addClass('help-block');
+         },
+         initInput: function() {  
+            pInitInput.apply(this);
+
+            //for bs2 set default class `input-medium` to standard inputs
+            var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false;
+            var defaultClass = 'input-medium';
+            
+            //add bs2 default class to standard inputs
+            //if(this.input.$input.is('input,select,textarea')) {
+            var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time'.split(','); 
+            if(~$.inArray(this.input.type, stdtypes) && emptyInputClass) {
+                this.input.options.inputclass = defaultClass;
+                this.input.$input.addClass(defaultClass);
+            }         
          }
     });    
     
@@ -17,6 +35,8 @@ Editableform based on Twitter Bootstrap
     
     //error classes
     $.fn.editableform.errorGroupClass = 'error';
-    $.fn.editableform.errorBlockClass = null;    
+    $.fn.editableform.errorBlockClass = null;
+    //engine 
+    $.fn.editableform.engine = 'bs2';   
     
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/editable-form/editable-form-bootstrap3.js b/src/editable-form/editable-form-bootstrap3.js
new file mode 100644
index 0000000..20f9645
--- /dev/null
+++ b/src/editable-form/editable-form-bootstrap3.js
@@ -0,0 +1,61 @@
+/*
+Editableform based on Twitter Bootstrap 3
+*/
+(function ($) {
+    "use strict";
+    
+    //store parent methods
+    var pInitInput = $.fn.editableform.Constructor.prototype.initInput;
+    
+    $.extend($.fn.editableform.Constructor.prototype, {
+        initTemplate: function() {
+            this.$form = $($.fn.editableform.template); 
+            this.$form.find('.control-group').addClass('form-group');
+            this.$form.find('.editable-error-block').addClass('help-block');
+        },
+        initInput: function() {  
+            pInitInput.apply(this);
+
+            //for bs3 set default class `input-sm` to standard inputs
+            var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false;
+            var defaultClass = 'input-sm';
+            
+            //bs3 add `form-control` class to standard inputs
+            var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time'.split(','); 
+            if(~$.inArray(this.input.type, stdtypes)) {
+                this.input.$input.addClass('form-control');
+                if(emptyInputClass) {
+                    this.input.options.inputclass = defaultClass;
+                    this.input.$input.addClass(defaultClass);
+                }
+            }             
+        
+            //apply bs3 size class also to buttons (to fit size of control)
+            var $btn = this.$form.find('.editable-buttons');
+            var classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' ');
+            for(var i=0; i<classes.length; i++) {
+                if(classes[i].toLowerCase() === 'input-sm') { 
+                    $btn.find('button').addClass('btn-sm');  
+                }
+                if(classes[i].toLowerCase() === 'input-lg') {
+                    $btn.find('button').addClass('btn-lg'); 
+                }
+            }
+        }
+    });    
+    
+    //buttons
+    $.fn.editableform.buttons = 
+      '<button type="submit" class="btn btn-primary editable-submit">'+
+        '<i class="glyphicon glyphicon-ok"></i>'+
+      '</button>'+
+      '<button type="button" class="btn btn-default editable-cancel">'+
+        '<i class="glyphicon glyphicon-remove"></i>'+
+      '</button>';         
+    
+    //error classes
+    $.fn.editableform.errorGroupClass = 'has-error';
+    $.fn.editableform.errorBlockClass = null;  
+    //engine
+    $.fn.editableform.engine = 'bs3';  
+}(window.jQuery));
\ No newline at end of file
diff --git a/src/editable-form/editable-form-jqueryui.js b/src/editable-form/editable-form-jqueryui.js
index 3c13f1f..0c7d64c 100644
--- a/src/editable-form/editable-form-jqueryui.js
+++ b/src/editable-form/editable-form-jqueryui.js
@@ -26,5 +26,7 @@ Editableform based on jQuery UI
     //error classes
     $.fn.editableform.errorGroupClass = null;
     $.fn.editableform.errorBlockClass = 'ui-state-error';
+    //engine
+    $.fn.editableform.engine = 'jqeury-ui';
     
 }(window.jQuery));
\ No newline at end of file
diff --git a/src/editable-form/editable-form.js b/src/editable-form/editable-form.js
index 5b8cbf5..b9361dc 100644
--- a/src/editable-form/editable-form.js
+++ b/src/editable-form/editable-form.js
@@ -28,6 +28,9 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
             //set initial value
             //todo: may be add check: typeof str === 'string' ? 
             this.value = this.input.str2value(this.options.value); 
+            
+            //prerender: get input.$input
+            this.input.prerender();
         },
         initTemplate: function() {
             this.$form = $($.fn.editableform.template); 
@@ -75,9 +78,8 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
             this.initInput();
             
             //append input to form
-            this.input.prerender();
             this.$form.find('div.editable-input').append(this.input.$tpl);            
-
+            
             //append form to container
             this.$div.append(this.$form);
             
@@ -615,4 +617,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
 
     //error class attached to editable-error-block
     $.fn.editableform.errorBlockClass = 'editable-error';
+    
+    //engine
+    $.fn.editableform.engine = 'jqeury';
 }(window.jQuery));
diff --git a/src/element/editable-element.js b/src/element/editable-element.js
index 92cdd9b..0f2e6ca 100644
--- a/src/element/editable-element.js
+++ b/src/element/editable-element.js
@@ -806,7 +806,7 @@ Makes editable any HTML element on the page. Applied as jQuery method.
         @since 1.4.5        
         @default #FFFF80 
         **/
-        highlight: '#FFFF80'        
+        highlight: '#FFFF80'
     };
     
 }(window.jQuery));
diff --git a/src/inputs/abstract.js b/src/inputs/abstract.js
index ba040c3..8a07490 100644
--- a/src/inputs/abstract.js
+++ b/src/inputs/abstract.js
@@ -159,7 +159,7 @@ To create your own input you can inherit from this class.
        },
 
        // -------- helper functions --------
-       setClass: function() {
+       setClass: function() {          
            if(this.options.inputclass) {
                this.$input.addClass(this.options.inputclass); 
            } 
@@ -191,9 +191,9 @@ To create your own input you can inherit from this class.
         
         @property inputclass 
         @type string
-        @default input-medium
+        @default null
         **/         
-        inputclass: 'input-medium',
+        inputclass: null,
         //scope for external methods (e.g. source defined as function)
         //for internal use only
         scope: null,
diff --git a/src/inputs/combodate/combodate.js b/src/inputs/combodate/combodate.js
index dbc35d7..c3e6a7b 100644
--- a/src/inputs/combodate/combodate.js
+++ b/src/inputs/combodate/combodate.js
@@ -64,7 +64,14 @@ $(function(){
     $.extend(Constructor.prototype, {
         render: function () {
             this.$input.combodate(this.options.combodate);
+                    
+            if($.fn.editableform.engine === 'bs3') {
+                this.$input.siblings().find('select').addClass('form-control');
+            }
             
+            if(this.options.inputclass) {
+                this.$input.siblings().find('select').addClass(this.options.inputclass);
+            }            
             //"clear" link
             /*
             if(this.options.clear) {
diff --git a/src/inputs/select2/select2.js b/src/inputs/select2/select2.js
index e08edcf..ab1fe1c 100644
--- a/src/inputs/select2/select2.js
+++ b/src/inputs/select2/select2.js
@@ -316,7 +316,7 @@ $(function(){
         @type string
         @default ', '        
         **/
-        viewseparator: ', '        
+        viewseparator: ', '
     });
 
     $.fn.editabletypes.select2 = Constructor;      
diff --git a/test/loader.js b/test/loader.js
index 21825fd..5f0eb73 100644
--- a/test/loader.js
+++ b/test/loader.js
@@ -14,15 +14,18 @@ define(function () {
     return {
         loadCss: loadCss,
         getConfig: function (baseUrl) {
-          
+
             var 
                 jqueryui_ver = '1.10.3',
             //    jqueryui_ver = '1.9.1',
+                bs_ver = '300',
+                bs_major_ver = bs_ver.substr(0,1),
                 paths = {
 //                    "bootstrap": "../test/libs/bootstrap221", 
 //                    "bootstrap": "../test/libs/bootstrap222", 
                    // "bootstrap": "../test/libs/bootstrap231", 
-                    "bootstrap": "../test/libs/bootstrap232", 
+                   // "bootstrap": "../test/libs/bootstrap232", 
+                    "bootstrap": "../test/libs/bootstrap"+bs_ver, 
                     
                   //  "jqueryui": "../test/libs/jquery-ui-"+jqueryui_ver+".custom", 
                     "jqueryui_js": "../test/libs/jquery-ui-"+jqueryui_ver+".custom/js/jquery-ui-"+jqueryui_ver+".custom", 
@@ -92,17 +95,27 @@ define(function () {
                     init: function(require) {
                         loadCss(require.toUrl("../css/bootstrap.css")); 
                         //add responsive css
-                        loadCss(require.toUrl("../css/bootstrap-responsive.css")); 
+                        if(bs_major_ver < 3) {
+                           loadCss(require.toUrl("../css/bootstrap-responsive.css"));
+                        } 
                     }                
                 },
                 'editable-form/editable-form-bootstrap': [
                     'editable-form/editable-form', 
                     'bootstrap/js/bootstrap'
                 ],
+                'editable-form/editable-form-bootstrap3': [
+                    'editable-form/editable-form', 
+                    'bootstrap/js/bootstrap'
+                ],
                 'containers/editable-popover': [
                     'containers/editable-inline', 
                     'bootstrap/js/bootstrap'
                 ],
+                'containers/editable-popover3': [
+                    'containers/editable-inline', 
+                    'bootstrap/js/bootstrap'
+                ],
                 'inputs/date/date': {
                     deps: ['require', 
                     'bootstrap/js/bootstrap',
@@ -217,12 +230,16 @@ define(function () {
             
             if(f === 'bootstrap') { 
                 //bootstrap
-                shim['editable-form/editable-form'].deps.push('inputs/date/datefield');
-                shim['editable-form/editable-form'].deps.push('inputs/datetime/datetimefield');
-                shim['editable-form/editable-form'].deps.push('inputs-ext/wysihtml5/wysihtml5');
-                shim['editable-form/editable-form'].deps.push('inputs/typeahead');
-                shim['element/editable-element'].deps.push('editable-form/editable-form-bootstrap');
-                shim['element/editable-element'].deps.push('containers/editable-popover');
+                shim['editable-form/editable-form'].deps = shim['editable-form/editable-form'].deps.concat( 
+                 [
+                  'inputs/date/datefield',
+                  'inputs/datetime/datetimefield',
+                  'inputs-ext/wysihtml5/wysihtml5',
+                  'inputs/typeahead'
+                 ]);
+                var suffix = bs_major_ver < 3 ? '' : bs_major_ver; 
+                shim['element/editable-element'].deps.push('editable-form/editable-form-bootstrap'+suffix);
+                shim['element/editable-element'].deps.push('containers/editable-popover'+suffix);
             } else if(f === 'jqueryui') {
                 //jqueryui
                 shim['editable-form/editable-form'].deps.push('inputs/dateui/dateuifield');